2.3.5 決策樹(shù)
決策樹(shù)是廣泛用于分類(lèi)和回歸任務(wù)的模型。本質(zhì)上童叠,它從一層層的 if/else 問(wèn)題中進(jìn)行學(xué)習(xí)框喳,并得出結(jié)論课幕。
這些問(wèn)題類(lèi)似于你在“20 Questions”游戲 9 中可能會(huì)問(wèn)的問(wèn)題厦坛。想象一下,你想要區(qū)分下面這四種動(dòng)物:熊乍惊、鷹杜秸、企鵝和海豚。你的目標(biāo)是通過(guò)提出盡可能少的 if/else 問(wèn)題來(lái)得到正確答案润绎。你可能首先會(huì)問(wèn):這種動(dòng)物有沒(méi)有羽毛撬碟,這個(gè)問(wèn)題會(huì)將可能的動(dòng)物減少到只有兩種。如果答案是“有”莉撇,你可以問(wèn)下一個(gè)問(wèn)題呢蛤,幫你區(qū)分鷹和企鵝。例如棍郎,你可以問(wèn)這種動(dòng)物會(huì)不會(huì)飛其障。如果這種動(dòng)物沒(méi)有羽毛,那么可能是海豚或熊涂佃,所以你需要問(wèn)一個(gè)問(wèn)題
來(lái)區(qū)分這兩種動(dòng)物——比如問(wèn)這種動(dòng)物有沒(méi)有鰭励翼。
這一系列問(wèn)題可以表示為一棵決策樹(shù),如圖 2-22 所示辜荠。
In[56]:
mglearn.plots.plot_animal_tree()
在這張圖中汽抚,樹(shù)的每個(gè)結(jié)點(diǎn)代表一個(gè)問(wèn)題或一個(gè)包含答案的終結(jié)點(diǎn)(也叫葉結(jié)點(diǎn))。樹(shù)的邊將問(wèn)題的答案與將問(wèn)的下一個(gè)問(wèn)題連接起來(lái)伯病。
用機(jī)器學(xué)習(xí)的語(yǔ)言來(lái)說(shuō)就是造烁,為了區(qū)分四類(lèi)動(dòng)物(鷹、企鵝午笛、海豚和熊)惭蟋,我們利用三個(gè)特征(“有沒(méi)有羽毛”“會(huì)不會(huì)飛”和“有沒(méi)有鰭”)來(lái)構(gòu)建一個(gè)模型。我們可以利用監(jiān)督學(xué)習(xí)從數(shù)據(jù)中學(xué)習(xí)模型季研,而無(wú)需人為構(gòu)建模型敞葛。
1. 構(gòu)造決策樹(shù)
我們?cè)趫D 2-23 所示的二維分類(lèi)數(shù)據(jù)集上構(gòu)造決策樹(shù)。這個(gè)數(shù)據(jù)集由 2 個(gè)半月形組成与涡,每個(gè)類(lèi)別都包含 50 個(gè)數(shù)據(jù)點(diǎn)惹谐。我們將這個(gè)數(shù)據(jù)集稱(chēng)為 two_moons持偏。
學(xué)習(xí)決策樹(shù),就是學(xué)習(xí)一系列 if/else 問(wèn)題氨肌,使我們能夠以最快的速度得到正確答案鸿秆。在機(jī)器學(xué)習(xí)中,這些問(wèn)題叫作測(cè)試(不要與測(cè)試集弄混怎囚,測(cè)試集是用來(lái)測(cè)試模型泛化性能的數(shù)據(jù))卿叽。
數(shù)據(jù)通常并不是像動(dòng)物的例子那樣具有二元特征(是 / 否)的形式,而是表示為連續(xù)特征恳守,比如圖 2-23 所示的二維數(shù)據(jù)集考婴。用于連續(xù)數(shù)據(jù)的測(cè)試形式是:“特征 i 的值是否大于 a ?”
為了構(gòu)造決策樹(shù)催烘,算法搜遍所有可能的測(cè)試沥阱,找出對(duì)目標(biāo)變量來(lái)說(shuō)信息量最大的那一個(gè)。
圖 2-24 展示了選出的第一個(gè)測(cè)試伊群。將數(shù)據(jù)集在 x[1]=0.0596 處垂直劃分可以得到最多信息考杉,它在最大程度上將類(lèi)別 0 中的點(diǎn)與類(lèi)別 1 中的點(diǎn)進(jìn)行區(qū)分。頂結(jié)點(diǎn)(也叫根結(jié)點(diǎn))表示整個(gè)數(shù)據(jù)集舰始,包含屬于類(lèi)別 0 的 50 個(gè)點(diǎn)和屬于類(lèi)別 1 的 50 個(gè)點(diǎn)崇棠。通過(guò)測(cè)試 x[1] <=0.0596 的真假來(lái)對(duì)數(shù)據(jù)集進(jìn)行劃分,在圖中表示為一條黑線丸卷。如果測(cè)試結(jié)果為真枕稀,那么將這個(gè)點(diǎn)分配給左結(jié)點(diǎn),左結(jié)點(diǎn)里包含屬于類(lèi)別 0 的 2 個(gè)點(diǎn)和屬于類(lèi)別 1 的 32 個(gè)點(diǎn)及老。否則將這個(gè)點(diǎn)分配給右結(jié)點(diǎn)抽莱,右結(jié)點(diǎn)里包含屬于類(lèi)別 0 的 48 個(gè)點(diǎn)和屬于類(lèi)別 1 的 18 個(gè)點(diǎn)。這兩個(gè)結(jié)點(diǎn)對(duì)應(yīng)于圖 2-24 中的頂部區(qū)域和底部區(qū)域骄恶。盡管第一次劃分已經(jīng)對(duì)兩個(gè)類(lèi)別做了很好的區(qū)分食铐,但底部區(qū)域仍包含屬于類(lèi)別 0 的點(diǎn),頂部區(qū)域也仍包含屬于類(lèi)別 1 的點(diǎn)僧鲁。我們可以在兩個(gè)區(qū)域中重復(fù)尋找最佳測(cè)試的過(guò)程虐呻,從而構(gòu)建出更準(zhǔn)確的模型。圖 2-25 展示了信息量最大的下一次劃分寞秃,這次劃分是基于 x[0] 做出的斟叼,分為左右兩個(gè)區(qū)域。
這一遞歸過(guò)程生成一棵二元決策樹(shù)春寿,其中每個(gè)結(jié)點(diǎn)都包含一個(gè)測(cè)試⌒淮玻或者你可以將每個(gè)測(cè)試看成沿著一條軸對(duì)當(dāng)前數(shù)據(jù)進(jìn)行劃分。這是一種將算法看作分層劃分的觀點(diǎn)识腿。由于每個(gè)測(cè)試僅關(guān)注一個(gè)特征,所以劃分后的區(qū)域邊界始終與坐標(biāo)軸平行骂束。對(duì)數(shù)據(jù)反復(fù)進(jìn)行遞歸劃分,直到劃分后的每個(gè)區(qū)域(決策樹(shù)的每個(gè)葉結(jié)點(diǎn))只包含單一目標(biāo)值(單一類(lèi)別或單一回歸值)成箫。如果樹(shù)中某個(gè)葉結(jié)點(diǎn)所包含數(shù)據(jù)點(diǎn)的目標(biāo)值都相同伟众,那么這個(gè)葉結(jié)點(diǎn)就是純的(pure)召廷。這個(gè)數(shù)據(jù)集的最終劃分結(jié)果見(jiàn)圖 2-26竞慢。
想要對(duì)新數(shù)據(jù)點(diǎn)進(jìn)行預(yù)測(cè)本冲,首先要查看這個(gè)點(diǎn)位于特征空間劃分的哪個(gè)區(qū)域劫扒,然后將該區(qū)域的多數(shù)目標(biāo)值(如果是純的葉結(jié)點(diǎn)沟饥,就是單一目標(biāo)值)作為預(yù)測(cè)結(jié)果。從根結(jié)點(diǎn)開(kāi)始對(duì)樹(shù)進(jìn)行遍歷就可以找到這一區(qū)域广料,每一步向左還是向右取決于是否滿足相應(yīng)的測(cè)試幼驶。決策樹(shù)也可以用于回歸任務(wù)盅藻,使用的方法完全相同汹族。預(yù)測(cè)的方法是顶瞒,基于每個(gè)結(jié)點(diǎn)的測(cè)試對(duì)樹(shù)進(jìn)行遍歷,最終找到新數(shù)據(jù)點(diǎn)所屬的葉結(jié)點(diǎn)财剖。這一數(shù)據(jù)點(diǎn)的輸出即為此葉結(jié)點(diǎn)中所有訓(xùn)練點(diǎn)的平均目標(biāo)值。
2. 控制決策樹(shù)的復(fù)雜度
通常來(lái)說(shuō),構(gòu)造決策樹(shù)直到所有葉結(jié)點(diǎn)都是純的葉結(jié)點(diǎn)袱贮,這會(huì)導(dǎo)致模型非常復(fù)雜体啰,并且對(duì)訓(xùn)練數(shù)據(jù)高度過(guò)擬合荒勇。純?nèi)~結(jié)點(diǎn)的存在說(shuō)明這棵樹(shù)在訓(xùn)練集上的精度是 100%。訓(xùn)練集中的每個(gè)數(shù)據(jù)點(diǎn)都位于分類(lèi)正確的葉結(jié)點(diǎn)中兢孝。在圖 2-26 的左圖中可以看出過(guò)擬合仅偎。你可以看到橘沥,在所有屬于類(lèi)別 0 的點(diǎn)中間有一塊屬于類(lèi)別 1 的區(qū)域。另一方面品姓,有一小條屬于類(lèi)別 0 的區(qū)域腹备,包圍著最右側(cè)屬于類(lèi)別 0 的那個(gè)點(diǎn)斤蔓。這并不是人們想象中決策邊界的樣子,這個(gè)決策邊界過(guò)于關(guān)注遠(yuǎn)離同類(lèi)別其他點(diǎn)的單個(gè)異常點(diǎn)漂羊。
防止過(guò)擬合有兩種常見(jiàn)的策略:一種是及早停止樹(shù)的生長(zhǎng)走越,也叫預(yù)剪枝(pre-pruning)耻瑟;另一種是先構(gòu)造樹(shù)喳整,但隨后刪除或折疊信息量很少的結(jié)點(diǎn)框都,也叫后剪枝(post-pruning)或剪枝(pruning)魏保。預(yù)剪枝的限制條件可能包括限制樹(shù)的最大深度、限制葉結(jié)點(diǎn)的最大數(shù)目猪杭,或者規(guī)定一個(gè)結(jié)點(diǎn)中數(shù)據(jù)點(diǎn)的最小數(shù)目來(lái)防止繼續(xù)劃分。scikit-learn 的決策樹(shù)在 DecisionTreeRegressor 類(lèi)和 DecisionTreeClassifier 類(lèi)中實(shí)現(xiàn)税手。
scikit-learn 只實(shí)現(xiàn)了預(yù)剪枝需纳,沒(méi)有實(shí)現(xiàn)后剪枝不翩。
我們?cè)谌橄侔?shù)據(jù)集上更詳細(xì)地看一下預(yù)剪枝的效果。和前面一樣器钟,我們導(dǎo)入數(shù)據(jù)集并將其分為訓(xùn)練集和測(cè)試集妙蔗。然后利用默認(rèn)設(shè)置來(lái)構(gòu)建模型,默認(rèn)將樹(shù)完全展開(kāi)(樹(shù)不斷分支穆役,直到所有葉結(jié)點(diǎn)都是純的)梳凛。我們固定樹(shù)的 random_state韧拒,用于在內(nèi)部解決平局問(wèn)題:
In[58]:
from sklearn.tree import DecisionTreeClassifier
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target, stratify=cancer.target, random_state=42)
tree = DecisionTreeClassifier(random_state=0)
tree.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(tree.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(tree.score(X_test, y_test)))
Out[58]:
Accuracy on training set: 1.000
Accuracy on test set: 0.937
不出所料,訓(xùn)練集上的精度是 100%蹈集,這是因?yàn)槿~結(jié)點(diǎn)都是純的拢肆,樹(shù)的深度很大靖诗,足以完美地記住訓(xùn)練數(shù)據(jù)的所有標(biāo)簽刊橘。測(cè)試集精度比之前講過(guò)的線性模型略低,線性模型的精度約為 95%攒庵。
如果我們不限制決策樹(shù)的深度败晴,它的深度和復(fù)雜度都可以變得特別大尖坤。因此,未剪枝的樹(shù)容易過(guò)擬合场梆,對(duì)新數(shù)據(jù)的泛化性能不佳』蛴停現(xiàn)在我們將預(yù)剪枝應(yīng)用在決策樹(shù)上感昼,這可以在完美擬合訓(xùn)練數(shù)據(jù)之前阻止樹(shù)的展開(kāi)。一種選擇是在到達(dá)一定深度后停止樹(shù)的展開(kāi)蜕琴。這里我們?cè)O(shè)置 max_depth=4凌简,這意味著只可以連續(xù)問(wèn) 4 個(gè)問(wèn)題(參見(jiàn)圖 2-24 和圖 2-26)雏搂。限制樹(shù)的深度可以減少過(guò)擬合。這會(huì)降低訓(xùn)練集的精度裳食,但可以提高測(cè)試集的精度:
In[59]:
tree = DecisionTreeClassifier(max_depth=4, random_state=0)
tree.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(tree.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(tree.score(X_test, y_test)))
Out[59]:
Accuracy on training set: 0.988
Accuracy on test set: 0.951
3. 分析決策樹(shù)
我們可以利用 tree 模塊的 export_graphviz 函數(shù)來(lái)將樹(shù)可視化诲祸。這個(gè)函數(shù)會(huì)生成一個(gè) .dot 格式的文件而昨,這是一種用于保存圖形的文本文件格式。我們?cè)O(shè)置為結(jié)點(diǎn)添加顏色的選項(xiàng)着憨,顏色表示每個(gè)結(jié)點(diǎn)中的多數(shù)類(lèi)別甲抖,同時(shí)傳入類(lèi)別名稱(chēng)和特征名稱(chēng)植袍,這樣可以對(duì)樹(shù)正確標(biāo)記:
In[60]:
from sklearn.tree import export_graphviz
export_graphviz(tree, out_file="tree.dot", class_names=["malignant","benign"],
feature_names=cancer.feature_names, impurity=False, filled=True)
4. 樹(shù)的特征重要性
查看整個(gè)樹(shù)可能非常費(fèi)勁,除此之外,我還可以利用一些有用的屬性來(lái)總結(jié)樹(shù)的工作原理厅篓。其中最常用的是特征重要性(feature importance)羽氮,它為每個(gè)特征對(duì)樹(shù)的決策的重要性進(jìn)行排序惫恼。對(duì)于每個(gè)特征來(lái)說(shuō),它都是一個(gè)介于 0 和 1 之間的數(shù)字令宿,其中 0 表示“根本沒(méi)用到”, 1 表示“完美預(yù)測(cè)目標(biāo)值”筛婉。特征重要性的求和始終為 1:
In[62]:
print("Feature importances:\n{}".format(tree.feature_importances_))
Out[62]:
Feature importances:
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.01
0.048 0. 0. 0.002 0. 0. 0. 0. 0. 0.727
0.046 0. 0. 0.014 0. 0.018 0.122 0.012 0. ]
我們可以將特征重要性可視化癞松,與我們將線性模型的系數(shù)可視化的方法類(lèi)似(圖 2-28):
In[63]:
def plot_feature_importances_cancer(model):
n_features = cancer.data.shape[1]
plt.barh(range(n_features), model.feature_importances_, align='center')
plt.yticks(np.arange(n_features), cancer.feature_names)
plt.xlabel("Feature importance")
plt.ylabel("Feature")
plt.ylim(-1, n_features)
plot_feature_importances_cancer(tree)
這里我們看到响蓉,頂部劃分用到的特征(“worst radius”)是最重要的特征枫甲。這也證實(shí)了我們?cè)诜治鰳?shù)時(shí)的觀察結(jié)論,即第一層劃分已經(jīng)將兩個(gè)類(lèi)別區(qū)分得很好言秸。
但是软能,如果某個(gè)特征的 feature_importance_ 很小查排,并不能說(shuō)明這個(gè)特征沒(méi)有提供任何信息跋核。這只能說(shuō)明該特征沒(méi)有被樹(shù)選中叛买,可能是因?yàn)榱硪粋€(gè)特征也包含了同樣的信息率挣。
與線性模型的系數(shù)不同椒功,特征重要性始終為正數(shù),也不能說(shuō)明該特征對(duì)應(yīng)哪個(gè)類(lèi)別丁屎。特征重要性告訴我們“worst radius”(最大半徑)特征很重要晨川,但并沒(méi)有告訴我們半徑大表示樣本是良性還是惡性共虑。事實(shí)上看蚜,在特征和類(lèi)別之間可能沒(méi)有這樣簡(jiǎn)單的關(guān)系供炎,你可以在下面的例子中看出這一點(diǎn):
In[64]:
tree = mglearn.plots.plot_tree_not_monotone()
display(tree)
Out[64]:
Feature importances: [ 0. 1.]
雖然我們主要討論的是用于分類(lèi)的決策樹(shù)音诫,但對(duì)用于回歸的決策樹(shù)來(lái)說(shuō)竭钝,所有內(nèi)容都是類(lèi)似的香罐,在 DecisionTreeRegressor 中實(shí)現(xiàn)庇茫〉┣回歸樹(shù)的用法和分析與分類(lèi)樹(shù)非常類(lèi)似宁炫。但在將基于樹(shù)的模型用于回歸時(shí)羔巢,我們想要指出它的一個(gè)特殊性質(zhì)竿秆。 DecisionTreeRegressor(以及其他所有基于樹(shù)的回歸模型)不能外推(extrapolate)袍辞,也不能在訓(xùn)練數(shù)據(jù)范圍之外進(jìn)行預(yù)測(cè)搅吁。
我們利用計(jì)算機(jī)內(nèi)存(RAM)歷史價(jià)格的數(shù)據(jù)集來(lái)更詳細(xì)地研究這一點(diǎn)谎懦。圖 2-31 給出了這個(gè)數(shù)據(jù)集的圖像界拦, x 軸為日期享甸, y 軸為那一年 1 兆字節(jié)(MB) RAM 的價(jià)格:
In[65]:
import pandas as pd
ram_prices = pd.read_csv("data/ram_price.csv")
plt.semilogy(ram_prices.date, ram_prices.price)
plt.xlabel("Year")
plt.ylabel("Price in $/Mbyte")
注意 y 軸的對(duì)數(shù)刻度日丹。在用對(duì)數(shù)坐標(biāo)繪圖時(shí)哲虾,二者的線性關(guān)系看起來(lái)非常好束凑,所以預(yù)測(cè)應(yīng)該相對(duì)比較容易汪诉,除了一些不平滑之處之外摩瞎。
我們將利用 2000 年前的歷史數(shù)據(jù)來(lái)預(yù)測(cè) 2000 年后的價(jià)格旗们,只用日期作為特征上渴。我們將對(duì)比兩個(gè)簡(jiǎn)單的模型: DecisionTreeRegressor 和 LinearRegression稠氮。我們對(duì)價(jià)格取對(duì)數(shù)隔披,使得二者關(guān)系的線性相對(duì)更好奢米。這對(duì) DecisionTreeRegressor 不會(huì)產(chǎn)生什么影響鬓长,但對(duì)LinearRegression 的影響卻很大(我們將在第 4 章中進(jìn)一步討論)涉波。訓(xùn)練模型并做出預(yù)測(cè)之后啤覆,我們應(yīng)用指數(shù)映射來(lái)做對(duì)數(shù)變換的逆運(yùn)算城侧。為了便于可視化嫌佑,我們這里對(duì)整個(gè)數(shù)據(jù)集進(jìn)行預(yù)測(cè)屋摇,但如果是為了定量評(píng)估炮温,我們將只考慮測(cè)試數(shù)據(jù)集:
In[66]:
from sklearn.tree import DecisionTreeRegressor
# 利用歷史數(shù)據(jù)預(yù)測(cè)2000年后的價(jià)格
data_train = ram_prices[ram_prices.date < 2000]
data_test = ram_prices[ram_prices.date >= 2000]
# 基于日期來(lái)預(yù)測(cè)價(jià)格
X_train = data_train.date[:, np.newaxis]
# 我們利用對(duì)數(shù)變換得到數(shù)據(jù)和目標(biāo)之間更簡(jiǎn)單的關(guān)系
y_train = np.log(data_train.price)
tree = DecisionTreeRegressor().fit(X_train, y_train)
linear_reg = LinearRegression().fit(X_train, y_train)
# 對(duì)所有數(shù)據(jù)進(jìn)行預(yù)測(cè)
X_all = ram_prices.date[:, np.newaxis]
pred_tree = tree.predict(X_all)
pred_lr = linear_reg.predict(X_all)
# 對(duì)數(shù)變換逆運(yùn)算
price_tree = np.exp(pred_tree)
price_lr = np.exp(pred_lr)
這里創(chuàng)建的圖 2-32 將決策樹(shù)和線性回歸模型的預(yù)測(cè)結(jié)果與真實(shí)值進(jìn)行對(duì)比:
In[67]:
plt.semilogy(data_train.date, data_train.price, label="Training data")
plt.semilogy(data_test.date, data_test.price, label="Test data")
plt.semilogy(ram_prices.date, price_tree, label="Tree prediction")
plt.semilogy(ram_prices.date, price_lr, label="Linear prediction")
plt.legend()
兩個(gè)模型之間的差異非常明顯倦挂。線性模型用一條直線對(duì)數(shù)據(jù)做近似方援,這是我們所知道的犯戏。這條線對(duì)測(cè)試數(shù)據(jù)(2000 年后的價(jià)格)給出了相當(dāng)好的預(yù)測(cè)先匪,不過(guò)忽略了訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)中一些更細(xì)微的變化呀非。與之相反镜盯,樹(shù)模型完美預(yù)測(cè)了訓(xùn)練數(shù)據(jù)哥桥。由于我們沒(méi)有限制樹(shù)的復(fù)雜度拟糕,因此它記住了整個(gè)數(shù)據(jù)集送滞。但是犁嗅,一旦輸入超出了模型訓(xùn)練數(shù)據(jù)的范圍褂微,模型就只能持續(xù)預(yù)測(cè)最后一個(gè)已知數(shù)據(jù)點(diǎn)宠蚂。樹(shù)不能在訓(xùn)練數(shù)據(jù)的范圍之外生成“新的”響應(yīng)求厕。
所有基于樹(shù)的模型都有這個(gè)缺點(diǎn)呀癣。
5. 優(yōu)點(diǎn)项栏、 缺點(diǎn)和參數(shù)
如前所述忘嫉,控制決策樹(shù)模型復(fù)雜度的參數(shù)是預(yù)剪枝參數(shù)庆冕,它在樹(shù)完全展開(kāi)之前停止樹(shù)的構(gòu)造。通常來(lái)說(shuō)拷姿,選擇一種預(yù)剪枝策略(設(shè)置 max_depth响巢、 max_leaf_nodes 或 min_samples_leaf)足以防止過(guò)擬合踪古。
與前面討論過(guò)的許多算法相比伏穆,決策樹(shù)有兩個(gè)優(yōu)點(diǎn):一是得到的模型很容易可視化枕扫,非專(zhuān)家也很容易理解(至少對(duì)于較小的樹(shù)而言)烟瞧;二是算法完全不受數(shù)據(jù)縮放的影響燕刻。由于每個(gè)特征被單獨(dú)處理请唱,而且數(shù)據(jù)的劃分也不依賴(lài)于縮放十绑,因此決策樹(shù)算法不需要特征預(yù)處理本橙,比如歸一化或標(biāo)準(zhǔn)化甚亭。特別是特征的尺度完全不一樣時(shí)或者二元特征和連續(xù)特征同時(shí)存在時(shí)亏狰,決策樹(shù)的效果很好暇唾。
決策樹(shù)的主要缺點(diǎn)在于策州,即使做了預(yù)剪枝旁仿,它也經(jīng)常會(huì)過(guò)擬合丁逝,泛化性能很差。因此誉尖,在大多數(shù)應(yīng)用中铡恕,往往使用下面介紹的集成方法來(lái)替代單棵決策樹(shù)探熔。
2.3.6 決策樹(shù)集成
集成(ensemble)是合并多個(gè)機(jī)器學(xué)習(xí)模型來(lái)構(gòu)建更強(qiáng)大模型的方法。在機(jī)器學(xué)習(xí)文獻(xiàn)中有許多模型都屬于這一類(lèi)其垄,但已證明有兩種集成模型對(duì)大量分類(lèi)和回歸的數(shù)據(jù)集都是有效的绿满,二者都以決策樹(shù)為基礎(chǔ)喇颁,分別是隨機(jī)森林(random forest)和梯度提升決策樹(shù)(gradient boosted decision tree)橘霎。
1. 隨機(jī)森林
我們剛剛說(shuō)過(guò)茎毁,決策樹(shù)的一個(gè)主要缺點(diǎn)在于經(jīng)常對(duì)訓(xùn)練數(shù)據(jù)過(guò)擬合七蜘。隨機(jī)森林是解決這個(gè)問(wèn)題的一種方法扮念。隨機(jī)森林本質(zhì)上是許多決策樹(shù)的集合柜与,其中每棵樹(shù)都和其他樹(shù)略有不同弄匕。隨機(jī)森林背后的思想是迁匠,每棵樹(shù)的預(yù)測(cè)可能都相對(duì)較好城丧,但可能對(duì)部分?jǐn)?shù)據(jù)過(guò)擬合。如果構(gòu)造很多樹(shù)蚊惯,并且每棵樹(shù)的預(yù)測(cè)都很好拣挪,但都以不同的方式過(guò)擬合菠劝,那么我們可以對(duì)這些樹(shù)的結(jié)果取平均值來(lái)降低過(guò)擬合赶诊。既能減少過(guò)擬合又能保持樹(shù)的預(yù)測(cè)能力,這可以在數(shù)學(xué)上嚴(yán)格證明锄码。
為了實(shí)現(xiàn)這一策略滋捶,我們需要構(gòu)造許多決策樹(shù)重窟。每棵樹(shù)都應(yīng)該對(duì)目標(biāo)值做出可以接受的預(yù)測(cè)巡扇,還應(yīng)該與其他樹(shù)不同乖坠。隨機(jī)森林的名字來(lái)自于將隨機(jī)性添加到樹(shù)的構(gòu)造過(guò)程中瓤帚,以確保每棵樹(shù)都各不相同。隨機(jī)森林中樹(shù)的隨機(jī)化方法有兩種:一種是通過(guò)選擇用于構(gòu)造樹(shù)的數(shù)據(jù)點(diǎn)筒扒,另一種是通過(guò)選擇每次劃分測(cè)試的特征。我們來(lái)更深入地研究這一過(guò)程冰蘑。
構(gòu)造隨機(jī)森林祠肥。
想要構(gòu)造一個(gè)隨機(jī)森林模型仇箱,你需要確定用于構(gòu)造的樹(shù)的個(gè)數(shù)(RandomForestRegressor 或 RandomForestClassifier 的 n_estimators 參數(shù))。比如我們想要構(gòu)造 10 棵樹(shù)权逗。這些樹(shù)在構(gòu)造時(shí)彼此完全獨(dú)立斟薇,算法對(duì)每棵樹(shù)進(jìn)行不同的隨機(jī)選擇奔垦,以確保樹(shù)和樹(shù)之間是有區(qū)別的惶岭。想要構(gòu)造一棵樹(shù)按灶,首先要對(duì)數(shù)據(jù)進(jìn)行自助采樣(bootstrap sample)鸯旁。也就是說(shuō),從 n_samples 個(gè)數(shù)據(jù)點(diǎn)中有放回地(即同一樣本可以被多次抽攘咳铩)重
復(fù)隨機(jī)抽取一個(gè)樣本铺罢,共抽取 n_samples 次残炮。這樣會(huì)創(chuàng)建一個(gè)與原數(shù)據(jù)集大小相同的數(shù)據(jù)集韭赘,但有些數(shù)據(jù)點(diǎn)會(huì)缺失(大約三分之一),有些會(huì)重復(fù)势就。
舉例說(shuō)明泉瞻,比如我們想要?jiǎng)?chuàng)建列表 ['a', 'b', 'c', 'd'] 的自助采樣。一種可能的自主采樣是 ['b', 'd', 'd', 'c']苞冯,另一種可能的采樣為 ['d', 'a', 'd', 'a']袖牙。
接下來(lái),基于這個(gè)新創(chuàng)建的數(shù)據(jù)集來(lái)構(gòu)造決策樹(shù)舅锄。但是鞭达,要對(duì)我們?cè)诮榻B決策樹(shù)時(shí)描述的算法稍作修改。在每個(gè)結(jié)點(diǎn)處巧娱,算法隨機(jī)選擇特征的一個(gè)子集碉怔,并對(duì)其中一個(gè)特征尋找最佳測(cè)試,而不是對(duì)每個(gè)結(jié)點(diǎn)都尋找最佳測(cè)試禁添。選擇的特征個(gè)數(shù)由 max_features 參數(shù)來(lái)控制撮胧。每個(gè)結(jié)點(diǎn)中特征子集的選擇是相互獨(dú)立的,這樣樹(shù)的每個(gè)結(jié)點(diǎn)可以使用特征的不同子集來(lái)做出決策老翘。
由于使用了自助采樣芹啥,隨機(jī)森林中構(gòu)造每棵決策樹(shù)的數(shù)據(jù)集都是略有不同的锻离。由于每個(gè)結(jié)點(diǎn)的特征選擇,每棵樹(shù)中的每次劃分都是基于特征的不同子集墓怀。這兩種方法共同保證隨機(jī)森林中所有樹(shù)都不相同汽纠。在這個(gè)過(guò)程中的一個(gè)關(guān)鍵參數(shù)是 max_features。如果我們?cè)O(shè)置 max_features 等于n_features傀履,那么每次劃分都要考慮數(shù)據(jù)集的所有特征虱朵,在特征選擇的過(guò)程中沒(méi)有添加隨機(jī)性(不過(guò)自助采樣依然存在隨機(jī)性)。如果設(shè)置 max_features 等于 1钓账,那么在劃分時(shí)將無(wú)法選擇對(duì)哪個(gè)特征進(jìn)行測(cè)試碴犬,只能對(duì)隨機(jī)選擇的某個(gè)特征搜索不同的閾值。因此梆暮,如果 max_features 較大服协,那么隨機(jī)森林中的樹(shù)將會(huì)十分相似,利用最獨(dú)特的特征可以輕松擬合數(shù)據(jù)啦粹。如果 max_features 較小偿荷,那么隨機(jī)森林中的樹(shù)將會(huì)差異很大,為了很好地?cái)M合數(shù)據(jù)唠椭,每棵樹(shù)的深度都要很大跳纳。
想要利用隨機(jī)森林進(jìn)行預(yù)測(cè),算法首先對(duì)森林中的每棵樹(shù)進(jìn)行預(yù)測(cè)贪嫂。對(duì)于回歸問(wèn)題棒旗,我們可以對(duì)這些結(jié)果取平均值作為最終預(yù)測(cè)。對(duì)于分類(lèi)問(wèn)題撩荣,則用到了“軟投票”(soft voting)策略。也就是說(shuō)饶深,每個(gè)算法做出“軟”預(yù)測(cè)餐曹,給出每個(gè)可能的輸出標(biāo)簽的概率。對(duì)所有樹(shù)的預(yù)測(cè)概率取平均值敌厘,然后將概率最大的類(lèi)別作為預(yù)測(cè)結(jié)果台猴。
分析隨機(jī)森林。
下面將由 5 棵樹(shù)組成的隨機(jī)森林應(yīng)用到前面研究過(guò)的 two_moons 數(shù)據(jù)集上:
In[68]:
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=100, noise=0.25, random_state=3)
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y,
random_state=42)
forest = RandomForestClassifier(n_estimators=5, random_state=2)
forest.fit(X_train, y_train)
作為隨機(jī)森林的一部分俱两,樹(shù)被保存在 estimator_ 屬性中饱狂。我們將每棵樹(shù)學(xué)到的決策邊界可視化,也將它們的總預(yù)測(cè)(即整個(gè)森林做出的預(yù)測(cè))可視化(圖 2-33):
In[69]:
fig, axes = plt.subplots(2, 3, figsize=(20, 10))
for i, (ax, tree) in enumerate(zip(axes.ravel(), forest.estimators_)):
ax.set_title("Tree {}".format(i))
mglearn.plots.plot_tree_partition(X_train, y_train, tree, ax=ax)
mglearn.plots.plot_2d_separator(forest, X_train, fill=True, ax=axes[-1, -1],
alpha=.4)
axes[-1, -1].set_title("Random Forest")
mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train)
你可以清楚地看到休讳,這 5 棵樹(shù)學(xué)到的決策邊界大不相同。每棵樹(shù)都犯了一些錯(cuò)誤尿孔,因?yàn)檫@里畫(huà)出的一些訓(xùn)練點(diǎn)實(shí)際上并沒(méi)有包含在這些樹(shù)的訓(xùn)練集中俊柔,原因在于自助采樣筹麸。
隨機(jī)森林比單獨(dú)每一棵樹(shù)的過(guò)擬合都要小皂甘,給出的決策邊界也更符合直覺(jué)忙干。在任何實(shí)際應(yīng)用中,我們會(huì)用到更多棵樹(shù)(通常是幾百或上千)较坛,從而得到更平滑的邊界留晚。
再舉一個(gè)例子酵紫,我們將包含 100 棵樹(shù)的隨機(jī)森林應(yīng)用在乳腺癌數(shù)據(jù)集上:
In[70]:
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target, random_state=0)
forest = RandomForestClassifier(n_estimators=100, random_state=0)
forest.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(forest.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(forest.score(X_test, y_test)))
Out[70]:
Accuracy on training set: 1.000
Accuracy on test set: 0.972
在沒(méi)有調(diào)節(jié)任何參數(shù)的情況下,隨機(jī)森林的精度為 97%错维,比線性模型或單棵決策樹(shù)都要好奖地。我們可以調(diào)節(jié) max_features 參數(shù),或者像單棵決策樹(shù)那樣進(jìn)行預(yù)剪枝需五。但是鹉动,隨機(jī)森林的默認(rèn)參數(shù)通常就已經(jīng)可以給出很好的結(jié)果。
與決策樹(shù)類(lèi)似宏邮,隨機(jī)森林也可以給出特征重要性泽示,計(jì)算方法是將森林中所有樹(shù)的特征重要性求和并取平均。一般來(lái)說(shuō)蜜氨,隨機(jī)森林給出的特征重要性要比單棵樹(shù)給出的更為可靠械筛。參見(jiàn)圖 2-34。
plot_feature_importances_cancer(forest)
如你所見(jiàn)飒炎,與單棵樹(shù)相比埋哟,隨機(jī)森林中有更多特征的重要性不為零。與單棵決策樹(shù)類(lèi)似郎汪,隨機(jī)森林也給了“worst radius”(最大半徑)特征很大的重要性赤赊,但從總體來(lái)看,它實(shí)際上卻選擇“worst perimeter”(最大周長(zhǎng))作為信息量最大的特征煞赢。由于構(gòu)造隨機(jī)森林過(guò)程中的隨機(jī)性抛计,算法需要考慮多種可能的解釋?zhuān)Y(jié)果就是隨機(jī)森林比單棵樹(shù)更能從總體把握數(shù)據(jù)的特征。
優(yōu)點(diǎn)照筑、 缺點(diǎn)和參數(shù)吹截。
用于回歸和分類(lèi)的隨機(jī)森林是目前應(yīng)用最廣泛的機(jī)器學(xué)習(xí)方法之一。
這種方法非常強(qiáng)大凝危,通常不需要反復(fù)調(diào)節(jié)參數(shù)就可以給出很好的結(jié)果波俄,也不需要對(duì)數(shù)據(jù)進(jìn)行縮放。從本質(zhì)上看蛾默,隨機(jī)森林擁有決策樹(shù)的所有優(yōu)點(diǎn)懦铺,同時(shí)彌補(bǔ)了決策樹(shù)的一些缺陷。仍然使用決策樹(shù)的一個(gè)原因是需要決策過(guò)程的緊湊表示支鸡》浚基本上不可能對(duì)幾十棵甚至上百棵樹(shù)做出詳細(xì)解釋?zhuān)S機(jī)森林中樹(shù)的深度往往比決策樹(shù)還要大(因?yàn)橛玫搅颂卣髯蛹┗韬病R虼耍绻阈枰钥梢暬姆绞较蚍菍?zhuān)家總結(jié)預(yù)測(cè)過(guò)程刘急,那么選擇單棵決策樹(shù)可能更好棚菊。雖然在大型數(shù)據(jù)集上構(gòu)建隨機(jī)森林可能比較費(fèi)時(shí)間,但在一臺(tái)計(jì)算機(jī)的多個(gè) CPU 內(nèi)核上并行計(jì)算也很容易叔汁。如果你用的是多核處理器(幾乎所有的現(xiàn)代化計(jì)算機(jī)都是)统求,你可以用 n_jobs 參數(shù)來(lái)調(diào)節(jié)使用的內(nèi)核個(gè)數(shù)。使用更多的 CPU 內(nèi)核据块,可以讓速度線性增加(使用 2 個(gè)內(nèi)核码邻,隨機(jī)森林的訓(xùn)練速度會(huì)加倍),但設(shè)置 n_jobs 大于內(nèi)核個(gè)數(shù)是沒(méi)有用
的另假。你可以設(shè)置 n_jobs=-1 來(lái)使用計(jì)算機(jī)的所有內(nèi)核像屋。
你應(yīng)該記住,隨機(jī)森林本質(zhì)上是隨機(jī)的边篮,設(shè)置不同的隨機(jī)狀態(tài)(或者不設(shè)置 random_state參數(shù))可以徹底改變構(gòu)建的模型己莺。森林中的樹(shù)越多,它對(duì)隨機(jī)狀態(tài)選擇的魯棒性就越好戈轿。如果你希望結(jié)果可以重現(xiàn)凌受,固定 random_state 是很重要的。
對(duì)于維度非常高的稀疏數(shù)據(jù)(比如文本數(shù)據(jù))思杯,隨機(jī)森林的表現(xiàn)往往不是很好胜蛉。對(duì)于這種數(shù)據(jù),使用線性模型可能更合適色乾。即使是非常大的數(shù)據(jù)集誊册,隨機(jī)森林的表現(xiàn)通常也很好,訓(xùn)練過(guò)程很容易并行在功能強(qiáng)大的計(jì)算機(jī)的多個(gè) CPU 內(nèi)核上暖璧。不過(guò)解虱,隨機(jī)森林需要更大的內(nèi)存,訓(xùn)練和預(yù)測(cè)的速度也比線性模型要慢漆撞。對(duì)一個(gè)應(yīng)用來(lái)說(shuō),如果時(shí)間和內(nèi)存很重要的話于宙,那么換用線性模型可能更為明智浮驳。
需要調(diào)節(jié)的重要參數(shù)有 n_estimators 和 max_features,可能還包括預(yù)剪枝選項(xiàng)(如 max_depth)捞魁。 n_estimators 總是越大越好至会。對(duì)更多的樹(shù)取平均可以降低過(guò)擬合,從而得到魯棒性更好的集成谱俭。不過(guò)收益是遞減的奉件,而且樹(shù)越多需要的內(nèi)存也越多宵蛀,訓(xùn)練時(shí)間也越長(zhǎng)。常用的經(jīng)驗(yàn)法則就是“在你的時(shí)間 / 內(nèi)存允許的情況下盡量多”县貌。
前面說(shuō)過(guò)术陶, max_features 決定每棵樹(shù)的隨機(jī)性大小,較小的 max_features 可以降低過(guò)擬合煤痕。一般來(lái)說(shuō)梧宫,好的經(jīng)驗(yàn)就是使用默認(rèn)值:對(duì)于分類(lèi),默認(rèn)值是 max_features=sqrt(n_features)摆碉;對(duì)于回歸塘匣,默認(rèn)值是 max_features=n_features。增大 max_features 或 max_leaf_nodes 有時(shí)也可以提高性能巷帝。它還可以大大降低用于訓(xùn)練和預(yù)測(cè)的時(shí)間和空間要求忌卤。
2. 梯度提升回歸樹(shù)(梯度提升機(jī))
梯度提升回歸樹(shù)是另一種集成方法,通過(guò)合并多個(gè)決策樹(shù)來(lái)構(gòu)建一個(gè)更為強(qiáng)大的模型楞泼。雖然名字中含有“回歸”驰徊,但這個(gè)模型既可以用于回歸也可以用于分類(lèi)。與隨機(jī)森林方法不同现拒,梯度提升采用連續(xù)的方式構(gòu)造樹(shù)辣垒,每棵樹(shù)都試圖糾正前一棵樹(shù)的錯(cuò)誤。默認(rèn)情況下印蔬,梯度提升回歸樹(shù)中沒(méi)有隨機(jī)化勋桶,而是用到了強(qiáng)預(yù)剪枝。梯度提升樹(shù)通常使用深度很薪拟(1到 5 之間)的樹(shù)例驹,這樣模型占用的內(nèi)存更少,預(yù)測(cè)速度也更快退唠。
梯度提升背后的主要思想是合并許多簡(jiǎn)單的模型(在這個(gè)語(yǔ)境中叫作弱學(xué)習(xí)器)鹃锈,比如深度較小的樹(shù)。每棵樹(shù)只能對(duì)部分?jǐn)?shù)據(jù)做出好的預(yù)測(cè)瞧预,因此屎债,添加的樹(shù)越來(lái)越多,可以不斷迭代提高性能垢油。
梯度提升樹(shù)經(jīng)常是機(jī)器學(xué)習(xí)競(jìng)賽的優(yōu)勝者盆驹,并且廣泛應(yīng)用于業(yè)界。與隨機(jī)森林相比滩愁,它通常對(duì)參數(shù)設(shè)置更為敏感躯喇,但如果參數(shù)設(shè)置正確的話,模型精度更高硝枉。
除了預(yù)剪枝與集成中樹(shù)的數(shù)量之外廉丽,梯度提升的另一個(gè)重要參數(shù)是 learning_rate(學(xué)習(xí)率)倦微,用于控制每棵樹(shù)糾正前一棵樹(shù)的錯(cuò)誤的強(qiáng)度。較高的學(xué)習(xí)率意味著每棵樹(shù)都可以做出較強(qiáng)的修正正压,這樣模型更為復(fù)雜欣福。通過(guò)增大 n_estimators 來(lái)向集成中添加更多樹(shù),也可以增加模型復(fù)雜度蔑匣,因?yàn)槟P陀懈鄼C(jī)會(huì)糾正訓(xùn)練集上的錯(cuò)誤劣欢。
下面是在乳腺癌數(shù)據(jù)集上應(yīng)用 GradientBoostingClassifier 的示例。默認(rèn)使用 100 棵樹(shù)裁良,最大深度是 3凿将,學(xué)習(xí)率為 0.1:
In[72]:
from sklearn.ensemble import GradientBoostingClassifier
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target, random_state=0)
gbrt = GradientBoostingClassifier(random_state=0)
gbrt.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))
Out[72]:
Accuracy on training set: 1.000
Accuracy on test set: 0.958
由于訓(xùn)練集精度達(dá)到 100%,所以很可能存在過(guò)擬合价脾。為了降低過(guò)擬合牧抵,我們可以限制最大深度來(lái)加強(qiáng)預(yù)剪枝,也可以降低學(xué)習(xí)率:
In[73]:
gbrt = GradientBoostingClassifier(random_state=0, max_depth=1)
gbrt.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))
Out[73]:
Accuracy on training set: 0.991
Accuracy on test set: 0.972
In[74]:
gbrt = GradientBoostingClassifier(random_state=0, learning_rate=0.01)
gbrt.fit(X_train, y_train)
print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))
Out[74]:
Accuracy on training set: 0.988
Accuracy on test set: 0.965
降低模型復(fù)雜度的兩種方法都降低了訓(xùn)練集精度侨把,這和預(yù)期相同犀变。在這個(gè)例子中,減小樹(shù)的最大深度顯著提升了模型性能秋柄,而降低學(xué)習(xí)率僅稍稍提高了泛化性能获枝。對(duì)于其他基于決策樹(shù)的模型,我們也可以將特征重要性可視化骇笔,以便更好地理解模型(圖 2-35)省店。由于我們用到了 100 棵樹(shù),所以即使所有樹(shù)的深度都是 1笨触,查看所有樹(shù)也是不現(xiàn)實(shí)的:
In[75]:
gbrt = GradientBoostingClassifier(random_state=0, max_depth=1)
gbrt.fit(X_train, y_train)
plot_feature_importances_cancer(gbrt)
可以看到懦傍,梯度提升樹(shù)的特征重要性與隨機(jī)森林的特征重要性有些類(lèi)似,不過(guò)梯度提升完全忽略了某些特征芦劣。由于梯度提升和隨機(jī)森林兩種方法在類(lèi)似的數(shù)據(jù)上表現(xiàn)得都很好粗俱,因此一種常用的方法就是先嘗試隨機(jī)森林,它的魯棒性很好虚吟。如果隨機(jī)森林效果很好寸认,但預(yù)測(cè)時(shí)間太長(zhǎng),或者機(jī)器學(xué)習(xí)模型精度小數(shù)點(diǎn)后第二位的提高也很重要串慰,那么切換成梯度提升通常會(huì)有用偏塞。
如果你想要將梯度提升應(yīng)用在大規(guī)模問(wèn)題上,可以研究一下 xgboost 包及其 Python 接口模庐,在寫(xiě)作本書(shū)時(shí),這個(gè)庫(kù)在許多數(shù)據(jù)集上的速度都比 scikit-learn 對(duì)梯度提升的實(shí)現(xiàn)要快(有時(shí)調(diào)參也更簡(jiǎn)單)油宜。
優(yōu)點(diǎn)掂碱、 缺點(diǎn)和參數(shù)怜姿。
梯度提升決策樹(shù)是監(jiān)督學(xué)習(xí)中最強(qiáng)大也最常用的模型之一。其主要缺點(diǎn)是需要仔細(xì)調(diào)參疼燥,而且訓(xùn)練時(shí)間可能會(huì)比較長(zhǎng)沧卢。與其他基于樹(shù)的模型類(lèi)似,這一算法不需要對(duì)數(shù)據(jù)進(jìn)行縮放就可以表現(xiàn)得很好醉者,而且也適用于二元特征與連續(xù)特征同時(shí)存在的數(shù)據(jù)集但狭。與其他基于樹(shù)的模型相同,它也通常不適用于高維稀疏數(shù)據(jù)撬即。
梯度提升樹(shù)模型的主要參數(shù)包括樹(shù)的數(shù)量 n_estimators 和學(xué)習(xí)率 learning_rate立磁,后者用于控制每棵樹(shù)對(duì)前一棵樹(shù)的錯(cuò)誤的糾正強(qiáng)度。這兩個(gè)參數(shù)高度相關(guān)剥槐,因?yàn)?learning_rate 越低唱歧,就需要更多的樹(shù)來(lái)構(gòu)建具有相似復(fù)雜度的模型。隨機(jī)森林的 n_estimators 值總是越大越好粒竖,但梯度提升不同颅崩,增大 n_estimators 會(huì)導(dǎo)致模型更加復(fù)雜,進(jìn)而可能導(dǎo)致過(guò)擬合蕊苗。通常的做法是根據(jù)時(shí)間和內(nèi)存的預(yù)算選擇合適的 n_estimators沿后,然后對(duì)不同learning_rate 進(jìn)行遍歷。
另一個(gè)重要參數(shù)是 max_depth(或 max_leaf_nodes)朽砰,用于降低每棵樹(shù)的復(fù)雜度尖滚。梯度提升模型的 max_depth 通常都設(shè)置得很小,一般不超過(guò) 5锅移。
2.3.7 核支持向量機(jī)
我們要討論的下一種監(jiān)督學(xué)習(xí)模型是核支持向量機(jī)(kernelized support vector machine)熔掺。在 2.3.3 節(jié)中,我們研究了將線性支持向量機(jī)用于分類(lèi)任務(wù)非剃。核支持向量機(jī)(通常簡(jiǎn)稱(chēng)為SVM)是可以推廣到更復(fù)雜模型的擴(kuò)展置逻,這些模型無(wú)法被輸入空間的超平面定義。雖然支持向量機(jī)可以同時(shí)用于分類(lèi)和回歸备绽,但我們只會(huì)介紹用于分類(lèi)的情況券坞,它在 SVC 中實(shí)現(xiàn)。
類(lèi)似的概念也適用于支持向量回歸肺素,后者在 SVR 中實(shí)現(xiàn)恨锚。
核支持向量機(jī)背后的數(shù)學(xué)有點(diǎn)復(fù)雜,已經(jīng)超出了本書(shū)的范圍倍靡。你可以閱讀 Hastie猴伶、Tibshirani 和 Friedman 合著的《統(tǒng)計(jì)學(xué)習(xí)基礎(chǔ)》一書(shū)(http://statweb.stanford.edu/~tibs/ElemStatLearn/)的第 12 章了解更多細(xì)節(jié)。不過(guò),我們會(huì)努力向讀者傳達(dá)這一方法背后的理念他挎。
1. 線性模型與非線性特征
如圖 2-15 所示筝尾,線性模型在低維空間中可能非常受限,因?yàn)榫€和平面的靈活性有限办桨。有一種方法可以讓線性模型更加靈活筹淫,就是添加更多的特征——舉個(gè)例子,添加輸入特征的交互項(xiàng)或多項(xiàng)式呢撞。
我們來(lái)看一下 2.3.5 節(jié)中用到的模擬數(shù)據(jù)集(見(jiàn)圖 2-29):
In[76]:
X, y = make_blobs(centers=4, random_state=8)
y = y % 2
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
用于分類(lèi)的線性模型只能用一條直線來(lái)劃分?jǐn)?shù)據(jù)點(diǎn),對(duì)這個(gè)數(shù)據(jù)集無(wú)法給出較好的結(jié)果(見(jiàn)圖 2-37):
In[77]:
from sklearn.svm import LinearSVC
linear_svm = LinearSVC().fit(X, y)
mglearn.plots.plot_2d_separator(linear_svm, X)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
現(xiàn)在我們對(duì)輸入特征進(jìn)行擴(kuò)展殊霞,比如說(shuō)添加第二個(gè)特征的平方(feature1 ** 2)作為一個(gè)新特征〈菰模現(xiàn)在我們將每個(gè)數(shù)據(jù)點(diǎn)表示為三維點(diǎn) (feature0, feature1, feature1 ** 2),而不是二維點(diǎn) (feature0, feature1)11脓鹃。這個(gè)新的表示可以畫(huà)成圖 2-38 中的三維散點(diǎn)圖:
In[78]:
# 添加第二個(gè)特征的平方逸尖,作為一個(gè)新特征
X_new = np.hstack([X, X[:, 1:] ** 2])
from mpl_toolkits.mplot3d import Axes3D, axes3d
figure = plt.figure()
# 3D可視化
ax = Axes3D(figure, elev=-152, azim=-26)
# 首先畫(huà)出所有y == 0的點(diǎn),然后畫(huà)出所有y == 1的點(diǎn)
mask = y == 0
ax.scatter(X_new[mask, 0], X_new[mask, 1], X_new[mask, 2], c='b',
cmap=mglearn.cm2, s=60)
ax.scatter(X_new[~mask, 0], X_new[~mask, 1], X_new[~mask, 2], c='r', marker='^',
cmap=mglearn.cm2, s=60)
ax.set_xlabel("feature0")
ax.set_ylabel("feature1")
ax.set_zlabel("feature1 ** 2")
在數(shù)據(jù)的新表示中娇跟,現(xiàn)在可以用線性模型(三維空間中的平面)將這兩個(gè)類(lèi)別分開(kāi)。我們可以用線性模型擬合擴(kuò)展后的數(shù)據(jù)來(lái)驗(yàn)證這一點(diǎn)(見(jiàn)圖 2-39):
In[79]:
linear_svm_3d = LinearSVC().fit(X_new, y)
coef, intercept = linear_svm_3d.coef_.ravel(), linear_svm_3d.intercept_
# 顯示線性決策邊界
figure = plt.figure()
ax = Axes3D(figure, elev=-152, azim=-26)
xx = np.linspace(X_new[:, 0].min() - 2, X_new[:, 0].max() + 2, 50)
yy = np.linspace(X_new[:, 1].min() - 2, X_new[:, 1].max() + 2, 50)
XX, YY = np.meshgrid(xx, yy)
ZZ = (coef[0] * XX + coef[1] * YY + intercept) / -coef[2]
ax.plot_surface(XX, YY, ZZ, rstride=8, cstride=8, alpha=0.3)
ax.scatter(X_new[mask, 0], X_new[mask, 1], X_new[mask, 2], c='b',
cmap=mglearn.cm2, s=60)
ax.scatter(X_new[~mask, 0], X_new[~mask, 1], X_new[~mask, 2], c='r', marker='^',
cmap=mglearn.cm2, s=60)
ax.set_xlabel("feature0")
ax.set_ylabel("feature1")
ax.set_zlabel("feature1 ** 2")
如果將線性 SVM 模型看作原始特征的函數(shù)太颤,那么它實(shí)際上已經(jīng)不是線性的了苞俘。它不是一條直線,而是一個(gè)橢圓龄章,你可以在下圖中看出(圖 2-40):
In[80]:
ZZ = YY ** 2
dec = linear_svm_3d.decision_function(np.c_[XX.ravel(), YY.ravel(), ZZ.ravel()])
plt.contourf(XX, YY, dec.reshape(XX.shape), levels=[dec.min(), 0, dec.max()],
cmap=mglearn.cm2, alpha=0.5)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
2. 核技巧
這里需要記住的是吃谣,向數(shù)據(jù)表示中添加非線性特征,可以讓線性模型變得更強(qiáng)大做裙。但是岗憋,通常來(lái)說(shuō)我們并不知道要添加哪些特征,而且添加許多特征(比如 100 維特征空間所有可能的交互項(xiàng))的計(jì)算開(kāi)銷(xiāo)可能會(huì)很大锚贱。幸運(yùn)的是仔戈,有一種巧妙的數(shù)學(xué)技巧,讓我們可以在更高維空間中學(xué)習(xí)分類(lèi)器拧廊,而不用實(shí)際計(jì)算可能非常大的新的數(shù)據(jù)表示监徘。這種技巧叫作核技巧(kernel trick),它的原理是直接計(jì)算擴(kuò)展特征表示中數(shù)據(jù)點(diǎn)之間的距離(更準(zhǔn)確地說(shuō)是內(nèi)積)吧碾,而不用實(shí)際對(duì)擴(kuò)展進(jìn)行計(jì)算凰盔。
對(duì)于支持向量機(jī),將數(shù)據(jù)映射到更高維空間中有兩種常用的方法:一種是多項(xiàng)式核倦春,在一定階數(shù)內(nèi)計(jì)算原始特征所有可能的多項(xiàng)式(比如 feature1 ** 2 * feature2 ** 5)户敬;另一種是徑向基函數(shù)(radial basis function落剪, RBF)核,也叫高斯核尿庐。高斯核有點(diǎn)難以解釋?zhuān)驗(yàn)樗鼘?duì)應(yīng)無(wú)限維的特征空間著榴。一種對(duì)高斯核的解釋是它考慮所有階數(shù)的所有可能的多項(xiàng)式,但階數(shù)越高屁倔,特征的重要性越小。不過(guò)在實(shí)踐中暮胧,核 SVM 背后的數(shù)學(xué)細(xì)節(jié)并不是很重要锐借,可以簡(jiǎn)單地總結(jié)出使用 RBF 核SVM 進(jìn)行預(yù)測(cè)的方法——我們將在下一節(jié)介紹這方面的內(nèi)容。
3. 理解SVM
在訓(xùn)練過(guò)程中往衷, SVM 學(xué)習(xí)每個(gè)訓(xùn)練數(shù)據(jù)點(diǎn)對(duì)于表示兩個(gè)類(lèi)別之間的決策邊界的重要性钞翔。通常只有一部分訓(xùn)練數(shù)據(jù)點(diǎn)對(duì)于定義決策邊界來(lái)說(shuō)很重要:位于類(lèi)別之間邊界上的那些點(diǎn)。這些點(diǎn)叫作支持向量(support vector)席舍,支持向量機(jī)正是由此得名布轿。想要對(duì)新樣本點(diǎn)進(jìn)行預(yù)測(cè),需要測(cè)量它與每個(gè)支持向量之間的距離来颤。分類(lèi)決策是基于它與支持向量之間的距離以及在訓(xùn)練過(guò)程中學(xué)到的支持向量重要性(保存在 SVC 的 dual_coef_屬性中)來(lái)做出的汰扭。
數(shù)據(jù)點(diǎn)之間的距離由高斯核給出:
krbf (x1, x2) = exp (-γ‖ x1 - x2‖ 2)
這里 x1 和 x2 是數(shù)據(jù)點(diǎn), ‖ x1 - x2‖ 表示歐氏距離福铅, γ(gamma)是控制高斯核寬度的參數(shù)萝毛。
圖 2-41 是支持向量機(jī)對(duì)一個(gè)二維二分類(lèi)數(shù)據(jù)集的訓(xùn)練結(jié)果。決策邊界用黑色表示滑黔,支持向量是尺寸較大的點(diǎn)笆包。下列代碼將在 forge 數(shù)據(jù)集上訓(xùn)練 SVM 并創(chuàng)建此圖:
In[81]:
from sklearn.svm import SVC
X, y = mglearn.tools.make_handcrafted_dataset()
svm = SVC(kernel='rbf', C=10, gamma=0.1).fit(X, y)
mglearn.plots.plot_2d_separator(svm, X, eps=.5)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
# 畫(huà)出支持向量
sv = svm.support_vectors_
# 支持向量的類(lèi)別標(biāo)簽由dual_coef_的正負(fù)號(hào)給出
sv_labels = svm.dual_coef_.ravel() > 0
mglearn.discrete_scatter(sv[:, 0], sv[:, 1], sv_labels, s=15, markeredgewidth=3)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
在這個(gè)例子中, SVM 給出了非常平滑且非線性(不是直線)的邊界略荡。這里我們調(diào)節(jié)了兩個(gè)參數(shù): C 參數(shù)和 gamma 參數(shù)庵佣,下面我們將詳細(xì)討論。
4. SVM調(diào)參
gamma 參數(shù)是上一節(jié)給出的公式中的參數(shù)汛兜,用于控制高斯核的寬度巴粪。它決定了點(diǎn)與點(diǎn)之間“靠近”是指多大的距離。 C 參數(shù)是正則化參數(shù)序无,與線性模型中用到的類(lèi)似验毡。它限制每個(gè)點(diǎn)的重要性(或者更確切地說(shuō),每個(gè)點(diǎn)的 dual_coef_)帝嗡。
我們來(lái)看一下晶通,改變這些參數(shù)時(shí)會(huì)發(fā)生什么(圖 2-42):
In[82]:
fig, axes = plt.subplots(3, 3, figsize=(15, 10))
for ax, C in zip(axes, [-1, 0, 3]):
for a, gamma in zip(ax, range(-1, 2)):
mglearn.plots.plot_svm(log_C=C, log_gamma=gamma, ax=a)
axes[0, 0].legend(["class 0", "class 1", "sv class 0", "sv class 1"],
ncol=4, loc=(.9, 1.2))
從左到右,我們將參數(shù) gamma 的值從 0.1 增加到 10哟玷。 gamma 較小狮辽,說(shuō)明高斯核的半徑較大一也,許多點(diǎn)都被看作比較靠近。這一點(diǎn)可以在圖中看出:左側(cè)的圖決策邊界非常平滑喉脖,越向右的圖決策邊界更關(guān)注單個(gè)點(diǎn)椰苟。小的 gamma 值表示決策邊界變化很慢,生成的是復(fù)雜度較低的模型树叽,而大的 gamma 值則會(huì)生成更為復(fù)雜的模型舆蝴。
從上到下,我們將參數(shù) C 的值從 0.1 增加到 1000题诵。與線性模型相同洁仗, C 值很小,說(shuō)明模型非常受限性锭,每個(gè)數(shù)據(jù)點(diǎn)的影響范圍都有限赠潦。你可以看到,左上角的圖中草冈,決策邊界看起來(lái)幾乎是線性的她奥,誤分類(lèi)的點(diǎn)對(duì)邊界幾乎沒(méi)有任何影響。再看左下角的圖怎棱,增大 C 之后這些點(diǎn)對(duì)模型的影響變大哩俭,使得決策邊界發(fā)生彎曲來(lái)將這些點(diǎn)正確分類(lèi)。我們將 RBF 核 SVM 應(yīng)用到乳腺癌數(shù)據(jù)集上拳恋。默認(rèn)情況下携茂, C=1, gamma=1/n_features:
In[83]:
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target, random_state=0)
svc = SVC()
svc.fit(X_train, y_train)
print("Accuracy on training set: {:.2f}".format(svc.score(X_train, y_train)))
print("Accuracy on test set: {:.2f}".format(svc.score(X_test, y_test)))
Out[83]:
Accuracy on training set: 1.00
Accuracy on test set: 0.63
這個(gè)模型在訓(xùn)練集上的分?jǐn)?shù)十分完美诅岩,但在測(cè)試集上的精度只有 63%讳苦,存在相當(dāng)嚴(yán)重的過(guò)擬合。雖然 SVM 的表現(xiàn)通常都很好吩谦,但它對(duì)參數(shù)的設(shè)定和數(shù)據(jù)的縮放非常敏感鸳谜。特別地,它要求所有特征有相似的變化范圍式廷。我們來(lái)研究處理這個(gè)問(wèn)題的幾種方法咐扭。
5. 為SVM預(yù)處理數(shù)據(jù)
解決這個(gè)問(wèn)題的一種方法就是對(duì)每個(gè)特征進(jìn)行縮放续誉,使其大致都位于同一范圍懊渡。核 SVM常用的縮放方法就是將所有特征縮放到 0 和 1 之間。我們將在第 3 章學(xué)習(xí)如何使用MinMaxScaler 預(yù)處理方法來(lái)做到這一點(diǎn)贰谣,到時(shí)會(huì)給出更多細(xì)節(jié)∪涑茫現(xiàn)在我們來(lái)“人工”做到這一點(diǎn):
In[85]:
# 計(jì)算訓(xùn)練集中每個(gè)特征的最小值
min_on_training = X_train.min(axis=0)
# 計(jì)算訓(xùn)練集中每個(gè)特征的范圍(最大值-最小值)
range_on_training = (X_train - min_on_training).max(axis=0)
# 減去最小值薛闪,然后除以范圍
# 這樣每個(gè)特征都是min=0和max=1
X_train_scaled = (X_train - min_on_training) / range_on_training
print("Minimum for each feature\n{}".format(X_train_scaled.min(axis=0)))
print("Maximum for each feature\n {}".format(X_train_scaled.max(axis=0)))
Out[85]:
Minimum for each feature
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
Maximum for each feature
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
In[86]:
# 利用訓(xùn)練集的最小值和范圍對(duì)測(cè)試集做相同的變換(詳見(jiàn)第3章)
X_test_scaled = (X_test - min_on_training) / range_on_training
In[87]:
svc = SVC()
svc.fit(X_train_scaled, y_train)
print("Accuracy on training set: {:.3f}".format(
svc.score(X_train_scaled, y_train)))
print("Accuracy on test set: {:.3f}".format(svc.score(X_test_scaled, y_test)))
Out[87]:
Accuracy on training set: 0.948
Accuracy on test set: 0.951
數(shù)據(jù)縮放的作用很大!實(shí)際上模型現(xiàn)在處于欠擬合的狀態(tài)俺陋,因?yàn)橛?xùn)練集和測(cè)試集的性能非常接近豁延,但還沒(méi)有接近 100% 的精度昙篙。從這里開(kāi)始,我們可以嘗試增大 C 或 gamma 來(lái)擬合更為復(fù)雜的模型诱咏。例如:
In[88]:
svc = SVC(C=1000)
svc.fit(X_train_scaled, y_train)
print("Accuracy on training set: {:.3f}".format(
svc.score(X_train_scaled, y_train)))
print("Accuracy on test set: {:.3f}".format(svc.score(X_test_scaled, y_test)))
Out[88]:
Accuracy on training set: 0.988
Accuracy on test set: 0.972
在這個(gè)例子中苔可,增大 C 可以顯著改進(jìn)模型,得到 97.2% 的精度袋狞。
6. 優(yōu)點(diǎn)焚辅、 缺點(diǎn)和參數(shù)
核支持向量機(jī)是非常強(qiáng)大的模型,在各種數(shù)據(jù)集上的表現(xiàn)都很好苟鸯。 SVM 允許決策邊界很復(fù)雜法焰,即使數(shù)據(jù)只有幾個(gè)特征。它在低維數(shù)據(jù)和高維數(shù)據(jù)(即很少特征和很多特征)上的表現(xiàn)都很好倔毙,但對(duì)樣本個(gè)數(shù)的縮放表現(xiàn)不好。在有多達(dá) 10 000 個(gè)樣本的數(shù)據(jù)上運(yùn)行 SVM可能表現(xiàn)良好乙濒,但如果數(shù)據(jù)量達(dá)到 100 000 甚至更大陕赃,在運(yùn)行時(shí)間和內(nèi)存使用方面可能會(huì)面臨挑戰(zhàn)。
SVM 的另一個(gè)缺點(diǎn)是颁股,預(yù)處理數(shù)據(jù)和調(diào)參都需要非常小心么库。這也是為什么如今很多應(yīng)用中用的都是基于樹(shù)的模型,比如隨機(jī)森林或梯度提升(需要很少的預(yù)處理甘有,甚至不需要預(yù)處理)诉儒。此外, SVM 模型很難檢查亏掀,可能很難理解為什么會(huì)這么預(yù)測(cè)忱反,而且也難以將模型向非專(zhuān)家進(jìn)行解釋。
不過(guò) SVM 仍然是值得嘗試的滤愕,特別是所有特征的測(cè)量單位相似(比如都是像素密度)而且范圍也差不多時(shí)温算。核 SVM 的重要參數(shù)是正則化參數(shù) C、核的選擇以及與核相關(guān)的參數(shù)间影。雖然我們主要講的是RBF 核注竿,但 scikit-learn 中還有其他選擇。 RBF 核只有一個(gè)參數(shù) gamma魂贬,它是高斯核寬度的倒數(shù)巩割。 gamma 和 C 控制的都是模型復(fù)雜度,較大的值都對(duì)應(yīng)更為復(fù)雜的模型付燥。因此宣谈,這兩個(gè)參數(shù)的設(shè)定通常是強(qiáng)烈相關(guān)的,應(yīng)該同時(shí)調(diào)節(jié)键科。
2.3.8 神經(jīng)網(wǎng)絡(luò)( 深度學(xué)習(xí))
一類(lèi)被稱(chēng)為神經(jīng)網(wǎng)絡(luò)的算法最近以“深度學(xué)習(xí)”的名字再度流行蒲祈。雖然深度學(xué)習(xí)在許多機(jī)器學(xué)習(xí)應(yīng)用中都有巨大的潛力甘萧,但深度學(xué)習(xí)算法往往經(jīng)過(guò)精確調(diào)整,只適用于特定的使用場(chǎng)景梆掸。這里只討論一些相對(duì)簡(jiǎn)單的方法扬卷,即用于分類(lèi)和回歸的多層感知機(jī)(multilayer perceptron, MLP)酸钦, 它可以作為研究更復(fù)雜的深度學(xué)習(xí)方法的起點(diǎn)怪得。 MLP 也被稱(chēng)為(普通)前饋神經(jīng)網(wǎng)絡(luò),有時(shí)也簡(jiǎn)稱(chēng)為神經(jīng)網(wǎng)絡(luò)卑硫。
1. 神經(jīng)網(wǎng)絡(luò)模型
MLP 可以被視為廣義的線性模型徒恋,執(zhí)行多層處理后得到結(jié)論。
還記得線性回歸的預(yù)測(cè)公式為:
? = w[0] * x[0] + w[1] * x[1] + … + w[p] * x[p] + b
簡(jiǎn)單來(lái)說(shuō)欢伏, ? 是輸入特征 x[0] 到 x[p] 的加權(quán)求和入挣,權(quán)重為學(xué)到的系數(shù) w[0] 到 w[p]。
這個(gè)模型需要學(xué)習(xí)更多的系數(shù)(也叫作權(quán)重):在每個(gè)輸入與每個(gè)隱單元(隱單元組成了隱層)之間有一個(gè)系數(shù)硝拧,在每個(gè)隱單元與輸出之間也有一個(gè)系數(shù)径筏。
從數(shù)學(xué)的角度看,計(jì)算一系列加權(quán)求和與只計(jì)算一個(gè)加權(quán)求和是完全相同的障陶,因此滋恬,為了讓這個(gè)模型真正比線性模型更為強(qiáng)大,我們還需要一個(gè)技巧抱究。在計(jì)算完每個(gè)隱單元的加權(quán)求和之后恢氯,對(duì)結(jié)果再應(yīng)用一個(gè)非線性函數(shù)——通常是校正非線性(rectifying nonlinearity,也叫校正線性單元或 relu)或正切雙曲線(tangens hyperbolicus鼓寺, tanh)勋拟。然后將這個(gè)函數(shù)的結(jié)果用于加權(quán)求和, 計(jì)算得到輸出 ?妈候。這兩個(gè)函數(shù)的可視化效果見(jiàn)圖 2-46指黎。 relu 截?cái)嘈∮? 的值,而 tanh 在輸入值較小時(shí)接近 -1州丹,在輸入值較大時(shí)接近 +1醋安。有了這兩種非線性函數(shù),神經(jīng)網(wǎng)絡(luò)可以學(xué)習(xí)比線性模型復(fù)雜得多的函數(shù)墓毒。
In[91]:
line = np.linspace(-3, 3, 100)
plt.plot(line, np.tanh(line), label="tanh")
plt.plot(line, np.maximum(line, 0), label="relu")
plt.legend(loc="best")
plt.xlabel("x")
plt.ylabel("relu(x), tanh(x)")
對(duì)于圖 2-45 所示的小型神經(jīng)網(wǎng)絡(luò)吓揪,計(jì)算回歸問(wèn)題的 ? 的完整公式如下(使用 tanh 非線性):
h[0] = tanh(w[0, 0] * x[0] + w[1, 0] * x[1] + w[2, 0] * x[2] + w[3, 0] * x[3] + b[0])
h[1] = tanh(w[0, 0] * x[0] + w[1, 0] * x[1] + w[2, 0] * x[2] + w[3, 0] * x[3] + b[1])
h[2] = tanh(w[0, 0] * x[0] + w[1, 0] * x[1] + w[2, 0] * x[2] + w[3, 0] * x[3] + b[2])
? = v[0] * h[0] + v[1] * h[1] + v[2] * h[2] + b
其中, w 是輸入 x 與隱層 h 之間的權(quán)重所计, v 是隱層 h 與輸出 ? 之間的權(quán)重柠辞。權(quán)重 w 和 v 要從數(shù)據(jù)中學(xué)習(xí)得到, x 是輸入特征主胧, ? 是計(jì)算得到的輸出叭首, h 是計(jì)算的中間結(jié)果习勤。需要用戶(hù)設(shè)置的一個(gè)重要參數(shù)是隱層中的結(jié)點(diǎn)個(gè)數(shù)。對(duì)于非常小或非常簡(jiǎn)單的數(shù)據(jù)集焙格,這個(gè)值可以小到 10图毕;對(duì)于非常復(fù)雜的數(shù)據(jù),這個(gè)值可以大到 10 000眷唉。
2. 神經(jīng)網(wǎng)絡(luò)調(diào)參
我們將 MLPClassifier 應(yīng)用到本章前面用過(guò)的 two_moons 數(shù)據(jù)集上予颤,以此研究 MLP 的工作原理。結(jié)果如圖 2-48 所示冬阳。
In[93]:
from sklearn.neural_network import MLPClassifier
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=100, noise=0.25, random_state=3)
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y,
random_state=42)
mlp = MLPClassifier(solver='lbfgs', random_state=0).fit(X_train, y_train)
mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3)
mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
如你所見(jiàn)蛤虐,神經(jīng)網(wǎng)絡(luò)學(xué)到的決策邊界完全是非線性的,但相對(duì)平滑肝陪。我們用到了solver='lbfgs'驳庭,這一點(diǎn)稍后會(huì)講到。
默認(rèn)情況下氯窍, MLP 使用 100 個(gè)隱結(jié)點(diǎn)饲常,這對(duì)于這個(gè)小型數(shù)據(jù)集來(lái)說(shuō)已經(jīng)相當(dāng)多了。我們可以減少其數(shù)量(從而降低了模型復(fù)雜度)荞驴,但仍然得到很好的結(jié)果(圖 2-49):
In[94]:
mlp = MLPClassifier(solver='lbfgs', random_state=0, hidden_layer_sizes=[10])
mlp.fit(X_train, y_train)
mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3)
mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
只有 10 個(gè)隱單元時(shí),決策邊界看起來(lái)更加參差不齊贯城。默認(rèn)的非線性是 relu熊楼,如圖 2-46 所示。如果使用單隱層能犯,那么決策函數(shù)將由 10 個(gè)直線段組成鲫骗。如果想得到更加平滑的決策邊界,可以添加更多的隱單元(見(jiàn)圖 2-48)踩晶、添加第二個(gè)隱層(見(jiàn)圖 2-50)或者使用 tanh非線性(見(jiàn)圖 2-51)执泰。
In[95]:
# 使用2個(gè)隱層,每個(gè)包含10個(gè)單元
mlp = MLPClassifier(solver='lbfgs', random_state=0,
hidden_layer_sizes=[10, 10])
mlp.fit(X_train, y_train)
mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3)
mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
In[96]:
# 使用2個(gè)隱層渡蜻,每個(gè)包含10個(gè)單元术吝,這次使用tanh非線性
mlp = MLPClassifier(solver='lbfgs', activation='tanh',
random_state=0, hidden_layer_sizes=[10, 10])
mlp.fit(X_train, y_train)
mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3)
mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
最后排苍,我們還可以利用 L2 懲罰使權(quán)重趨向于 0,從而控制神經(jīng)網(wǎng)絡(luò)的復(fù)雜度学密,正如我們?cè)趲X回歸和線性分類(lèi)器中所做的那樣淘衙。 MLPClassifier 中調(diào)節(jié) L2 懲罰的參數(shù)是 alpha(與線性回歸模型中的相同),它的默認(rèn)值很心迥骸(弱正則化)彤守。圖 2-52 顯示了不同 alpha 值對(duì) two_moons 數(shù)據(jù)集的影響毯侦,用的是 2 個(gè)隱層的神經(jīng)網(wǎng)絡(luò),每層包含 10 個(gè)或 100 個(gè)單元:
In[97]:
fig, axes = plt.subplots(2, 4, figsize=(20, 8))
for axx, n_hidden_nodes in zip(axes, [10, 100]):
for ax, alpha in zip(axx, [0.0001, 0.01, 0.1, 1]):
mlp = MLPClassifier(solver='lbfgs', random_state=0,
hidden_layer_sizes=[n_hidden_nodes, n_hidden_nodes],
alpha=alpha)
mlp.fit(X_train, y_train)
mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3, ax=ax)
mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train, ax=ax)
ax.set_title("n_hidden=[{}, {}]\nalpha={:.4f}".format(
n_hidden_nodes, n_hidden_nodes, alpha))
現(xiàn)在你可能已經(jīng)認(rèn)識(shí)到了具垫,控制神經(jīng)網(wǎng)絡(luò)復(fù)雜度的方法有很多種:隱層的個(gè)數(shù)侈离、每個(gè)隱層中的單元個(gè)數(shù)與正則化(alpha)。實(shí)際上還有更多做修,但這里不再過(guò)多介紹霍狰。神經(jīng)網(wǎng)絡(luò)的一個(gè)重要性質(zhì)是,在開(kāi)始學(xué)習(xí)之前其權(quán)重是隨機(jī)設(shè)置的饰及,這種隨機(jī)初始化會(huì)影響學(xué)到的模型蔗坯。也就是說(shuō),即使使用完全相同的參數(shù)燎含,如果隨機(jī)種子不同的話宾濒,我們也可能得到非常不一樣的模型。如果網(wǎng)絡(luò)很大屏箍,并且復(fù)雜度選擇合理的話绘梦,那么這應(yīng)該不會(huì)對(duì)精度有太大影響,但應(yīng)該記住這一點(diǎn)(特別是對(duì)于較小的網(wǎng)絡(luò))赴魁。圖 2-53 顯示了幾個(gè)模型的圖像卸奉,所有模型都使用相同的參數(shù)設(shè)置進(jìn)行學(xué)習(xí):
In[98]:
fig, axes = plt.subplots(2, 4, figsize=(20, 8))
for i, ax in enumerate(axes.ravel()):
mlp = MLPClassifier(solver='lbfgs', random_state=i,
hidden_layer_sizes=[100, 100])
mlp.fit(X_train, y_train)
mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3, ax=ax)
mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train, ax=ax)
為了在現(xiàn)實(shí)世界的數(shù)據(jù)上進(jìn)一步理解神經(jīng)網(wǎng)絡(luò),我們將 MLPClassifier 應(yīng)用在乳腺癌數(shù)據(jù)集上颖御。首先使用默認(rèn)參數(shù):
In[99]:
print("Cancer data per-feature maxima:\n{}".format(cancer.data.max(axis=0)))
Out[99]:
Cancer data per-feature maxima:
[ 28.110 39.280 188.500 2501.000 0.163 0.345 0.427
0.201 0.304 0.097 2.873 4.885 21.980 542.200
0.031 0.135 0.396 0.053 0.079 0.030 36.040
49.540 251.200 4254.000 0.223 1.058 1.252 0.291
0.664 0.207]
In[100]:
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target, random_state=0)
mlp = MLPClassifier(random_state=42)
mlp.fit(X_train, y_train)
print("Accuracy on training set: {:.2f}".format(mlp.score(X_train, y_train)))
print("Accuracy on test set: {:.2f}".format(mlp.score(X_test, y_test)))
Out[100]:
Accuracy on training set: 0.92
Accuracy on test set: 0.90
MLP 的精度相當(dāng)好榄棵,但沒(méi)有其他模型好。與較早的 SVC 例子相同潘拱,原因可能在于數(shù)據(jù)的縮放疹鳄。神經(jīng)網(wǎng)絡(luò)也要求所有輸入特征的變化范圍相似,最理想的情況是均值為 0芦岂、方差為1瘪弓。我們必須對(duì)數(shù)據(jù)進(jìn)行縮放以滿足這些要求。同樣禽最,我們這里將人工完成腺怯,但在第 3 章將會(huì)介紹用 StandardScaler 自動(dòng)完成:
In[101]:
# 計(jì)算訓(xùn)練集中每個(gè)特征的平均值
mean_on_train = X_train.mean(axis=0)
# 計(jì)算訓(xùn)練集中每個(gè)特征的標(biāo)準(zhǔn)差
std_on_train = X_train.std(axis=0)
# 減去平均值,然后乘以標(biāo)準(zhǔn)差的倒數(shù)
# 如此運(yùn)算之后川无, mean=0瓢喉, std=1
X_train_scaled = (X_train - mean_on_train) / std_on_train
# 對(duì)測(cè)試集做相同的變換(使用訓(xùn)練集的平均值和標(biāo)準(zhǔn)差)
X_test_scaled = (X_test - mean_on_train) / std_on_train
mlp = MLPClassifier(random_state=0)
mlp.fit(X_train_scaled, y_train)
print("Accuracy on training set: {:.3f}".format(
mlp.score(X_train_scaled, y_train)))
print("Accuracy on test set: {:.3f}".format(mlp.score(X_test_scaled, y_test)))
Out[101]:
Accuracy on training set: 0.991
Accuracy on test set: 0.965
ConvergenceWarning:
Stochastic Optimizer: Maximum iterations reached and the optimization
hasn't converged yet.
縮放之后的結(jié)果要好得多,而且也相當(dāng)有競(jìng)爭(zhēng)力舀透。不過(guò)模型給出了一個(gè)警告栓票,告訴我們已經(jīng)達(dá)到最大迭代次數(shù)。這是用于學(xué)習(xí)模型的 adam 算法的一部分,告訴我們應(yīng)該增加迭代次數(shù):
In[102]:
mlp = MLPClassifier(max_iter=1000, random_state=0)
mlp.fit(X_train_scaled, y_train)
print("Accuracy on training set: {:.3f}".format(
mlp.score(X_train_scaled, y_train)))
print("Accuracy on test set: {:.3f}".format(mlp.score(X_test_scaled, y_test)))
Out[102]:
Accuracy on training set: 0.995
Accuracy on test set: 0.965
增加迭代次數(shù)僅提高了訓(xùn)練集性能走贪,但沒(méi)有提高泛化性能佛猛。不過(guò)模型的表現(xiàn)相當(dāng)不錯(cuò)。由于訓(xùn)練性能和測(cè)試性能之間仍有一些差距坠狡,所以我們可以嘗試降低模型復(fù)雜度來(lái)得到更好的泛化性能继找。這里我們選擇增大 alpha 參數(shù)(變化范圍相當(dāng)大,從 0.0001 到 1)逃沿,以此向權(quán)重添加更強(qiáng)的正則化:
In[103]:
mlp = MLPClassifier(max_iter=1000, alpha=1, random_state=0)
mlp.fit(X_train_scaled, y_train)
print("Accuracy on training set: {:.3f}".format(
mlp.score(X_train_scaled, y_train)))
print("Accuracy on test set: {:.3f}".format(mlp.score(X_test_scaled, y_test)))
Out[103]:
Accuracy on training set: 0.988
Accuracy on test set: 0.972
這得到了與我們目前最好的模型相同的性能婴渡。
雖然可以分析神經(jīng)網(wǎng)絡(luò)學(xué)到了什么,但這通常比分析線性模型或基于樹(shù)的模型更為復(fù)雜凯亮。
要想觀察模型學(xué)到了什么边臼,一種方法是查看模型的權(quán)重。你可以在 scikit-learn 示例庫(kù)中查看這樣的一個(gè)示例(http://scikit-learn.org/stable/auto_examples/neural_networks/plot_mnist_filters.html)假消。對(duì)于乳腺癌數(shù)據(jù)集柠并,這可能有點(diǎn)難以理解。下面這張圖(圖 2-54)顯示了連接輸入和第一個(gè)隱層之間的權(quán)重富拗。圖中的行對(duì)應(yīng) 30 個(gè)輸入特征臼予,列對(duì)應(yīng) 100 個(gè)隱單元。淺色代表較大的正值啃沪,而深色代表負(fù)值粘拾。
In[104]:
plt.figure(figsize=(20, 5))
plt.imshow(mlp.coefs_[0], interpolation='none', cmap='viridis')
plt.yticks(range(30), cancer.feature_names)
plt.xlabel("Columns in weight matrix")
plt.ylabel("Input feature")
plt.colorbar()
我們可以推斷,如果某個(gè)特征對(duì)所有隱單元的權(quán)重都很小创千,那么這個(gè)特征對(duì)模型來(lái)說(shuō)就“不太重要”缰雇。可以看到签餐,與其他特征相比寓涨,“mean smoothness”“mean compactness”以及“smoothness error” 和“fractal dimension error”之間的特征的權(quán)重都相對(duì)較小盯串。這可能說(shuō)明這些特征不太重要氯檐,也可能是我們沒(méi)有用神經(jīng)網(wǎng)絡(luò)可以使用的方式來(lái)表示這些特征。我們還可以將連接隱層和輸出層的權(quán)重可視化体捏,但它們更加難以解釋冠摄。
雖然 MLPClassifier 和 MLPRegressor 為最常見(jiàn)的神經(jīng)網(wǎng)絡(luò)架構(gòu)提供了易于使用的接口,但它們只包含神經(jīng)網(wǎng)絡(luò)潛在應(yīng)用的一部分几缭。如果你有興趣使用更靈活或更大的模型河泳,我們建議你看一下除了 scikit-learn 之外的很棒的深度學(xué)習(xí)庫(kù)。對(duì)于 Python 用戶(hù)來(lái)說(shuō)年栓,最為完善的是 keras拆挥、 lasagna 和 tensor-flow。 lasagna 是基于 theano 庫(kù)構(gòu)建的,而 keras 既可以用 tensor-flow 也可以用 theano纸兔。這些庫(kù)提供了更為靈活的接口惰瓜,可以用來(lái)構(gòu)建神經(jīng)網(wǎng)絡(luò)并跟蹤深度學(xué)習(xí)研究的快速發(fā)展。所有流行的深度學(xué)習(xí)庫(kù)也都允許使用高性能的圖形處理單元(GPU)汉矿,而 scikit-learn 不支持 GPU崎坊。使用 GPU 可以將計(jì)算速度加快 10 到 100倍, GPU 對(duì)于將深度學(xué)習(xí)方法應(yīng)用到大型數(shù)據(jù)集上至關(guān)重要洲拇。
3. 優(yōu)點(diǎn)奈揍、 缺點(diǎn)和參數(shù)
在機(jī)器學(xué)習(xí)的許多應(yīng)用中,神經(jīng)網(wǎng)絡(luò)再次成為最先進(jìn)的模型赋续。它的主要優(yōu)點(diǎn)之一是能夠獲取大量數(shù)據(jù)中包含的信息男翰,并構(gòu)建無(wú)比復(fù)雜的模型。給定足夠的計(jì)算時(shí)間和數(shù)據(jù)蚕捉,并且仔細(xì)調(diào)節(jié)參數(shù)奏篙,神經(jīng)網(wǎng)絡(luò)通常可以打敗其他機(jī)器學(xué)習(xí)算法(無(wú)論是分類(lèi)任務(wù)還是回歸任務(wù))迫淹。這就引出了下面要說(shuō)的缺點(diǎn)秘通。神經(jīng)網(wǎng)絡(luò)——特別是功能強(qiáng)大的大型神經(jīng)網(wǎng)絡(luò)——通常需要很長(zhǎng)的訓(xùn)練時(shí)間。它還需要仔細(xì)地預(yù)處理數(shù)據(jù)敛熬,正如我們這里所看到的肺稀。與 SVM 類(lèi)似,神經(jīng)網(wǎng)絡(luò)在“均勻”數(shù)據(jù)上的性能最好应民,其中“均勻”是指所有特征都具有相似的含義话原。如果數(shù)據(jù)包含不同種類(lèi)的特征,那么基于樹(shù)的模型可能表現(xiàn)得更好诲锹。神經(jīng)網(wǎng)絡(luò)調(diào)參本身也是一門(mén)藝術(shù)繁仁。調(diào)節(jié)神經(jīng)網(wǎng)絡(luò)模型和訓(xùn)練模型的方法有很多種,我們只是蜻蜓點(diǎn)水地嘗試了幾種而已归园。
估計(jì)神經(jīng)網(wǎng)絡(luò)的復(fù)雜度黄虱。 最重要的參數(shù)是層數(shù)和每層的隱單元個(gè)數(shù)。你應(yīng)該首先設(shè)置 1 個(gè)或 2 個(gè)隱層庸诱,然后可以逐步增加捻浦。每個(gè)隱層的結(jié)點(diǎn)個(gè)數(shù)通常與輸入特征個(gè)數(shù)接近,但在幾千個(gè)結(jié)點(diǎn)時(shí)很少會(huì)多于特征個(gè)數(shù)桥爽。
在考慮神經(jīng)網(wǎng)絡(luò)的模型復(fù)雜度時(shí)朱灿,一個(gè)有用的度量是學(xué)到的權(quán)重(或系數(shù))的個(gè)數(shù)。如果你有一個(gè)包含 100 個(gè)特征的二分類(lèi)數(shù)據(jù)集钠四,模型有 100 個(gè)隱單元盗扒,那么輸入層和第一個(gè)隱層之間就有 100 * 100 = 10 000 個(gè)權(quán)重。在隱層和輸出層之間還有 100 * 1 = 100 個(gè)權(quán)重,總共約 10 100 個(gè)權(quán)重侣灶。如果添加含有 100 個(gè)隱單元的第二個(gè)隱層习霹,那么在第一個(gè)隱層和第二個(gè)隱層之間又有 100 * 100 = 10 000 個(gè)權(quán)重,總數(shù)變?yōu)榧s 20 100 個(gè)權(quán)重炫隶。如果你使用包含1000 個(gè)隱單元的單隱層淋叶,那么在輸入層和隱層之間需要學(xué)習(xí) 100 * 1000 = 100 000 個(gè)權(quán)重,隱層到輸出層之間需要學(xué)習(xí) 1000 * 1 = 1000 個(gè)權(quán)重伪阶,總共 101 000 個(gè)權(quán)重煞檩。如果再添加第二個(gè)隱層,就會(huì)增加 1000 * 1000 = 1 000 000 個(gè)權(quán)重栅贴,總數(shù)變?yōu)榫薮蟮?1 101 000 個(gè)權(quán)重斟湃,這比含有 2 個(gè)隱層、每層 100 個(gè)單元的模型要大 50 倍檐薯。
神經(jīng)網(wǎng)絡(luò)調(diào)參的常用方法是凝赛,首先創(chuàng)建一個(gè)大到足以過(guò)擬合的網(wǎng)絡(luò),確保這個(gè)網(wǎng)絡(luò)可以對(duì)任務(wù)進(jìn)行學(xué)習(xí)坛缕。知道訓(xùn)練數(shù)據(jù)可以被學(xué)習(xí)之后墓猎,要么縮小網(wǎng)絡(luò),要么增大 alpha 來(lái)增強(qiáng)正則化赚楚,這可以提高泛化性能毙沾。
在我們的實(shí)驗(yàn)中,主要關(guān)注模型的定義:層數(shù)宠页、每層的結(jié)點(diǎn)個(gè)數(shù)左胞、正則化和非線性。這些內(nèi)容定義了我們想要學(xué)習(xí)的模型举户。還有一個(gè)問(wèn)題是烤宙, 如何學(xué)習(xí)模型或用來(lái)學(xué)習(xí)參數(shù)的算法,這一點(diǎn)由 solver 參數(shù)設(shè)定俭嘁。 solver 有兩個(gè)好用的選項(xiàng)躺枕。默認(rèn)選項(xiàng)是 'adam',在大多數(shù)情況下效果都很好兄淫,但對(duì)數(shù)據(jù)的縮放相當(dāng)敏感(因此屯远,始終將數(shù)據(jù)縮放為均值為 0蔓姚、方差為 1 是很重要的)捕虽。另一個(gè)選項(xiàng)是 'lbfgs',其魯棒性相當(dāng)好坡脐,但在大型模型或大型數(shù)據(jù)集上的時(shí)間會(huì)比較長(zhǎng)泄私。還有更高級(jí)的 'sgd' 選項(xiàng),許多深度學(xué)習(xí)研究人員都會(huì)用到。 'sgd'選項(xiàng)還有許多其他參數(shù)需要調(diào)節(jié)晌端,以便獲得最佳結(jié)果捅暴。你可以在用戶(hù)指南中找到所有這些參數(shù)及其定義。當(dāng)你開(kāi)始使用 MLP 時(shí)咧纠,我們建議使用 'adam' 和 'lbfgs'蓬痒。
2.4 分類(lèi)器的不確定度估計(jì)
我們還沒(méi)有談到 scikit-learn 接口的另一個(gè)有用之處,就是分類(lèi)器能夠給出預(yù)測(cè)的不確定度估計(jì)漆羔。一般來(lái)說(shuō)梧奢,你感興趣的不僅是分類(lèi)器會(huì)預(yù)測(cè)一個(gè)測(cè)試點(diǎn)屬于哪個(gè)類(lèi)別,還包括它對(duì)這個(gè)預(yù)測(cè)的置信程度演痒。在實(shí)踐中亲轨,不同類(lèi)型的錯(cuò)誤會(huì)在現(xiàn)實(shí)應(yīng)用中導(dǎo)致非常不同的結(jié)果。想象一個(gè)用于測(cè)試癌癥的醫(yī)療應(yīng)用鸟顺。假陽(yáng)性預(yù)測(cè)可能只會(huì)讓患者接受額外的測(cè)試惦蚊,但假陰性預(yù)測(cè)卻可能導(dǎo)致重病沒(méi)有得到治療。第 6 章會(huì)進(jìn)一步探討這一主題讯嫂。scikit-learn 中有兩個(gè)函數(shù)可用于獲取分類(lèi)器的不確定度估計(jì): decision_function 和predict_proba蹦锋。大多數(shù)分類(lèi)器(但不是全部)都至少有其中一個(gè)函數(shù),很多分類(lèi)器兩個(gè)都有欧芽。我們來(lái)構(gòu)建一個(gè) GradientBoostingClassifier 分類(lèi)器(同時(shí)擁有 decision_function和 predict_proba 兩個(gè)方法)晕粪,看一下這兩個(gè)函數(shù)對(duì)一個(gè)模擬的二維數(shù)據(jù)集的作用:
In[105]:
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.datasets import make_circles
X, y = make_circles(noise=0.25, factor=0.5, random_state=1)
# 為了便于說(shuō)明,我們將兩個(gè)類(lèi)別重命名為"blue"和"red"
y_named = np.array(["blue", "red"])[y]
# 我們可以對(duì)任意個(gè)數(shù)組調(diào)用train_test_split
# 所有數(shù)組的劃分方式都是一致的
X_train, X_test, y_train_named, y_test_named, y_train, y_test = \
train_test_split(X, y_named, y, random_state=0)
# 構(gòu)建梯度提升模型
gbrt = GradientBoostingClassifier(random_state=0)
gbrt.fit(X_train, y_train_named)
2.4.1 決策函數(shù)
對(duì)于二分類(lèi)的情況渐裸, decision_function 返回值的形狀是 (n_samples,)巫湘,為每個(gè)樣本都返回一個(gè)浮點(diǎn)數(shù):
In[106]:
print("X_test.shape: {}".format(X_test.shape))
print("Decision function shape: {}".format(
gbrt.decision_function(X_test).shape))
Out[106]:
X_test.shape: (25, 2)
Decision function shape: (25,)
對(duì)于類(lèi)別 1 來(lái)說(shuō),這個(gè)值表示模型對(duì)該數(shù)據(jù)點(diǎn)屬于“正”類(lèi)的置信程度昏鹃。正值表示對(duì)正類(lèi)的偏好尚氛,負(fù)值表示對(duì)“反類(lèi)”(其他類(lèi))的偏好:
In[107]:
# 顯示decision_function的前幾個(gè)元素
print("Decision function:\n{}".format(gbrt.decision_function(X_test)[:6]))
Out[107]:
Decision function:
[ 4.136 -1.683 -3.951 -3.626 4.29 3.662]
我們可以通過(guò)僅查看決策函數(shù)的正負(fù)號(hào)來(lái)再現(xiàn)預(yù)測(cè)值:
In[108]:
print("Thresholded decision function:\n{}".format(
gbrt.decision_function(X_test) > 0))
print("Predictions:\n{}".format(gbrt.predict(X_test)))
Out[108]:
Thresholded decision function:
[ True False False False True True False True True True False True
True False True False False False True True True True True False
False]
Predictions:
['red' 'blue' 'blue' 'blue' 'red' 'red' 'blue' 'red' 'red' 'red' 'blue'
'red' 'red' 'blue' 'red' 'blue' 'blue' 'blue' 'red' 'red' 'red' 'red'
'red' 'blue' 'blue']
對(duì)于二分類(lèi)問(wèn)題,“反”類(lèi)始終是 classes_ 屬性的第一個(gè)元素洞渤,“正”類(lèi)是 classes_ 的第二個(gè)元素阅嘶。因此,如果你想要完全再現(xiàn) predict 的輸出载迄,需要利用 classes_ 屬性:
In[109]:
# 將布爾值True/False轉(zhuǎn)換成0和1
greater_zero = (gbrt.decision_function(X_test) > 0).astype(int)
# 利用0和1作為classes_的索引
pred = gbrt.classes_[greater_zero]
# pred與gbrt.predict的輸出完全相同
print("pred is equal to predictions: {}".format(
np.all(pred == gbrt.predict(X_test))))
Out[109]:
pred is equal to predictions: True
decision_function 可以在任意范圍取值讯柔,這取決于數(shù)據(jù)與模型參數(shù):
In[110]:
decision_function = gbrt.decision_function(X_test)
print("Decision function minimum: {:.2f} maximum: {:.2f}".format(
np.min(decision_function), np.max(decision_function)))
Out[110]:
Decision function minimum: -7.69 maximum: 4.29
由于可以任意縮放,因此 decision_function 的輸出往往很難解釋护昧。
在下面的例子中魂迄,我們利用顏色編碼在二維平面中畫(huà)出所有點(diǎn)的 decision_function,還有決策邊界惋耙,后者我們之間見(jiàn)過(guò)捣炬。我們將訓(xùn)練點(diǎn)畫(huà)成圓熊昌,將測(cè)試數(shù)據(jù)畫(huà)成三角(圖 2-55):
In[111]:
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
mglearn.tools.plot_2d_separator(gbrt, X, ax=axes[0], alpha=.4,
fill=True, cm=mglearn.cm2)
scores_image = mglearn.tools.plot_2d_scores(gbrt, X, ax=axes[1],
alpha=.4, cm=mglearn.ReBl)
for ax in axes:
# 畫(huà)出訓(xùn)練點(diǎn)和測(cè)試點(diǎn)
mglearn.discrete_scatter(X_test[:, 0], X_test[:, 1], y_test,
markers='^', ax=ax)
mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train,
markers='o', ax=ax)
ax.set_xlabel("Feature 0")
ax.set_ylabel("Feature 1")
cbar = plt.colorbar(scores_image, ax=axes.tolist())
axes[0].legend(["Test class 0", "Test class 1", "Train class 0",
"Train class 1"], ncol=4, loc=(.1, 1.1))
既給出預(yù)測(cè)結(jié)果,又給出分類(lèi)器的置信程度湿酸,這樣給出的信息量更大婿屹。但在上面的圖像中,很難分辨出兩個(gè)類(lèi)別之間的邊界推溃。
2.4.2 預(yù)測(cè)概率
predict_proba 的輸出是每個(gè)類(lèi)別的概率昂利,通常比 decision_function 的輸出更容易理解。對(duì)于二分類(lèi)問(wèn)題铁坎,它的形狀始終是 (n_samples, 2):
In[112]:
print("Shape of probabilities: {}".format(gbrt.predict_proba(X_test).shape))
Out[112]:
Shape of probabilities: (25, 2)
每行的第一個(gè)元素是第一個(gè)類(lèi)別的估計(jì)概率页眯,第二個(gè)元素是第二個(gè)類(lèi)別的估計(jì)概率。由于predict_proba 的輸出是一個(gè)概率厢呵,因此總是在 0 和 1 之間窝撵,兩個(gè)類(lèi)別的元素之和始終為 1:
In[113]:
# 顯示predict_proba的前幾個(gè)元素
print("Predicted probabilities:\n{}".format(
gbrt.predict_proba(X_test[:6])))
Out[113]:
Predicted probabilities:
[[ 0.016 0.984]
[ 0.843 0.157]
[ 0.981 0.019]
[ 0.974 0.026]
[ 0.014 0.986]
[ 0.025 0.975]]
由于兩個(gè)類(lèi)別的概率之和為 1,因此只有一個(gè)類(lèi)別的概率超過(guò) 50%襟铭。這個(gè)類(lèi)別就是模型的預(yù)測(cè)結(jié)果碌奉。
在上一個(gè)輸出中可以看到,分類(lèi)器對(duì)大部分點(diǎn)的置信程度都是相對(duì)較高的寒砖。不確定度大小實(shí)際上反映了數(shù)據(jù)依賴(lài)于模型和參數(shù)的不確定度赐劣。過(guò)擬合更強(qiáng)的模型可能會(huì)做出置信程度更高的預(yù)測(cè),即使可能是錯(cuò)的哩都。復(fù)雜度越低的模型通常對(duì)預(yù)測(cè)的不確定度越大魁兼。如果模型給出的不確定度符合實(shí)際情況,那么這個(gè)模型被稱(chēng)為校正(calibrated)模型漠嵌。在校正模型中咐汞,如果預(yù)測(cè)有 70% 的確定度,那么它在 70% 的情況下正確儒鹿。
在下面的例子中(圖 2-56)化撕,我們?cè)俅谓o出該數(shù)據(jù)集的決策邊界,以及類(lèi)別 1 的類(lèi)別概率:
In[114]:
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
mglearn.tools.plot_2d_separator(
gbrt, X, ax=axes[0], alpha=.4, fill=True, cm=mglearn.cm2)
scores_image = mglearn.tools.plot_2d_scores(
gbrt, X, ax=axes[1], alpha=.5, cm=mglearn.ReBl, function='predict_proba')
for ax in axes:
# plot training and test points
mglearn.discrete_scatter(X_test[:, 0], X_test[:, 1], y_test,
markers='^', ax=ax)
mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train,
markers='o', ax=ax)
ax.set_xlabel("Feature 0")
ax.set_ylabel("Feature 1")
# don't want a transparent colorbar
cbar = plt.colorbar(scores_image, ax=axes.tolist())
cbar.set_alpha(1)
cbar.draw_all()
axes[0].legend(["Test class 0", "Test class 1", "Train class 0",
"Train class 1"], ncol=4, loc=(.1, 1.1))
這張圖中的邊界更加明確约炎,不確定的小塊區(qū)域清晰可見(jiàn)植阴。scikit-learn 網(wǎng) 站(http://scikit-learn.org/stable/auto_examples/classification/plot_classifier_comparison.html)給出了許多模型的對(duì)比,以及不確定度估計(jì)的形狀圾浅。
2.4.3 多分類(lèi)問(wèn)題的不確定度
到目前為止侨舆,我們只討論了二分類(lèi)問(wèn)題中的不確定度估計(jì)还蹲。但 decision_function 和predict_proba 也適用于多分類(lèi)問(wèn)題翘鸭。我們將這兩個(gè)函數(shù)應(yīng)用于鳶尾花(Iris)數(shù)據(jù)集靴姿,這是一個(gè)三分類(lèi)數(shù)據(jù)集:
In[115]:
from sklearn.datasets import load_iris
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, random_state=42)
gbrt = GradientBoostingClassifier(learning_rate=0.01, random_state=0)
gbrt.fit(X_train, y_train)
In[116]:
print("Decision function shape: {}".format(gbrt.decision_function(X_test).shape))
# 顯示決策函數(shù)的前幾個(gè)元素
print("Decision function:\n{}".format(gbrt.decision_function(X_test)[:6, :]))
Out[116]:
Decision function shape: (38, 3)
Decision function:
[[-0.529 1.466 -0.504]
[ 1.512 -0.496 -0.503]
[-0.524 -0.468 1.52 ]
[-0.529 1.466 -0.504]
[-0.531 1.282 0.215]
[ 1.512 -0.496 -0.503]]
對(duì)于多分類(lèi)的情況鹰霍, decision_function 的形狀為 (n_samples, n_classes)导犹,每一列對(duì)應(yīng)每個(gè)類(lèi)別的“確定度分?jǐn)?shù)”凤优,分?jǐn)?shù)較高的類(lèi)別可能性更大,得分較低的類(lèi)別可能性較小。你可以找出每個(gè)數(shù)據(jù)點(diǎn)的最大元素株搔,從而利用這些分?jǐn)?shù)再現(xiàn)預(yù)測(cè)結(jié)果:
In[117]:
print("Argmax of decision function:\n{}".format(
np.argmax(gbrt.decision_function(X_test), axis=1)))
print("Predictions:\n{}".format(gbrt.predict(X_test)))
Out[117]:
Argmax of decision function:
[1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0]
Predictions:
[1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0]
predict_proba 輸出的形狀相同剖淀,也是 (n_samples, n_classes)。同樣纤房,每個(gè)數(shù)據(jù)點(diǎn)所有可能類(lèi)別的概率之和為 1:
In[118]:
# 顯示predict_proba的前幾個(gè)元素
print("Predicted probabilities:\n{}".format(gbrt.predict_proba(X_test)[:6]))
# 顯示每行的和都是1
print("Sums: {}".format(gbrt.predict_proba(X_test)[:6].sum(axis=1)))
Out[118]:
Predicted probabilities:
[[ 0.107 0.784 0.109]
[ 0.789 0.106 0.105]
[ 0.102 0.108 0.789]
[ 0.107 0.784 0.109]
[ 0.108 0.663 0.228]
[ 0.789 0.106 0.105]]
Sums: [ 1. 1. 1. 1. 1. 1.]
同樣纵隔,我們可以通過(guò)計(jì)算 predict_proba 的 argmax 來(lái)再現(xiàn)預(yù)測(cè)結(jié)果:
In[119]:
print("Argmax of predicted probabilities:\n{}".format(
np.argmax(gbrt.predict_proba(X_test), axis=1)))
print("Predictions:\n{}".format(gbrt.predict(X_test)))
Out[119]:
Argmax of predicted probabilities:
[1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0]
Predictions:
[1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0]
總 之, predict_proba 和 decision_function 的 形 狀 始 終 相 同炮姨, 都 是 (n_samples, n_classes)——除了二分類(lèi)特殊情況下的 decision_function捌刮。對(duì)于二分類(lèi)的情況, decision_function 只有一列舒岸,對(duì)應(yīng)“正”類(lèi) classes_[1]绅作。這主要是由于歷史原因。如果有 n_classes 列蛾派,你可以通過(guò)計(jì)算每一列的 argmax 來(lái)再現(xiàn)預(yù)測(cè)結(jié)果俄认。但如果類(lèi)別是字符串,或者是整數(shù)洪乍,但不是從 0 開(kāi)始的連續(xù)整數(shù)的話眯杏,一定要小心。如果你想要對(duì)比 predict 的結(jié)果與 decision_function 或 predict_proba 的結(jié)果壳澳,一定要用分類(lèi)器的classes_ 屬性來(lái)獲取真實(shí)的屬性名稱(chēng):
In[120]:
logreg = LogisticRegression()
# 用Iris數(shù)據(jù)集的類(lèi)別名稱(chēng)來(lái)表示每一個(gè)目標(biāo)值
named_target = iris.target_names[y_train]
logreg.fit(X_train, named_target)
print("unique classes in training data: {}".format(logreg.classes_))
print("predictions: {}".format(logreg.predict(X_test)[:10]))
argmax_dec_func = np.argmax(logreg.decision_function(X_test), axis=1)
print("argmax of decision function: {}".format(argmax_dec_func[:10]))
print("argmax combined with classes_: {}".format(
logreg.classes_[argmax_dec_func][:10]))
Out[120]:
unique classes in training data: ['setosa' 'versicolor' 'virginica']
predictions: ['versicolor' 'setosa' 'virginica' 'versicolor' 'versicolor'
'setosa' 'versicolor' 'virginica' 'versicolor' 'versicolor']
argmax of decision function: [1 0 2 1 1 0 1 2 1 1]
argmax combined with classes_: ['versicolor' 'setosa' 'virginica' 'versicolor'
'versicolor' 'setosa' 'versicolor' 'virginica' 'versicolor' 'versicolor']
2.5 小結(jié)與展望
本章首先討論了模型復(fù)雜度岂贩,然后討論了泛化,或者說(shuō)學(xué)習(xí)一個(gè)能夠在前所未見(jiàn)的新數(shù)據(jù)上表現(xiàn)良好的模型巷波。這就引出了欠擬合和過(guò)擬合的概念萎津,前者是指一個(gè)模型無(wú)法獲取訓(xùn)練數(shù)據(jù)中的所有變化,后者是指模型過(guò)分關(guān)注訓(xùn)練數(shù)據(jù)抹镊,但對(duì)新數(shù)據(jù)的泛化性能不好姜性。然后本章討論了一系列用于分類(lèi)和回歸的機(jī)器學(xué)習(xí)模型,各個(gè)模型的優(yōu)點(diǎn)和缺點(diǎn)髓考,以及如何控制它們的模型復(fù)雜度部念。我們發(fā)現(xiàn),對(duì)于許多算法而言氨菇,設(shè)置正確的參數(shù)對(duì)模型性能至關(guān)重要儡炼。有些算法還對(duì)輸入數(shù)據(jù)的表示方式很敏感,特別是特征的縮放查蓉。因此乌询,如果盲目地將一個(gè)算法應(yīng)用于數(shù)據(jù)集,而不去理解模型所做的假設(shè)以及參數(shù)設(shè)定的含義豌研,不太可能會(huì)得到精度高的模型妹田。
本章包含大量有關(guān)算法的信息唬党,在繼續(xù)閱讀后續(xù)章節(jié)之前你不必記住所有這些細(xì)節(jié)。但是鬼佣,這里提到的有關(guān)模型的某些知識(shí)(以及在特定情況下使用哪種模型)對(duì)于在實(shí)踐中成功應(yīng)用機(jī)器學(xué)習(xí)模型是很重要的驶拱。關(guān)于何時(shí)使用哪種模型,下面是一份快速總結(jié)晶衷。
最近鄰
適用于小型數(shù)據(jù)集蓝纲,是很好的基準(zhǔn)模型,很容易解釋晌纫。
線性模型
非乘懊裕可靠的首選算法,適用于非常大的數(shù)據(jù)集锹漱,也適用于高維數(shù)據(jù)箭养。
樸素貝葉斯
只適用于分類(lèi)問(wèn)題。比線性模型速度還快哥牍,適用于非常大的數(shù)據(jù)集和高維數(shù)據(jù)露懒。精度通常要低于線性模型。
決策樹(shù)
速度很快砂心,不需要數(shù)據(jù)縮放懈词,可以可視化,很容易解釋辩诞。
隨機(jī)森林
幾乎總是比單棵決策樹(shù)的表現(xiàn)要好坎弯,魯棒性很好,非常強(qiáng)大译暂。不需要數(shù)據(jù)縮放抠忘。不適用于高維稀疏數(shù)據(jù)。
梯度提升決策樹(shù)
精度通常比隨機(jī)森林略高外永。與隨機(jī)森林相比崎脉,訓(xùn)練速度更慢,但預(yù)測(cè)速度更快伯顶,需要的內(nèi)存也更少囚灼。比隨機(jī)森林需要更多的參數(shù)調(diào)節(jié)。
支持向量機(jī)
對(duì)于特征含義相似的中等大小的數(shù)據(jù)集很強(qiáng)大祭衩。需要數(shù)據(jù)縮放灶体,對(duì)參數(shù)敏感。
神經(jīng)網(wǎng)絡(luò)
可以構(gòu)建非常復(fù)雜的模型掐暮,特別是對(duì)于大型數(shù)據(jù)集而言蝎抽。對(duì)數(shù)據(jù)縮放敏感,對(duì)參數(shù)選取敏感路克。大型網(wǎng)絡(luò)需要很長(zhǎng)的訓(xùn)練時(shí)間樟结。
面對(duì)新數(shù)據(jù)集养交,通常最好先從簡(jiǎn)單模型開(kāi)始,比如線性模型瓢宦、樸素貝葉斯或最近鄰分類(lèi)器碎连,看能得到什么樣的結(jié)果。對(duì)數(shù)據(jù)有了進(jìn)一步了解之后刁笙,你可以考慮用于構(gòu)建更復(fù)雜模型的算法破花,比如隨機(jī)森林谦趣、梯度提升決策樹(shù)疲吸、 SVM 或神經(jīng)網(wǎng)絡(luò)。現(xiàn)在你應(yīng)該對(duì)如何應(yīng)用前鹅、調(diào)節(jié)和分析我們介紹過(guò)的模型有了一定的了解摘悴。本章主要介紹了二分類(lèi)問(wèn)題,因?yàn)檫@通常是最容易理解的舰绘。不過(guò)本章大多數(shù)算法都可以同時(shí)用于分類(lèi)和回歸蹂喻,而且所有分類(lèi)算法都可以同時(shí)用于二分類(lèi)和多分類(lèi)。你可以嘗試將這些算法應(yīng)用于scikit-learn 的內(nèi)置數(shù)據(jù)集捂寿,比如用于回歸的 boston_housing 或 diabetes 數(shù)據(jù)集口四,或者用于多分類(lèi)的 digits 數(shù)據(jù)集。在不同的數(shù)據(jù)集上實(shí)驗(yàn)這些算法秦陋,可以讓你更好地感受它們所需的訓(xùn)練時(shí)間蔓彩、分析模型的難易程度以及它們對(duì)數(shù)據(jù)表示的敏感程度。
雖然我們分析了不同的參數(shù)設(shè)定對(duì)算法的影響驳概,但在生產(chǎn)環(huán)境中實(shí)際構(gòu)建一個(gè)對(duì)新數(shù)據(jù)泛化性能很好的模型要更復(fù)雜一些赤嚼。我們將在第 6 章介紹正確調(diào)參的方法和自動(dòng)尋找最佳參數(shù)的方法。
不過(guò)首先顺又,我們將在下一章深入討論無(wú)監(jiān)督學(xué)習(xí)和預(yù)處理更卒。