線性模型的sklearn應用


線性模型利用輸入特徵的線性函數(linear function)進行預測。

一. linear models for regression

用於迴歸的線性模型:對單一特徵的預測結果是一條直線,兩個特徵時是一個平面,更高維度(即更多特徵)時是一個超平面。
假設目標y 是特徵的線性組合,這是一個非常強的(也有點不現實的)假設。對於有多個特徵的數據集而言,線性模型可以非常強大(過擬合的可能性也會變大)。特別地,如果特徵數量大於訓練數據點的數量,任何目標y 都可以(在訓練集上)用線性函數完美擬合。

1.線性迴歸 linear regression

also (ordinary least squares,OLS,普通最小二乘法)
目標:使得對訓練集的預測值與真實的目標值y之間的均方誤差最小。
均方誤差(mean squared error):預測值與真實值之差的平方和除以樣本數。
特點:線性迴歸沒有參數,這是一個優點,但也因此無法控制模型的複雜度。
一般格式:

from sklearn.linear_model import LinearRegression

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
# 實例化模型並擬合訓練集,得到擬合後的模型
lr = LinearRegression().fit(X_train, y_train)
# 查看模型參數  
lr.coef_       # 係數W
lr.intercept_  # 截距或偏移b
# 查看擬合優度
lr.score(X_train,y_train)  # 訓練集的R方
lr.score(X_test, y_test)   # 測試集的R方
  1. lr.coef_ ,lr.intercept_:結尾處奇怪的下劃線。scikit-learn總是將從訓練數據中得出的值保存在以下劃線結尾的屬性中。爲了與用戶設置的參數區分開。
  2. 訓練集和測試集上的分數非常接近。這說明可能存在欠擬合,而不是過擬合。
應用:波士頓房價數據集

506 個樣本和13個特徵。

from sklearn.datasets import load_boston  # 函數名
from sklearn.model_selection import train_test_split

data = load_boston()
X_train,X_test,y_train,y_test = train_test_split(data.data,data.target,random_state=42)
data.keys()
print(data.DESCR)
## 擬合模型,查看參數
lr = LinearRegression().fit(X_train, y_train)  
print(lr.score(X_train,y_train))  # 0.7480872598623441
print(lr.score(X_test, y_test))   # 0.6844267283527123
print(lr.coef_)  # 13個特徵的係數組成的array
print(lr.intercept_)

在這裏插入圖片描述
過擬合的明顯標誌:訓練集和測試集之間的性能差別較大,
一種解決方法:找到一個可以控制複雜度的模型。例如嶺迴歸和lasso

2. 嶺迴歸 ridge regression

標準線性迴歸最常用的替代方法之一,適用於明顯過擬合的迴歸。
產生結果:測試集上擬合效果明顯增加,訓練集上效果略低。

特點: 嶺迴歸中,對係數(w)的選擇不僅要擬合訓練數據,還要擬合附加約束,還希望係數儘量小。即:w 的所有元素都應接近於0(即模型更簡單,所以更不容易過擬合)。
直觀上來看,這意味着每個特徵對輸出的影響應儘可能小(即斜率很小),同時仍給出很好的預測結果。這種約束是正則化的一個例子。

正則化(regularization): 對模型做顯式約束,降低模型複雜度,以避免過擬合。(約束模型,讓模型不那麼複雜,是對模型複雜的懲罰)

嶺迴歸用到的這種被稱爲L2 正則化
從數學的觀點來看,Ridge 懲罰了係數的L2 範數或w 的歐式長度。

原理:Ridge 是一種約束更強的模型,所以更不容易過擬合。複雜度更小的模型意味着在訓練集上的性能更差,但泛化性能更好。 如果我們只對泛化性能感興趣,所以應該選擇Ridge 模型而不是LinearRegression 模型。

應用

仍使用上述數據集

from sklearn.linear_model import Ridge

ridge = Ridge().fit(X_train,y_train)
print(ridge.score(X_train,y_train))  # 0.7461161787884156
print(ridge.score(X_test, y_test))   # 0.6789748327846077

