一、作業說明
給定訓練集spam_train.csv,要求根據每個ID各種屬性值來判斷該ID對應角色是Winner還是Losser(收入是否大於50K),這是一個典型的二分類問題。
訓練集介紹:
(1)、CSV文件,大小為4000行X59列;
(2)、4000行數據對應着4000個角色,ID編號從1到4001;
(3)、59列數據中, 第一列為角色ID,最后一列為分類結果,即label(0、1兩種),中間的57列為角色對應的57種屬性值;
(4)、數據集地址:https://pan.baidu.com/s/1mG7ndtlT4jWYHH9V-Rj_5g, 提取碼:hwzf 。
二、思路分析及實現
2.1 思路分析
這是一個典型的二分類問題,結合課上所學內容,決定采用Logistic回歸算法。
與線性回歸用於預測不同,Logistic回歸則常用於分類(通常是二分類問題)。Logistic回歸實質上就是在普通的線性回歸后面加上了一個sigmoid函數,把線性回歸預測到的數值壓縮成為一個概率,進而實現二分類(關於線性回歸模型,可參考上一次作業)。
在損失函數方面,Logistic回歸並沒有使用傳統的歐式距離來度量誤差,而使用了交叉熵(用於衡量兩個概率分布之間的相似程度)。
2.2 數據預處理
在機器學習中,數據的預處理是非常重要的一環,能直接影響到模型效果的好壞。本次作業的數據相對簡單純凈,在數據預處理方面並不需要花太多精力。
首先是空值處理(盡管沒看到空值,但為了以防萬一,還是做一下),所有空值用0填充(也可以用平均值、中位數等,視具體情況而定)。
接着就是把數據范圍盡量scale到同一個數量級上,觀察數據后發現,多數數據值為0,非0值也都在1附近,只有倒數第二列和倒數第三列數據值較大,可以將這兩列分別除上每列的平均值,把數值范圍拉到1附近。
由於並沒有給出這57個屬性具體是什么屬性,因此無法對數據進行進一步的挖掘應用。
上述操作完成后,將表格的第2列至58列取出為x(shape為4000X57),將最后一列取出做label y(shape為4000X1)。進一步划分訓練集和驗證集,分別取x、y中前3500個樣本為訓練集x_test(shape為3500X57),y_test(shape為3500X1),后500個樣本為驗證集x_val(shape為500X57),y_val(shape為500X1)。
數據預處理到此結束。
1 # 從csv中讀取有用的信息 2 df = pd.read_csv('spam_train.csv') 3 # 空值填0 4 df = df.fillna(0) 5 # (4000, 59) 6 array = np.array(df) 7 # (4000, 57) 8 x = array[:, 1:-1] 9 # scale 10 x[-1] /= np.mean(x[-1]) 11 x[-2] /= np.mean(x[-2]) 12 # (4000, ) 13 y = array[:, -1] 14 15 # 划分訓練集與驗證集 16 x_train, x_val = x[0:3500, :], x[3500:4000, :] 17 y_train, y_val = y[0:3500], y[3500:4000]
2.3 模型建立
2.3.1 線性回歸
先對數據做線性回歸,得出每個樣本對應的回歸值。下式為對第n個樣本的回歸,回歸結果為
。
1 y_pre = weights.dot(x_val[j, :]) + bias
2.3.2 sigmoid函數壓縮回歸值
之后將回歸結果送進sigmoid函數,得到概率值。
1 sig = 1 / (1 + np.exp(-y_pre)
2.3.3 誤差反向傳播
接着就到重頭戲了。眾所周知,不管線性回歸還是Logistic回歸,其關鍵和核心就在於通過誤差的反向傳播來更新參數,進而使模型不斷優化。因此,損失函數的確定及對各參數的求導就成了重中之重。在分類問題中,模型一般針對各類別輸出一個概率分布,因此常用交叉熵作為損失函數。交叉熵可用於衡量兩個概率分布之間的相似、統一程度,兩個概率分布越相似、越統一,則交叉熵越小;反之,兩概率分布之間差異越大、越混亂,則交叉熵越大。
下式表示k分類問題的交叉熵,P為label,是一個概率分布,常用one_hot編碼。例如針對3分類問題而言,若樣本屬於第一類,則P為(1,0,0),若屬於第二類,則P為(0,1,0),若屬於第三類,則為(0,0,1)。即所屬的類概率值為1,其他類概率值為0。Q為模型得出的概率分布,可以是(0.1,0.8,0.1)等。
在實際應用中,為求導方便,常使用以e為底的對數。
針對本次作業而言,雖然模型只輸出了一個概率值p,但由於處理的是二分類問題,因此可以很快求出另一概率值為1-p,即可視為模型輸出的概率分布為Q(p,1-p)。將本次的label視為概率分布P(y,1-y),即Winner(label為1)的概率分布為(1,0),分類為Losser(label為0)的概率分布為(0,1)。
損失函數對權重w求偏導,可得:
因為:
所以有:
同理,損失函數對偏置b求偏導,可得:
1 # 在所有數據上計算梯度,梯度計算時針對損失函數求導,num為樣本數量 2 for j in range(num): 3 # 線性函數 4 y_pre = weights.dot(x_train[j, :]) + bias 5 # sigmoid函數壓縮回歸值,求得概率 6 sig = 1 / (1 + np.exp(-y_pre)) 7 # 對偏置b求梯度 8 b_g += (-1) * (y_train[j] - sig) 9 # 對權重w求梯度,2 * reg_rate * weights[k] 為正則項,防止過擬合 10 for k in range(dim): 11 w_g[k] += (-1) * (y_train[j] - sig) * x_train[j, k] + 2 * reg_rate * weights[k]
2.3.4 參數更新
求出梯度后,再拿原參數減去梯度與學習率的乘積,即可實現參數的更新。
1 # num為樣本數量 2 b_g /= num 3 w_g /= num 4 5 # adagrad 6 bg2_sum += b_g**2 7 wg2_sum += w_g**2 8 9 # 更新權重和偏置 10 bias -= learning_rate/bg2_sum**0.5 * b_g 11 weights -= learning_rate/wg2_sum**0.5 * w_g
三、代碼分享與結果展示
3.1 源代碼

1 import pandas as pd 2 import numpy as np 3 4 5 # 更新參數,訓練模型 6 def train(x_train, y_train, epoch): 7 num = x_train.shape[0] 8 dim = x_train.shape[1] 9 bias = 0 # 偏置值初始化 10 weights = np.ones(dim) # 權重初始化 11 learning_rate = 1 # 初始學習率 12 reg_rate = 0.001 # 正則項系數 13 bg2_sum = 0 # 用於存放偏置值的梯度平方和 14 wg2_sum = np.zeros(dim) # 用於存放權重的梯度平方和 15 16 for i in range(epoch): 17 b_g = 0 18 w_g = np.zeros(dim) 19 # 在所有數據上計算梯度,梯度計算時針對損失函數求導 20 for j in range(num): 21 y_pre = weights.dot(x_train[j, :]) + bias 22 sig = 1 / (1 + np.exp(-y_pre)) 23 b_g += (-1) * (y_train[j] - sig) 24 for k in range(dim): 25 w_g[k] += (-1) * (y_train[j] - sig) * x_train[j, k] + 2 * reg_rate * weights[k] 26 b_g /= num 27 w_g /= num 28 29 # adagrad 30 bg2_sum += b_g ** 2 31 wg2_sum += w_g ** 2 32 # 更新權重和偏置 33 bias -= learning_rate / bg2_sum ** 0.5 * b_g 34 weights -= learning_rate / wg2_sum ** 0.5 * w_g 35 36 # 每訓練100輪,輸出一次在訓練集上的正確率 37 # 在計算loss時,由於涉及到log()運算,因此可能出現無窮大,計算並打印出來的loss為nan 38 # 有興趣的同學可以把下面涉及到loss運算的注釋去掉,觀察一波打印出的loss 39 if i % 3 == 0: 40 # loss = 0 41 acc = 0 42 result = np.zeros(num) 43 for j in range(num): 44 y_pre = weights.dot(x_train[j, :]) + bias 45 sig = 1 / (1 + np.exp(-y_pre)) 46 if sig >= 0.5: 47 result[j] = 1 48 else: 49 result[j] = 0 50 51 if result[j] == y_train[j]: 52 acc += 1.0 53 # loss += (-1) * (y_train[j] * np.log(sig) + (1 - y_train[j]) * np.log(1 - sig)) 54 # print('after {} epochs, the loss on train data is:'.format(i), loss / num) 55 print('after {} epochs, the acc on train data is:'.format(i), acc / num) 56 57 return weights, bias 58 59 60 # 驗證模型效果 61 def validate(x_val, y_val, weights, bias): 62 num = 500 63 # loss = 0 64 acc = 0 65 result = np.zeros(num) 66 for j in range(num): 67 y_pre = weights.dot(x_val[j, :]) + bias 68 sig = 1 / (1 + np.exp(-y_pre)) 69 if sig >= 0.5: 70 result[j] = 1 71 else: 72 result[j] = 0 73 74 if result[j] == y_val[j]: 75 acc += 1.0 76 # loss += (-1) * (y_val[j] * np.log(sig) + (1 - y_val[j]) * np.log(1 - sig)) 77 return acc / num 78 79 80 def main(): 81 # 從csv中讀取有用的信息 82 df = pd.read_csv('spam_train.csv') 83 # 空值填0 84 df = df.fillna(0) 85 # (4000, 59) 86 array = np.array(df) 87 # (4000, 57) 88 x = array[:, 1:-1] 89 # scale 90 x[:, -1] /= np.mean(x[:, -1]) 91 x[:, -2] /= np.mean(x[:, -2]) 92 # (4000, ) 93 y = array[:, -1] 94 95 # 划分訓練集與驗證集 96 x_train, x_val = x[0:3500, :], x[3500:4000, :] 97 y_train, y_val = y[0:3500], y[3500:4000] 98 99 epoch = 30 # 訓練輪數 100 # 開始訓練 101 w, b = train(x_train, y_train, epoch) 102 # 在驗證集上看效果 103 acc = validate(x_val, y_val, w, b) 104 print('The acc on val data is:', acc) 105 106 107 if __name__ == '__main__': 108 main()
3.2 結果展示
可以看出,在訓練30輪后,分類正確率能達到94%左右。
參考資料:
李宏毅老師機器學習課程視頻:https://www.bilibili.com/video/av10590361
李宏毅老師機器學習課程講義資料:http://speech.ee.ntu.edu.tw/~tlkagk/courses_ML17_2.html