Python機器學習基礎教程-第2章-監督學習之線性模型

前言

本系列教程基本就是摘抄《Python機器學習基礎教程》中的例子內容。html

爲了便於跟蹤和學習,本系列教程在Github上提供了jupyter notebook 版本:python

Github倉庫:https://github.com/Holy-Shine/Introduciton-2-ML-with-Python-notebookgit

系列教程總目錄 Python機器學習基礎教程github

引子

先導入必要的包算法

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import mglearn

%matplotlib inline

線性模型是在實踐中普遍使用的一類模型,幾十年來被普遍研究,它能夠追溯到一百多年前。線性模型利用輸入特徵的線性函數(linear function)進行預測,稍後會對此進行解釋。數組

1. 用於迴歸的線性模型

對於迴歸問題,線性模型預測的通常公式以下:dom

$$ŷ = w[0] * x[0] + w[1] * x[1] + … + w[p] * x[p] + b$$機器學習

這裏 $x[0]$ 到 $x[p]$ 表示單個數據點的特徵(本例中特徵個數爲 $p+1$),$w$ 和 $b$ 是學習模型的 參數,$ŷ$ 是模型的預測結果。對於單一特徵的數據集,公式以下:函數

$$ŷ = w[0] * x[0] + b$$性能

你可能還記得,這就是高中數學裏的直線方程。這裏 $w[0]$ 是斜率,$b$ 是 $y$ 軸偏移。對於有 更多特徵的數據集,$w$ 包含沿每一個特徵座標軸的斜率。或者,你也能夠將預測的響應值看 做輸入特徵的加權求和,權重由 $w$ 的元素給出(能夠取負值)。 下列代碼能夠在一維 wave 數據集上學習參數 $w[0]$ 和 $b$:

mglearn.plots.plot_linear_regression_wave()

<center><img src="https://i.loli.net/2019/06/13/5d01e56b0668c23025.jpg" width=60%></center> <center><b>圖 2-11:線性模型對 wave 數據集的預測結果</b></center>

咱們在圖中添加了座標網格,便於理解直線的含義。從 w[0] 能夠看出,斜率應該在 0.4 左右,在圖像中也能夠直觀地確認這一點。截距是指預測直線與 y 軸的交點:比 0 略小,也能夠在圖像中確認。

用於迴歸的線性模型能夠表示爲這樣的迴歸模型:對單一特徵的預測結果是一條直線,兩個特徵時是一個平面,或者在更高維度(即更多特徵)時是一個超平面。

若是將直線的預測結果與上一章圖 2-10 中 KNeighborsRegressor 的預測結果進行比較,你會發現直線的預測能力很是受限。彷佛數據的全部細節都丟失了。從某種意義上來講,這種說法是正確的。假設目標 y 是特徵的線性組合,這是一個很是強的(也有點不現實的)假設。但觀察一維數據得出的觀點有些片面。對於有多個特徵的數據集而言,線性模型能夠很是強大。特別地,若是特徵數量大於訓練數據點的數量,任何目標 y 均可以(在訓練集上)用線性函數完美擬合。

有許多不一樣的線性迴歸模型。這些模型之間的區別在於如何從訓練數據中學習參數 w 和 b,以及如何控制模型複雜度。下面介紹最多見的線性迴歸模型。

2. 線性迴歸(又名普通最小二乘法)

線性迴歸,或者普通最小二乘法(ordinary least squares,OLS),是迴歸問題最簡單也最經典的線性方法。線性迴歸尋找參數 w 和 b,使得對訓練集的預測值與真實的迴歸目標值 y 之間的均方偏差最小。均方偏差(mean squared error)是預測值與真實值之差的平方和除以樣本數。線性迴歸沒有參數,這是一個優勢,但也所以沒法控制模型的複雜度。

下列代碼能夠生成圖 2-11 中的模型:

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

X, y = mglearn.datasets.make_wave(n_samples=60)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

lr = LinearRegression().fit(X_train, y_train)

「斜率」參數(w,也叫做權重或係數)被保存在 coef_ 屬性中,而偏移或截距(b)被保存在 intercept_ 屬性中:

print("lr.coef_: {}".format(lr.coef_))
print("lr.intercept_: {}".format(lr.intercept_))

[out]

lr.coef_: [0.39390555] lr.intercept_: -0.031804343026759746

