背景介紹
這是筆者參加的第一個(gè)大數(shù)據(jù)比賽执庐,預(yù)賽最好成績(jī)是前50名,但是由于后來(lái)競(jìng)爭(zhēng)越發(fā)激烈(分類(lèi)正確率差0.01,排名都能差上10多名...)和興趣漸漸轉(zhuǎn)到研究NLP上,最終沒(méi)有堅(jiān)持迭代優(yōu)化模型,還是非常遺憾的。參加比賽的直接原因是來(lái)自慶恒學(xué)長(zhǎng)的鼓勵(lì)仇参,他和他的小伙伴在10月份一舉拿下了天池算法大賽的冠軍,欽羨不已婆殿,遂決定實(shí)戰(zhàn)一下诈乒,提升下自己的能力。
筆者從對(duì)打比賽“一無(wú)所知”到能夠快速建模迭代實(shí)驗(yàn)婆芦,這其中的經(jīng)歷對(duì)于初學(xué)者還是有非常大的參考意義的抓谴,所以,我打算寫(xiě)一篇博文寞缝,將其中的點(diǎn)點(diǎn)滴滴記錄下來(lái)癌压。如果讀者想要更進(jìn)一步的提升的話(huà),強(qiáng)推冠軍兄弟們開(kāi)源的代碼荆陆,其中的特征工程滩届、二分類(lèi)為主線(xiàn),多分類(lèi)作為特征輔助的思路非常的棒被啼!這里附上傳送門(mén):商鋪定位代碼開(kāi)源
比賽鏈接的傳送門(mén)也一并奉上:商場(chǎng)中精確定位用戶(hù)所在商鋪
建模分析
來(lái)自寒小陽(yáng)老師博文機(jī)器學(xué)習(xí)系列(3)_邏輯回歸應(yīng)用之Kaggle泰坦尼克之災(zāi)的一張圖片帜消,總結(jié)了利用機(jī)器學(xué)習(xí)解決問(wèn)題的思考流程:
這篇博文非常好,是我的入門(mén)博文浓体,值得一讀泡挺。當(dāng)然遇到簡(jiǎn)單的題目套用這些流程沒(méi)問(wèn)題,但最好是活學(xué)活用命浴,避免機(jī)械化娄猫。
下面我將從拿到數(shù)據(jù),分析題目生闲、數(shù)據(jù)預(yù)處理媳溺、特征工程、模型選擇碍讯、模型優(yōu)化悬蔽、交叉驗(yàn)證、模型融合這幾個(gè)方面來(lái)介紹捉兴,這幾個(gè)流程也是機(jī)器學(xué)習(xí)比賽中常見(jiàn)的思路蝎困。
分析題目
主辦方提供了3個(gè)文件录语,店鋪和商場(chǎng)信息表、用戶(hù)在店鋪內(nèi)交易表禾乘、評(píng)測(cè)集澎埠。這三個(gè)表包含的字段可以到比賽信息界面找到,利用這些信息我們需要預(yù)測(cè)用戶(hù)所在的shop_id信息盖袭,很明顯的多分類(lèi)問(wèn)題嘛(實(shí)力打臉...后來(lái)證明失暂,將這個(gè)問(wèn)題用二分類(lèi)正負(fù)樣本的方式解決彼宠,正確率要高...),不過(guò)這里就先按照多分類(lèi)的思路先寫(xiě)著鳄虱,畢竟效果也不錯(cuò),二分類(lèi)的思路確實(shí)需要有些比賽經(jīng)驗(yàn)老手才會(huì)這么做...
數(shù)據(jù)預(yù)處理
一開(kāi)始的思路很明確凭峡,有了第一張表拙已,我們可以將shop和mall的對(duì)應(yīng)關(guān)系找出來(lái),訓(xùn)練len(mall)的多分類(lèi)器摧冀。所以倍踪,數(shù)據(jù)預(yù)處理的第一步,就是將對(duì)應(yīng)的數(shù)據(jù)按照mall分到對(duì)應(yīng)的文件夾下(注意索昂,我這里為了處理方便建车,將文件名改成英文了):
import pandas as pd #數(shù)據(jù)分析
import numpy as np #科學(xué)計(jì)算
from pandas import Series,DataFrame
shop_info = pd.read_csv("data/train-ccf_first_round_shop_info.csv")
user_info = pd.read_csv("data/train-ccf_first_round_user_shop_behavior.csv")
test_info = pd.read_csv("data/test-evaluation_public.csv")
# mall對(duì)應(yīng)的shop信息
mall_shop = {}
for i in range(len(shop_info["mall_id"])):
mall_shop.setdefault(shop_info["mall_id"][i], []).append(shop_info["shop_id"][i])
print len(mall_shop) # 97個(gè)mall,d1中記錄了每個(gè)mall中有多少個(gè)shop
# 根據(jù)mall_id來(lái)劃分97份訓(xùn)練、測(cè)試集
import os
import sys
test_info1 = test_info.copy()
user_info1 = user_info.copy()
for i in range(len(mall_shop)):
if not os.path.exists(mall_shop.keys()[i]): os.makedirs('data/' + mall_shop.keys()[i])
df1 = test_info1[test_info1['mall_id'] == mall_shop.keys()[i]]
df1.drop(['mall_id'], axis=1, inplace=True)
df1.to_csv('data/' + mall_shop.keys()[i] + '/test.csv', index = False)
df2 = user_info1[user_info1['shop_id'].isin(mall_shop.values()[i])]
df2.to_csv('data/' + mall_shop.keys()[i] + '/train.csv', index = False)
特征工程
因?yàn)?7個(gè)mall的分類(lèi)器是類(lèi)似的椒惨,所以在測(cè)試的時(shí)候缤至,隨機(jī)選用商場(chǎng)m_1790作為參考。
在打比賽界康谆,有句話(huà)流傳頗廣:特征工程決定了最終效果的上界领斥,各種分類(lèi)方法的區(qū)別只是在于能否逼近這個(gè)上界而已。這句話(huà)足以證明特征工程的重要性沃暗。如果讀者之前去看了這個(gè)比賽第一名的開(kāi)源項(xiàng)目月洛,你會(huì)發(fā)現(xiàn),他們的特征工程居然有這么多孽锥,看上去林林總總嚼黔,但每個(gè)特征類(lèi)別,對(duì)于最終的效果提升肯定是有幫助的:
特征
標(biāo)記特征:
記錄中是否有連接的wifi
記錄中是含否有null
記錄中wifi與候選shop出現(xiàn)過(guò)的wifi重合的個(gè)數(shù)
"總量-比例"特征
該mall的總歷史記錄數(shù)惜辑、候選shop在其中的占比
該user的總歷史記錄數(shù)隔崎、候選shop在其中的占比
wifi歷史上出現(xiàn)過(guò)的總次數(shù)、候選shop在其中的占比
在當(dāng)前排序位置(如最強(qiáng)韵丑、第二強(qiáng)爵卒、第三強(qiáng)...)上wifi歷史上出現(xiàn)過(guò)的總次數(shù)、候選shop在其中的占比
連接的wifi出現(xiàn)的總次數(shù)撵彻、候選shop在其中的占比
經(jīng)緯度網(wǎng)格(將經(jīng)緯度按不同精度劃分成網(wǎng)格)中的總記錄數(shù)钓株、候選shop在其中的占比
對(duì)于特征3实牡、4,每條記錄中的10個(gè)wifi由強(qiáng)到弱排列轴合,可生成10個(gè)特征创坞。
差值特征:
wifi強(qiáng)度 - 候選shop的歷史記錄中該wifi的平均強(qiáng)度
wifi強(qiáng)度 - 候選shop的歷史記錄中該wifi的最小強(qiáng)度
wifi強(qiáng)度 - 候選shop的歷史記錄中該wifi的最大強(qiáng)度
三個(gè)wifi強(qiáng)度差值特征,按照信號(hào)強(qiáng)度由強(qiáng)到弱排列受葛,可生成10個(gè)特征题涨。
距離特征:
與候選shop位置的GPS距離(L2)
與候選shop歷史記錄中心位置的GPS距離(L2)
與候選shop對(duì)應(yīng)wifi信號(hào)強(qiáng)度的距離(L1、L2)
其他特征:
特征中還包括多分類(lèi)的輸出概率总滩。另外纲堵,還有一些利用規(guī)則定義的距離特征,這里不再詳述
筆者的特征工程就很直接了闰渔,經(jīng)過(guò)反復(fù)測(cè)試席函,發(fā)現(xiàn)wifi特征的重要性最大,其他特征給的幫助一般冈涧,所以決定建立一個(gè)由特征構(gòu)成的詞袋向量茂附,有這個(gè)wifi的根據(jù)強(qiáng)度設(shè)定一個(gè)正值,沒(méi)有這個(gè)wifi的督弓,設(shè)置為0营曼,跟詞袋模型會(huì)遇到的問(wèn)題一樣,最終的特征矩陣非常稀疏愚隧,導(dǎo)致后面過(guò)擬合蒂阱,這里的思路是可以取topk的重要wifi,組成詞袋,經(jīng)過(guò)測(cè)試奸攻,topk的方案蒜危,范化能力明顯要強(qiáng),驗(yàn)證了上面的想法睹耐。
提取topk-wifi特征的代碼如下:
"""
wifi feature like bag of words
"""
# version-2: add "true" signal
# version-3: only choose top5 intensity wifi
from pandas import DataFrame,Series
kan_wifi_infos = kan.wifi_infos
"""
選擇topk強(qiáng)度的wifi
"""
def chooseTopk(infos, k):
"""
choose topk intensity wifi
:infos: DataFrame infos
:k: topk
"""
new_infos = []
for j in range(len(infos)):
orignal_list = infos[j].split(';')
intensity = []
sel_list = []
for i in range(len(orignal_list)):
intensity.append(int(orignal_list[i].split('|')[1]))
np_intensity = np.array(intensity)
index = np.argsort(np_intensity)[::-1] # big2small
if index.shape[0] >= k:
for num in range(k):
sel_list.append(orignal_list[index[num]])
else:
for num in range(index.shape[0]):
sel_list.append(orignal_list[index[num]])
join_str = ";"
print join_str.join(set(sel_list))
new_infos.append(join_str.join(set(sel_list)))
return new_infos
new_wifi_infos = chooseTopk(kan_wifi_infos, 5)
new_wifi_infos = Series(new_wifi_infos)
"""
詞袋模型構(gòu)建過(guò)程
"""
wifi_info = []
wifi_column = []
wifi_name = {}
for j in range(len(new_wifi_infos)):
wifi_list = new_wifi_infos[j].split(';')
wifi_own = []
# for i in range(len(wifi_list)):
# wifi_name[wifi_list[i].split('|')[0]] = [] # problem????
for i in range(len(wifi_list)):
# wifi_own.insert(-1,wifi_list[i].split('|')[0])
if wifi_list[i].split('|')[0] not in wifi_column:
wifi_name[wifi_list[i].split('|')[0]] = []
# wifi_column.insert(-1, wifi_list[i].split('|')[0]) # ordered list for wifis
wifi_column.append(wifi_list[i].split('|')[0]) # ordered list for wifis
# temp = wifi_column
# scale
wifi_name[wifi_list[i].split('|')[0]].extend([0]*j) # head 0
wifi_name[wifi_list[i].split('|')[0]].extend([(100+int(wifi_list[i].split('|')[1]))*0.01])
elif wifi_list[i].split('|')[0] not in wifi_own: # 1 line has the same wifi-number
wifi_name[wifi_list[i].split('|')[0]].extend([(100+int(wifi_list[i].split('|')[1]))*0.01])
else:
pass
# add 0 to end of list
# wifi_name[wifi_list[0] / wifi_column].extend([0])
wifi_own.append(wifi_list[i].split('|')[0])
retE = [i for i in wifi_column if i not in wifi_own]
for num in retE: # cha_ji
wifi_name[num].extend([0]) # end 0
print "out of for-for"
train_df = DataFrame(wifi_name) # 這便是要訓(xùn)練的特征向量
模型選擇
模型選擇部分辐赞,一開(kāi)始選用logistic regression和樸素貝葉斯模型,效果一般硝训,大概提交正確率能達(dá)到60%响委,速度很快但是畢竟是打比賽,看的還是準(zhǔn)確率呀...這兩個(gè)部分的特征數(shù)據(jù)都需要做歸一化窖梁,否則會(huì)產(chǎn)生很大的偏差赘风,歸一化方法,這里也分享出來(lái)纵刘,里面還有sklearn.scaler的一個(gè)小坑邀窃,調(diào)研了一會(huì)才搞定:
"""
longitude latitude 4 and scale
"""
from sklearn import preprocessing
longi = (kan['longitude']*1000000%10000).as_matrix().reshape(-1, 1) # 1D->2D
lati = kan['latitude'].as_matrix().reshape(-1, 1)*1000000%10000
scaler = preprocessing.StandardScaler()
longi_scale_param = scaler.fit(longi)
kan_longi_scaled = scaler.fit_transform(longi, longi_scale_param)
lati_scale_param = scaler.fit(lati)
kan_lati_scaled = scaler.fit_transform(lati, lati_scale_param)
kan_longi_scaled1 = kan_longi_scaled.ravel() # 2D->1D
kan_lati_scaled1 = kan_lati_scaled.ravel()
# print type(kan_lati_scaled1) # numpy.ndarray
longi_scaled = pd.Series(kan_longi_scaled1, index=kan.index)
lati_scaled = pd.Series(kan_lati_scaled1, index=kan.index)
測(cè)試模型效果的時(shí)候,選擇的label肯定得是數(shù)字化的吧假哎?所以瞬捕,這里一開(kāi)始還有一個(gè)label的encoder步驟鞍历,最終提交文件的時(shí)候,再decoder恢復(fù)成原先的shop_id信息肪虎,實(shí)現(xiàn)代碼如下:
kan = pd.read_csv("data/m_1790/train.csv", parse_dates = ['time_stamp'])
"""
y LabelEncoder
"""
leshop = preprocessing.LabelEncoder()
shop_label = leshop.fit_transform(kan.shop_id)
...
result_shop_id = leshop.inverse_transform(predictions.astype(np.int32))
之后選用了隨機(jī)森林模型劣砍,效果出奇的好,交叉驗(yàn)證上的爭(zhēng)取率能達(dá)到92%扇救,實(shí)際提交也能到89%刑枝,要知道,這才只是baseline,還沒(méi)有經(jīng)過(guò)模型優(yōu)化迅腔,當(dāng)時(shí)第一名的正確率也就91%的樣子装畅,當(dāng)時(shí)高興得不得了,也一股做起钾挟,做起了參數(shù)調(diào)優(yōu)的事情洁灵,這部分內(nèi)容先不講饱岸,放在模型優(yōu)化部分掺出。
之后筆者用上了kaggle比賽中如雷貫耳的xgboost,效果是真的好,線(xiàn)上提交成績(jī)上了90,但是速度不是很快苫费,在筆者實(shí)驗(yàn)室服務(wù)器上汤锨,97個(gè)mall單進(jìn)程跑的話(huà)要將近2-3個(gè)小時(shí),筆者分析主要還是特征太稀疏百框,維度過(guò)大的原因把闲礼。
還有一個(gè)新潮的模型是微軟開(kāi)源的lightGBM,號(hào)稱(chēng)比xgboost效果一樣好铐维,但是更輕量柬泽,速度更快,可惜網(wǎng)上的資料少的可憐嫁蛇,由于時(shí)間緊也沒(méi)多折騰锨并,以后有空抽時(shí)間好好研究下,做個(gè)博文介紹一番...
模型優(yōu)化
我這里說(shuō)的模型優(yōu)化主要是調(diào)參睬棚,用到的技術(shù)主要是網(wǎng)格搜索和貪心方式第煮。
- 網(wǎng)格搜索
網(wǎng)格搜索就是設(shè)置參數(shù)的一個(gè)集合范圍,讓程序不斷嘗試抑党,最終給你一個(gè)好的參數(shù)搭配組合包警,有點(diǎn)是范圍取值合理,得到的真真是全局最優(yōu)解呀,缺點(diǎn)也很明顯底靠,特別耗費(fèi)時(shí)間害晦。實(shí)現(xiàn)代碼如下:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
# Set the parameters by cross-validation
tuned_parameters = {'n_estimators': [10, 20, 50], 'max_depth': [None, 1, 2, 3], 'min_samples_split': [1, 2, 3]}
all_data = train_df1
X = all_data.as_matrix()[:,:-1]
y = all_data.as_matrix()[:,-1]
# clf = ensemble.RandomForestRegressor(n_estimators=500, n_jobs=1, verbose=1)
clf = GridSearchCV(RandomForestClassifier(), tuned_parameters, cv=5, n_jobs=-1, verbose=1)
clf.fit(X, y)
print clf.best_estimator_
- 貪心算法
貪心法求的是局部最優(yōu)解(當(dāng)且僅當(dāng)所有參數(shù)去耦合的情況下,是全局最優(yōu)的)暑中。貪心法操作起來(lái)也非常方便壹瘟,網(wǎng)格搜索找到一個(gè)最好的參數(shù)苟呐,之后固定這個(gè)參數(shù),再網(wǎng)格搜索下一個(gè)參數(shù)的集合俐筋,找到下一個(gè)最好的參數(shù)牵素,依次進(jìn)行。方法比較簡(jiǎn)單澄者,這里就不給出實(shí)現(xiàn)過(guò)程了笆呆。
交叉驗(yàn)證
交叉驗(yàn)證的目的是啥?答:我們總不能得到一個(gè)結(jié)果就提交一次結(jié)果粱挡,通過(guò)看線(xiàn)上評(píng)測(cè)結(jié)果來(lái)判斷模型是否有改進(jìn)把(一般線(xiàn)上評(píng)測(cè)都是有每日提交限制的)赠幕。
所以,交叉驗(yàn)證引進(jìn)的目的询筏,就是提供一套線(xiàn)下評(píng)測(cè)的方法榕堰。交叉驗(yàn)證的方法如下:
這里以隨機(jī)森林模型的交叉驗(yàn)證為例:
# cross-validation
from sklearn.ensemble import RandomForestClassifier
from sklearn import cross_validation
model = RandomForestClassifier(n_estimators=50, min_samples_split=3)
all_data = train_df1
X = all_data.as_matrix()[:,:-1]
y = all_data.as_matrix()[:,-1]
model.fit(X, y)
print cross_validation.cross_val_score(model, X, y, cv=5)
模型融合
模型融合的重要性這里需要強(qiáng)調(diào),它能很好的結(jié)合多個(gè)模型的優(yōu)點(diǎn)嫌套,取長(zhǎng)補(bǔ)短逆屡,從而得到更好的預(yù)測(cè)結(jié)果。
網(wǎng)上模型融合的博文很多踱讨,我這里只談?wù)勎易约旱目捶ㄎ赫幔P腿诤闲枰燃?jí)的選手才能融合,效果相差很大的模型不適用痹筛,模型融合甚至可以包括多個(gè)不同參數(shù)的相同模型融合...
由于模型融合有很多trick莺治,我這個(gè)新手也需要在這方面,多多積累經(jīng)驗(yàn)才是帚稠。最簡(jiǎn)單的模型融合方法谣旁,就是構(gòu)建一個(gè)打分系統(tǒng),根據(jù)少數(shù)服從多數(shù)的原則滋早,得到最終的預(yù)測(cè)結(jié)果榄审。
優(yōu)化改進(jìn)
冠軍兄弟開(kāi)源的二分類(lèi)方案,是將target商鋪設(shè)成1(正樣本),其他所有商鋪設(shè)成0(負(fù)樣本),這么一個(gè)負(fù)樣本明顯多于正樣本的二分類(lèi)模型馆衔,這個(gè)時(shí)候就可以采用一些正負(fù)樣本不均衡時(shí)采用的方法瘟判,網(wǎng)上檢索關(guān)鍵詞有相應(yīng)的教程,冠軍兄弟解決數(shù)據(jù)不均衡的方法就是隨機(jī)采樣負(fù)樣本這種簡(jiǎn)單方便的方法角溃。
因?yàn)樘斐貜?fù)賽需要用到他們的平臺(tái)拷获,所以mapreduce和sql成了比較重要的基本技能,開(kāi)源代碼有很多在平臺(tái)實(shí)戰(zhàn)的trick减细,真打到復(fù)賽的時(shí)候匆瓜,也能成為優(yōu)化改進(jìn)的經(jīng)驗(yàn)來(lái)用把。
××××××××××××××××××××××××××××××××××××××××××
本文屬于筆者(EdwardChou)原創(chuàng)
轉(zhuǎn)載請(qǐng)注明出處
××××××××××××××××××××××××××××××××××××××××××