記第一次參加的數據挖掘比賽
1.比賽鏈接:天池工業AI大賽-智能製造質量預測,最終排名89/2529 2.Github鏈接:IntelligentMachine
3.博客地址:記第一次參加的數據挖掘比賽
大四的這學期確實比較輕鬆,在保完研基本就沒啥事情做了(主要就是做了一個校園招聘的數據調查項目,一些外包,以及給兩個師兄師姐上python課,還有學車),在充分的感受了大學的美好生活後,還是決定好好學點東西,於是決定系統的學習一下機器學習、深度學習。以前我比較排斥看網課自學,因為覺得有點浪費時間,但是現在時間比較多,所以在coursera上選了幾門課開始學習。但是這麼多年過去了,看見數學公式就有點打瞌睡的習慣還是沒能改變,再加上吳恩達的deeplearning好幾門都沒有中文字幕,所以覺得光看視頻還是不行,就決定找個實際的比賽來做。於是,逛了下天池和kaggle後,選擇了這個錢最多的題目(雖然錢再多也不關我事)。
概述
- 該比賽分為初賽、複賽和決賽,很遺憾,我只走到了複賽。初賽、複賽都是A/B榜切換機制,平時是A榜單數據驗證,一天只能驗證一次,B榜在最後兩天放出來,只有兩次提交機會,對A榜單過擬合的會哭得很慘…初賽只有500條訓練數據,卻有8000多列,需要預測100條數據,複賽是700×5000這樣的維度,主辦方解釋是因為這些數據非常難採集。。。所以我的第一感覺就是,這是一個很不靠譜的比賽,事實也證明,運氣成分很大。比賽中,我的主要目的是學習。在此之前,關於機器學習只手動實現過KNN,ANN, 樸素貝葉斯等簡單的演算法,其他的演算法都只停留在了解一丟丟原理的層面上,從沒有在程序中實踐過,連sklearn都還沒安裝。通過近一個月的比賽後,算是熟練掌握了pandas
umpysklearnxgboost等庫的調用。。。因為缺少經驗,所以在比賽中我大量閱讀別人的博客、代碼,也向一些有經驗的同學請教(感謝滿績王的親情指導),這樣做確實比我瞎嘗試要好得多。
代碼主要分為兩部分,特徵工程 + 機器學習,和大多數博客一樣,特徵工程佔據了絕大多數時間,而對於這樣一個 500 x 8000的數據,特徵工程簡直就是玄學。
比賽中所用的演算法和技術回顧
特徵工程
所有代碼都包含在了github的項目中,都寫為了獨立的函數
- 去除錯誤列去除數據中缺失值過多的列或行,參數得自己調整。
- 去除錯誤行對於不符合正態分布3 sigmoid的行(根據正太分布原理,數值分布在(μ—3σ,μ+3σ)中的概率為0.9974),超過一定數量就刪除該行。這部分代碼如下:
# 去除不符合正太分布的行
def remove_wrong_row(data): # 計算每一列數據的上界 upper = data.mean(axis=0) + 3 * data.std(axis=0) # 計算每一列數據的下界 lower = data.mean(axis=0) - 3 * data.std(axis=0) # 計算每一行中超過上界的數值的數量wrong_data1 = (data > upper).sum(axis=1).reset_index()
wrong_data1.columns = [row, na_count] # 參數是經過調試的 # 記錄數量超過一定值的行數 wrong_row1 = wrong_data1[wrong_data1.na_count >= 40].row.values # 計算每一行中超過下界的數值的數量 wrong_data2 = (data < lower).sum(axis=1).reset_index() wrong_data2.columns = [row, na_count] wrong_row2 = wrong_data2[wrong_data2.na_count >= 95].row.values wrong_row = np.concatenate((wrong_row1, wrong_row2))# 去除不符合正太分布的行
data.drop(wrong_row, axis=0, inplace=True) return data
- 填補缺失值由於缺失的值比較多,單純的使用fillna()的效果不是很好,在滿績王的啟發下,寫了KNN 近鄰填充。就是利用歐式距離上距離每一行最近的K行在某一列的平均值來填充該行在該列的缺失值。具體實現代碼如下(我已經盡量使用矩陣的演算法來計算距離,避免使用for循環,但代碼中還是使用了一層for循環,如果哪位大佬知道更好的方式,拜託告訴我):
def knn_fill_nan(data, K):
# 計算每一行的空值,如有空值則進行填充,沒有空值的行用於做訓練數據 data_row = data.isnull().sum(axis=1).reset_index() data_row.columns = [raw_row, nan_count] # 空值行(需要填充的行) data_row_nan = data_row[data_row.nan_count > 0].raw_row.values # 非空行 原始數據data_no_nan = data.drop(data_row_nan, axis=0)
# 空行 原始數據 data_nan = data.loc[data_row_nan] for row in data_row_nan: data_row_need_fill = data_nan.loc[row] # 找出空列,並利用非空列做KNN data_col_index = data_row_need_fill.isnull().reset_index() data_col_index.columns = [col, is_null] is_null_col = data_col_index[data_col_index.is_null == 1].col.values data_col_no_nan_index = data_col_index[data_col_index.is_null == 0].col.values# 保存需要填充的行的非空列
data_row_fill = data_row_need_fill[data_col_no_nan_index] # 廣播,矩陣 - 向量 data_diff = data_no_nan[data_col_no_nan_index] - data_row_need_fill[data_col_no_nan_index] # 求歐式距離 # data_diff = data_diff.apply(lambda x: x**2) data_diff = (data_diff ** 2).sum(axis=1) data_diff = data_diff.apply(lambda x: np.sqrt(x)) data_diff = data_diff.reset_index() data_diff.columns = [raw_row, diff_val]data_diff_sum = data_diff.sort_values(by=diff_val, ascending=True)
data_diff_sum_sorted = data_diff_sum.reset_index() # 取出K個距離最近的row top_k_diff_row = data_diff_sum_sorted.loc[0:K - 1].raw_row.values # 根據row 和 col值確定需要填充的數據的具體位置(可能是多個) # 填充的數據為最近的K個值的平均值 top_k_diff_val = data.loc[top_k_diff_row][is_null_col].sum(axis=0) / K # 將計算出來的列添加至非空列 data_row_fill = pd.concat([data_row_fill, pd.DataFrame(top_k_diff_val)]).T # print(data_no_nan.shape)data_no_nan = data_no_nan.append(data_row_fill, ignore_index=True)
# print(data_no_nan.shape) print(填補缺失值完成) return data_no_nan
- 去除日期列在本比賽環境中,日期對生產誤差應該沒有影響,但是會影響我們的線性模型,所以刪除
- 將非浮點數列轉化為浮點數列或直接刪除主要是將數據值為字母、單詞等列轉換為浮點數,這些列主要是生產工具的名稱,可能會有影響,在經過嘗試之後,發現直接刪除這些列效果更好一丟丟。
- 去除皮爾森係數在[-0.1, 0.1]之間的特徵列上過概率統計的應該還記得它,它描述了兩個數據之間的線形相關性,值屬於[-1, 1],-1表示完全負相關,1表示完全相關,0表示完全不相關。這裡去除了絕對值小於0.1的特徵,也就是幾乎沒什麼關係的列。
- 特徵選擇這是特徵工程中最重要的一步,看了很多博客里的方法,做了很多嘗試,最終選擇使用了模型融合(在複賽A榜中效果較好)。使用了RandomForestRegressor()/ AdaBoostRegressor()/ ExtraTreesRegressor()對每一列數據進行評分,選擇評分最好的100列,再進行融合(其實到這裡我已經感覺很玄學了)。使用了這個方法之後,我跑一遍程序的時間夠我看一部電影了。
- 數據規範化(Normalization)這是一項基本操作,由於數據與數據之間值的差距特別大,為了減少誤差,須通過規範化,將所有數據的值都映射到同一範圍內。規範化的具體實現有很多種,我最終使用的是sklearn.preprocess.scale()。另外,如果自己實現的話,代碼如下:
# 自定義規範化數據
def normalize_data(data):# 最大最小值規範化
return data.apply(lambda x: (x - np.min(x)) / (np.max(x) - np.min(x))) # z-socre 規範化 # return data.apply(lambda x: (x - np.average(x)) / np.std(x))
- 至此,特徵工程的所有工作就完成了。最終特徵的維數在170左右,根據參數的不同會有一些變化。
機器學習
一開始在技術圈看到有人討論,很多人都說這是一個線性模型,所以我就主要關注線性模型了。我嘗試了很多模型,關於每種模型我就簡單解釋一下了,原理都可以自己檢索。
- 最小二乘線性擬合, sklearn.linear_model.LinearRegression
最常規的線性回歸模型,使用最小二乘法做擬合,效果一般
- 最小二乘線性擬合 + L2正則,sklearn.linear_model.Ridge
引入了L2正則的線性擬合,因為數據特徵過多,樣本太少,過擬合一直都是整個比賽中最大的問題。為了避免過擬合,首先想到的就是L2正則。效果確實好多了。
- Ridge + Bagging 集成學習
使用Ridge為基學習器,使用Bagging做集成學習,由於基礎模型的選擇有限,所以這裡選擇了Bagging做融合。Bagging是從同一數據集中隨機抽取不同的數據做訓練,講道理非常適合這種樣本非常少的情況。本地線下交叉驗證的結果與單純的Ridge差不多,但是感覺更穩定。初賽最終提交的就是這種演算法產出的數據。
- xgboost及調參
xgboost在很多文章中都被推崇,初賽中也使用了xgboost,但是還不怎麼會調參,所以效果不是很好。在複賽A榜階段,使用xgboost取得了不錯的成果,並通過將xgboost與Bagging進一步融合,再將以前提提交的線上驗證比較好的答案融合,得到了A榜最好的一次成績(其實也很糟糕…)
總結
- 1.這次比賽中所用的演算法都是比較常規的,由於數據集的特點(緯度太高,樣本太少),幾乎沒有關注數據本身的意義
- 2.這個比賽很玄學,但真的很佩服那些A/B榜切換時幾乎沒怎麼變化的大佬
- 3.多使用matplotlib.pyplot對各種樣本進行繪圖,直觀的觀察數據往往能發現一些神奇的東西
推薦閱讀:
※我的機器學習計算資源
※Fenchel-Lengendre Duality觀點下的優化演算法們(I):前言
※知識布局-tensorflow-梯度下降
※混合模型與EM演算法