Ridge 模型可以人爲在模型的簡單性與訓練集性能之間做出權衡。通過設置alpha 參數。

alpha 參數:簡單性和訓練集性能二者對於模型的重要程度。
默認alpha=1.0。alpha 的最佳設定取決於具體數據集。
增大alpha 會增強正則化的約束,避免過擬合,使得係數更加趨向於0,降低訓練集性能, 但可能 會提高泛化性能,只是可能。
對於非常小的alpha 值,係數幾乎沒有受到限制,會得到與LinearRegression 類似的模型:沒有做正則化的線性迴歸(即alpha=0)

調整alpha:

## alpha=10
ridge10 = Ridge(alpha=10).fit(X_train,y_train)  # 增大alpha使得嶺迴歸的約束效果更強
print(ridge10.score(X_train,y_train))  # 0.7398240895568371
print(ridge10.score(X_test, y_test))   # 0.6724237562438147

## alpha=0.1
ridge01 = Ridge(alpha=0.1).fit(X_train,y_train)  # 增大alpha使得嶺迴歸的約束效果更強
print(ridge01.score(X_train,y_train))  # 0.7480303017255328
print(ridge01.score(X_test, y_test))   # 0.6838049959091365
數據量對模型性能的影響

將模型性能作爲數據集大小的函數進行繪圖,這樣的圖像叫作學習曲線

由於嶺迴歸是正則化的,因此訓練分數要整體低於線性迴歸,但嶺迴歸的測試分數更高,特別是對較小的子數據集。如果少於400 個數據點,線性迴歸學不到任何內容。隨着模型可用的數據越來越多,兩個模型的性能都在提升,最終線性迴歸的性能可以追上嶺迴歸。
All in all,如果有足夠多的訓練數據,正則化變得不那麼重要,嶺迴歸和線性迴歸將具有相同的性能。(因爲如果添加更多數據,模型將更加難以過擬合或記住所有的數據)

3. Lasso

使用L1 正則化來約束係數使其接近於0。
L1 正則化的結果是,使用lasso 時某些係數剛好爲0。這說明某些特徵被模型完全忽略。
可以看作是一種自動化的特徵選擇:模型更容易解釋,也可以呈現模型最重要的特徵。

由於用到的特徵數少,所以訓練集和測試集性能都低於前兩種模型。

from sklearn.linear_model import Lasso

lasso = Lasso().fit(X_train,y_train)
print(lasso.score(X_train,y_train))  # 0.6948040743556284
print(lasso.score(X_test, y_test))   # 0.6516957380017043
print(np.sum(lasso.coef_ != 0))  # 用到了幾個特徵  10
  1. 正則化參數 alpha:表示約束的程度,控制係數趨於0的強度。默認alpha=1.0。
    α 越小越接近普通迴歸。欠擬合時減小 α,過擬合時增大。
  2. 參數 max_iter:迭代的最大次數

調整參數:

# alpha變小,模型變複雜,更接近於普通迴歸
lasso001 = Lasso(alpha=0.01,max_iter=100000).fit(X_train,y_train)  
print(lasso001.score(X_train,y_train))  # 0.7476750179795366
print(lasso001.score(X_test, y_test))   # 0.6828293208366181
print(np.sum(lasso001.coef_ != 0))      # 13

Conclusion:
實踐中,一般首選嶺迴歸。
何時考慮Lasso?

  1. 特徵很多,你認爲只有幾個是重要的,選擇Lasso 可能更好。
  2. 想要一個容易解釋的模型,因爲Lasso只選擇了一部分輸入特徵(根本原因是使用了L1正則化)。

scikit-learn 還提供了ElasticNet類,結合了Lasso 和Ridge 的懲罰項。在實踐中,這種結合的效果最好,不過代價是要調節兩個參數:一個用於L1 正則化,一個用於L2 正則化。

二. linear models for classification

