前言
大家可以先看看《機器學習理論基礎》這個系列,就會發現自己編寫一個學習算法還是挺麻煩的,特别是稍微複雜一點的,在某些文章中我也提到過sklearn,這個庫我個人覺得是機器學習必備,使用簡單但功能強大,堪稱神庫。sklearn全稱是scikit-learn,網址在https://scikit-learn.org/stable/index.html。這個系列就結合一些執行個體,介紹一下這個庫的用法。
Sklearn的基本模組化流程
Sklearn對所有模型的模組化流程都是通用的,這是它非常了不起的地方,總共隻有三步。
1.執行個體化,建立評估模型對象;
2.通過模型接口訓練模型;
3.通過模型結果提取需要的資訊。
相應的,第一步,需要了解執行個體化時需要使用的參數。第二第三步需要了解資料屬性和資料接口。
下面就是示例,其實也就是三步而已。
from sklearn import tree #導入需要的子產品
clf = tree.DecisionTreeClassifier() #執行個體化
clf = clf.fit(X_train,y_train) #用訓練集資料訓練模型
result = clf.score(X_test,y_test) #導入測試集,從接口中調用需要的資訊(正确率)或者predict
先導知識
如果大家不了解決策樹的話,可以看看這篇文章。機器學習理論基礎(5)-決策樹
決策樹模型參數
Sklearn中決策樹的類都在”tree“這個子產品下,決策樹有分類樹(tree.DecisionTreeClassifier)也有回歸樹(tree.DecisionTreeRegressor)。這裡隻講分類樹。
class sklearn.tree.DecisionTreeClassifier(criterion='gini', splitter='best',
max_depth=None, min_samples_split=2, min_samples_leaf=1,
min_weight_fraction_leaf=0.0, max_features=None, random_state=None,
max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None,
class_weight=None, presort='deprecated', ccp_alpha=0.0)
習慣就好,sklearn的類參數都是非常多。需要了解的參數我們隻講8個。
criterion
兩個參數,其實就是ID3和CART算法的差別。
- 輸入”entropy“,使用資訊熵(Entropy)
- 輸入”gini“,使用基尼系數(Gini Impurity)
random_state & splitter
決策樹在建樹時,是靠優化節點來追求一棵優化的樹,但最優的節點能夠保證最優的樹嗎?內建算法被用來解決這個問題:既然一棵樹不能保證最優,那就建更多的不同的樹,然後從中取最好的。怎樣從一組資料集中建不同的樹?在每次分枝時,不從使用全部特征,而是随機選取一部分特征,從中選取不純度相關名額最優的作為分枝用的節點。這樣,每次生成的樹也就不同了。
random_state 用來設定分枝中的随機模式的參數,預設None。也可以輸入任意整數作為随機數生成種子。設定的話,是為了保證每次随機生成的數是一緻的(即使是随機的);如果不設定,那麼每次生成的随機數都是不同的,
也就是說決策樹其實是随機的(可以通過多運作幾次score測試下)。
splitter也是用來控制決策樹中的随機選項的,有兩種輸入值,輸入”best",決策樹在分枝時雖然随機,但是還是會優先選擇更重要的特征進行分枝(重要性可以通過屬性feature_importances_檢視)
#特征重要性
clf.feature_importances_
[*zip(feature_name,clf.feature_importances_)]
輸入“random",決策樹在分枝時會更加随機,樹會因為含有更多的不必要資訊而更深更大,并因這些不必要資訊而降低對訓練集的拟合。這也是防止過拟合的一種方式。當你預測到你的模型會過拟合,用這兩個參數來幫助你降低樹建成之後過拟合的可能性。當然,樹一旦建成,我們依然是使用剪枝參數來防止過拟合。
剪枝參數
為了讓決策樹有更好的泛化性,我們要對決策樹進行剪枝。剪枝政策對決策樹的影響巨大,正确的剪枝政策是優化決策樹算法的核心。sklearn為我們提供了不同的剪枝政策:
max_depth
限制樹的最大深度,超過設定深度的樹枝全部剪掉。
這是用得最廣泛的剪枝參數,在高次元低樣本量時非常有效。決策樹多生長一層,對樣本量的需求會增加一倍,是以限制樹深度能夠有效地限制過拟合。實際使用時,建議從=3開始嘗試,看看拟合的效果再決定是否增加設定深度。
min_samples_leaf & min_samples_split
min_samples_leaf限定,一個節點在分枝後的每個子節點都必須包含至少min_samples_leaf個訓練樣本,否則分枝就不會發生。一般來說,建議從=5開始使用。如果葉節點中含有的樣本量變化很 大,建議輸入浮點數作為樣本量的百分比來使用。
min_samples_split限定,一個節點必須要包含至少min_samples_split個訓練樣本,這個節點才允許被分枝,否則分枝就不會發生。
這兩個參數就是限定葉子和節點的訓練樣本個數,如果數目太少,就沒必要分支,防止過拟合。
max_features & min_impurity_decrease
一般配合max_depth使用,用作樹的”精修“。
max_features限制分枝時考慮的特征個數,超過限制個數的特征都會被舍棄。max_features是用來限制高次元資料的過拟合的剪枝參數,但其方法比較暴力,是直接限制可以使用的特征數量而強行使決策樹停下的參數,在不知道決策樹中的各個特征的重要性的情況下,強行設定這個參數可能會導緻模型學習不足。如果希望通過降維的方式防止過拟合,建議使用PCA,ICA或者特征選擇子產品中的降維算法。 min_impurity_decrease限制資訊增益的大小,資訊增益小于設定數值的分枝不會發生。
目标權重參數
class_weight & min_weight_fraction_leaf
完成樣本标簽平衡的參數。樣本不平衡是指在一組資料集中,标簽的一類天生占有很大的比例。是以我們要使用class_weight參數對樣本标簽進行一定的均衡,給少量的标簽更多的權重,讓模型更偏向少數類,向捕獲少數類的方向模組化。該參數預設None,此模式表示自動給與資料集中的所有标簽相同的權重。
有了權重之後,樣本量就不再是單純地記錄數目,而是受輸入的權重影響了,是以這時候剪枝,就需要搭配min_weight_fraction_leaf這個基于權重的剪枝參數來使用。
重要屬性和接口
屬性是在模型訓練之後,能夠調用檢視的模型的各種性質。對決策樹來說,最重要的是feature_importances_,能夠檢視各個特征對模型的重要性。 sklearn中許多算法的接口都是相似的,比如說我們之前已經用到的fit和score,幾乎對每個算法都可以使用。除了這兩個接口之外,決策樹最常用的接口還有
apply和predict。apply中輸入測試集傳回每個測試樣本所在的葉子節點的索引,predict輸入測試集傳回每個測試樣本的标簽。
#apply傳回每個測試樣本所在的葉子節點的索引
clf.apply(Xtest)
#predict傳回每個測試樣本的分類/回歸結果
clf.predict(Xtest)
在這裡不得不提的是,所有接口中要求輸入X_train和X_test的部分,
輸入的特征矩陣必須至少是一個二維矩陣。sklearn不接受任何一維矩陣作為特征矩陣被輸入。
執行個體:泰坦尼克号幸存者預測
資料集來自: https://www.kaggle.com/c/titanic。
如何探索資料
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
data = pd.read_csv(r"./data.csv",index_col = 0)
探索資料常用指令:head 其實就是簡單看看前幾行的資料,看看表格到底長什麼樣,直接用data的話就是展示的太多了。
data.head()
探索資料常用指令:info data.info()
看到輸出為:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 891 entries, 1 to 891
Data columns (total 11 columns):
Survived 891 non-null int64
Pclass 891 non-null int64
Name 891 non-null object #object代表不是數字,但決策樹分類器隻能處理數字
Sex 891 non-null object
Age 714 non-null float64 #有部分缺失值
SibSp 891 non-null int64
Parch 891 non-null int64
Ticket 891 non-null object
Fare 891 non-null float64
Cabin 204 non-null object #有超多缺失值
Embarked 889 non-null object
dtypes: float64(2), int64(4), object(5)
memory usage: 83.5+ KB
資料預處理
篩選特征
我們預測的是存活的幾率,那麼Name就不重要了。其次,Cabin并沒有告訴我們有用的資訊,比如頭等艙還是經濟艙,而且缺失值實在太多了,這裡就去掉。最後,Ticket同樣可以删掉。
data.drop(["Cabin","Name","Ticket"],inplace=True,axis=1)
data.head()
處理缺失值
年齡和是否幸存有較強的相關性,是以缺失值我們需要填補,這裡使用平均年齡填補。順便把有缺失值的行删除。
data["Age"] = data["Age"].fillna(data["Age"].mean())
data = data.dropna()
處理非數字
#将二分類變量轉換為數值型變量
data["Sex"] = (data["Sex"]== "male").astype("int")
#将三分類變量轉換為數值型變量
labels = data["Embarked"].unique().tolist()
data["Embarked"] = data["Embarked"].apply(lambda x: labels.index(x))
當然這樣直接的轉成0,1,2是有前提的:就是特征的不同取值是沒有聯系的。比如說有一列是“教育水準”,分為“國中,高中,大學”,就不能單純的換成0,1,2,因為不同學曆之間的關系,沒辦法通過這種方式表達出來。大家要深入了解的話,可搜尋“one-hot”編碼。
拆分資料
先把資料和标簽拆分,然後按經典的三七分,把資料拆分。由于是随機抽取的,是以索引是亂的。
X = data.iloc[:,data.columns != "Survived"]
y = data.iloc[:,data.columns == "Survived"]
from sklearn.model_selection import train_test_split
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y,test_size=0.3)
Xtrain.head(10)
修正測試集和訓練集的索引。
for i in [Xtrain, Xtest, Ytrain, Ytest]:
i.index = range(i.shape[0])
訓練模型
按照基本流程,三部曲,訓練模型。得到分數後,再做十次交叉驗證,看看模型的穩定性。
clf = DecisionTreeClassifier(random_state=25)
clf = clf.fit(Xtrain, Ytrain)
score_ = clf.score(Xtest, Ytest)
print(score_)
#進行十次交叉驗證
score = cross_val_score(clf,X,y,cv=10).mean()
print(score)
結果一般:
0.7865168539325843
0.7739274770173645
調整參數
先從max_depth開始調,為了看看到底是過拟合還是欠拟合,最好還是把訓練集和測試集的表現都比較一下。
tr = []
te = []
for i in range(10):
clf = DecisionTreeClassifier(random_state=25
,max_depth=i+1
,criterion="entropy"
)
clf = clf.fit(Xtrain, Ytrain)
score_tr = clf.score(Xtrain,Ytrain)
score_te = cross_val_score(clf,X,y,cv=10).mean()
tr.append(score_tr)
te.append(score_te)
print(max(te))
plt.plot(range(1,11),tr,color="red",label="train")
plt.plot(range(1,11),te,color="blue",label="test")
plt.xticks(range(1,11))
plt.legend()
plt.show()
輸出:
0.8177860061287026
結果還是隻有81%,可以看到是有過拟合的傾向,我們還得繼續調整參數。
網格搜尋
網格搜尋是幫助我們同時調整多個參數的技術,本質是枚舉各個參數,一個一個嘗試,看看哪個效果最好。我們先會給網格一個字典,對應的是調整的參數和調整參數的範圍。是以網格搜尋的時間是很長的,大家不要随便設定參數範圍。
import numpy as np
parameters = {'splitter':('best','random')
,'criterion':("gini","entropy")
,"max_depth":[*range(1,10)]
,'min_samples_leaf':[*range(1,50,5)]
,'min_impurity_decrease':[*np.linspace(0,0.5,20)]
}
clf = DecisionTreeClassifier(random_state=25)
#交叉驗證的次數依然是10次
GS = GridSearchCV(clf, parameters, cv=10)
GS.fit(Xtrain,Ytrain)
print(GS.best_params_)
print(GS.best_score_)
輸出為:
{'criterion': 'gini',
'max_depth': 5,
'min_impurity_decrease': 0.0,
'min_samples_leaf': 1,
'splitter': 'random'}
0.8344051446945338
相比之前的分數提高了,可見網格産生了效果,可以找到較好的參數。為什麼不是最好的參數?那是因為網格搜尋也是有問題的,比如說它有四個參數,但用了其中三個參數,就能達到一個很好的效果,網格也不能舍棄最後一個參數,必須要使用完。是以有時候使用網格得到更差的分數,也是很正常的。
這裡我們就簡單學習了決策樹的重要參數和執行個體,大家要深入了解還需多寫多練。