你可能注意到了 coef_ 和 intercept_ 結尾處奇怪的下劃線。scikit-learn 老是將從訓練數據中得出的值保存在如下劃線結尾的屬性中。這是爲了將其與用戶設置的參數區分開。

intercept_ 屬性是一個浮點數,而 coef_ 屬性是一個 NumPy 數組,每一個元素對應一個輸入特徵。因爲 wave 數據集中只有一個輸入特徵,因此 lr.coef_ 中只有一個元素。咱們來看一下訓練集和測試集的性能:

print("Training set score: {:.2f}".format(lr.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lr.score(X_test, y_test)))

[out]

Training set score: 0.67 Test set score: 0.66

$R^2$ 約爲 0.66,這個結果不是很好,但咱們能夠看到,訓練集和測試集上的分數很是接近。這說明可能存在欠擬合,而不是過擬合。對於這個一維數據集來講,過擬合的風險很小,由於模型很是簡單(或受限)。然而,對於更高維的數據集(即有大量特徵的數據集),線性模型將變得更增強大,過擬合的可能性也會變大。咱們來看一下 LinearRegression 在更復雜的數據集上的表現,好比波士頓房價數據集。記住,這個數據集有 506 個樣本和 105個導出特徵。首先,加載數據集並將其分爲訓練集和測試集。而後像前面同樣構建線性迴歸模型:

X, y= mglearn.datasets.load_extended_boston()

X_train, X_test, y_train, y_test = train_test_split(X,y, random_state=0)
lr=LinearRegression().fit(X_train, y_train)

比較一下訓練集和測試集的分數就能夠發現,咱們在訓練集上的預測很是準確,但測試集上的 $R^2$ 要低不少:

print("Training set score: {:.2f}".format(lr.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lr.score(X_test, y_test)))

[out]

Training set score: 0.95 Test set score: 0.61

訓練集和測試集之間的性能差別是過擬合的明顯標誌,所以咱們應該試圖找到一個能夠控制複雜度的模型。標準線性迴歸最經常使用的替代方法之一就是嶺迴歸(ridge regression),下面來看一下。

3. 嶺迴歸

嶺迴歸也是一種用於迴歸的線性模型,所以它的預測公式與普通最小二乘法相同。但在嶺迴歸中,對係數(w)的選擇不只要在訓練數據上獲得好的預測結果,並且還要擬合附加約束。咱們還但願係數儘可能小。換句話說,w 的全部元素都應接近於 0。直觀上來看,這意味着每一個特徵對輸出的影響應儘量小(即斜率很小),同時仍給出很好的預測結果。這種約束是所謂正則化(regularization)的一個例子。正則化是指對模型作顯式約束,以免過擬合。嶺迴歸用到的這種被稱爲 L2 正則化。

嶺迴歸在 linear_model.Ridge 中實現。來看一下它對擴展的波士頓房價數據集的效果如何

from sklearn.linear_model import Ridge

ridge = Ridge().fit(X_train, y_train)
print("Training set score: {:.2f}".format(ridge.score(X_train, y_train)))
print("Test set score: {:.2f}".format(ridge.score(X_test, y_test)))

[out] :

Training set score: 0.89 Test set score: 0.75

能夠看出, Ridge 在訓練集上的分數要低於 LinearRegression ,但在測試集上的分數更高。這和咱們的預期一致。線性迴歸對數據存在過擬合。 Ridge 是一種約束更強的模型,因此更不容易過擬合。複雜度更小的模型意味着在訓練集上的性能更差,但泛化性能更好。因爲咱們只對泛化性能感興趣,因此應該選擇 Ridge 模型而不是 LinearRegression 模型。

Ridge 模型在模型的簡單性(係數都接近於 0)與訓練集性能之間作出權衡。簡單性和訓練集性能兩者對於模型的重要程度能夠由用戶經過設置 alpha 參數來指定。在前面的例子中,咱們用的是默認參數 alpha=1.0 。但沒有理由認爲這會給出最佳權衡。 alpha 的最佳設定值取決於用到的具體數據集。增大 alpha 會使得係數更加趨向於 0,從而下降訓練集性能,但可能會提升泛化性能。例如:

ridge10=Ridge(alpha=10).fit(X_train, y_train)
print("Training set score: {:.2f}".format(ridge10.score(X_train, y_train)))
print("Test set score: {:.2f}".format(ridge10.score(X_test, y_test)))

