記第一次參加的數據挖掘比賽

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演算法

TAG:數據挖掘 | 機器學習 | 阿里天池 |