機器學習完整過程案例分布解析,python代碼解析


所謂學習問題,是指觀察由n個樣本組成的集合,並依據這些數據來預測未知數據的性質。

學習任務(一個二分類問題):

區分一個普通的互聯網檢索Query是否具有某個垂直領域的意圖。如果如今有一個O2O領域的垂直搜索引擎,專門為用戶提供團購、優惠券的檢索;同一時候存在一個通用的搜索引擎,比方百度,通用搜索引擎希望可以識別出一個Query是否具有O2O檢索意圖,如果有則調用O2O垂直搜索引擎,獲取結果作為通用搜索引擎的結果補充。

我們的目的是學習出一個分類器(classifier),分類器能夠理解為一個函數,其輸入為一個Query,輸出為0(表示該Query不具有o2o意圖)或1(表示該Query具有o2o意圖)。

特征提取

要完畢這樣一個學習任務,首先我們必須找出決定一個Query是否具有O2O意圖的影響因素,這些影響因素稱之為特征(feature)。

特征的好壞非常大程度上決定了分類器的效果。在機器學習領域我們都知道特征比模型(學習算法)更重要。(順便說一下,工業界的人都是這么覺得的,學術界的人可能不以為然。他們整天搗鼓算法,發出來的文章大部分都沒法在實際中應用。

)舉個樣例,如果我們的特征選得非常好。可能我們用簡單的規則就能推斷出終於的結果。甚至不須要模型。比方,要推斷一個人是男還是女(人類當然非常好推斷。一看就知道,這里我們如果由計算機來完畢這個任務,計算機有非常多傳感器(攝像頭、體重器等等)能夠採集到各種數據),我們能夠找到非常多特征:身高、體重、皮膚顏色、頭發長度等等。由於依據統計我們知道男人一般比女人重。比女人高,皮膚比女人黑,頭發比女人短。所以這些特征都有一定的區分度。可是總有反例存在。

我們用最好的算法可能准確率也達不到100%。如果計算機還能夠讀取人的身份證號碼,那么我們可能獲得一個更強的特征:身份證號碼的倒數第二位是否是偶數。依據身份證編碼規則,我們知道男性的身份證號碼的倒數第二位是奇數,女生是偶數。

因此。有了這個特征其它的特征都不須要了,並且我們的分類器也非常easy,不須要復雜的算法。

言歸正傳。對於O2O Query意圖識別這一學習任務,我們可以用的特征可能有:Query在垂直引擎里可以檢索到的結果數量、Query在垂直引擎里可以檢索到的結果的類目困惑度(perplexity)(檢索結果的類目越集中說明其意圖越強)、Query是否能預測到特征的O2O商品類目、Query是否包括O2O產品詞或品牌詞、Query在垂直引擎的歷史展現次數(PV)和點擊率(ctr)、Query在垂直引擎的檢索結果相關性等等。

特征表示

特征表示是對特征提取結果的再加工,目的是增強特征的表示能力,防止模型(分類器)過於復雜和學習困難。比方對連續的特征值進行離散化,就是一種經常使用的方法。

這里我們以“Query在垂直引擎里能夠檢索到的結果數量”這一特征為例,簡要介紹一下特征值分段的過程。首先,分析一下這一維特征的分布情況。我們對這一維特征值的最小值、最大值、平均值、方差、中位數、三分位數、四分位數、某些特定值(比方零值)所占比例等等都要有一個大致的了解。獲取這些值,python編程語言的numpy模塊有非常多現成的函數能夠調用。最好的辦法就是可視化。借助python的matplotlib工具我們能夠非常easy地划出數據分布的直方圖,從而推斷出我們應該對特征值划多少個區間。每一個區間的范圍是如何的。比方說我們要對“結果數量”這一維特征值除了“0”以為的其它值均勻地分為10個區間,即每一個區間內的樣本數大致同樣。

“0”是一個特殊的值,因此我們想把它分到一個單獨的區間,這樣我們一共同擁有11個區間。python代碼實現例如以下:

復制代碼
import numpy as np

def bin(bins):
    assert isinstance(bins, (list, tuple))
    def scatter(x):
        if x == 0: return 0
        for i in range(len(bins)):
            if x <= bins[i]: return i + 1
        return 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)
復制代碼

我們首先獲取每一個區間的起止范圍,即分別算法特征向量的10個百分位數,並依此為基礎算出新的特征值(通過bin函數,一個numpy的universal function)。

訓練數據

這里我們通過有監督學習的方法來擬合分類器模型。

所謂有監督學習是指通過提供一批帶有標注(學習的目標)的數據(稱之為訓練樣本),學習器通過分析數據的規律嘗試擬合出這些數據和學習目標間的函數。使得定義在訓練集上的整體誤差盡可能的小,從而利用學得的函數來預測未知數據的學習方法。注意這不是一個嚴格的定義,而是我依據自己的理解簡化出來的。

