Query意图分析:记一次完整的机器学习过程(scikit learn library学习笔记)
所謂學(xué)習(xí)問(wèn)題,是指觀察由n個(gè)樣本組成的集合,并根據(jù)這些數(shù)據(jù)來(lái)預(yù)測(cè)未知數(shù)據(jù)的性質(zhì)。
學(xué)習(xí)任務(wù)(一個(gè)二分類(lèi)問(wèn)題):
區(qū)分一個(gè)普通的互聯(lián)網(wǎng)檢索Query是否具有某個(gè)垂直領(lǐng)域的意圖。假設(shè)現(xiàn)在有一個(gè)O2O領(lǐng)域的垂直搜索引擎,專(zhuān)門(mén)為用戶提供團(tuán)購(gòu)、優(yōu)惠券的檢索;同時(shí)存在一個(gè)通用的搜索引擎,比如百度,通用搜索引擎希望能夠識(shí)別出一個(gè)Query是否具有O2O檢索意圖,如果有則調(diào)用O2O垂直搜索引擎,獲取結(jié)果作為通用搜索引擎的結(jié)果補(bǔ)充。
我們的目的是學(xué)習(xí)出一個(gè)分類(lèi)器(classifier),分類(lèi)器可以理解為一個(gè)函數(shù),其輸入為一個(gè)Query,輸出為0(表示該Query不具有o2o意圖)或1(表示該Query具有o2o意圖)。
特征提取:
要完成這樣一個(gè)學(xué)習(xí)任務(wù),首先我們必須找出決定一個(gè)Query是否具有O2O意圖的影響因素,這些影響因素稱之為特征(feature)。特征的好壞很大程度上決定了分類(lèi)器的效果。在機(jī)器學(xué)習(xí)領(lǐng)域我們都知道特征比模型(學(xué)習(xí)算法)更重要。(順便說(shuō)一下,工業(yè)界的人都是這么認(rèn)為的,學(xué)術(shù)界的人可能不以為然,他們整天搗鼓算法,發(fā)出來(lái)的文章大部分都沒(méi)法在實(shí)際中應(yīng)用。)舉個(gè)例子,如果我們的特征選得很好,可能我們用簡(jiǎn)單的規(guī)則就能判斷出最終的結(jié)果,甚至不需要模型。比如,要判斷一個(gè)人是男還是女(人類(lèi)當(dāng)然很好判斷,一看就知道,這里我們假設(shè)由計(jì)算機(jī)來(lái)完成這個(gè)任務(wù),計(jì)算機(jī)有很多傳感器(攝像頭、體重器等等)可以采集到各種數(shù)據(jù)),我們可以找到很多特征:身高、體重、皮膚顏色、頭發(fā)長(zhǎng)度等等。因?yàn)楦鶕?jù)統(tǒng)計(jì)我們知道男人一般比女人重,比女人高,皮膚比女人黑,頭發(fā)比女人短;所以這些特征都有一定的區(qū)分度,但是總有反例存在。我們用最好的算法可能準(zhǔn)確率也達(dá)不到100%。假設(shè)計(jì)算機(jī)還能夠讀取人的身份證號(hào)碼,那么我們可能獲得一個(gè)更強(qiáng)的特征:身份證號(hào)碼的倒數(shù)第二位是否是偶數(shù)。根據(jù)身份證編碼規(guī)則,我們知道男性的身份證號(hào)碼的倒數(shù)第二位是奇數(shù),女生是偶數(shù)。因此,有了這個(gè)特征其他的特征都不需要了,而且我們的分類(lèi)器也很簡(jiǎn)單,不需要復(fù)雜的算法。
言歸正傳,對(duì)于O2O Query意圖識(shí)別這一學(xué)習(xí)任務(wù),我們可以用的特征可能有:Query在垂直引擎里能夠檢索到的結(jié)果數(shù)量、Query在垂直引擎里能夠檢索到的結(jié)果的類(lèi)目困惑度(perplexity)(檢索結(jié)果的類(lèi)目越集中說(shuō)明其意圖越強(qiáng))、Query能否預(yù)測(cè)到特征的O2O商品類(lèi)目、Query是否包含O2O產(chǎn)品詞或品牌詞、Query在垂直引擎的歷史展現(xiàn)次數(shù)(PV)和點(diǎn)擊率(ctr)、Query在垂直引擎的檢索結(jié)果相關(guān)性等等。
特征表示:
特征表示是對(duì)特征提取結(jié)果的再加工,目的是增強(qiáng)特征的表示能力,防止模型(分類(lèi)器)過(guò)于復(fù)雜和學(xué)習(xí)困難。比如對(duì)連續(xù)的特征值進(jìn)行離散化,就是一種常用的方法。這里我們以“Query在垂直引擎里能夠檢索到的結(jié)果數(shù)量”這一特征為例,簡(jiǎn)要介紹一下特征值分段的過(guò)程。首先,分析一下這一維特征的分布情況,我們對(duì)這一維特征值的最小值、最大值、平均值、方差、中位數(shù)、三分位數(shù)、四分位數(shù)、某些特定值(比如零值)所占比例等等都要有一個(gè)大致的了解。獲取這些值,python編程語(yǔ)言的numpy模塊有很多現(xiàn)成的函數(shù)可以調(diào)用。最好的辦法就是可視化,借助python的matplotlib工具我們可以很容易地劃出數(shù)據(jù)分布的直方圖,從而判斷出我們應(yīng)該對(duì)特征值劃多少個(gè)區(qū)間,每個(gè)區(qū)間的范圍是怎樣的。比如說(shuō)我們要對(duì)“結(jié)果數(shù)量”這一維特征值除了“0”以為的其他值均勻地分為10個(gè)區(qū)間,即每個(gè)區(qū)間內(nèi)的樣本數(shù)大致相同。“0”是一個(gè)特殊的值,因此我們想把它分到一個(gè)單獨(dú)的區(qū)間,這樣我們一共有11個(gè)區(qū)間。python代碼實(shí)現(xiàn)如下:
| import numpy as npdef bin(bins):assert isinstance(bins, (list, tuple))def scatter(x):if x == 0: return 0for i in range(len(bins)):if x <= bins[i]: return i + 1return len(bins)return np.frompyfunc(scatter, 1, 1)data = np.loadtxt("D:\query_features.xls", dtype='int') # descrete o2o_result_num = data[:,0] o2o_has_result = o2o_result_num[o2o_result_num > 0] bins = [ np.percentile(o2o_has_result, x) for x in range(10, 101, 10) ] data[:,0] = bin(bins)(o2o_result_num) |
我們首先獲取每個(gè)區(qū)間的起止范圍,即分別算法特征向量的10個(gè)百分位數(shù),并依此為基礎(chǔ)算出新的特征值(通過(guò)bin函數(shù),一個(gè)numpy的universal function)。
訓(xùn)練數(shù)據(jù):
這里我們通過(guò)有監(jiān)督學(xué)習(xí)的方法來(lái)擬合分類(lèi)器模型。所謂有監(jiān)督學(xué)習(xí)是指通過(guò)提供一批帶有標(biāo)注(學(xué)習(xí)的目標(biāo))的數(shù)據(jù)(稱之為訓(xùn)練樣本),學(xué)習(xí)器通過(guò)分析數(shù)據(jù)的規(guī)律嘗試擬合出這些數(shù)據(jù)和學(xué)習(xí)目標(biāo)間的函數(shù),使得定義在訓(xùn)練集上的總體誤差盡可能的小,從而利用學(xué)得的函數(shù)來(lái)預(yù)測(cè)未知數(shù)據(jù)的學(xué)習(xí)方法。注意這不是一個(gè)嚴(yán)格的定義,而是我根據(jù)自己的理解簡(jiǎn)化出來(lái)的。
一批帶有標(biāo)注的訓(xùn)練數(shù)據(jù)從何而來(lái),一般而言都需要人工標(biāo)注。我們從搜索引擎的日志里隨機(jī)采集一批Query,并且保證這批Query能夠覆蓋到每維特征的每個(gè)取值(從這里也可以看出為什么要做特征分區(qū)間或離散化了,因?yàn)槿绮贿@樣做我們就不能保證能夠覆蓋到每維特征的每個(gè)取值)。然后,通過(guò)人肉的方法給這邊Query打上是否具有O2O意圖的標(biāo)簽。數(shù)據(jù)標(biāo)注是一個(gè)痛苦而漫長(zhǎng)的過(guò)程,需要具有一定領(lǐng)域知識(shí)的人來(lái)干這樣的活。標(biāo)注質(zhì)量的好壞很有可能會(huì)影響到學(xué)習(xí)到的模型(這里指分類(lèi)器)在未知Query上判別效果的好壞。即正確的老師更可能教出正確的學(xué)生,反之,錯(cuò)誤的老師教壞學(xué)生的可能性越大。在我自己標(biāo)注數(shù)據(jù)的過(guò)程中,發(fā)現(xiàn)有一些Query的O2O意圖比較模棱兩可,導(dǎo)致我后來(lái)回頭看的時(shí)候總覺(jué)得自己標(biāo)得不對(duì),反反復(fù)復(fù)修改了好幾次。
選擇模型:
在我們的問(wèn)題中,模型就是要學(xué)習(xí)的分類(lèi)器。有監(jiān)督學(xué)習(xí)的分類(lèi)器有很多,比如決策樹(shù)、隨機(jī)森林、邏輯回歸、梯度提升、SVM等等。如何為我們的分類(lèi)問(wèn)題選擇合適的機(jī)器學(xué)習(xí)算法呢?當(dāng)然,如果我們真正關(guān)心準(zhǔn)確率,那么最佳方法是測(cè)試各種不同的算法(同時(shí)還要確保對(duì)每個(gè)算法測(cè)試不同參數(shù)),然后通過(guò)交叉驗(yàn)證選擇最好的一個(gè)。但是,如果你只是為你的問(wèn)題尋找一個(gè)“足夠好”的算法,或者一個(gè)起點(diǎn),也是有一些還不錯(cuò)的一般準(zhǔn)則的,比如如果訓(xùn)練集很小,那么高偏差/低方差分類(lèi)器(如樸素貝葉斯分類(lèi)器)要優(yōu)于低偏差/高方差分類(lèi)器(如k近鄰分類(lèi)器),因?yàn)楹笳呷菀走^(guò)擬合。然而,隨著訓(xùn)練集的增大,低偏差/高方差分類(lèi)器將開(kāi)始勝出(它們具有較低的漸近誤差),因?yàn)楦咂罘诸?lèi)器不足以提供準(zhǔn)確的模型。
這里我們重點(diǎn)介紹一次完整的機(jī)器學(xué)習(xí)全過(guò)程,所以不花大篇幅在模型選擇的問(wèn)題上,推薦大家讀一些這篇文章:《如何選擇機(jī)器學(xué)習(xí)分類(lèi)器?》。
通過(guò)交叉驗(yàn)證擬合模型:
機(jī)器學(xué)習(xí)會(huì)學(xué)習(xí)數(shù)據(jù)集的某些屬性,并運(yùn)用于新數(shù)據(jù)。這就是為什么習(xí)慣上會(huì)把數(shù)據(jù)分為兩個(gè)集合,由此來(lái)評(píng)價(jià)算法的優(yōu)劣。這兩個(gè)集合,一個(gè)叫做訓(xùn)練集(train data),我們從中獲得數(shù)據(jù)的性質(zhì);一個(gè)叫做測(cè)試集(test data),我們?cè)诖藴y(cè)試這些性質(zhì),即模型的準(zhǔn)確率。將一個(gè)算法作用于一個(gè)原始數(shù)據(jù),我們不可能只做出隨機(jī)的劃分一次train和test data,然后得到一個(gè)準(zhǔn)確率,就作為衡量這個(gè)算法好壞的標(biāo)準(zhǔn)。因?yàn)檫@樣存在偶然性。我們必須好多次的隨機(jī)的劃分train data和test data,分別在其上面算出各自的準(zhǔn)確率。這樣就有一組準(zhǔn)確率數(shù)據(jù),根據(jù)這一組數(shù)據(jù),就可以較好的準(zhǔn)確的衡量算法的好壞。交叉驗(yàn)證就是一種在數(shù)據(jù)量有限的情況下的非常好evaluate performance的方法。
| 1 from sklearn import cross_validation2 from sklearn import tree3 from sklearn import ensemble4 from sklearn import linear_model5 from sklearn import svm6 7 lr = linear_model.LogisticRegression()8 lr_scores = cross_validation.cross_val_score(lr, train_data, train_target, cv=5)9 print("logistic regression accuracy:") 10 print(lr_scores) 11 12 clf = tree.DecisionTreeClassifier(criterion='entropy', max_depth=8, min_samples_split=5) 13 clf_scores = cross_validation.cross_val_score(clf, train_data, train_target, cv=5) 14 print("decision tree accuracy:") 15 print(clf_scores) 16 17 rfc = ensemble.RandomForestClassifier(criterion='entropy', n_estimators=3, max_features=0.5, min_samples_split=5) 18 rfc_scores = cross_validation.cross_val_score(rfc, train_data, train_target, cv=5) 19 print("random forest accuracy:") 20 print(rfc_scores) 21 22 etc = ensemble.ExtraTreesClassifier(criterion='entropy', n_estimators=3, max_features=0.6, min_samples_split=5) 23 etc_scores = cross_validation.cross_val_score(etc, train_data, train_target, cv=5) 24 print("extra trees accuracy:") 25 print(etc_scores) 26 27 gbc = ensemble.GradientBoostingClassifier() 28 gbc_scores = cross_validation.cross_val_score(gbc, train_data, train_target, cv=5) 29 print("gradient boosting accuracy:") 30 print(gbc_scores) 31 32 svc = svm.SVC() 33 svc_scores = cross_validation.cross_val_score(svc, train_data, train_target, cv=5) 34 print("svm classifier accuracy:") 35 print(svc_scores) |
上面的代碼我們嘗試同交叉驗(yàn)證的方法對(duì)比五種不同模型的準(zhǔn)確率,結(jié)果如下:
| 1 logistic regression accuracy:2 [ 0.76953125 0.83921569 0.85433071 0.81102362 0.83858268]3 decision tree accuracy:4 [ 0.73828125 0.8 0.77559055 0.71653543 0.83464567]5 random forest accuracy:6 [ 0.75 0.76862745 0.76377953 0.77165354 0.80314961]7 extra trees accuracy:8 [ 0.734375 0.78039216 0.7992126 0.76377953 0.79527559]9 gradient boosting accuracy: 10 [ 0.7578125 0.81960784 0.83464567 0.80708661 0.84251969] 11 svm classifier accuracy: 12 [ 0.703125 0.78431373 0.77952756 0.77952756 0.80708661] |
在O2O意圖識(shí)別這個(gè)學(xué)習(xí)問(wèn)題上,邏輯回歸分類(lèi)器具有最好的準(zhǔn)確率,其次是梯度提升分類(lèi)器;決策樹(shù)和隨機(jī)森林在我們的測(cè)試結(jié)果中并沒(méi)有體現(xiàn)出明顯的差異,可能是我們的特殊數(shù)量太少并且樣本數(shù)也較少的原因;另外大名典典的SVM的表現(xiàn)卻比較讓人失望??傮w而言,準(zhǔn)確率只有82%左右,分析其原因,一方面我們實(shí)現(xiàn)的特征數(shù)量較少;另一方面暫時(shí)未能實(shí)現(xiàn)區(qū)分能力強(qiáng)的特征。后續(xù)會(huì)對(duì)此持續(xù)優(yōu)化。
由于邏輯回歸分類(lèi)器具有最好的性能,我們決定用全部是可能訓(xùn)練數(shù)據(jù)來(lái)擬合之。
| lr = lr.fit(train_data, train_target) |
模型數(shù)據(jù)持久化:
學(xué)到的模型要能夠在將來(lái)利用起來(lái),就必須把模型保存下來(lái),以便下次使用。同時(shí),數(shù)據(jù)離散化或數(shù)據(jù)分區(qū)的范圍數(shù)據(jù)也要保存下來(lái),在預(yù)測(cè)的時(shí)候同樣也需要對(duì)特征進(jìn)行區(qū)間劃分。python提供了pickle模塊用來(lái)序列號(hào)對(duì)象,并保存到硬盤(pán)上。同時(shí),scikit-learn庫(kù)也提供了更加高效的模型持久化模塊,可以直接使用。
| 1 from sklearn.externals import joblib 2 joblib.dump(lr, 'D:\lr.model') 3 import pickle 4 bin_file = open(r'D:\result_bin.data', 'wb') 5 pickle.dump(bins, bin_file) 6 bin_file.close() |
分類(lèi)器的使用:
現(xiàn)在大功告成了,我們需要做的就是用學(xué)習(xí)到的分類(lèi)器來(lái)判斷一個(gè)新的Query到底是否具有O2O意圖。因?yàn)槲覀兎诸?lèi)器的輸入是Query的特征向量,而不是Query本身,因此我們需要實(shí)現(xiàn)提取好Query的特征。假設(shè)我們已經(jīng)離線算好了每個(gè)Query的特征,現(xiàn)在使用的時(shí)候只需要將其加載進(jìn)內(nèi)場(chǎng)即可。分類(lèi)器使用的過(guò)程首先是從硬盤(pán)讀取模型數(shù)據(jù)和Query特征,然后調(diào)用模型對(duì)Query進(jìn)行預(yù)測(cè),輸出結(jié)果。
| 1 # load result bin data and model2 bin_file = open(r'D:\result_bin.data', 'rb')3 bins = pickle.load(bin_file)4 bin_file.close()5 6 lr = joblib.load('D:\lr.model')7 8 # load data9 query = np.genfromtxt(r'D:\o2o_query_rec\all_query', dtype='U2', comments=None, converters={0: lambda x: str(x, 'utf-8')}) 10 feature = np.loadtxt(r'D:\o2o_query_rec\all_features', dtype='int', delimiter='\001') 11 12 # descrite 13 feature[:,0] = bin(bins)(feature[:,0]) 14 feature[:,1] = ufunc_segment(feature[:,1]) 15 16 # predict 17 result = lr.predict(feature) 18 19 # save result 20 #np.savetxt(r'D:\o2o_query_rec\classify_result.txt', np.c_[query, result], fmt=u"%s", delimiter="\t") 21 result_file = open(r'D:\o2o_query_rec\classify_result.txt', 'w') 22 i = 0 23 for q in query: 24 result_file.write('%s\t%d\n' % (q, result[i])) 25 i += 1 26 result_file.close() |
需要注意的是我們Query的編碼是UTF-8,load的時(shí)候需要做相應(yīng)的轉(zhuǎn)換。另外,在python 3.3版本,numpy的savetxt函數(shù)并不能正確保持UTF-8格式的中文Query(第20行注釋掉的代碼輸出的Query都變成了bytes格式的),如果小伙伴們有更好的辦法能夠解決這個(gè)問(wèn)題,請(qǐng)告訴我,謝謝!
?
轉(zhuǎn)載于:https://www.cnblogs.com/DjangoBlog/p/6212911.html
總結(jié)
以上是生活随笔為你收集整理的Query意图分析:记一次完整的机器学习过程(scikit learn library学习笔记)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 大话西游之大圣娶亲中的至尊宝和紫霞仙子
- 下一篇: Anaconda3自带jupyter