作業說明
數據:train.csv test.csv correct_answer.csv
資料:github李宏毅作業
收集到一些美國人的年齡、學歷、地區等資料給你,如果是訓練集,會在同一個csv中以‘income’告訴你這些人的工資收入是否超過50K。如果是測試集,工資收入會在correct_answer中,以Label形式告訴你是否爲“ >50K”。
我們要做的是,通過人的14項基本信息,預測他的工資是否上50K,從而判斷他是WInner or Loser。
數據清洗
我們在拿到數據的時候,其實可以看到數據集中有缺少的數據(在表格中以‘?’代替的數據)。如圖,我們可以直接刪除此行數據,這種方式叫做數據清洗。
但是如果在訓練集中我們進行了數據清洗,其'income'的同時刪去,對訓練沒有壞的影響。但是在測試集中也有數據不全的部分,如果我們對test清洗了,那麼在test的人數會變少,就無法對於correct_answer中的答案。所以我們可以將test、correct_answer合併之後再做清洗。
數據分析
在機器學習中,我們能直接處理的都是數字,現在首要問題是將這些數據都轉換成一個個數值。我們該如何處理呢?先把表格中是數字、是字符的區分開吧,分爲A數據集與B數據集。
在B數據集中我們發現 'sex' 和 ‘income’ 這兩列都只有兩種結果,直接布爾化處理。
trainData['sex'] = (trainData['sex'] == 'male')
trainData['income'] = (trainData['income'] == ' >50K')
但是B數據集中其他數據我們就需要使用一個特別的函數,pd.get_dummies(B數據集)。這個函數的具體介紹參考:特徵提取之pd.get_dummies()。
這裏我們簡略的解釋一下,pd.get_dummies()就是將離散型特徵的每一種取值都看成一種狀態,若你的這一特徵中有N個不相同的取值,那麼我們就可以將該特徵抽象成N種不同的狀態,one-hot編碼保證了每一個取值只會使得一種狀態處於“激活態”,也就是說這N種狀態中只有一個狀態位值爲1,其他狀態位都是0。
B = pd.get_dummies(B) # 處理非字符的數據集
trainData = pd.concat([A, B], axis=1) # 將數據集A、B連接起來
在執行代碼之後,我們發現原先爲15列的trainData變成了100多列。這就是pd.get_dummies()導致的。他將一列的不同值也轉變成了單獨的一列。我們可以從下圖看到,這是我中途保存的csv。
這樣的數據我們還不能直接使用,因爲此案例中,訓練的列數會比測試的列數多一列:native_country_ Holand-Netherlands。我們要把此列刪除,使測試時參數數目對應。
trainData = trainData.drop(['native_country_ Holand-Netherlands'], axis=1).values
訓練
我們使用Logistic Regression邏輯迴歸設想這個問題。Logistic Regression和Linear Regression的差別主要在前者的輸出範圍是0-1,後者是任何值。我們可以這樣理解,其實Logistict Regression就是將Linear Regression的公式外面再套一個函數,導致其輸出在0-1之間。具體推導可以看李宏毅的機器學習視頻。
|
|
由圖右可知,我們先求出u1, u2, E1, E2(由E1, E2有權相加,得到E)。由於我不會打公式,都直接上圖吧。u與E的公式如下:。79是邏輯迴歸的兩分類中一份分類的數目,各自u、E的數目不一樣的。
此公式的訓練代碼
def g_train(X, Y):
# 我們需要u1, u2, E1, E2來計算 z=w*x+b的w、b
num = X.shape[0]
cnt1 = 0
cnt2 = 0
sum1 = np.zeros((X.shape[1],)) # (101,)
sum2 = np.zeros((X.shape[1],))
for i in range(num):
if Y[i] == 1:
sum1 += X[i]
cnt1 += 1
else:
sum2 += X[i]
cnt2 += 1
u1 = sum1 / cnt1
u2 = sum2 / cnt2 # 找到了平均值
E1 = np.zeros((X.shape[1], X.shape[1])) # (101, 101)
E2 = np.zeros((X.shape[1], X.shape[1])) # (101, 101)
for i in range(num):
if Y[i] == 1:
# E1 += np.dot(X[i] - u1, (X[i] - u1).T)
E1 += np.dot(np.transpose([X[i] - u1]), [X[i] - u1])
else:
# E2 += np.dot(X[i] - u2, (X[i] - u2).T)
E2 += np.dot(np.transpose([X[i] - u2]), [X[i] - u2])
E1 = E1 / float(cnt1)
E2 = E2 / float(cnt2)
E = E1 * (float(cnt1) / num) + E2 * (float(cnt2) / num)
# print ('findParams_U1', u1.shape, u1)
# print ('findParams_U2', u2.shape, u2)
# print ('findParams_E', E.shape, E)
return u1, u2, E, cnt1, cnt2
此公式的訓練代碼
def g_pridict(X, Y, u1, u2, E, N1, N2):
E_inv = inv(E) # 居然碰到奇異矩陣的問題
w = np.dot((u1 - u2), E_inv)
b = (-0.5) * np.dot(np.dot(u1.T, E_inv), u1) + (0.5) * np.dot(np.dot(u2.T, E_inv), u2) + np.log(float(N1)/N2)
z = np.dot(w, X.T) + b
y = sigmoid(z)
cnt1 = 0
cnt2 = 0
y = np.around(y)
for i in range(Y.shape[0]):
if y[i] == Y[i]:
cnt1 += 1
else:
cnt2 += 1
print ('[Generative]測試數據共', Y.shape[0], '個,判斷正確', cnt1, '個,判斷錯誤', cnt2, '個')
print ('準確率:', float(cnt1) / Y.shape[0]*100, '%')
return y
剩下我還使用了mini-batch SGD、Discriminative方式。效果如下:
所有代碼
#coding=utf-8
import numpy as np
from random import shuffle
from numpy.linalg import inv
from math import floor, log
import matplotlib.pyplot as plt
import os
import argparse
import pandas as pd
dir = 'F:/machine/HW2/data'
def washData(pathData, pathAnswer='Nothing'):
# 14個屬性+收入屬性
# 數據清洗
df_x = pd.read_csv(pathData)
# 在執行清洗之前,合併數據和答案,方便將行數據對應清洗
if(pathAnswer != 'Nothing'): # 表示是測試數據,真的有pathAnswer
df_ans = pd.read_csv(pathAnswer)
df_x = pd.concat([df_x, df_ans['label']], axis=1) # 注意訓練集裏面列名是'income', 這裏是'label'
df_x.rename(columns={'label': 'income'}, inplace=True) # label -> income
else :
df_x['income'] = (df_x['income'] == ' >50K')
df_x = df_x.replace(' ?', np.nan) # 將數據中存在'?'的行用NAN替代
df_x = df_x.dropna() # 將含有NAN的行刪除
# 修改性別項 和 分離income項
df_x['sex'] = (df_x['sex'] == 'male')
data_y = df_x[['income']].astype(np.int64) # df_x[[]]兩重括號才能保持其DataFrame屬性, 一重括號data_y變成Series屬性
del df_x['income']
# 將數據分成數字和非數字 兩部分
object_columns = [col for col in df_x.columns if df_x[col].dtypes == "object"] # 陷阱:in df.columns可以,in range(df.columns)不行
no_object_columns = [col for col in df_x.columns if df_x[col].dtypes == 'int64']
object_data = df_x[object_columns]
no_object_data = df_x[no_object_columns]
# set every element in object rows as an attribute
object_data = pd.get_dummies(object_data) # 走到這一步其實很多列映射的值都一樣
# 將數字部分和非數字部分都合併起來,還是我們的數據集
data_x = pd.concat([no_object_data, object_data], axis=1)
data_x = data_x.astype(np.int64)
# 數據都變成了一些數字
data_x = (data_x - data_x.mean()) / data_x.std()
if pathAnswer == 'Nothing': # 對比train.csv和test.csv發現如下項對應不了,故train.csv中獲取的此元素刪掉
del data_x['native_country_ Holand-Netherlands']
return data_x.values, data_y.values # 分別爲14列、1列 # 這.values是陷阱啊!!!沒有要不得,findParams會取不出數字的
def sigmoid(z):
z = 1 / (1.0 + np.exp(-z))
return z
def g_train(X, Y):
# 我們需要u1, u2, E1, E2來計算 z=w*x+b的w、b
num = X.shape[0]
cnt1 = 0
cnt2 = 0
sum1 = np.zeros((X.shape[1],)) # (101,)
sum2 = np.zeros((X.shape[1],))
for i in range(num):
if Y[i] == 1:
sum1 += X[i]
cnt1 += 1
else:
sum2 += X[i]
cnt2 += 1
u1 = sum1 / cnt1
u2 = sum2 / cnt2 # 找到了平均值
E1 = np.zeros((X.shape[1], X.shape[1])) # (101, 101)
E2 = np.zeros((X.shape[1], X.shape[1])) # (101, 101)
for i in range(num):
if Y[i] == 1:
# E1 += np.dot(X[i] - u1, (X[i] - u1).T)
E1 += np.dot(np.transpose([X[i] - u1]), [X[i] - u1])
else:
# E2 += np.dot(X[i] - u2, (X[i] - u2).T)
E2 += np.dot(np.transpose([X[i] - u2]), [X[i] - u2])
E1 = E1 / float(cnt1)
E2 = E2 / float(cnt2)
E = E1 * (float(cnt1) / num) + E2 * (float(cnt2) / num)
# print ('findParams_U1', u1.shape, u1)
# print ('findParams_U2', u2.shape, u2)
# print ('findParams_E', E.shape, E)
return u1, u2, E, cnt1, cnt2
def g_pridict(X, Y, u1, u2, E, N1, N2):
E_inv = inv(E) # 居然碰到奇異矩陣的問題
w = np.dot((u1 - u2), E_inv)
b = (-0.5) * np.dot(np.dot(u1.T, E_inv), u1) + (0.5) * np.dot(np.dot(u2.T, E_inv), u2) + np.log(float(N1)/N2)
z = np.dot(w, X.T) + b
y = sigmoid(z)
cnt1 = 0
cnt2 = 0
y = np.around(y)
for i in range(Y.shape[0]):
if y[i] == Y[i]:
cnt1 += 1
else:
cnt2 += 1
print ('[Generative]測試數據共', Y.shape[0], '個,判斷正確', cnt1, '個,判斷錯誤', cnt2, '個')
print ('準確率:', float(cnt1) / Y.shape[0]*100, '%')
return y
def sgd_train(X, Y, batchSize=300, eta=0.0001, lambdaL2=0.0): # 用梯度求這個,是錯誤方向?
w = np.zeros(X.shape[1]) # (101, )
b = 0.0
list_cost = []
for i in range(0, X.shape[0] // batchSize * batchSize, batchSize):
batch = X[i:i + batchSize, :] # (30, 101)
y_ = np.squeeze(Y[i: i + batchSize]) # 這個函數是針對 np.dot的。
hypo = np.dot(batch, w) # 按公式獲取了預測值, 結果是(30, )
hypo = np.around(hypo) # 上行算出來的是小數,我們要二分類
loss = hypo - y_ # (30, )
cost = np.sum(loss**2) / (2.0 * batchSize)
list_cost.append(cost)
grad = np.sum(np.dot(loss.T, batch)) / batchSize
lambdaL2 = np.sum(loss) / batchSize
w = w - eta * grad
b = b - eta * lambdaL2
# print (list_cost) # 並沒有逐漸變小的結果
return w, b
def sgd_predict(X, Y, w, b):
y = np.dot(X, w) + b
# y = sigmoid(y)
y = np.around(y)
cnt1 = 0
cnt2 = 0
for i in range(Y.shape[0]):
if y[i] == Y[i]:
cnt1 += 1
else:
cnt2 += 1
print ('[mini-batch-SGD]預測數據共', Y.shape[0], '個,判斷正確', cnt1, '個,判斷錯誤', cnt2, '個')
print ('準確率:', float(cnt1) / Y.shape[0] * 100, '%')
def lr_train(X, Y, batchSize=300, eta=0.001, lambdaL2=0.0):
# 使用最大似然函數求解
w = np.zeros(X.shape[1])
b = 0.0
list_cost = []
for i in range(0, X.shape[0] // batchSize * batchSize, batchSize):
batch = X[i:i + batchSize, :] # (30, 101)
y_ = np.squeeze(Y[i: i + batchSize])
z = np.dot(batch, w) + b
y = sigmoid(z)
loss = y - y_
# 計算交叉熵, 由於是矩陣(,101)*(101,)=標量,所以巧妙求和了
cross_entropy = (-1) * (np.dot(y_.T, np.log(y)) + np.dot((1 - y_.T), np.log(1 - y))) # 存在log(0)的情況
list_cost.append(cross_entropy)
w = w - eta * np.dot(batch.T, loss)
b = b - eta * (np.sum(loss) / batchSize)
return w, b
def lr_pridect(X, Y, w, b):
z = np.dot(X, w) + b
y = sigmoid(z)
y = np.around(y)
cnt1 = 0
cnt2 = 0
for i in range(Y.shape[0]):
if y[i] == Y[i]:
cnt1 += 1
else :
cnt2 += 1
print ('[Logistic Regression]預測數據共', Y.shape[0], '個,判斷正確', cnt1, '個,判斷錯誤', cnt2, '個')
print ('準確率:', float(cnt1) / Y.shape[0] * 100, '%')
trainX, trainY = washData(dir+'/train.csv') # trainX是DataFrame(30162, 101) (30162,)
testX, testY = washData(dir+'/test.csv', dir+'/correct_answer.csv') # (15060, 101) (15060,)
# Generative 公式的方法
u1, u2, E, N1, N2 = g_train(trainX, trainY)
my_ans = g_pridict(testX, testY, u1, u2, E, N1, N2)
np.savetxt(dir+'/my_ans_1.csv', my_ans)
# mini_batch SGD 這個方向是低正確率的
w, b = sgd_train(trainX, trainY)
sgd_predict(testX, testY, w, b)
#
w, b = lr_train(trainX, trainY)
lr_pridect(testX, testY, w, b)
總結
此次學習,重點在數據分析,這方面學到了很多,能一個函數處理原始訓練數據、原始測試數據與其答案。現在自己在提升準確率方面還很單純,不瞭解該怎麼做,每次只有跟着公式敲敲打打;對於矩陣相乘方面,還沒有掌握於心,容易犯迷糊。在寫博客時,自己雖想講得清楚明白,但心中沒有講解需要的誠懇,這是自己博客的很大缺失,希望改進。
下面是我對助教的代碼的註釋,大家可以參考一下:
#coding=utf-8
import numpy as np
from random import shuffle
from numpy.linalg import inv
from math import floor, log
import os
import argparse
import pandas as pd
output_dir = "output/"
def dataProcess_X(rawData):
# sex 只有兩個屬性 先drop之後處理
if "income" in rawData.columns: # if in 是訓練集
Data = rawData.drop(["sex", 'income'], axis=1)
else: # 是測試集
Data = rawData.drop(["sex"], axis=1)
listObjectColumn = [col for col in Data.columns if Data[col].dtypes == "object"] #讀取非數字的column
listNonObjedtColumn = [x for x in list(Data) if x not in listObjectColumn] #數字的column
ObjectData = Data[listObjectColumn]
NonObjectData = Data[listNonObjedtColumn]
#insert set into nonobject data with male = 0 and female = 1
NonObjectData.insert(0 ,"sex", (rawData["sex"] == " Female").astype(np.int))
#set every element in object rows as an attribute
# print('編碼前:', ObjectData) -------------------
ObjectData = pd.get_dummies(ObjectData)
# print('編碼後:', ObjectData) -------------------
Data = pd.concat([NonObjectData, ObjectData], axis=1) # 列相連接、並列
# print('列名:', Data.columns) # 原本數字的在前,字符的在後,sex是第一個
Data_x = Data.astype("int64")
# Data_y = (rawData["income"] == " <=50K").astype(np.int)
#normalize
# pandas.std() 計算的是樣本標準偏差,默認ddof = 1。如果我們知道所有的分數,那麼我們就有了總體
# ——因此,要使用 pandas 進行歸一化處理,我們需要將“ddof”設置爲 0。
Data_x = (Data_x - Data_x.mean()) / Data_x.std() # pandas.mean()求每一列自己的平均值
## 保存數字型數據,通過分析此文件,發現它將原來列屬性中不同的值分成了新的列。所以文件的列數激增。
# if "income" in rawData.columns:
# Data_x.to_csv('F:/machine/HW2/dta/train_num_data.csv')
# 疑惑,目前沒有進行數據清洗,即數據不全處。
return Data_x
def dataProcess_Y(rawData):
df_y = rawData['income'] # 太帥了這個用法, 並且使用的時候我們可以不轉換爲數組
Data_y = pd.DataFrame((df_y==' >50K').astype("int64"), columns=["income"])
return Data_y
def sigmoid(z):
res = 1 / (1.0 + np.exp(-z)) # 整體的函數
return np.clip(res, 1e-8, (1-(1e-8))) # (輸入的數組,限定的最小值,限定的最大值)
def _shuffle(X, Y): #X and Y are np.array
randomize = np.arange(X.shape[0]) # [0-32561)
np.random.shuffle(randomize) # 洗牌,打亂順序
return (X[randomize], Y[randomize])
def split_valid_set(X, Y, percentage):
all_size = X.shape[0] # 32561
valid_size = int(floor(all_size * percentage)) # 3256
X, Y = _shuffle(X, Y) # 將數據打亂
# 將數據分成 percentage: 1-percentage兩部分
X_valid, Y_valid = X[ : valid_size], Y[ : valid_size]
X_train, Y_train = X[valid_size:], Y[valid_size:]
return X_train, Y_train, X_valid, Y_valid
def valid(X, Y, mu1, mu2, shared_sigma, N1, N2):
sigma_inv = inv(shared_sigma) # 矩陣求逆
w = np.dot((mu1-mu2), sigma_inv)
X_t = X.T
b = (-0.5) * np.dot(np.dot(mu1.T, sigma_inv), mu1) + (0.5) * np.dot(np.dot(mu2.T, sigma_inv), mu2) + np.log(float(N1)/N2)
a = np.dot(w,X_t) + b # 唉,弄了半天,代碼沒有問題,只不過w在PPT上用的是wT這個名字,但是意義是一樣的。
y = sigmoid(a) # a就是線性裏面的y了,在邏輯迴歸裏面只不過套了哥函數,將其分佈改成0-1之間
y_ = np.around(y) # 四捨五入的值,即二分類
# squeeze()將維度裏面爲1的值維度去掉。Y(3256,1) y_(3256,)
result = (np.squeeze(Y) == y_) # result(3256,) [true or false]
# 我訓練集的前半部分得出的函數,對後半部分的測試成功率
print('Valid acc = %f' % (float(result.sum()) / result.shape[0]))
return
def train(X_train, Y_train):
# vaild_set_percetange = 0.1
# X_train, Y_train, X_valid, Y_valid = split_valid_set(X, Y, vaild_set_percetange)
#Gussian distribution parameters
train_data_size = X_train.shape[0]
cnt1 = 0
cnt2 = 0
mu1 = np.zeros((X_train.shape[1],))
mu2 = np.zeros((X_train.shape[1],))
for i in range(train_data_size):
if Y_train[i] == 1: # >50k
mu1 += X_train[i]
cnt1 += 1
else:
mu2 += X_train[i]
cnt2 += 1
mu1 /= cnt1 # 均值U
mu2 /= cnt2
sigma1 = np.zeros((X_train.shape[1], X_train.shape[1])) # (106,106)
sigma2 = np.zeros((X_train.shape[1], X_train.shape[1]))
for i in range(train_data_size):
if Y_train[i] == 1:
# sigma1 += np.dot(np.transpose([X_train[i] - mu1]), [X_train[i] - mu1]) # 分佈∑1 # 公式有誤??
sigma1 += np.dot(np.transpose([X_train[i] - mu1]), [X_train[i] - mu1]) # 分佈∑1 # 公式有誤??
else:
sigma2 += np.dot(np.transpose([X_train[i] - mu2]), [X_train[i] - mu2]) # 分佈∑2
sigma1 /= cnt1
sigma2 /= cnt2
shared_sigma = (float(cnt1) / train_data_size) * sigma1 + (float(cnt2) / train_data_size) * sigma2 # 分佈∑
N1 = cnt1
N2 = cnt2
return mu1, mu2, shared_sigma, N1, N2 # 現在將公式的參數全部求出了
if __name__ == "__main__":
trainData = pd.read_csv("F:/machine/HW2/data/train.csv") # 第一行會作爲列名 (32561,15)
testData = pd.read_csv("F:/machine/HW2/data/test.csv") # (16281,14)沒有數據
ans = pd.read_csv("F:/machine/HW2/data/correct_answer.csv") # (16281, 2) 2 = id + label
#here is one more attribute in trainData
# 刪除訓練集中 有['native_country_ Holand-Netherlands']的那一列, 因爲測試集裏面無此國家項,即無此列
x_train = dataProcess_X(trainData).drop(['native_country_ Holand-Netherlands'], axis=1).values # (32561,107-1)
x_test = dataProcess_X(testData).values # (16281,106)
y_train = dataProcess_Y(trainData).values # (32561,1)
y_ans = ans['label'].values # (16281,)if 達到50K then 1 else 0 answer for test
vaild_set_percetage = 0.1
X_train, Y_train, X_valid, Y_valid = split_valid_set(x_train, y_train, vaild_set_percetage) # 返回的是打亂了、分割了的數據
mu1, mu2, shared_sigma, N1, N2 = train(X_train, Y_train)
valid(X_valid, Y_valid, mu1, mu2, shared_sigma, N1, N2)
mu1, mu2, shared_sigma, N1, N2 = train(x_train, y_train) # 開始對整個訓練集訓練
sigma_inv = inv(shared_sigma)
w = np.dot((mu1 - mu2), sigma_inv)
X_t = x_test.T
b = (-0.5) * np.dot(np.dot(mu1.T, sigma_inv), mu1) + (0.5) * np.dot(np.dot(mu2.T, sigma_inv), mu2) + np.log(
float(N1) / N2)
a = np.dot(w, X_t) + b
y = sigmoid(a)
y_ = np.around(y).astype(np.int)
df = pd.DataFrame({"id" : np.arange(1,16282), "label": y_})
result = (np.squeeze(y_ans) == y_)
print('Test acc = %f' % (float(result.sum()) / result.shape[0]))
df = pd.DataFrame({"id": np.arange(1, 16282), "label": y_})
if not os.path.exists(output_dir):
os.mkdir(output_dir)
df.to_csv(os.path.join(output_dir+'gd_output.csv'), sep='\t', index=False)