[out]

Training set score: 0.79 Test set score: 0.64

減少 alpha 可讓係數受到的限制更小。對於很是小的 alpha 值,係數幾乎沒有受到限制,咱們獲得一個與 LinearRegression 相似的模型:

ridge01 = Ridge(alpha=0.1).fit(X_train, y_train)
print("Training set score: {:.2f}".format(ridge01.score(X_train, y_train)))
print("Test set score: {:.2f}".format(ridge01.score(X_test, y_test)))

[out]

Training set score: 0.93 Test set score: 0.77

這裏 alpha=0.1 彷佛效果不錯。咱們能夠嘗試進一步減少 alpha 以提升泛化性能。第 5 章將會討論選擇參數的正確方法。

咱們還能夠查看 alpha 取不一樣值時模型的 coef_ 屬性,從而更加定性地理解 alpha 參數是如何改變模型的。更大的 alpha 表示約束更強的模型,因此咱們預計大 alpha 對應的 coef_ 元素比小 alpha 對應的 coef_ 元素要小。這一點能夠在圖 2-12 中獲得證明:

plt.plot(ridge.coef_, 's', label="Ridge alpha=1")
plt.plot(ridge10.coef_, '^', label="Ridge alpha=10")
plt.plot(ridge01.coef_, 'v', label="Ridge alpha=0.1")
plt.plot(lr.coef_, 'o', label="LinearRegression")
plt.xlabel("Coefficient index")
plt.ylabel("Coefficient magnitude")
plt.hlines(0, 0, len(lr.coef_))
plt.ylim(-25, 25)
plt.legend()

<center><img src="https://i.loli.net/2019/06/13/5d01e56b2feaf87934.jpg" width=60%></center> <center><b>圖 2-12:不一樣 alpha 值的嶺迴歸與線性迴歸的係數比較</b></center>

這裏 x 軸對應 coef_ 的元素: x=0 對應第一個特徵的係數, x=1 對應第二個特徵的係數,以此類推,一直到 x=100 。y 軸表示該係數的具體數值。這裏須要記住的是,對於 alpha=10 ,係數大多在 -3 和 3 之間。對於 alpha=1 的 Ridge 模型,係數要稍大一點。對於 alpha=0.1 ,點的範圍更大。對於沒有作正則化的線性迴歸(即 alpha=0 ),點的範圍很大,許多點都超出了圖像的範圍。

還有一種方法能夠用來理解正則化的影響,就是固定 alpha 值,但改變訓練數據量。對於圖 2-13 來講,咱們對波士頓房價數據集作二次抽樣,並在數據量逐漸增長的子數據集上分別對 LinearRegression 和 Ridge(alpha=1) 兩個模型進行評估(將模型性能做爲數據集大小的函數進行繪圖,這樣的圖像叫做學習曲線):

mglearn.plots.plot_ridge_n_samples()

<center><img src="https://i.loli.net/2019/06/13/5d01e56b1730438899.jpg" width=60%></center> <center><b>圖 2-13:嶺迴歸和線性迴歸在波士頓房價數據集上的學習曲線</b></center>

正如所預計的那樣,不管是嶺迴歸仍是線性迴歸,全部數據集大小對應的訓練分數都要高於測試分數。因爲嶺迴歸是正則化的,所以它的訓練分數要總體低於線性迴歸的訓練分數。但嶺迴歸的測試分數要更高,特別是對較小的子數據集。若是少於 400 個數據點,線性迴歸學不到任何內容。隨着模型可用的數據愈來愈多,兩個模型的性能都在提高,最終線性迴歸的性能追上了嶺迴歸。這裏要記住的是,若是有足夠多的訓練數據,正則化變得不那麼重要,而且嶺迴歸和線性迴歸將具備相同的性能(在這個例子中,兩者相同剛好發生在整個數據集的狀況下,這只是一個巧合)。圖 2-13 中還有一個有趣之處,就是線性迴歸的訓練性能在降低。若是添加更多數據,模型將更加難以過擬合或記住全部的數據。

4. lasso

除了 Ridge ,還有一種正則化的線性迴歸是 Lasso 。與嶺迴歸相同,使用 lasso 也是約束係數使其接近於 0,但用到的方法不一樣,叫做 L1 正則化。L1 正則化的結果是,使用 lasso 時某些係數恰好爲 0。這說明某些特徵被模型徹底忽略。這能夠看做是一種自動化的特徵選擇。某些係數恰好爲 0,這樣模型更容易解釋,也能夠呈現模型最重要的特徵。

