【火爐煉AI】機器學習019-專案案例:使用SVM迴歸器估算交通流量
(本文所使用的Python庫和版本號: Python 3.5, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2 )
我們都知道,SVM是一個很好地分類器,不僅適用於線性分類模型,而且還適用於非線性模型,但是,在另一方面,SVM不僅可以用於解決分類問題,還可以用於解決迴歸問題。
本專案打算使用SVM迴歸器來估算交通流量,所使用的方法和過程與我的上一篇文章【火爐煉AI】機器學習018-專案案例:根據大樓進出人數預測是否舉辦活動非常類似,所採用的資料處理方法也是大同小異。
1. 準備資料集
本專案所使用的資料集來源於UCI大學資料集,很巧合的是,這個資料集與上一篇文章(大樓進出人數)的資料集位於同一個網頁中。
1.1 瞭解資料集
本資料集統計了洛杉磯Dodgers棒球隊在主場比賽期間,體育場周圍馬路通過的車流量,資料存放在兩個檔案下:Dodgers.data檔案和Dodgers.events檔案,這兩個檔案各列的主要說明為:
Dodgers.data中有50400個樣本資料,每一行的基本屬性為:
而Dodgers.events檔案有中81行資料,每一行資料的基本屬性為:
1.2 資料規整
本專案的資料規整主要是將這兩個檔案中的內容整合到一個可用的資料集中。
1.2.1 規整之一:讀取檔案錯誤及其解決辦法
本來,我以為讀取這兩個檔案直接用pd.read_csv()即可,就像我以前的多篇文章中用到的一樣,然而,本專案中,直接呼叫該方法讀取檔案卻報錯。
# 1 準備資料集
# 從檔案中載入資料集
feature_data_path='E:\PyProjects\DataSet\BuildingInOut/Dodgers.data'
feature_set=pd.read_csv(feature_data_path,header=None)
print(feature_set.info())
# print(feature_set.head())
# print(feature_set.tail()) # 檢查沒有問題
label_data_path='E:\PyProjects\DataSet\BuildingInOut/Dodgers.events'
label_set=pd.read_csv(label_data_path,header=None)
print(label_set.info())
# print(label_set.head())
# print(label_set.tail()) # 讀取沒有問題,
複製程式碼
上面的程式碼在第二個pd.read_csv()時報以下錯誤,看來是原始檔案的編碼出現問題。
-----------------------輸---------出--------------------------------
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa0 in position 5: invalid start byte
-------------------------------完-------------------------------------
檢視原始Dodger.events檔案,我們可以發現,每一行的末尾有一個不能識別的未知字元。
此時我的解決辦法是:用記事本將Dodger.events檔案開啟,在“另存為”中,將編碼格式修改為"UTF-8",儲存為一個新的檔案,此時新檔案中就沒有了未知字元,如下圖所示。
再用pd.read_csv()就沒有任何問題。
1.2.2 規整之二:刪除缺失資料,拆分資料
由於Dodger.data檔案中的樣本存在缺失資料,其車流量為-1的表示缺失資料,對缺失資料的處理方式有很多種,此處由於整個樣本量比較大,故而我直接將缺失資料刪除。另外,由於原始資料中並不全都是用逗號分隔開,故而需要將列進行分隔,程式碼如下:
# 刪除缺失資料
feature_set2=feature_set[feature_set[1]!=-1] # 只獲取不是-1的DataFrame即可。
# print(feature_set2) # 沒有問題
feature_set2=feature_set2.reset_index(drop=True)
print(feature_set2.head())
# 第0列既包含日期,又包含時間,故要拆分成兩列
need_split_col=feature_set2[0].copy()
feature_set2[0]=need_split_col.map(lambda x: x.split()[0].strip())
feature_set2[2]=need_split_col.map(lambda x: x.split()[1].strip())
print(feature_set2.head()) # 拆分沒有問題
複製程式碼
------------------------輸---------出--------------------------------
0 1 0 4/11/2005 7:35 23 1 4/11/2005 7:40 42 2 4/11/2005 7:45 37 3 4/11/2005 7:50 24 4 4/11/2005 7:55 39 0 1 2 0 4/11/2005 23 7:35 1 4/11/2005 42 7:40 2 4/11/2005 37 7:45 3 4/11/2005 24 7:50 4 4/11/2005 39 7:55
-------------------------------完-------------------------------------
1.2.3 規整之三:日期格式的統一化
在我們進行這兩個DataFrame的合併和日期比較之前,需要先將這兩個DataFrame中的日期格式統一化,從兩個檔案中讀取的日期都是String型別,但是從Dodgers.data中讀取的日期格式是如4/11/2005的形式,而從Dodgers.events中讀取的日期格式是比如:05/01/05形式,顯然這兩個String之間難以直接比較。幸好Pandas有內建的to_datetime函式,可以直接將這兩種日期進行格式統一化。程式碼為:
# 將兩個DataFrame中的日期格式統一,兩個DataFrame中的日期目前還是String型別,格式不統一無法比較
feature_set2[0]=pd.to_datetime(feature_set2[0])
print(feature_set2[0][:5]) # 列印第0列的前5行
label_set[0]=pd.to_datetime(label_set[0])
print(label_set[0][:5])
複製程式碼
------------------------輸---------出--------------------------------
0 2005-04-11
1 2005-04-11
2 2005-04-11
3 2005-04-11
4 2005-04-11
Name: 0, dtype: datetime64[ns]
0 2005-04-12
1 2005-04-13
2 2005-04-15
3 2005-04-16
4 2005-04-17
Name: 0, dtype: datetime64[ns]
-------------------------------完-------------------------------------
1.2.4 規整之四:合併兩個檔案到一個資料集中
在合併檔案的時候,我們要知道哪些特徵屬性是進行機器學習所必須的,此處我們選擇的特徵列包括有(日期,時間,對手球隊名,是否比賽期間),故而我們需要從Dodgers.data檔案中選取日期和時間,從Dodger.events檔案中選取對手球隊名和是否比賽期間的資訊,放置到一個資料集中。具體程式碼如下:
# 合併兩個檔案到一個資料集中
feature_set2[3]='NoName' # 對手球隊名稱暫時用NoName來初始化
feature_set2[4]=0 # 是否比賽期間暫時用否來代替
def calc_mins(time_str):
nums=time_str.split(':')
return 60*int(nums[0])+int(nums[1]) # 將時間轉換為分鐘數
for row_id,date in enumerate(label_set[0]): # 先取出label中的日期
temp_df=feature_set2[feature_set2[0]==date]
if temp_df is None:
continue
# 只要這一天有比賽,不管是不是正在比賽,都把對手球隊名稱寫入第3列
rows=temp_df.index.tolist()
feature_set2.loc[rows,3]=label_set.iloc[row_id,4]
start_min=calc_mins(label_set.iloc[row_id,1])
stop_min=calc_mins(label_set.iloc[row_id,2])
for row in temp_df[2]: # 在逐一判斷時間是否位於label中時間之間
feature_min=calc_mins(row)
if feature_min>=start_min and feature_min<=stop_min:
feature_row=temp_df[temp_df[2]==row].index.tolist()
feature_set2.loc[feature_row,4]=1
# feature_set2.to_csv('d:/feature_set2_Dodgers.csv') # 儲存後列印檢視沒有問題
複製程式碼
開啟儲存的feature_set2_Dodgers.csv,檢視發現有很多NoName的行數,這些行數表示當天沒有比賽,故而沒有對手名。對於NoName樣本的處理方式也有多種多用,根據具體的需求不同而不同,可以把它當做ground truth來對待,也可以直接刪除,也可以保留作為一種情況來進行訓練。此處我直接將其刪除。
1.2.5 規整之五:將日期轉換為星期數並儲存資料集
這部分主要是將日期轉換為星期數,並儲存資料集到硬碟,方便下次直接讀取檔案。程式碼為:
feature_set3=feature_set2[feature_set2[3]!='NoName'].reset_index(drop=True) # 去掉NoName的樣本
# 進一步處理,由於日期在以後的日子裡不可重複,作為feature並不合適,而可以用星期數來代替,
feature_set3[5]=feature_set3[0].map(lambda x: x.strftime('%w')) # 將日期轉換為星期數
feature_set3=feature_set3.reindex(columns=[0,2,5,3,4,1])
print(feature_set3.tail()) # 檢視轉換沒有問題
feature_set3.to_csv('E:\PyProjects\DataSet\BuildingInOut/Dodgers_Sorted_Set.txt') # 將整理好的資料集儲存,下次可以直接讀取
複製程式碼
------------------------輸---------出--------------------------------
0 2 5 3 4 1
22411 2005-09-29 23:35 4 Arizona 0 9
22412 2005-09-29 23:40 4 Arizona 0 13
22413 2005-09-29 23:45 4 Arizona 0 11
22414 2005-09-29 23:50 4 Arizona 0 14
22415 2005-09-29 23:55 4 Arizona 0 17
-------------------------------完-------------------------------------
########################小**********結#################
1. 這個專案的主要難點也在於資料處理方面,其主要的規整方法和上一篇文章類似。
#####################################################
2. 構建SVM迴歸模型
使用SVM構建迴歸模型的關鍵在於匯入SVR模組,而不是分類模型中所用的SVC模組,其SVR所用的引數也需要做相應的調整,此處夠安靜SVM迴歸模型的程式碼為:
from sklearn.svm import SVR # 此處不一樣,匯入的是SVR而不是SVC
regressor = SVR(kernel='rbf',C=10.0,epsilon=0.2) # 這些引數是優化得來
regressor.fit(train_X, train_y)
複製程式碼
------------------------輸---------出--------------------------------
SVR(C=10.0, cache_size=200, coef0=0.0, degree=3, epsilon=0.2, gamma='auto', kernel='rbf', max_iter=-1, shrinking=True, tol=0.001, verbose=False)
-------------------------------完-------------------------------------
在對模型進行定義和訓練之後,就需要使用測試集對模型進行測試,如下是測試程式碼和輸出結果:
y_predict_test=regressor.predict(test_X)
# 使用評價指標來評估模型的好壞
import sklearn.metrics as metrics
print('平均絕對誤差:{}'.format(
round(metrics.mean_absolute_error(y_predict_test,test_y),2)))
print('均方誤差MSE:{}'.format(
round(metrics.mean_squared_error(y_predict_test,test_y),2)))
print('中位數絕對誤差:{}'.format(
round(metrics.median_absolute_error(y_predict_test,test_y),2)))
print('解釋方差分:{}'.format(
round(metrics.explained_variance_score(y_predict_test,test_y),2)))
print('R方得分:{}'.format(
round(metrics.r2_score(y_predict_test,test_y),2)))
複製程式碼
------------------------輸---------出--------------------------------
平均絕對誤差:5.16
均方誤差MSE:50.45
中位數絕對誤差:3.75
解釋方差分:0.63
R方得分:0.62
-------------------------------完-------------------------------------
貌似結果不太好,可能SVR中的引數還需要進一步優化.
很多朋友給我留言,問我訓練好的SVM模型如何儲存和重新呼叫,這部分內容去我在前面的文章中已經介紹過了,具體請看:【火爐煉AI】機器學習003-簡單線性迴歸器的建立,測試,模型儲存和載入
注:本部分程式碼已經全部上傳到(我的github)上,歡迎下載。
參考資料:
1, Python機器學習經典例項,Prateek Joshi著,陶俊傑,陳小莉譯