二分類:ŷ = w[0] * x[0] + w[1] * x[1] + …+ w[p] * x[p] + b > 0
前面類似線性迴歸公式,但沒有返回特徵的加權求和,而是爲預測設置了閾值0。如果函數值小於0,預測類別-1;
如果函數值大於0,預測類別+1。
對於所有用於分類的線性模型,這個預測規則都是通用的。

迴歸與分類的模型區別:對於用於迴歸的線性模型,輸出ŷ 是特徵的線性函數,是直線、平面或超平面(對於更高維的數據集)。對於用於分類的線性模型,決策邊界是輸入的線性函數。換句話說,(二元)線性分類器是利用直線、平面或超平面來分開兩個類別的分類器。

最常見的兩種線性分類算法

  1. Logistic 迴歸(logistic regression):在linear_model.LogisticRegression 中實現。雖然名字中含有regression,但它是一種分類算法。
  2. 線性支持向量機(linear support vector machine, 線性SVM), 後者在svm.LinearSVC(SVC 代表支持向量分類器)中實現。

兩個模型都默認使用L2 正則化,就像Ridge 迴歸。
決定正則化強度的參數:C。
C 值越大,正則化越弱,模型將儘可能擬合訓練集,強調每個數據點都分類正確的重要性,但可能無法掌握類別的整體分佈,有過擬合的傾向,避免欠擬合。
C 值越小,正則化越強,模型更強調使係數向量(w)接近於0,儘量適應“大多數”數據點,避免過擬合,但是可能欠擬合。

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

與迴歸類似,用於分類的線性模型在低維空間中可能非常受限,在高維空間中,會變得非常強大。注意:當考慮更多特徵時,避免過擬合變得越來越重要。

1. Logistic迴歸-乳腺癌數據集
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(logreg.score(X_train,y_train))  # 0.9553990610328639
print(logreg.score(X_test,y_test))    # 0.958041958041958

C=1 的默認值給出了相當好的性能,但由於訓練集和測試集的性能非常接近,所以模型很可能是欠擬合的。增大C試試:

## C=100
logreg100 = LogisticRegression(C=100).fit(X_train,y_train)
print(logreg100.score(X_train,y_train))  # 0.971830985915493
print(logreg100.score(X_test,y_test))    # 0.965034965034965

增大C,得到更高的訓練集精度,也得到了稍高的測試集精度,這也證實了我們的直覺,即更復雜的模型應該性能更好。

減小C,可能欠擬合

## C=0.01
logreg001 = LogisticRegression(C=0.01).fit(X_train,y_train)
print(logreg001.score(X_train,y_train))  # 0.9342723004694836
print(logreg001.score(X_test,y_test))    # 0.9300699300699301

畫圖查看三種參數對係數的效果:

logreg.coef_.shape  # (1, 30)

## 正則化參數C 取三個不同的值時模型學到的係數
%matplotlib notebook  # 比inline顯示的清晰

fig = plt.figure(figsize=(8,6))
plt.plot(logreg.coef_.T,marker='o',label='C=1',linestyle='')  # 30個特徵的係數矩陣轉置 30*1
plt.plot(logreg100.coef_.T,marker='^',label='C=100',linestyle='')  # 點是上三角形
plt.plot(logreg001.coef_.T,marker='s',label='C=0.01',linestyle='')  # 點是方形
plt.hlines(y=0,xmin=0,xmax=cancer.data.shape[1])  # 添加水平線
plt.xticks(range(cancer.data.shape[1]),cancer.feature_names,rotation=90,fontsize=8)  # x軸刻度範圍,標籤,角度,字號
plt.ylim(-5,5)
plt.xlabel('Coefficient index')
plt.ylabel('Coefficient magnitude')
plt.legend()
# 由於x座標標籤很長,notebook顯示不全,需要調整下方比例
plt.subplots_adjust(bottom=0.32)  # left=0.18, wspace=0.25, hspace=0.25,, top=0.91   # 周邊在子圖中佔多大比例