咱們將 lasso 應用在擴展的波士頓房價數據集上:

from sklearn.linear_model import Lasso

lasso = Lasso().fit(X_train, y_train)
print("Training set score: {:.2f}".format(lasso.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lasso.score(X_test, y_test)))
print("Number of features used: {}".format(np.sum(lasso.coef_ != 0)))
print("Number of all feature: {}".format(lasso.coef_.shape[0]))

[out]

Training set score: 0.29 Test set score: 0.21 Number of features used: 4 Number of all feature: 104

如你所見, Lasso 在訓練集與測試集上的表現都不好。這表示存在欠擬合,咱們發現模型只用到了 105 個特徵中的 4 個。與 Ridge 相似, Lasso 也有一個正則化參數 alpha ,能夠控制係數趨向於 0 的強度。在上一個例子中,咱們用的是默認值 alpha=1.0 。爲了下降欠擬合,咱們嘗試減少 alpha 。這麼作的同時,咱們還須要增長 max_iter 的值(運行迭代的最大次數):

# 咱們增大max_iter的值,不然模型會警告咱們,說應該增大max_iter
lasso001 = Lasso(alpha=0.01, max_iter=100000).fit(X_train, y_train)
print("Training set score: {:.2f}".format(lasso001.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lasso001.score(X_test, y_test)))
print("Number of features used: {}".format(np.sum(lasso001.coef_ != 0)))

[out]

Training set score: 0.90 Test set score: 0.77 Number of features used: 33

alpha 值變小,咱們能夠擬合一個更復雜的模型,在訓練集和測試集上的表現也更好。模型性能比使用 Ridge 時略好一點,並且咱們只用到了 105 個特徵中的 33 個。這樣模型可能更容易理解。

但若是把 alpha 設得過小,那麼就會消除正則化的效果,並出現過擬合,獲得與LinearRegression 相似的結果:

lasso00001 = Lasso(alpha=0.0001, max_iter=100000).fit(X_train, y_train)
print("Training set score: {:.2f}".format(lasso00001.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lasso00001.score(X_test, y_test)))
print("Number of features used: {}".format(np.sum(lasso00001.coef_ != 0)))

[out]

Training set score: 0.95 Test set score: 0.64 Number of features used: 96

再次像圖 2-12 那樣對不一樣模型的係數進行做圖,見圖 2-14:

plt.plot(lasso.coef_, 's', label="Lasso alpha=1")
plt.plot(lasso001.coef_, '^', label="Lasso alpha=0.01")
plt.plot(lasso00001.coef_, 'v', label="Lasso alpha=0.0001")
plt.plot(ridge01.coef_, 'o', label="Ridge alpha=0.1")
plt.legend(ncol=2, loc=(0, 1.05))
plt.ylim(-25, 25)
plt.xlabel("Coefficient index")
plt.ylabel("Coefficient magnitude")

<center><img src="https://i.loli.net/2019/06/13/5d01e56b28f7022674.jpg" width=60%></center> <center><b>圖 2-14:不一樣 alpha 值的 lasso 迴歸與嶺迴歸的係數比較</b></center>

在 alpha=1 時,咱們發現不只大部分系數都是 0(咱們已經知道這一點),並且其餘係數也都很小。將 alpha 減少至 0.01 ,咱們獲得圖中向上的三角形,大部分特徵等於 0。alpha=0.0001 時,咱們獲得正則化很弱的模型,大部分系數都不爲 0,而且還很大。爲了便於比較,圖中用圓形表示 Ridge 的最佳結果。 alpha=0.1 的 Ridge 模型的預測性能與alpha=0.01 的 Lasso 模型相似,但 Ridge 模型的全部係數都不爲 0。

在實踐中,在兩個模型中通常首選嶺迴歸。但若是特徵不少,你認爲只有其中幾個是重要的,那麼選擇 Lasso 可能更好。一樣,若是你想要一個容易解釋的模型, Lasso 能夠給出更容易理解的模型,由於它只選擇了一部分輸入特徵。 scikit-learn 還提供了 ElasticNet類,結合了 Lasso 和 Ridge 的懲罰項。在實踐中,這種結合的效果最好,不過代價是要調節兩個參數:一個用於 L1 正則化,一個用於 L2 正則化。

