5.2 Scikit-Learn 簡介
譯者:飛龍
譯文沒有得到原作者授權,不保證與原文的意思嚴格一致。
有幾個 Python 庫提供一系列機器學習算法的實現。最著名的是 Scikit-Learn,一個提供大量常見算法的高效版本的軟件包。 Scikit-Learn 的特點是簡潔,統一,流線型的 API,以及非常實用和完整的在線文檔。這種一致性的好處是,一旦了解了 Scikit-Learn 中一種類型的模型的基本用法和語法,切換到新的模型或算法就非常簡單。
本節提供了 Scikit-Learn API 的概述;對這些API元素的了解,會成為理解以下章節中機器學習算法和方法的更深入的實際討論的基礎。
我們將首先介紹 Scikit-Learn 中的數據表示形式,然后設計 Estimator API,最后通過一個更有趣的例子,使用這些工具來探索一組手寫數字圖像。
Scikit-Learn 中的數據表示
機器學習是從數據創建模型:因此,我們將首先討論如何表示數據,以便計算機理解。 在 Scikit-Learn 中考慮數據的最佳方式就是數據表。
數據作為表
一個基本表格是二維數據網格,其中行表示數據集的各個元素,列表示與這些元素中的每一個相關的數量。 例如,考慮 Iris 數據集,由 Ronald Fisher 于 1936 年進行了著名的分析。我們可以使用 Seaborn 庫以 Pandas DataFrame 的形式下載此數據集:
import seaborn as sns
iris = sns.load_dataset('iris')
iris.head()
這里的每一行數據指代一個觀察到的花,行數是數據集中花的總數。 一般來說,我們將把矩陣行作為樣本,將行數稱為n_samples
。
類似地,數據的每一列都是描述每個樣本的特定的定量信息。 一般來說,我們將把矩陣的列稱為特征,列數稱為n_features
。
特征矩陣
該表的布局清楚地表明,信息可以當做二維數組或矩陣,我們稱之為特征矩陣。 按照慣例,這個特征矩陣通常被存儲在一個名為X
的變量中。特征矩陣被假設為二維的,形狀為[n_samples,n_features]
,并且最常使用NumPy
數組或Pandas DataFrame
來存放,盡管有些 Scikit-Learn 模型也接受 SciPy 稀疏矩陣。
樣本(即行)總是指代由數據集描述的各個對象。 例如,樣本可能是一朵花,一個人,一個文檔,一個圖像,一個聲音文件,一個視頻,一個天文物體,或者你可以用一組定量測量來描述的任何東西。
特征(即列)總是指以定量方式描述每個樣本的不同觀察結果。 特征通常是實值,但在某些情況下可能是布爾值或離散值。
目標數組
除了特征矩陣X
之外,我們還通常使用標簽或目標數組,按照慣例,我們通常稱為y
。目標數組通常是一維,長度為n_samples
,通常包含在 NumPy 數組或 Pandas Series 中。目標數組可以具有連續的數值或離散分類/標簽。雖然一些 Scikit-Learn 估計器確實以二維[n_samples,n_targets]
目標數組的形式處理多個目標值,但我們將主要處理一維目標數組的常見情況。
常常有一點令人困惑的是,目標數組與其他特征列的不同之處。目標數組的特征在于,它通常是我們要從數據中預測的數量:在統計學上,它是因變量。例如,在上述數據中,我們可能希望構建一個模型,可以基于其他度量來預測花的種類;在這種情況下,物種列將被視為目標數組。
考慮到這個目標數組,我們可以使用 Seaborn(參見可視化與 Seaborn)來方便地顯示數據:
%matplotlib inline
import seaborn as sns; sns.set()
sns.pairplot(iris, hue='species', size=1.5);
對于 Scikit-Learn 中的使用,我們會從DataFrame
提取特征矩陣和目標數組。我們可以用一些第三章中的 PandasDataFrame
操作實現。
X_iris = iris.drop('species', axis=1)
X_iris.shape
# (150, 4)
y_iris = iris['species']
y_iris.shape
# (150,)
總之,特征和目標值的預期布局,可以在下圖中顯示:
將這個數據合理格式化之后,我們可以轉而思考 Scikit-Learn 的估計器 API 了。
Scikit-Learn 的估計器 API
Scikit-Learn API 的設計思想,是 Scikit-Learn API 的說明書所述的以下指導原則:
一致性:所有對象共享一個通用接口,從一組有限方法抽取,具有一致的文檔。
檢查:所有指定的參數值都公開為公共屬性。
有限對象層次:只有算法由 Python 類表示;數據集以標準格式(NumPy 數組,Pandas DataFrames,SciPy 稀疏矩陣)表示,參數名稱使用標準 Python 字符串。
組成:許多機器學習任務可以表達為更基礎的算法的序列,而 Scikit-Learn 可以盡可能地利用這一點。
敏感默認值:當模型需要用戶指定的參數時,庫定義了一個適當的默認值。
在實踐中,一旦理解了基本原理,這些原則使 Scikit-Learn 非常容易使用。 Scikit-Learn 中的每個機器學習算法都通過 Estimator API 實現,該 API 為廣泛的機器學習應用提供了一致的接口。
API 基礎
通常,使用 Scikit-Learn 估計器 API 的步驟如下(我們將在以下部分中詳細介紹一些詳細示例)。
- 通過從 Scikit-Learn 導入適當的估計類,來選擇一類模型。
- 通過使用所需的值實例化此類,來選擇模型超參數。
- 在上述討論之后,將數據排列成特征矩陣和目標向量。
- 通過調用模型實例的
fit
方法,使用模型來擬合數據。 - 將模型應用于新數據:
- 對于監督學習,我們通常使用
predict()
方法預測未知數據的標簽。 - 對于無監督學習,我們經常使用
transform()
或predict()
方法來轉換或推斷數據的屬性。
- 對于監督學習,我們通常使用
我們現在將逐步介紹幾個簡單示例,應用監督和無監督學習方法。
監督學習示例:簡單線性回歸
作為這個過程的一個例子,讓我們考慮一個簡單的線性回歸,也就是說,一種常見情況,使用直線來擬合(x,y)
數據。 我們將以下簡單數據用于回歸示例:
import matplotlib.pyplot as plt
import numpy as np
rng = np.random.RandomState(42)
x = 10 * rng.rand(50)
y = 2 * x - 1 + rng.randn(50)
plt.scatter(x, y);
有了這些數據,我們可以使用前面提到的秘籍。 我們來看一下這個過程:
1. 選擇一個模型類
在 Scikit-Learn 中,每個模型類都由 Python 類表示。 所以,例如,如果我們想要計算一個簡單的線性回歸模型,我們可以導入線性回歸類:
from sklearn.linear_model import LinearRegression
要注意也存在更通用的線性回歸模型,你可以在 sklearn.linear_model 模型文檔中了解更多。
2. 選擇模型超參數
一個重點是,模型類與模型實例不一樣。
一旦我們決定了我們的模型類,我們仍然有一些選擇。根據我們正在使用的模型類,我們可能需要回答以下一個或多個問題:
- 我們希望擬合偏移(即縱截距)嗎?
- 我們是否希望將模型歸一化?
- 我們是否希望預處理我們的特征,來增加模型的靈活性?
- 我們想在我們的模型中使用什么程度的正則化?
- 我們想要使用多少個模型組件?
這些是重要選擇的示例,在選擇模型類后必須做出。這些選擇通常表示為超參數,或在模型擬合數據之前必須設置的參數。在 Scikit-Learn 中,通過在模型實例化下傳遞值來選擇超參數。我們將在超參數和模型驗證中,探討如何定量地改進超參數的選擇。
對于我們的線性回歸示例,我們可以實例化LinearRegression
類,并指定我們想使用fit_intercept
超參數擬合截距:
model = LinearRegression(fit_intercept=True)
model
# LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
請記住,當模型被實例化時,唯一的操作是存儲這些超參數值。 特別是,我們還沒有將模型應用于任何數據:Scikit-Learn API 非常清楚模型選擇和模型對數據應用之間的區別。
3. 將數據排列為特征矩陣和目標向量
以前,我們詳細介紹了 Scikit-Learn 數據表示,它需要二維特征矩陣和一維目標數組。 這里我們的目標變量y
已經是正確的形式(長度為n_samples
的數組),但是我們需要調整數據x
,使其成為大小為[n_samples,n_features]
的矩陣。 在這種情況下,這相當于一維數組的簡單重塑:
X = x[:, np.newaxis]
X.shape
# (50, 1)
4. 使用模型來擬合數據
現在是時候將模型應用于數據了。這可以使用fit
方法來完成。
model.fit(X, y)
# LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
這個fit
命令會導致一些模型相關的內部計算,這些計算的結果存儲在特定于模型的屬性中,用戶可以探索。 在 Scikit-Learn 中,按照慣例,在fit
過程中學習的所有模型參數,都有尾隨的下劃線;例如在這個線性模型中,我們有以下這些東西:
model.coef_
# array([ 1.9776566])
model.intercept_
# -0.90331072553111635
這兩個參數表示對數據的簡單線性擬合的斜率和截距。 與數據定義相比,我們看到它們非常接近輸入斜率 2 和截距 -1。
經常出現的一個問題是,這些內部模型參數的不確定性。 一般來說,Scikit-Learn 不提供從內部模型參數本身得出結論的工具:模型參數的解釋更多是統計建模問題,而不是機器學習問題。 機器學習的重點是模型預測。 如果你希望深入了解模型中參數的含義,則可以使用其他工具,包括 Python Statsmodels 包。
5. 預測未知數據的標簽
一旦模型訓練完成,監督機器學習的主要任務是,根據對不是訓練集的一部分的新數據做出評估。 在 Scikit-Learn 中,可以使用predict
方法來完成。 對于這個例子,我們的“新數據”將是一個x
值的網格,我們將詢問模型預測的y
值:
xfit = np.linspace(-1, 11)
像之前一樣,我們需要將這些x
值調整為[n_samples, n_features]
的特征矩陣,之后我們可以將它扔給模型了。
Xfit = xfit[:, np.newaxis]
yfit = model.predict(Xfit)
最后,讓我們通過首先繪制原始數據,之后是這個模型,來展示結果。
plt.scatter(x, y)
plt.plot(xfit, yfit);
通常,通過將其結果與某些已知基準進行比較,來評估模型的功效,如下例所示。
監督學習示例,鳶尾花分類
我們來看看這個過程的另一個例子,使用我們前面討論過的 Iris 數據集。 我們的問題是這樣的:給出一個模型,使用 Iris 數據的一部分進行培訓,我們如何能夠預測剩余的標簽?
對于這個任務,我們將使用一個非常簡單的生成模型,稱為高斯樸素貝葉斯,它們通過假設每個類別服從軸對齊的高斯分布(更多細節參見樸素貝葉斯分類)。 因為高斯樸素貝葉斯如此之快,沒有超參數可供選擇。在探索是否可以通過更復雜的模型做出改進之前,它通常是一個用作基準分類的良好模型。
我們想對之前沒有看到的數據進行評估,因此我們將數據分成訓練集和測試集。 這可以手工完成,但是使用train_test_split
工具更方便:
from sklearn.cross_validation import train_test_split
Xtrain, Xtest, ytrain, ytest = train_test_split(X_iris, y_iris,
random_state=1)
排列好數據之后,我們可以遵循秘籍來預測標簽了。
from sklearn.naive_bayes import GaussianNB # 1. choose model class
model = GaussianNB() # 2. instantiate model
model.fit(Xtrain, ytrain) # 3. fit model to data
y_model = model.predict(Xtest) # 4. predict on new data
最后,我們可以使用accuracy_score
工具來查看匹配真實值的預測標簽的百分比。
from sklearn.metrics import accuracy_score
accuracy_score(ytest, y_model)
# 0.97368421052631582
準確率超過了 97%,我們可以看到,即使是這個非常樸素的分類算法,對于特定數據集也是高效的。
無監督學習示例:Iris 降維
作為無監督學習問題的一個例子,我們來看一下 Iris 數據的降維,以便更容易地將其視覺化。 回想一下,Iris 數據是四維的:每個樣本都記錄了四個特征。
降維的任務是詢問是否存在合適的低維表示,保留數據的基本特征。 降維通常用于來輔助數據可視化:畢竟,繪制二維數據比四維或更高維度中更容易!
在這里,我們將使用主成分分析(PCA,參見主成分分析),這是一種快速線性降維技術。 我們要求模型返回兩個組件 - 即數據的二維表示。
按照先前列出的步驟,我們有:
from sklearn.decomposition import PCA # 1. Choose the model class
model = PCA(n_components=2) # 2. Instantiate the model with hyperparameters
model.fit(X_iris) # 3. Fit to data. Notice y is not specified!
X_2D = model.transform(X_iris) # 4. Transform the data to two dimensions
現在我們來繪制結果。 一個快速的方法是,將結果插入到原始的 Iris DataFrame 中,并使用 Seaborn 的lmplot
來顯示結果:
iris['PCA1'] = X_2D[:, 0]
iris['PCA2'] = X_2D[:, 1]
sns.lmplot("PCA1", "PCA2", hue='species', data=iris, fit_reg=False);
我們看到,在二維表示中,物種的分隔相當良好,盡管 PCA 算法不知道物種標簽! 這對我們來說,正如我們以前看到的那樣,相對簡單的分類可能對數據集有效。
無監督學習示例:Iris 聚類
接下來我們來看一下 Iris 數據的聚類應用。 聚類算法嘗試找到不同的數據分析,而不參考任何標簽。 在這里,我們將使用一種稱為高斯混合模型(GMM)的強大的聚類方法,在高斯混合模型中有更詳細的討論。 GMM 嘗試將數據建模為高斯數據塊的集合。
我們可以這樣擬合高斯混合模型:
from sklearn.mixture import GMM # 1. Choose the model class
model = GMM(n_components=3,
covariance_type='full') # 2. Instantiate the model with hyperparameters
model.fit(X_iris) # 3. Fit to data. Notice y is not specified!
y_gmm = model.predict(X_iris) # 4. Determine cluster labels
像之前一樣,我們向 IrisDataFrame
添加簇的標簽,并使用 Seaborn 繪制結果:
iris['cluster'] = y_gmm
sns.lmplot("PCA1", "PCA2", data=iris, hue='species',
col='cluster', fit_reg=False);
通過按照簇號分割數據,我們看到 GMM 算法已經恢復了潛在的標簽:組 0 已經完全分離了 setosa 物種,而在 versicolor 和 virginica 之間仍然存在少量的混合。 這意味著即使沒有專家告訴我們個別花朵的物種標簽,這些花朵的度量是非常明顯的,我們可以用簡單的聚類算法,自動識別這些不同種類的物種的存在! 這種算法可能會進一步向專家提供現場線索,關于他們正在觀察的樣本之間關系。
應用:手寫體數字探索
為了在一個更有趣的問題上演示這些原理,我們來考慮一個光學字符識別問題:識別手寫數字。 粗略來說,這個問題涉及定位和識別圖像中的字符。 在這里,我們將使用捷徑,并使用 Scikit-Learn 的一組預格式化數字,這是內置在庫中的。
加載和展示數字的數據
我們使用 Scikit-Learn 的數據訪問接口,并看一看這個數據:
from sklearn.datasets import load_digits
digits = load_digits()
digits.images.shape
# (1797, 8, 8)
這個圖像的數據是三維數組:1797 個樣本,每個包含 8x8 的像素。讓我們先展示前一百個。
import matplotlib.pyplot as plt
fig, axes = plt.subplots(10, 10, figsize=(8, 8),
subplot_kw={'xticks':[], 'yticks':[]},
gridspec_kw=dict(hspace=0.1, wspace=0.1))
for i, ax in enumerate(axes.flat):
ax.imshow(digits.images[i], cmap='binary', interpolation='nearest')
ax.text(0.05, 0.05, str(digits.target[i]),
transform=ax.transAxes, color='green')
為了在 Scikit-Learn 中處理這些數據,我們需要一個二維的[n_samples,n_features]
表示。 我們可以將圖像中的每個像素視為一個特征:即通過展開像素陣列,使得我們具有長度為 64 的數組,代表每個數字的像素值。 另外,我們需要目標數組,它為每個數字給出了先前確定的標簽。 這兩個數量分別內置在數字數據集的data
和target
屬性中:
X = digits.data
X.shape
# (1797, 64)
y = digits.target
y.shape
# (1797,)
我們可以看到,有 1797 個樣本和 64 個特征。
無監督學習:降維
我們希望在 64 維參數空間內可視化我們的點,但很難有效地在這樣一個高維空間中可視化點。 相反,我們將使用無監督的方法將維度減小到 2。 在這里,我們將利用一種稱為 Isomap 的流形學習算法(參見流形學習),并將數據轉換為兩個維度:
from sklearn.manifold import Isomap
iso = Isomap(n_components=2)
iso.fit(digits.data)
data_projected = iso.transform(digits.data)
data_projected.shape
# (1797, 2)
我們可以看到,投影的數據現在是二維了。讓我們繪制數據,來看看是否可以從結構中學到什么東西。
plt.scatter(data_projected[:, 0], data_projected[:, 1], c=digits.target,
edgecolor='none', alpha=0.5,
cmap=plt.cm.get_cmap('spectral', 10))
plt.colorbar(label='digit label', ticks=range(10))
plt.clim(-0.5, 9.5);
這個繪圖給了我們很好的直覺,在更大的 64 維空間中各種數字的分離程度如何。 例如,零(黑色)和一(紫色)在參數空間中幾乎沒有重疊。 直觀上來說,這是有道理的:零的圖像中間是空的,而一的中間通常會有墨跡。 另一方面,一和四之間似乎有一個或多或少的連續頻譜:我們可以理解,有些人在一上畫了個“帽子”,從而使他們看起來像四。
然而,總的來說,不同的組的似乎在參數空間中分離良好的:這告訴我們,即使是一個非常簡單的監督分類算法,應該也適合于這些數據。 讓我們試試看吧。
對數字分類
讓我們對數字應用分類算法。就像之前的 Iris 數據那樣,我們將數據分為訓練和測試集,之后擬合高斯樸素貝葉斯模型。
Xtrain, Xtest, ytrain, ytest = train_test_split(X, y, random_state=0)
from sklearn.naive_bayes import GaussianNB
model = GaussianNB()
model.fit(Xtrain, ytrain)
y_model = model.predict(Xtest)
既然我們預測了我們的模型,我們可以通過比較測試集和預測,來看看它的準確度。
from sklearn.metrics import accuracy_score
accuracy_score(ytest, y_model)
# 0.83333333333333337
即使是這個非常簡單的模型,我們發現數字分類的準確率約為 80%! 然而,這個單一的數字并沒有告訴我們哪里不對 - 一個很好的方式是使用混淆矩陣,我們可以用 Scikit-Learn 和 Seaborn 進行計算:
from sklearn.metrics import confusion_matrix
mat = confusion_matrix(ytest, y_model)
sns.heatmap(mat, square=True, annot=True, cbar=False)
plt.xlabel('predicted value')
plt.ylabel('true value');
這顯示了錯誤標記的點往往是什么:例如,這里的大量二被錯誤分類為一或者八。 獲取模型特征的直覺的另一種方法,是用預測的標簽再次繪制輸入。 我們將使用綠色標簽表示正確,紅色標簽表示不正確:
fig, axes = plt.subplots(10, 10, figsize=(8, 8),
subplot_kw={'xticks':[], 'yticks':[]},
gridspec_kw=dict(hspace=0.1, wspace=0.1))
test_images = Xtest.reshape(-1, 8, 8)
for i, ax in enumerate(axes.flat):
ax.imshow(test_images[i], cmap='binary', interpolation='nearest')
ax.text(0.05, 0.05, str(y_model[i]),
transform=ax.transAxes,
color='green' if (ytest[i] == y_model[i]) else 'red')
檢查這個數據的這個子集,我們可以深入了解,算法在哪里可能表現不是最好。 為了超過我們 80% 的分類準確率,我們可能會采用更復雜的算法,如支持向量機(參見支持向量機),隨機森林(參見決策樹和隨機森林)或其他分類方式。
總結
在本節中,我們已經介紹了 Scikit-Learn 數據表示的基本特征和估計器 API。 不管估計類型如何,都需要相同的導入/實例化/擬合/預測模式。 為了掌握有關估計 API 的信息,你可以瀏覽 Scikit-Learn 文檔,并開始在數據上嘗試各種模型。
在下一節中,我們將探討機器學習中最重要的主題:如何選擇和驗證你的模型。