昨天把二元分類的數學原理說明了…最重要的是我們可以二元分類的損失函數如下,這個其實是後邊要講一般分類問題的損失函數的特例:
先取得訓練用的資料集,我們下載到硬碟裡,
#假設是 Fields 登入他的帳號來執行的 “~/” = ”/home/fields”
wget https://www.csie.ntu.edu.tw/%7Ecjlin/libsvmtools/datasets/binary/a1a -O ~/a1a.train
wget https://www.csie.ntu.edu.tw/%7Ecjlin/libsvmtools/datasets/binary/a1a.t -O ~/a1a.test
數據集來自 UCI (UC Irvine Machine Learning Repository),用來預測年收入是否超過五萬美金。 下列節錄資料集的三行,樣式為第一個值 +1 或 -1 是 Label,代表年收入超過五萬美金否『是』或『否』,我們必須把他變成1或是0,後面 N:M 都是特徵值,代表年齡, 種族, 性別等,不過我們只需要N 這個值(亦即 :M 要刪掉),N代表在 UCI 共有 123個特徵值中,非零值的位置。這被稱為 LIBSVM 格式。
-1 2:1 6:1 18:1 20:1 37:1 42:1 48:1 64:1 71:1 73:1 74:1 76:1 81:1 83:1
+1 5:1 11:1 15:1 32:1 39:1 40:1 52:1 63:1 67:1 73:1 74:1 76:1 78:1 83:1
-1 5:1 16:1 30:1 35:1 41:1 64:1 67:1 73:1 74:1 76:1 80:1 83:1
import mxnet as mx
from mxnet import nd, autograd, gluon
data_ctx = mx.cpu()
#我們假設有一塊Nvida 支援 CUDA顯示卡來加速計算,如果沒有則改為mx.cpu()
model_ctx = mx.gpu()
with open("/home/fields/a1a.train") as f:
train_raw = f.read()
with open("/home/fields/a1a.test") as f:
test_raw = f.read()
讀進資料後,我們要把資料做一點清理,這在機器學習是經常面臨的數據預處理,我們在此練習一下:
def process_data(raw_data):
train_lines = raw_data.splitlines()
num_examples = len(train_lines)
num_features = 123 #UCI 資料集有123個特徵
X = nd.zeros((num_examples, num_features), ctx=data_ctx)
#預設為0,我們要從資料集找到非零值,把他改為1
Y = nd.zeros((num_examples, 1), ctx=data_ctx)
for i, line in enumerate(train_lines):
tokens = line.split()
label = (int(tokens[0]) + 1) / 2 # 我們轉換資料集的 Label 『是』『否』 {-1,1} 成我們計算損失函數所需的 {0,1}
Y[i] = label
for token in tokens[1:]:
index = int(token[:-2]) - 1
X[i, index] = 1
return X, Y
處理後的訓練與測試資料集
Xtrain, Ytrain = process_data(train_raw)
Xtest, Ytest = process_data(test_raw)
有了資料集,我們看看如果用 Gluon 實作:
batch_size = 64
#用 Gluon 提供的 Data Iterator
train_data = gluon.data.DataLoader(gluon.data.ArrayDataset(Xtrain, Ytrain),
batch_size=batch_size, shuffle=True)
test_data = gluon.data.DataLoader(gluon.data.ArrayDataset(Xtest, Ytest),
batch_size=batch_size, shuffle=True)
def logistic(z):
return 1. / (1. + nd.exp(-z))
def log_loss(output, y):
yhat = logistic(output)
return - nd.nansum( y * nd.log(yhat) + (1-y) * nd.log(1-yhat))
net = gluon.nn.Dense(1)
net.collect_params().initialize(mx.init.Normal(sigma=1.), ctx=model_ctx)
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.01})
epochs = 30
loss_sequence = []
num_examples = len(Xtrain)
for e in range(epochs):
cumulative_loss = 0
for i, (data, label) in enumerate(train_data):
data = data.as_in_context(model_ctx) #如果用 GPU加速,需要把資料引入 GPU
label = label.as_in_context(model_ctx)
with autograd.record():
output = net(data)
loss = log_loss(output, label)
loss.backward()
trainer.step(batch_size)
cumulative_loss += nd.sum(loss).asscalar()
print("Epoch %s, loss: %s" % (e, cumulative_loss ))
loss_sequence.append(cumulative_loss)
這裡的程式碼如果全部都採用 Gluon,會更簡潔,但我們局部從無到有,逐步了解如果我們自己發明更好的損失函數,我們可以就自己改寫上面的 loss = log_loss(output, label)
二元分類在許多應用上很有用處,例如:分辨是否為垃圾信件;某疾病判定陽性或陰性。但是我們也常遇到多類別的分類,例如:辨識數字,產品分類,電影分級等等。
我們假設要進行 k 類別(以下圖示意,有三個輸出單元,那 k = 3)的分類 ,假設特徵值有 4 個,以下圖表達:
我們沿用線性迴歸輸出為
但是現在是分類問題,那我們要做的是再加一個轉換f,當 x 這樣本是第i個分類時,我們希望其機率最大
我們巧妙的這樣設計:讓輸出值成 1 到 k 這幾個值是呈現離散分佈的,符合
所以我們嘗試用這個softmax函數來當作這個離散分佈的表達,的確符合上述兩個條件:
那我們就可以改寫輸出為
其中類別 j 是:
上述都是一個樣本來說明,當我們採 mini Batch 我們抽樣 s 個樣本,而輸入有f個特徵,我們要預測k種可能的分類時,我們就必須泛化改寫維度來表達。
這樣我們就可以用較簡潔的方式來表達,注意 偏差 會自動廣播成 ,這樣才能進行下列的矩陣加法
要計算兩個分佈有多大的距離,有 Cross Entropy 函數。
(公式一)
這個函數解決在k個分類中,只有一個正確的分類值是1,其他為零這種情境下,我們要量測其誤差的方法。
定義 Cross Entropy Loss Function:
(公式二)
觀察上面(公式一)式子,有加註j 下標的符號代表 向量 裡的第j個元素,只有0或1的可能。我們規定每個樣本只能有一個分類,亦即向量 中只有第 個元素為1;其他都是0。結合(公式一)變成:
(公式三)
結合(公式三)與 (公式二)那損失函數的公式就變成:
再進一步我們可以把最小化損失函數取 exp 運算後變成 最大化
這代表聯合機率。有這些後我們就可以往下進行用準確率做模型的評估了。
專案緣起記錄在 【UP, Scrum 與 AI專案】