5. 用於分類的線性模型

線性模型也普遍應用於分類問題。咱們首先來看二分類。這時能夠利用下面的公式進行 預測:

$$ŷ = w[0] * x[0] + w[1] * x[1] + …+ w[p] * x[p] + b > 0$$

這個公式看起來與線性迴歸的公式很是類似,但咱們沒有返回特徵的加權求和,而是爲預測設置了閾值(0)。若是函數值小於 0,咱們就預測類別 -1;若是函數值大於 0,咱們就預測類別 +1。對於全部用於分類的線性模型,這個預測規則都是通用的。一樣,有不少種不一樣的方法來找出係數(w)和截距(b)。

對於用於迴歸的線性模型,輸出 ŷ 是特徵的線性函數,是直線、平面或超平面(對於更高維的數據集)。對於用於分類的線性模型,決策邊界是輸入的線性函數。換句話說,(二元)線性分類器是利用直線、平面或超平面來分開兩個類別的分類器。本節咱們將看到這方面的例子。

學習線性模型有不少種算法。這些算法的區別在於如下兩點:

  • 係數和截距的特定組合對訓練數據擬合好壞的度量方法;
  • 是否使用正則化,以及使用哪一種正則化方法。

不一樣的算法使用不一樣的方法來度量「對訓練集擬合好壞」。因爲數學上的技術緣由,不可能調節 w 和 b 使得算法產生的誤分類數量最少。對於咱們的目的,以及對於許多應用而言,上面第一點(稱爲損失函數)的選擇並不重要。

最多見的兩種線性分類算法是 Logistic 迴歸(logistic regression)和線性支持向量機(linear support vector machine,線性 SVM),前者在 linear_model.LogisticRegression 中實現,後者在 svm.LinearSVC (SVC 表明支持向量分類器)中實現。雖然 LogisticRegression的名字中含有迴歸(regression),但它是一種分類算法,並非迴歸算法,不該與LinearRegression 混淆。

咱們能夠將 LogisticRegression 和 LinearSVC 模型應用到 forge 數據集上,並將線性模型找到的決策邊界可視化(圖 2-15):

from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC

X, y = mglearn.datasets.make_forge()

figs, axes = plt.subplots(1,2, figsize=(10,3))

for model, ax in zip([LinearSVC(), LogisticRegression()], axes):
    clf = model.fit(X,y)
    mglearn.plots.plot_2d_separator(clf, X, fill=False, eps=0.5, ax=ax, alpha=.7)
    mglearn.discrete_scatter(X[:,0], X[:,1],y, ax=ax)
    ax.set_title("{}".format(clf.__class__.__name__))
    ax.set_xlabel("Feature 0")
    ax.set_ylabel("Feature 1")
axes[0].legend()

<center><img src="https://i.loli.net/2019/06/13/5d01e56b1a84b68821.jpg" width=80%></center> <center><b>圖 2-15:線性 SVM 和 Logistic 迴歸在 forge 數據集上的決策邊界(均爲默認參數)</b></center>

在這張圖中, forge 數據集的第一個特徵位於 x 軸,第二個特徵位於 y 軸,與前面相同。圖中分別展現了 LinearSVC 和 LogisticRegression 獲得的決策邊界,都是直線,將頂部歸爲類別 1 的區域和底部歸爲類別 0 的區域分開了。換句話說,對於每一個分類器而言,位於黑線上方的新數據點都會被劃爲類別 1,而在黑線下方的點都會被劃爲類別 0。

兩個模型獲得了類似的決策邊界。注意,兩個模型中都有兩個點的分類是錯誤的。兩個模型都默認使用 L2 正則化,就像 Ridge 對迴歸所作的那樣。

對於 LogisticRegression 和 LinearSVC ,決定正則化強度的權衡參數叫做 C 。 C 值越大,對應的正則化越弱。換句話說,若是參數 C 值較大,那麼 LogisticRegression 和LinearSVC 將盡量將訓練集擬合到最好,而若是 C 值較小,那麼模型更強調使係數向量(w)接近於 0。參數 C 的做用還有另外一個有趣之處。較小的 C 值可讓算法儘可能適應「大多數」數據點,而較大的 C 值更強調每一個數據點都分類正確的重要性。下面是使用 LinearSVC 的圖示(圖 2-16):

mglearn.plots.plot_linear_svc_regularization()