一批帶有標注的訓練數據從何而來,一般而言都須要人工標注。我們從搜索引擎的日志里隨機採集一批Query,而且保證這批Query可以覆蓋到每維特征的每一個取值(從這里也可以看出為什么要做特征分區間或離散化了,由於如不這樣做我們就不能保證可以覆蓋到每維特征的每一個取值)。然后,通過人肉的方法給這邊Query打上是否具有O2O意圖的標簽。數據標注是一個痛苦而漫長的過程。須要具有一定領域知識的人來干這種活。

標注質量的好壞非常有可能會影響到學習到的模型(這里指分類器)在未知Query上判別效果的好壞。

即正確的老師更可能教出正確的學生,反之,錯誤的老師教壞學生的可能性越大。在我自己標注數據的過程中,發現有一些Query的O2O意圖比較模棱兩可,導致我后來回頭看的時候總認為自己標得不正確。反重復復改動了好幾次。

選擇模型

在我們的問題中,模型就是要學習的分類器。有監督學習的分類器有非常多,比方決策樹、隨機森林、邏輯回歸、梯度提升、SVM等等。

怎樣為我們的分類問題選擇合適的機器學習算法呢?當然,假設我們真正關心准確率。那么最佳方法是測試各種不同的算法(同一時候還要確保對每一個算法測試不同參數)。然后通過交叉驗證選擇最好的一個。可是,假設你僅僅是為你的問題尋找一個“足夠好”的算法,或者一個起點,也是有一些還不錯的一般准則的。比方假設訓練集非常小。那么高偏差/低方差分類器(如朴素貝葉斯分類器)要優於低偏差/高方差分類器(如k近鄰分類器),由於后者easy過擬合。然而,隨着訓練集的增大,低偏差/高方差分類器將開始勝出(它們具有較低的漸近誤差),由於高偏差分類器不足以提供准確的模型。

這里我們重點介紹一次完整的機器學習全過程,所以不花大篇幅在模型選擇的問題上。推薦大家讀一些這篇文章:《怎樣選擇機器學習分類器?》。

通過交叉驗證擬合模型

機器學習會學習數據集的某些屬性,並運用於新數據。

這就是為什么習慣上會把數據分為兩個集合,由此來評價算法的優劣。這兩個集合。一個叫做訓練集(train data),我們從中獲得數據的性質;一個叫做測試集(test data)。我們在此測試這些性質,即模型的准確率。

將一個算法作用於一個原始數據。我們不可能僅僅做出隨機的划分一次train和test data,然后得到一個准確率。就作為衡量這個算法好壞的標准。

由於這樣存在偶然性。我們必須好多次的隨機的划分train data和test data。分別在其上面算出各自的准確率。這樣就有一組准確率數據,依據這一組數據,就能夠較好的准確的衡量算法的好壞。

交叉驗證就是一種在數據量有限的情況下的很好evaluate performance的方法。

復制代碼
 1 from sklearn import cross_validation
 2 from sklearn import tree
 3 from sklearn import ensemble
 4 from sklearn import linear_model
 5 from sklearn import svm
 6 
 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)
復制代碼

上面的代碼我們嘗試同交叉驗證的方法對照五種不同模型的准確率,結果例如以下:

復制代碼
 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意圖識別這個學習問題上,邏輯回歸分類器具有最好的准確率。其次是梯度提升分類器;決策樹和隨機森林在我們的測試結果中並沒有體現出明顯的差異。可能是我們的特殊數量太少而且樣本數也較少的原因;另外大名典典的SVM的表現卻比較讓人失望。

整體而言,准確率僅僅有82%左右,分析其原因。一方面我們實現的特征數量較少;還有一方面臨時未能實現區分能力強的特征。興許會對此持續優化。

因為邏輯回歸分類器具有最好的性能。我們決定用所有是可能訓練數據來擬合之。

lr = lr.fit(train_data, train_target)

模型數據持久化

學到的模型要可以在將來利用起來,就必須把模型保存下來,以便下次使用。同一時候。數據離散化或數據分區的范圍數據也要保存下來。在預測的時候相同也須要對特征進行區間划分。python提供了pickle模塊用來序列號對象,並保存到硬盤上。同一時候。scikit-learn庫也提供了更加高效的模型持久化模塊。可以直接使用。

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()

分類器的使用

如今大功告成了。我們須要做的就是用學習到的分類器來推斷一個新的Query究竟是否具有O2O意圖。由於我們分類器的輸入是Query的特征向量,而不是Query本身,因此我們須要實現提取好Query的特征。

如果我們已經離線算好了每一個Query的特征。如今使用的時候僅僅須要將其載入進內場就可以。

分類器使用的過程首先是從硬盤讀取模型數據和Query特征。然后調用模型對Query進行預測。輸出結果。

復制代碼
 1 # load result bin data and model
 2 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 data
 9 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的時候須要做對應的轉換。

另外。在python 3.3版本號,numpy的savetxt函數並不能正確保持UTF-8格式的中文Query(第20行凝視掉的代碼輸出的Query都變成了bytes格式的)。假設小伙伴們有更好的辦法可以解決問題。請告訴我,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM