李宏毅机器学习--课后作业HW_1
看了李宏毅老師講的機器學習課程,真的是受益匪淺,老師講課非常有意思,不是空洞的講數學公式,以及抽象的理論,而是通過在課堂上加入游戲元素來引導大家學習,我個人覺得這種方式是很好的,而且原理講的也很清楚,適合小白學習,這里有B站的視頻鏈接,想學習的可以去看一下【李宏毅《機器學習》-嗶哩嗶哩】,可以配著學習筆記學(李宏毅機器學習筆記,來自于Datawhale的學習筆記,這個平臺也是國內一個很好的學習AI的開源平臺,有興趣的可以去關注下),本文借鑒了很多博主的博客,大部分內容來自于博主永緣客,并結合自己理解的,寫一篇博客記錄一下,水平有限,如果有任何錯誤或者問題,歡迎指正或者交流。
先看一下作業是什么。
作業描述
讓機器預測到豐原站在下一個小時會觀測到的PM2.5。舉例來說,現在是2017-09-29 08:00:00 ,那么要預測2017-09-29 09:00:00豐原站的PM2.5值會是多少。
數據介紹: 本次作業使用豐原站的觀測記錄,分成?train set?跟?test set,train set 是豐原站每個月的前20天所有資料,test set則是從豐原站剩下的資料中取樣出來。?train.csv:每個月前20天每個小時的氣象資料(每小時有18種測資)。共12個月。?test.csv:從剩下的資料當中取樣出連續的10小時為一筆,前九小時的所有觀測數據當作feature,第十小時的PM2.5當作answer。一共取出240筆不重複的 test data,請根據feature預測這240筆的PM2.5。
Sample_code:leeml-notes/docs/Homework/HW_1 at master · datawhalechina/leeml-notes · GitHub,數據下載鏈接,有原始數據以及結果
打開train.csv文件,如下:
?
第一行是說明,分別是時間,地點,物質的名稱,以及0-23時他們的濃度。
注意RAINFALL參數的值是NR(No?Rain),表示沒有下雨,后面可以將它的值改為0便于處理。?
?打開test.csv文件,如下:
1.解讀一下題目:
Dataset里面我們要用的兩個csv文件:數據集(train.csv)和測試集(test.csv)。
大概的內容是數據集中記錄了12個月的前20天每天24個小時的18個物質濃度的資料,然后測試集就是剩余的數據中再取的。從上述我們可以得到以下結論:
- ?模型的輸入是前9個小時的所有觀測數據,即9*18的參數值,
- ?模型輸出是一個值表示預測的第10個小時的PM2.5含量。
?所以最適合的模型是Regression model(回歸模型)。 - 根據作業要求可知,需要用到連續9個時間點的氣象觀測數據,來預測第10個時間點的PM2.5含量。針對每一天來說,其包含的信息維度為(18,24)(18項指標,24個時間節點)。可以將0到8時的數據截取出來,形成一個維度為(18,9)的數據幀,作為訓練數據,將9時的PM2.5含量取出來,作為該訓練數據對應的label;同理可取1到9時的數據作為訓練用的數據幀,10時的PM2.5含量作為label......以此分割,可將每天的信息分割為15個shape為(18,9)的數據幀和與之對應的15個label,來自博主秋沐霖。我們可以用滑動窗口的思想,需要將一個月的第一天到第二十天橫向排序,取大小為9的窗口,從第一天的第0時一直可以劃到第20天的第14時,這樣每個月的數據量就會多9個,這樣積累的話使整個數據量就會更多。
圖來自博主秋沐霖。
?2.數據預處理:
我們把題目搞清楚以后就開始對要訓練的數據(train.csv)進行預處理,因為在python中數據是通過矩陣來保存的,所以我們第一步就是刪減掉不需要的行與列,然后將其保存到矩陣中。
# 引入必要的包 import pandas as pd import numpy as np# 讀取數據保存到data中,路徑根據你保存的train.csv位置而有變化 data = pd.read_csv('./dataset/train.csv', encoding='utf-8') # print(data)# 行保留所有,列從第三列開始往后才保留,這樣去除了數據中的時間、地點、參數等信息 data = data.iloc[:, 3:] # print(data)# 將所有NR的值全部置為0方便之后處理 data[data == 'NR'] = 0 # print(data)# 將data的所有數據轉換為二維數據并用raw_data來保存 raw_data = data.to_numpy() # print(raw_data) # 可以每一步都打印出結果,看到數據的變化接下來,將每個月的數據按天數橫著排放在一起。
month_data = {}# month 從0-11 共12個月 # 返回一個18行480列的數組,保存一個月的data(一月20天,一天24小時) # day從0-19 共20天 for month in range(12):sample = np.empty([18, 480])for day in range(20):# raw的行每次取18行,列取全部列。送到sample中(sample是18行480列)# 行給全部行,列只給24列,然后列往后增加sample[:, day * 24: (day + 1) * 24] = raw_data[18 * (20 * month + day): 18 * (20 * month + day + 1),:]month_data[month] = sample這樣每個月的數據就是18行480(24*20)列,一共12個月的數據
然后繼續對上述數據進行提取:
上述所有工作做完之后,X就是包含所有數據的數組,Y就是所有數據的結果,但此時還不可以進行訓練,還需要兩個步驟才可以。
3.Normalize(標準化)與訓練集分類
數據的標準化(normalization)是將數據按比例縮放,使之落入一個小的特定區間。
在某些比較和評價的指標處理中經常會用到,去除數據的單位限制,將其轉化為無量綱的純數值,
便于不同單位或量級的指標能夠進行比較和加權。
最常見的標準化方法就是Z標準化,也是SPSS中最為常用的標準化方法,spss默認的標準化方法就是z-score標準化。
也叫標準差標準化,這種方法給予原始數據的均值(mean)和標準差(standard deviation)進行數據的標準化。
經過處理的數據符合標準正態分布,即均值為0,標準差為1,注意,一般來說z-score不是歸一化,而是標準化,歸一化只是標準化的一種[lz]。
其轉化函數為:
x* = (x - μ ) / σ
其中μ為所有樣本數據的均值,σ為所有樣本數據的標準差。
詳細的可以參考這篇博客數據預處理-歸一化/數據轉換。
mean_x = np.mean(x, axis=0) # 18 * 9 求均值,axis = 0表示對各列求值,返回 1* 列數 的矩陣 std_x = np.std(x, axis=0) # 18 * 9 求標準差,axis = 0表示對各列求值,返回 1* 列數 的矩陣 for i in range(len(x)): # 12 * 471for j in range(len(x[0])): # 18 * 9if std_x[j] != 0:x[i][j] = (x[i][j] - mean_x[j]) / std_x[j]接下來是將訓練數據按8:2拆成訓練數據和驗證數據。這樣的好處是因為最終只給我們test data的輸入而沒有給我們輸出,所以我們無法定量我們模型的好壞,而使用驗證數據可以簡單驗證我們模型的好壞,讓我們自己心里有數。
# 將訓練數據拆成訓練數據:驗證數據=8:2,這樣用來驗證 import mathx_train_set = x[: math.floor(len(x) * 0.8), :] y_train_set = y[: math.floor(len(y) * 0.8), :] x_validation = x[math.floor(len(x) * 0.8):, :] y_validation = y[math.floor(len(y) * 0.8):, :] print(x_train_set) print(y_train_set) print(x_validation) print(y_validation) print(len(x_train_set)) print(len(y_train_set)) print(len(x_validation)) print(len(y_validation))?運行以后結果如下:
?可以看到訓練的數據是:4521,驗證的數據是:1131。
4.訓練模型
4.1 回歸模型
采用最普通的線性回歸模型,并沒有用上訓練集中所有的數據,只用到了每個數據幀樣本中的9個PM2.5含量值:
xi為對應數據幀中第i個PM2.5含量,wi為其對應的權重值,b為偏置項,y為該數據幀樣本的預測結果。
4.2 模型訓練
對數據做好處理以后,我們開始訓練我們的模型,使用的是Loss Function(損失函數)和GD(Gradient Descent梯度下降)算法。
# 用來做參數vector的維數,加1是為了對bias好處理(還有個誤差項)。即h(x)=w1x1+w2x2+''+wnxn+b dim = 18 * 9 + 1 # 生成一個dim行1列的數組用來保存參數值 w = np.ones([dim, 1]) # np.ones來生成12*471行1列的全1數組,np.concatenate,axis=1 # 表示按列將兩個數組拼接起來,即在x最前面新加一列內容,之前x是12*471行 # 18*9列的數組,新加一列之后變為12*471行18*9+1列的數組''' x = np.concatenate((np.ones([12 * 471, 1]), x), axis=1).astype(float) learning_rate = 100 # 學習率 iter_time = 10000 # 迭代次數# 生成dim行即163行1列的數組,用來使用adagrad算法更新學習率 adagrad = np.zeros([dim, 1])# 因為新的學習率是learning_rate/sqrt(sum_of_pre_grads**2), # 而adagrad=sum_of_grads**2,所以處在分母上而迭代時adagrad可能為0, # 所以加上一個極小數,使其不除0 eps = 0.0000000001 for t in range(iter_time):# rmse loss函數是從0-n的(X*W-Y)**2之和/(471*12)再開根號,# 即使用均方根誤差(root mean square error),具體可看公式,# /471/12即/N(次數)'''loss = np.sqrt(np.sum(np.power(np.dot(x, w) - y, 2)) / 471 / 12) if (t % 100 == 0): # 每一百次迭代就輸出其損失print(str(t) + ":" + str(loss))# dim*1 x.transpose即x的轉置,后面是X*W-Y,即2*(x的轉置*(X*W-Y))是梯度,# 具體可由h(x)求偏微分獲得.最后生成1行18*9+1列的數組。轉置后的X,其每一行# 是一個參數,與h(x)-y的值相乘之后是參數W0的修正值,同理可得W0-Wn的修正值# 保存到1行18*9+1列的數組中,即gradientgradient = 2 * np.dot(x.transpose(), np.dot(x, w) - y)# adagrad用于保存前面使用到的所有gradient的平方,進而在更新時用于調整學習率adagrad += gradient ** 2 w = w - learning_rate * gradient / np.sqrt(adagrad + eps) # 更新權重 np.save('weight.npy', w) # 將參數保存下來注意的是,loss function我們選擇的是均方根誤差(Root Mean Square Error),即下圖所示的公式:
至于學習率,最簡單的辦法就是固定學習率,每次迭代的學習率都相同,但這樣效果也會很差,良好的學習率應隨迭代次數依次減少,所以我們使用自適應學習率的adagrad算法(想了解更清楚的可以去看一下李宏毅老師的課程,講的很詳細),即每次學習率都等于其除以之前所有的梯度平方和再開根號,即下圖所示的公式:?
????????
gradient就是一個對Loss函數進行每個參數的偏微分而得到的矩陣。?
5.載入驗證集進行驗證
w = np.load('weight.npy') # 使用x_validation和y_validation來計算模型的準確率,因為X已經normalize了, # 所以不需要再來一遍,只需在x_validation上添加新的一列作為bias的乘數即可 x_validation = np.concatenate((np.ones([1131, 1]), x_validation), axis=1).astype(float) ans_y = np.dot(x_validation, w) loss = np.sqrt(np.sum(np.power(ans_y - y_validation, 2)) / 1131) print(loss)細心的你肯定注意到了,我在第四步進行訓練使用的訓練集是整個X,而沒使用我分開好的x_train_set,這是因為使用了整個X訓練之后就直接可以預測testdata中的值并輸出了,而使用x_train_set進行訓練再使用x_validation和y_validation進行驗證是為了預估模型的好壞。但我還是使用了兩種訓練集分別進行訓練,并都用x_validation和y_validation進行驗證,結果如下:
- ?使用全部X進行訓練,并通過x_validation和y_validation進行驗證,結果是
訓練集中最后一次迭代的loss為5.68 測試集中loss為5.42
但上述情況是因為x_validation本身包括在訓練集中,所以loss比下面方法更低在意料之中
- ?使用x_train_set進行訓練,并通過x_validation和y_validation進行驗證,結果是
訓練集中最后一次迭代的loss為5.72 測試集中loss為5.66
這樣看來,我們的模型還算可以,所以就可以進行最后一步預測test data了
還需要補充的是,loss function一定和訓練的loss function一致才可以通過比較它倆loss大小來評估模型好壞。然后就是如果使用x_train_set進行訓練時,將訓練步驟的X全部換成x_train_set,并且在”/471/12”的時候除的是x_validation的大小,即/4521
6.預測testdata得到預測結果
testdata = pd.read_csv('./dataset/test.csv', header=None, encoding='utf-8') test_data = testdata.iloc[:, 2:] # 取csv文件中的全行數即第3列到結束的列數所包含的數據 test_data[test_data == 'NR'] = 0 # 將testdata中的NR替換為0 test_data = test_data.to_numpy() # 將其轉換為數組 # 創建一個240行18*9列的空數列用于保存textdata的輸入 test_x = np.empty([240, 18 * 9], dtype=float) for i in range(240): # 共240個測試輸入數據test_x[i, :] = test_data[18 * i: 18 * (i + 1), :].reshape(1, -1) # 下面是Normalize,且必須跟training data是同一種方法進行Normalize for i in range(len(test_x)):for j in range(len(test_x[0])):if std_x[j] != 0:test_x[i][j] = (test_x[i][j] - mean_x[j]) / std_x[j] # 在test_x前面拼接一列全1數組,構成240行,163列數據 test_x = np.concatenate((np.ones([240, 1]), test_x), axis=1).astype(float) # 進行預測 w = np.load('weight.npy') ans_y = np.dot(test_x, w) # test data的預測值ans_y=test_x與W的積 # 將預測結果填入文件當中 import csvwith open('submit.csv', mode='w', newline='') as submit_file:csv_writer = csv.writer(submit_file)header = ['id', 'value']print(header)csv_writer.writerow(header)for i in range(240):row = ['id_' + str(i), ans_y[i][0]]csv_writer.writerow(row)print(row)以上所有代碼都是在Jupyter里面運行的,我自己都跑了一遍,沒有問題。自己又在VS Code里面跑了也可以正常運行。?
在VS Code里面跑的全部代碼:
import pandas as pd import numpy as np import math import csv# 讀取數據保存到data中,路徑根據你保存的train.csv位置而有變化 data = pd.read_csv('~/GitHub/leeml-notes/docs/Homework/HW_1/dataset/train.csv', encoding='utf-8') # print(data)# 行保留所有,列從第三列開始往后才保留,這樣去除了數據中的時間、地點、參數等信息 data = data.iloc[:, 3:] # print(data)# 將所有NR的值全部置為0方便之后處理 data[data == 'NR'] = 0 # print(data)# 將data的所有數據轉換為二維數據并用raw_data來保存 raw_data = data.to_numpy() # print(raw_data) # 可以每一步都打印出結果,看到數據的變化 month_data = {}# month 從0-11 共12個月 # 返回一個18行480列的數組,保存一個月的data(一月20天,一天24小時) # day從0-19 共20天 for month in range(12):sample = np.empty([18, 480])for day in range(20):# raw的行每次取18行,列取全部列。送到sample中(sample是18行480列)# 行給全部行,列只給24列,然后列往后增加sample[:, day * 24: (day + 1) * 24] = raw_data[18 * (20 * month + day): 18 * (20 * month + day + 1),:]month_data[month] = sample # 一共480個小時,每9個小時一個數據(480列最后一列不可以計入,因為如果取到最后一行那么最后一個數據 # 便沒有了結果{需要9個小時的輸入和第10個小時的第10行作為結果}),480-1-9+1=471。 # 471*12個數據集按行排列,每一行一個數據;數據每小時有18個特征,而每個數據9個小時,共18*9列 x = np.empty([12 * 471, 18 * 9], dtype=float)# 結果是471*12個數據,每個數據對應一個結果,即第10小時的PM2.5濃度 y = np.empty([12 * 471, 1], dtype=float) for month in range(12): # month 0-11for day in range(20): # day 0-19for hour in range(24): # hour 0-23if day == 19 and hour > 14: # 取到行18,列9的塊后,就不可再取了continue# 取對應month:行都要取,列取9個,依次進行,最后將整個數據reshape成一行數據(列數無所謂)。然后賦給x,x內的坐標只是為了保證其從0-471*12# vector dim:18*9# value,結果對應的行數一直是第9列(即第10行PM2.5)然后列數隨著取得數據依次往后進行x[month * 471 + day * 24 + hour, :] = month_data[month][:, day * 24 + hour: day * 24 + hour + 9].reshape(1, -1)y[month * 471 + day * 24 + hour, 0] = month_data[month][9, day * 24 + hour + 9]mean_x = np.mean(x, axis=0) # 18 * 9 求均值,axis = 0表示對各列求均值,返回 1* 列數 的矩陣 std_x = np.std(x, axis=0) # 18 * 9 求標準差,axis = 0表示對各列求均值,返回 1* 列數 的矩陣 for i in range(len(x)): # 12 * 471for j in range(len(x[0])): # 18 * 9if std_x[j] != 0:x[i][j] = (x[i][j] - mean_x[j]) / std_x[j] # 將訓練數據拆成訓練數據:驗證數據=8:2,這樣用來驗證 x_train_set = x[: math.floor(len(x) * 0.8), :] y_train_set = y[: math.floor(len(y) * 0.8), :] x_validation = x[math.floor(len(x) * 0.8):, :] y_validation = y[math.floor(len(y) * 0.8):, :] print(x_train_set) print(y_train_set) print(x_validation) print(y_validation) print(len(x_train_set)) print(len(y_train_set)) print(len(x_validation)) print(len(y_validation)) # 用來做參數vector的維數,加1是為了對bias好處理(還有個誤差項)。即h(x)=w1x1+w2x2+''+wnxn+b dim = 18 * 9 + 1 # 生成一個dim行1列的數組用來保存參數值,對比源碼我這里改成了ones而不是zeros w = np.ones([dim, 1]) '''np.ones來生成12*471行1列的全1數組,np.concatenate,axis=1 表示按列將兩個數組拼接起來,即在x最前面新加一列內容,之前x是12*471行 18*9列的數組,新加一列之后變為12*471行18*9+1列的數組''' x = np.concatenate((np.ones([12 * 471, 1]), x), axis=1).astype(float) learning_rate = 100 # 學習率 iter_time = 10000 # 迭代次數 adagrad = np.zeros([dim, 1]) # 生成dim行即163行1列的數組,用來使用adagrad算法更新學習率'''因為新的學習率是learning_rate/sqrt(sum_of_pre_grads**2), 而adagrad=sum_of_grads**2,所以處在分母上而迭代時adagrad可能為0, 所以加上一個極小數,使其不除0''' eps = 0.0000000001 for t in range(iter_time):'''rmse loss函數是從0-n的(X*W-Y)**2之和/(471*12)再開根號,即使用均方根誤差(root mean square error),具體可百度其公式,/471/12即/N(次數)'''loss = np.sqrt(np.sum(np.power(np.dot(x, w) - y,2)) / 471 / 12) if (t % 100 == 0): # 每一百次迭代就輸出其損失print(str(t) + ":" + str(loss))'''dim*1 x.transpose即x的轉置,后面是X*W-Y,即2*(x的轉置*(X*W-Y))是梯度,具體可由h(x)求偏微分獲得.最后生成1行18*9+1列的數組。轉置后的X,其每一行是一個參數,與h(x)-y的值相乘之后是參數W0的修正值,同理可得W0-Wn的修正值保存到1行18*9+1列的數組中,即gradient'''gradient = 2 * np.dot(x.transpose(), np.dot(x,w) - y)# adagrad用于保存前面使用到的所有gradient的平方,進而在更新時用于調整學習率adagrad += gradient ** 2 w = w - learning_rate * gradient / np.sqrt(adagrad + eps) # 更新權重 np.save('weight.npy', w) # 將參數保存下來w = np.load('weight.npy') # 使用x_validation和y_validation來計算模型的準確率,因為X已經normalize了, # 所以不需要再來一遍,只需在x_validation上添加新的一列作為bias的乘數即可 x_validation = np.concatenate((np.ones([1131, 1]), x_validation), axis=1).astype(float) ans_y = np.dot(x_validation, w) loss = np.sqrt(np.sum(np.power(ans_y - y_validation, 2)) / 1131) print('The Loss is :' + str(loss))# 改成文件存在自己電腦的位置 testdata = pd.read_csv('~/GitHub/leeml-notes/docs/Homework/HW_1/dataset/test.csv', header=None, encoding='utf-8') test_data = testdata.iloc[:, 2:] # 取csv文件中的全行數即第3列到結束的列數所包含的數據 test_data = test_data.replace(['NR'], [0.0]) # 將testdata中的NR替換為0 test_data = test_data.to_numpy() # 將其轉換為數組 # 創建一個240行18*9列的空數列用于保存textdata的輸入 test_x = np.empty([240, 18 * 9], dtype=float) for i in range(240): # 共240個測試輸入數據test_x[i, :] = test_data[18 * i: 18 * (i + 1), :].reshape(1, -1) # 下面是Normalize,且必須跟training data是同一種方法進行Normalize for i in range(len(test_x)):for j in range(len(test_x[0])):if std_x[j] != 0:test_x[i][j] = (test_x[i][j] - mean_x[j]) / std_x[j] # 在test_x前面拼接一列全1數組,構成240行,163列數據 test_x = np.concatenate((np.ones([240, 1]), test_x), axis=1).astype(float) # 進行預測 w = np.load('weight.npy') ans_y = np.dot(test_x, w) # test data的預測值ans_y=test_x與W的積 # 將預測結果填入文件當中with open('submit.csv', mode='w', newline='') as submit_file:csv_writer = csv.writer(submit_file)header = ['id', 'value']print(header)csv_writer.writerow(header)for i in range(240):row = ['id_' + str(i), ans_y[i][0]]csv_writer.writerow(row)print(row)7.優化
本文用的是均方根誤差和Adagrad算法,還有很多更好的其他的算法,還可以加入正則項,參考博主秋沐霖,大家可以多去試試。
總結
以上是生活随笔為你收集整理的李宏毅机器学习--课后作业HW_1的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Keil5(MDK与C51版本共存)下载
- 下一篇: 大数据技术——hadoop组件