<center><img src="https://i.loli.net/2019/06/13/5d01e56b2765b66381.jpg" width=80%></center> <center><b>圖 2-16:不一樣 C 值的線性 SVM 在 forge 數據集上的決策邊界</b></center>

在左側的圖中, C 值很小,對應強正則化。大部分屬於類別 0 的點都位於底部,大部分屬於類別 1 的點都位於頂部。強正則化的模型會選擇一條相對水平的線,有兩個點分類錯誤。在中間的圖中, C 值稍大,模型更關注兩個分類錯誤的樣本,使決策邊界的斜率變大。最後,在右側的圖中,模型的 C 值很是大,使得決策邊界的斜率也很大,如今模型對類別 0 中全部點的分類都是正確的。類別 1 中仍有一個點分類錯誤,這是由於對這個數據集來講,不可能用一條直線將全部點都分類正確。右側圖中的模型儘可能使全部點的分類都正確,但可能沒法掌握類別的總體分佈。換句話說,這個模型極可能過擬合。

與迴歸的狀況相似,用於分類的線性模型在低維空間中看起來可能很是受限,決策邊界只能是直線或平面。一樣,在高維空間中,用於分類的線性模型變得很是強大,當考慮更多特徵時,避免過擬合變得愈來愈重要。

咱們在乳腺癌數據集上詳細分析 LogisticRegression :

from sklearn.datasets import load_breast_cancer
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)
logreg = LogisticRegression().fit(X_train, y_train)
print("Training set score: {:.3f}".format(logreg.score(X_train, y_train)))
print("Test set score: {:.3f}".format(logreg.score(X_test, y_test)))

[out]

Training set score: 0.955 Test set score: 0.958

C=1 的默認值給出了至關好的性能,在訓練集和測試集上都達到 95% 的精度。但因爲訓練集和測試集的性能很是接近,因此模型極可能是欠擬合的。咱們嘗試增大 C 來擬合一個更靈活的模型:

logreg100 = LogisticRegression(C=100).fit(X_train, y_train)
print("Training set score: {:.3f}".format(logreg100.score(X_train, y_train)))
print("Test set score: {:.3f}".format(logreg100.score(X_test, y_test)))

[out]

Training set score: 0.972 Test set score: 0.965

使用 C=100 能夠獲得更高的訓練集精度,也獲得了稍高的測試集精度,這也證明了咱們的直覺,即更復雜的模型應該性能更好。 咱們還能夠研究使用正則化更強的模型時會發生什麼。設置 C=0.01 :

logreg001 = LogisticRegression(C=0.01).fit(X_train, y_train)
print("Training set score: {:.3f}".format(logreg001.score(X_train, y_train)))
print("Test set score: {:.3f}".format(logreg001.score(X_test, y_test)))

[out]

Training set score: 0.934 Test set score: 0.930

正如咱們所料,在圖 2-1 中將已經欠擬合的模型繼續向左移動,訓練集和測試集的精度都比採用默認參數時更小。

最後,來看一下正則化參數 C 取三個不一樣的值時模型學到的係數(圖 2-17):

plt.plot(logreg.coef_.T, 'o', label="C=1")
plt.plot(logreg100.coef_.T, '^', label="C=100")
plt.plot(logreg001.coef_.T, 'v', label="C=0.001")
plt.xticks(range(cancer.data.shape[1]), cancer.feature_names, rotation=90)
plt.hlines(0, 0, cancer.data.shape[1])
plt.ylim(-5, 5)
plt.xlabel("Coefficient index")
plt.ylabel("Coefficient magnitude")
plt.legend()

<center><img src="https://i.loli.net/2019/06/13/5d01e56b2ab2b85354.jpg" width=60%></center> <center><b>圖 2-17:不一樣 C 值的 Logistic 迴歸在乳腺癌數據集上學到的係數</b></center>

因爲 LogisticRegression 默認應用 L2 正則化,因此其結果與圖 2-12 中 Ridge 的結果相似。更強的正則化使得係數更趨向於 0,但係數永遠不會正好等於 0。進一步觀察圖像,還能夠在第 3 個係數那裏發現有趣之處,這個係數是「平均周長」(mean perimeter)。C=100 和 C=1 時,這個係數爲負,而C=0.001 時這個係數爲正,其絕對值比 C=1 時還要大。在解釋這樣的模型時,人們可能會認爲,係數能夠告訴咱們某個特徵與哪一個類別有關。例如,人們可能會認爲高「紋理錯誤」(texture error)特徵與「惡性」樣本有關。但「平均周長」係數的正負號發生變化,說明較大的「平均周長」能夠被看成「良性」的指標或「惡性」的指標,具體取決於咱們考慮的是哪一個模型。這也說明,對線性模型係數的解釋應該始終持保留態度。