在這裏插入圖片描述
可以看出,第三個係數在不同的C值下正負不同,說明該係數在不同的C值下被當做不同類的指標,說明對係數的解釋不是絕對的,跟具體模型有關。所以對線性模型係數的解釋應該始終持保留態度。

?plt.hlines :Plot horizontal lines at each y from xmin to xmax. 畫水平線
plt.hlines(y, xmin, xmax, colors=‘k’, linestyles=‘solid’, label=’’)

用於二分類的線性模型:penalty 參數,選擇哪種正則化方式,
‘l1’: 模型只使用特徵的一個子集
‘l2’:使用全部特徵

如果想要一個可解釋性更強的模型,使用L1 正則化可能更好,因爲它約束模型只使用少數幾個特徵。

## 參數penalty選擇正則化方式
%matplotlib notebook

fig = plt.figure(figsize=(7,6))
plt.hlines(0,xmin=0,xmax=cancer.data.shape[1])
plt.xticks(range(cancer.data.shape[1]),cancer.feature_names,rotation=90,fontsize=8)  # x軸刻度範圍標籤角度
plt.ylim(-5,5)
plt.xlabel('Coefficient index')
plt.ylabel('Coefficient magnitude')
plt.subplots_adjust(bottom=0.32)  

for C,marker in zip([0.001,1,100],['o','^','s']):
    lr_l1 = LogisticRegression(C=C, penalty="l1").fit(X_train, y_train)
    print(lr_l1.score(X_train, y_train))
    print(lr_l1.score(X_test, y_test))
    
    plt.plot(lr_l1.coef_.T,marker=marker,linestyle='',label='C={:.3f}'.format(C))
    
plt.legend()

在這裏插入圖片描述
在這裏插入圖片描述

2. 用於多分類-多分類Logistic與線性SVM

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

多分類Logistic 迴歸的數學原理稍有不同,但也是對每個類別都有一個係數向量和一個截距,也使用了相同的預測方法。

線性SVM應用

研究一個簡單的二維三分類數據集,每個類別的數據都是從一個高斯分佈中採樣得出的。

?make_blobs :Generate isotropic Gaussian blobs for clustering.生成各向同性的高斯點以進行聚類。
make_blobs(n_samples=100, n_features=2, centers=None, cluster_std=1.0, center_box=(-10.0, 10.0), shuffle=True, random_state=None)

from sklearn.datasets import make_blobs
from sklearn.svm import LinearSVC

# 生成各向同性的高斯點以進行聚類。
X,y = make_blobs(random_state=42)

畫出數據:

?sns.scatterplot :Draw a scatter plot with possibility of several semantic groupings.繪製散點圖,可能會出現多個語義分組。
sns.scatterplot(x=None, y=None, hue=None, style=None,size=None,data=None, palette=None)
詳見docstring

import seaborn as sns
%matplotlib inline

# 不需要構建數據框,畫分類散點圖
sns.scatterplot(X[:,0],X[:,1],hue=y,hue_order=[0,1,2],palette=['orange','steelblue','green'])  # 分類散點圖,hue標註類別,palette調色板
plt.xlabel('Feature0')
plt.ylabel('Feature1')
# plt.legend(['class 0','class 1','class 2'])  設圖例不對

報錯警示:一開始寫錯了,將plt.xlabel(name)寫成了plt.xlabel=name,這樣後面再改回正確的就報錯了,因爲已經對xlabel函數賦值了,只能重啓kernel或者重新導入matplotlib

在這裏插入圖片描述
訓練一個LinearSVC 分類器

linear_svm = LinearSVC().fit(X,y)
print(linear_svm.coef_.shape)  # (3, 2) 因爲有三類,每一類有一個係數向量,一共三個,2個特徵
print(linear_svm.intercept_.shape)
linear_svm.score(X,y)  # 1.0

?np.linspace : Return evenly spaced numbers over a specified interval.
返回一段區間內均勻分佈的點
np.linspace(start, stop, num=50, endpoint=True, retstep=False, # 是否返回間隔
dtype=None, axis=0)

來自於《Python機器學習基礎教程》的實踐。

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