Winner or Loser -------李宏毅機器學習HW2

作業說明

數據:train.csv  test.csv correct_answer.csv

資料:github李宏毅作業

收集到一些美國人的年齡、學歷、地區等資料給你,如果是訓練集,會在同一個csv中以‘income’告訴你這些人的工資收入是否超過50K。如果是測試集,工資收入會在correct_answer中,以Label形式告訴你是否爲“ >50K”。

train
correct_answer

我們要做的是,通過人的14項基本信息,預測他的工資是否上50K,從而判斷他是WInner or Loser。

數據清洗

我們在拿到數據的時候,其實可以看到數據集中有缺少的數據(在表格中以‘?’代替的數據)。如圖,我們可以直接刪除此行數據,這種方式叫做數據清洗。

train中數據不全

但是如果在訓練集中我們進行了數據清洗,其'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。

使用pd.get_dummies()之後的數據

這樣的數據我們還不能直接使用,因爲此案例中,訓練的列數會比測試的列數多一列: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)










 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章