若是想要一個可解釋性更強的模型,使用 L1 正則化可能更好,由於它約束模型只使用少 數幾個特徵。下面是使用 L1 正則化的係數圖像和分類精度(圖 2-18)。

<center><img src="https://i.loli.net/2019/06/13/5d01e56b2e14546796.jpg" width=60%></center> <center><b>圖 2-18:對於不一樣的 C 值,L1 懲罰的 Logistic 迴歸在乳腺癌數據集上學到的係數</b></center>

for C, marker in zip([0.001, 1, 100], ['o', '^', 'v']):
    lr_l1 = LogisticRegression(C=C, penalty="l1").fit(X_train, y_train)
    print("Training accuracy of l1 logreg with C={:.3f}: {:.2f}".format(
    C, lr_l1.score(X_train, y_train)))
    print("Test accuracy of l1 logreg with C={:.3f}: {:.2f}".format(
    C, lr_l1.score(X_test, y_test)))
    plt.plot(lr_l1.coef_.T, marker, label="C={:.3f}".format(C))
plt.xticks(range(cancer.data.shape[1]), cancer.feature_names, rotation=90)
plt.hlines(0, 0, cancer.data.shape[1])
plt.xlabel("Coefficient index")
plt.ylabel("Coefficient magnitude")
plt.ylim(-5, 5)
plt.legend(loc=3)

如你所見,用於二分類的線性模型與用於迴歸的線性模型有許多類似之處。與用於迴歸的線性模型同樣,模型的主要差異在於 penalty 參數,這個參數會影響正則化,也會影響模型是使用全部可用特徵仍是隻選擇特徵的一個子集。

6. 用於多分類的線性模型

許多線性分類模型只適用於二分類問題,不能輕易推廣到多類別問題(除了 Logistic 迴歸)。將二分類算法推廣到多分類算法的一種常見方法是「一對其他」(one-vs.-rest)方法。在「一對其他」方法中,對每一個類別都學習一個二分類模型,將這個類別與全部其餘類別儘可能分開,這樣就生成了與類別個數同樣多的二分類模型。在測試點上運行全部二類分類器來進行預測。在對應類別上分數最高的分類器「勝出」,將這個類別標籤返回做爲預測結果。

每一個類別都對應一個二類分類器,這樣每一個類別也都有一個係數(w)向量和一個截距(b)。下面給出的是分類置信方程,其結果中最大值對應的類別即爲預測的類別標籤:

$$ w[0] * x[0] + w[1] * x[1] + … + w[p] * x[p] + b$$

多分類 Logistic 迴歸背後的數學與「一對其他」方法稍有不一樣,但它也是對每一個類別都有一個係數向量和一個截距,也使用了相同的預測方法。

咱們將「一對其他」方法應用在一個簡單的三分類數據集上。咱們用到了一個二維數據集,每一個類別的數據都是從一個高斯分佈中採樣得出的(見圖 2-19):

from sklearn.datasets import make_blobs

X, y=make_blobs(random_state=42)
mglearn.discrete_scatter(X[:,0],X[:,1],y)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
plt.legend(["Class 0", "Class 1", "Class 2"])

<center><img src="https://i.loli.net/2019/06/13/5d01e56b18d7321180.jpg" width=60%></center> <center><b>圖 2-19:包含 3 個類別的二維玩具數據集</b></center>

如今,在這個數據集上訓練一個 LinearSVC 分類器:

linear_svm = LinearSVC().fit(X, y)
print("Coefficient shape: ", linear_svm.coef_.shape)
print("Intercept shape: ", linear_svm.intercept_.shape)

[out]

Coefficient shape: (3, 2) Intercept shape: (3,)

們看到, coef_ 的形狀是 (3, 2) ,說明 coef_ 每行包含三個類別之一的係數向量,每列包含某個特徵(這個數據集有 2 個特徵)對應的係數值。如今 intercept_ 是一維數組,保存每一個類別的截距。

