監督學習算法——線性模型——《python機器學習基礎教程》

監督學習算法

常用的監督機器學習算法有:
1.K近鄰(kNN,k-NearestNeighbor)
2.線性模型
3.樸素貝葉斯(Naive Bayesian)
4.決策樹(Decision Tree)
5.決策樹集成
6.核支持向量機(SVM,Support Vector Machine)
7.神經網絡


線性模型

目錄

1. 用於迴歸的線性模型

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

3. 嶺迴歸

4. lasso

5. 用於分類的線性模型

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

7. 優點、缺點和參數


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

1. 用於迴歸的線性模型

對於迴歸問題,線性模型預測的一般公式如下:

ŷ=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:

import mglearn
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Ridge
mglearn.plots.plot_linear_regression_wave()
plt.show()


#輸出如下:
w[0]: 0.393906  b: -0.031804

圖 2-11:線性模型對 wave 數據集的預測結果

我們在圖中添加了座標網格,便於理解直線的含義。從 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
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_:", lr.coef_)
print("lr.intercept_:", lr.intercept_)
print("Training set score: {:.2f}".format(lr.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lr.score(X_test, y_test)))


#輸出如下:
lr.coef_: [0.39390555]
lr.intercept_: -0.031804343026759746
Training set score: 0.67
Test set score: 0.66

intercept_ 屬性是一個浮點數,而 coef_ 屬性是一個 NumPy 數組,每個元素對應一個輸入特徵。由於 wave 數據集中只有一個輸入特徵,所以 lr.coef_ 中只有一個元素。

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 要低很多:

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)
print("Training set score: {:.2f}".format(lr.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lr.score(X_test, y_test)))

#輸出如下:
Training set score: 0.95
Test set score: 0.61

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

3. 嶺迴歸

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

關於正則化的一點個人理解:

  • 正則化:使係數接近0,斜率更小,圖像趨於水平。
  • L2正則化:使所有係數接近0
  • L1正則化:使某些係數接近0

嶺迴歸在 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)))

#輸出如下:
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)))

#輸出如下:
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)))

#輸出如下:
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 中得到證實:

import mglearn
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Ridge
from sklearn.linear_model import LinearRegression
X, y = mglearn.datasets.load_extended_boston()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
from sklearn.linear_model import Ridge
lr = LinearRegression().fit(X_train, y_train)
ridge = Ridge().fit(X_train, y_train)
ridge10 = Ridge(alpha=10).fit(X_train, y_train)
ridge01 = Ridge(alpha=0.1).fit(X_train, y_train)
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")
xlims = plt.xlim()
plt.hlines(0, xlims[0], xlims[1])
plt.xlim(xlims)
plt.ylim(-25, 25)
plt.legend()
plt.show()

圖 2-12:不同 alpha 值的嶺迴歸與線性迴歸的係數比較

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

圖 2-13:嶺迴歸和線性迴歸在波士頓房價數據集上的學習曲線

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

4. lasso

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

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

import mglearn
from sklearn.model_selection import train_test_split
import numpy as np
X, y = mglearn.datasets.load_extended_boston()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
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:", np.sum(lasso.coef_ != 0))

#輸出如下:
Training set score: 0.29
Test set score: 0.21
Number of features used: 4

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

import mglearn
from sklearn.model_selection import train_test_split
import numpy as np
X, y = mglearn.datasets.load_extended_boston()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
from sklearn.linear_model import Lasso
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:", np.sum(lasso001.coef_ != 0))

#輸出如下:
Training set score: 0.90
Test set score: 0.77
Number of features used: 33

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

但如果把 alpha 設得太小,那麼就會消除正則化的效果,並出現過擬合,得到與LinearRegression 類似的結果:

import mglearn
from sklearn.model_selection import train_test_split
import numpy as np
X, y = mglearn.datasets.load_extended_boston()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
from sklearn.linear_model import Lasso
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:", np.sum(lasso00001.coef_ != 0))

#輸出如下:
Training set score: 0.95
Test set score: 0.64
Number of features used: 96

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

import mglearn
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Ridge
X, y = mglearn.datasets.load_extended_boston()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
from sklearn.linear_model import Lasso
ridge01 = Ridge(alpha=0.1).fit(X_train, y_train)
lasso = Lasso().fit(X_train, y_train)
lasso001 = Lasso(alpha=0.01, max_iter=100000).fit(X_train, y_train)
lasso00001 = Lasso(alpha=0.0001, max_iter=100000).fit(X_train, y_train)
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")
plt.show()

圖 2-14:不同 alpha 值的 lasso 迴歸與嶺迴歸的係數比較

在 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):

import mglearn
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
X, y = mglearn.datasets.make_forge()
fig, 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(clf.__class__.__name__)
    ax.set_xlabel("Feature 0")
    ax.set_ylabel("Feature 1")
axes[0].legend()
plt.show()

圖 2-15:線性 SVM 和 Logistic 迴歸在 forge 數據集上的決策邊界(均爲默認參數)

在這張圖中, 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()

圖 2-16:不同 C 值的線性 SVM 在 forge 數據集上的決策邊界

在左側的圖中, 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)))

# 輸出如下:
Training set score: 0.953
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)))
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)
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)))

#輸出如下:
Training set score: 0.969
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)))

#輸出如下:
Training set score: 0.934
Test set score: 0.930

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

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

import mglearn
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
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)
logreg100 = LogisticRegression(C=100).fit(X_train, y_train)
logreg001 = LogisticRegression(C=0.01).fit(X_train, y_train)
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)
xlims = plt.xlim()
plt.hlines(0, xlims[0], xlims[1])
plt.xlim(xlims)
plt.ylim(-5, 5)
plt.xlabel("Feature")
plt.ylabel("Coefficient magnitude")
plt.legend()
plt.show()

圖 2-17:不同 C 值的 Logistic 迴歸在乳腺癌數據集上學到的係數

由於 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)。

for C, marker in zip([0.001, 1, 100], ['o', '^', 'v']):
    lr_l1 = LogisticRegression(C=C, solver='liblinear', 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)
xlims = plt.xlim()
plt.hlines(0, xlims[0], xlims[1])
plt.xlim(xlims)
plt.xlabel("Feature")
plt.ylabel("Coefficient magnitude")

plt.ylim(-5, 5)
plt.legend(loc=3)


#輸出如下:
Training accuracy of l1 logreg with C=0.001: 0.91
Test accuracy of l1 logreg with C=0.001: 0.92
Training accuracy of l1 logreg with C=1.000: 0.96
Test accuracy of l1 logreg with C=1.000: 0.96
Training accuracy of l1 logreg with C=100.000: 0.99
Test accuracy of l1 logreg with C=100.000: 0.98

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

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

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

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

w[0]∗x[0]+w[1]∗x[1]+…+w[p]∗x[p]+bw[0]∗x[0]+w[1]∗x[1]+…+w[p]∗x[p]+b

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

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

import mglearn
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
from sklearn.datasets import load_breast_cancer
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"])
plt.show()

圖 2-19:包含 3 個類別的二維玩具數據集

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

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

#輸出如下:
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_,
                                  mglearn.cm3.colors):
    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))

圖 2-20:三個“一對其餘”分類器學到的決策邊界

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

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

X, y = make_blobs(random_state=42)
linear_svm = LinearSVC().fit(X, y)
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_,
                                  mglearn.cm3.colors):
    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")
plt.show()

圖 2-21:三個“一對其餘”分類器得到的多分類決策邊界

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 節會介紹幾個線性模型不適用的例子。

轉載於:https://www.cnblogs.com/HolyShine/p/11016263.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章