咱們將這 3 個二類分類器給出的直線可視化(圖 2-20):

mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
line = np.linspace(-15, 15)
for coef, intercept, color in zip(linear_svm.coef_, linear_svm.intercept_, ['b','r','g']):
    plt.plot(line, -(line*coef[0]+intercept)/coef[1], c=color)
plt.ylim(-10,15)
plt.xlim(-10, 8)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
plt.legend(['Class 0', 'Class 1', 'Class 2', 'Line class 0', 'Line class 1',
'Line class 2'], loc=(1.01, 0.3))

<center><img src="https://i.loli.net/2019/06/13/5d01e56b2c5eb15132.jpg" width=80%></center> <center><b>圖 2-20:三個「一對其他」分類器學到的決策邊界</b></center>

你能夠看到,訓練集中全部屬於類別 0 的點都在與類別 0 對應的直線上方,這說明它們位於這個二類分類器屬於「類別 0」的那一側。屬於類別 0 的點位於與類別 2 對應的直線上方,這說明它們被類別 2 的二類分類器劃爲「其他」。屬於類別 0 的點位於與類別 1 對應的直線左側,這說明類別 1 的二元分類器將它們劃爲「其他」。所以,這一區域的全部點都會被最終分類器劃爲類別 0(類別 0 的分類器的分類置信方程的結果大於 0,其餘兩個類別對應的結果都小於 0)。

但圖像中間的三角形區域屬於哪個類別呢,3 個二類分類器都將這一區域內的點劃爲「其他」。這裏的點應該劃歸到哪個類別呢?答案是分類方程結果最大的那個類別,即最接近的那條線對應的類別。下面的例子(圖 2-21)給出了二維空間中全部區域的預測結果:

mglearn.plots.plot_2d_classification(linear_svm, X, fill=True, alpha=.7)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
line = np.linspace(-15, 15)
for coef, intercept, color in zip(linear_svm.coef_, linear_svm.intercept_,
['b', 'r', 'g']):
    plt.plot(line, -(line * coef[0] + intercept) / coef[1], c=color)
plt.legend(['Class 0', 'Class 1', 'Class 2', 'Line class 0', 'Line class 1',
'Line class 2'], loc=(1.01, 0.3))
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")

<center><img src="https://i.loli.net/2019/06/13/5d01e8313540f37143.jpg" width=80%></center> <center><b>圖 2-21:三個「一對其他」分類器獲得的多分類決策邊界</b></center>

7. 優勢、缺點和參數

線性模型的主要參數是正則化參數,在迴歸模型中叫做 alpha ,在 LinearSVC 和 Logistic-Regression 中叫做 C 。 alpha 值較大或 C 值較小,說明模型比較簡單。特別是對於迴歸模型而言,調節這些參數很是重要。一般在對數尺度上對 C 和 alpha 進行搜索。你還須要肯定的是用 L1 正則化仍是 L2 正則化。若是你假定只有幾個特徵是真正重要的,那麼你應該用L1 正則化,不然應默認使用 L2 正則化。若是模型的可解釋性很重要的話,使用 L1 也會有幫助。因爲 L1 只用到幾個特徵,因此更容易解釋哪些特徵對模型是重要的,以及這些特徵的做用。

線性模型的訓練速度很是快,預測速度也很快。這種模型能夠推廣到很是大的數據集,對稀疏數據也頗有效。若是你的數據包含數十萬甚至上百萬個樣本,你可能須要研究如何使用 LogisticRegression 和 Ridge 模型的 solver='sag' 選項,在處理大型數據時,這一選項比默認值要更快。其餘選項還有 SGDClassifier 類和 SGDRegressor 類,它們對本節介紹的線性模型實現了可擴展性更強的版本。

線性模型的另外一個優勢在於,利用咱們之間見過的用於迴歸和分類的公式,理解如何進行預測是相對比較容易的。不幸的是,每每並不徹底清楚係數爲何是這樣的。若是你的數據集中包含高度相關的特徵,這一問題尤其突出。在這種狀況下,可能很難對係數作出解釋。

若是特徵數量大於樣本數量,線性模型的表現一般都很好。它也經常使用於很是大的數據集,只是由於訓練其餘模型並不可行。但在更低維的空間中,其餘模型的泛化性能可能更好。2.3.7 節會介紹幾個線性模型不適用的例子。

相關文章
相關標籤/搜索