文章目錄
本章主要是講模型原理,即模型是如何工作的?瞭解這些內容有助於快速選擇合適的模型,正確的訓練算法(GD等),以及一套適當的超參數。
1. 線性迴歸
預測公式(輸入x,預測y)
y ^ = θ 0 + θ 1 x 1 + θ 2 x 2 + . . . + θ n x n \hat y = \theta_0 + \theta_1x_1 + \theta_2 x_2 + ... + \theta_n x_n y ^ = θ 0 + θ 1 x 1 + θ 2 x 2 + . . . + θ n x n
y ^ \hat y y ^ 是預測值
n n n 是特徵的數量
x i x_i x i 是第i個特徵值
θ j \theta_j θ j 是第j個模型參數(包括偏置項θ 0 \theta_0 θ 0 以及特徵權重θ 1 \theta_1 θ 1 ,θ 2 \theta_2 θ 2 … θ n \theta_n θ n )
預測公式的向量化表示:
y ^ = h θ ( X ) = θ T ⋅ X \hat y = h_\theta(X) = \theta^T\cdot X y ^ = h θ ( X ) = θ T ⋅ X
θ T \theta^T θ T 爲轉置後的行向量,不再是列向量。X爲特徵向量。(二維平面中可理解爲一元一次的直線方程)。
那麼如何使用這個線性迴歸模型呢?無非就是迭代訓練直到找到最適應訓練集的那個 θ \theta θ 。怎麼纔算是最適應?此時需要一個衡量的標準,比如MSE,MAE等。
成本函數(也叫損失函數) MSE:
M S E ( X , h θ ) = 1 m ∑ i = 1 m ( θ T ⋅ X ( i ) − y ( i ) ) 2 MSE(X,h_\theta) = \frac{1}{m}\sum_{i=1}^m(\theta^T\cdot X^{(i)} - y^{(i)} )^2 M S E ( X , h θ ) = m 1 i = 1 ∑ m ( θ T ⋅ X ( i ) − y ( i ) ) 2
所以,我們要求出損失函數最小時的 θ 值 \theta值 θ 值 。
1.1 標準方程
通過上面的分析,要求損失最小時的 θ \theta θ ,有一個閉式解公式可以直接得出:
θ ^ = ( X T ⋅ X ) − 1 ⋅ X T ⋅ y \hat \theta = (X^T \cdot X)^{-1}\cdot X^T \cdot y θ ^ = ( X T ⋅ X ) − 1 ⋅ X T ⋅ y
驗證公式:
import numpy as np
X = 2 * np. random. rand( 100 , 1 )
y = 4 + 3 * X + np. random. randn( 100 , 1 )
X_b = np. c_[ np. ones( ( 100 , 1 ) ) , X]
theta_best = np. linalg. inv( X_b. T. dot( X_b) ) . dot( X_b. T) . dot( y)
theta_best
原函數的參數爲:θ 0 = 4 , θ 1 = 3 \theta_0=4,\theta_1=3 θ 0 = 4 , θ 1 = 3 ,我們訓練出來的參數還比較接近θ 0 = 4.305 , θ 1 = 2.943 \theta_0=4.305,\theta_1=2.943 θ 0 = 4 . 3 0 5 , θ 1 = 2 . 9 4 3 。如果把噪聲去掉,預測出來的就是4和3。
我們用得出的θ \theta θ 進行預測:
X_new = np. array( [ [ 0 ] , [ 2 ] ] )
X_new_b = np. c_[ np. ones( ( 2 , 1 ) ) , X_new]
y_predict = X_new_b. dot( theta_best)
y_predict
繪製模型預測結果
plt. plot( X_new, y_predict, "r-" )
plt. plot( X, y, "b." )
plt. axis( [ 0 , 2 , 0 , 15 ] )
plt. show( )
sklearn的實現(注意:
sk將截距θ 0 \theta_0 θ 0 和係數θ 1 \theta_1 θ 1 分開了)。
from sklearn. linear_model import LinearRegression
lin_reg = LinearRegression( )
lin_reg. fit( X, y)
lin_reg. intercept_, lin_reg. coef_
lin_reg. predict( X_new)
1.2 計算複雜度
求逆矩陣 X T ⋅ X X^T \cdot X X T ⋅ X ,是n 乘 n矩陣(n是特徵數量)。這種矩陣求逆的計算複雜度
通常爲O ( n 2.4 ) O(n^{2.4}) O ( n 2 . 4 ) 到 O ( n 3 ) O(n^3) O ( n 3 ) 。如果特徵數量n翻倍,比如由1變爲2,那麼計算時間大約爲 2 2.4 = 5.3 2^{2.4} = 5.3 2 2 . 4 = 5 . 3 倍到 2 3 = 8 2^3=8 2 3 = 8 倍之間。
2. 梯度下降
找到最優解 θ \theta θ 的一種優化算法,通過不斷的迭代不斷調整參數,使損失函數最小化。
具體做法:通過測量參數向量θ \theta θ 的相關誤差函數的局部梯度,並不斷沿着降低梯度的方向調整,直到梯度降爲0,到到最小值。
梯度下降
中一個重要參數
:每一步的步長,取決於超參數學習率。學習率太低,需要大量迭代,學習率太高,容易扯着襠,直接跳過最低點,可能無法找到最小值。
貼張大神吳恩達的幫助圖理解下:
注意:
應用梯度下降時,需保證所有特徵值的大小比例都差不多(比如使用StandardScaler類),否則收斂時間會長很多。
訓練模型就是搜尋使成本函數(在訓練集上)最小化的參數組合。
2.1 批量梯度下降
要實現梯度下降,需要計算關於參數θ j \theta_j θ j 的成本函數的梯度,即偏導數。(根據複合函數的求導法則得到
)
成本函數的偏導數:
∂ ∂ θ j M S E ( θ ) = 2 m ∑ i = 1 m ( θ T ⋅ x ( i ) − y ( i ) ) x j ( i ) \frac{\partial}{\partial\theta_j}MSE(\theta) = \frac{2}{m}\sum_{i=1}^m(\theta^T\cdot x^{(i)} - y^{(i)} )x_j^{(i)} ∂ θ j ∂ M S E ( θ ) = m 2 i = 1 ∑ m ( θ T ⋅ x ( i ) − y ( i ) ) x j ( i )
成本函數的梯度向量:
∇ θ M S E ( θ ) = 2 m X T ⋅ ( X ⋅ θ − y ) \nabla_\theta MSE(\theta) = \frac{2}{m}X^T \cdot (X\cdot \theta - y) ∇ θ M S E ( θ ) = m 2 X T ⋅ ( X ⋅ θ − y )
計算梯度下降的每一步時,都是基於完整的訓練集X。所以叫批量梯度下降。
得到梯度向量之後,朝反方向下坡。也就是從θ \theta θ 中減去∇ θ M S E ( θ ) \nabla_\theta MSE(\theta) ∇ θ M S E ( θ ) 。梯度下降步長:
θ ( n e x t s t e p ) = θ − η ∇ θ M S E ( θ ) \theta^{(next step)} = \theta - \eta \nabla_\theta MSE(\theta) θ ( n e x t s t e p ) = θ − η ∇ θ M S E ( θ )
快速實現
eta = 0.1
n_iterations = 1000
m = 100
theta = np. random. randn( 2 , 1 )
for iteration in range ( n_iterations) :
gradients = 2 / m * X_b. T. dot( X_b. dot( theta) - y)
theta = theta - eta * gradients
if iteration < 3 :
print ( theta)
theta
3種不同學習率前10步的梯度下降尋找最優解過程:
theta_path_bgd = [ ]
def plot_gradient_descent ( theta, eta, theta_path= None ) :
m = len ( X_b)
plt. plot( X, y, "b." )
n_iterations = 1000
for iteration in range ( n_iterations) :
if iteration < 10 :
y_predict = X_new_b. dot( theta)
style = "b-" if iteration > 0 else "r--"
plt. plot( X_new, y_predict, style)
gradients = 2 / m * X_b. T. dot( X_b. dot( theta) - y)
theta = theta - eta * gradients
if theta_path is not None :
theta_path. append( theta)
plt. xlabel( "$x_1$" , fontsize= 18 )
plt. axis( [ 0 , 2 , 0 , 15 ] )
plt. title( r"$\eta = {}$" . format ( eta) , fontsize= 16 )
np. random. seed( 42 )
theta = np. random. randn( 2 , 1 )
plt. figure( figsize= ( 10 , 4 ) )
plt. subplot( 131 ) ; plot_gradient_descent( theta, eta= 0.02 )
plt. ylabel( "$y$" , rotation= 0 , fontsize= 18 )
plt. subplot( 132 ) ; plot_gradient_descent( theta, eta= 0.1 , theta_path= theta_path_bgd)
plt. subplot( 133 ) ; plot_gradient_descent( theta, eta= 0.5 )
save_fig( "gradient_descent_plot" )
plt. show( )
要找到合適的學習率,可以使用網格搜索。
2.2 隨機梯度下降
每一步在訓練集中隨機選擇一個實例,並且僅基於該單個實例來計算梯度。但是,由於算法的隨機性質,SGD比BGD要不規則得多。
優點:可以逃離局部最優,缺點:永遠定位不出最小值。解決方法:逐步降低學習率(每次迭代的學習率都不同)。這個過程叫做模擬退火
。確定每個迭代學習率的函數叫學習計劃
。
利用簡單的學習計劃實現隨機梯度下降SGD:
theta_path_sgd = [ ]
m = len ( X_b)
np. random. seed( 42 )
n_epochs = 50
t0, t1 = 5 , 50
def learning_schedule ( t) :
return t0 / ( t + t1)
theta = np. random. randn( 2 , 1 )
for epoch in range ( n_epochs) :
for i in range ( m) :
if epoch == 0 and i < 20 :
y_predict = X_new_b. dot( theta)
style = "b-" if i > 0 else "r--"
plt. plot( X_new, y_predict, style)
random_index = np. random. randint( m)
xi = X_b[ random_index: random_index+ 1 ]
yi = y[ random_index: random_index+ 1 ]
gradients = 2 * xi. T. dot( xi. dot( theta) - yi)
eta = learning_schedule( epoch * m + i)
theta = theta - eta * gradients
theta_path_sgd. append( theta)
plt. plot( X, y, "b." )
plt. xlabel( "$x_1$" , fontsize= 18 )
plt. ylabel( "$y$" , rotation= 0 , fontsize= 18 )
plt. axis( [ 0 , 2 , 0 , 15 ] )
save_fig( "sgd_plot" )
plt. show( )
sklearn SGD實現:
from sklearn. linear_model import SGDRegressor
sgd_reg = SGDRegressor( max_iter= 50 , tol= - np. infty, penalty= None , eta0= 0.1 , random_state= 42 )
sgd_reg. fit( X, y. ravel( ) )
sgd_reg. intercept_, sgd_reg. coef_
2.3 小批量梯度下降
每一步的梯度計算,基於一小部分隨機的實例集。
theta_path_mgd = [ ]
n_iterations = 50
minibatch_size = 20
np. random. seed( 42 )
theta = np. random. randn( 2 , 1 )
t0, t1 = 200 , 1000
def learning_schedule ( t) :
return t0 / ( t + t1)
t = 0
for epoch in range ( n_iterations) :
shuffled_indices = np. random. permutation( m)
X_b_shuffled = X_b[ shuffled_indices]
y_shuffled = y[ shuffled_indices]
for i in range ( 0 , m, minibatch_size) :
t += 1
xi = X_b_shuffled[ i: i+ minibatch_size]
yi = y_shuffled[ i: i+ minibatch_size]
gradients = 2 / minibatch_size * xi. T. dot( xi. dot( theta) - yi)
eta = learning_schedule( t)
theta = theta - eta * gradients
theta_path_mgd. append( theta)
theta_path_bgd = np. array( theta_path_bgd)
theta_path_sgd = np. array( theta_path_sgd)
theta_path_mgd = np. array( theta_path_mgd)
plt. figure( figsize= ( 7 , 4 ) )
plt. plot( theta_path_sgd[ : , 0 ] , theta_path_sgd[ : , 1 ] , "r-s" , linewidth= 1 , label= "Stochastic" )
plt. plot( theta_path_mgd[ : , 0 ] , theta_path_mgd[ : , 1 ] , "g-+" , linewidth= 2 , label= "Mini-batch" )
plt. plot( theta_path_bgd[ : , 0 ] , theta_path_bgd[ : , 1 ] , "b-o" , linewidth= 3 , label= "Batch" )
plt. legend( loc= "upper left" , fontsize= 16 )
plt. xlabel( r"$\theta_0$" , fontsize= 20 )
plt. ylabel( r"$\theta_1$ " , fontsize= 20 , rotation= 0 )
plt. axis( [ 2.5 , 4.5 , 2.3 , 3.9 ] )
save_fig( "gradient_descent_paths_plot" )
plt. show( )
三種梯度下降的介紹
3. 多項式迴歸
如果數據比簡單的直線更爲複雜怎麼辦?其實也可以用線性模型來擬合非線性數據
。
方法:將每個特徵的冪次方添加爲一個新特徵,然後在擴展的特徵集上訓練線性模型。這種方法被稱爲多項式迴歸
。
製造些非線性數據
import numpy as np
import numpy. random as rnd
np. random. seed( 42 )
m = 100
X = 6 * np. random. rand( m, 1 ) - 3
y = 0.5 * X** 2 + X + 2 + np. random. randn( m, 1 )
plt. plot( X, y, "b." )
plt. axis( [ - 3 , 3 , 0 , 10 ] )
plt. xlabel( "$x_1$" , fontsize= 18 )
plt. ylabel( "$y$" , rotation= 0 , fontsize= 18 )
plt. show( )
save_fig( "quadratic_data_plot" )
顯然,直線不可能擬合這個數據。使用sk將每個特徵的平方擴展爲新的特徵加入訓練集。然後用線性迴歸去擬合非線性的訓練數據。
from sklearn. preprocessing import PolynomialFeatures
poly_features = PolynomialFeatures( degree= 2 , include_bias= False )
X_poly = poly_features. fit_transform( X)
X[ : 2 ]
X_poly[ : 2 ]
lin_reg = LinearRegression( )
lin_reg. fit( X_poly, y)
lin_reg. intercept_, lin_reg. coef_
X_new= np. linspace( - 3 , 3 , 100 ) . reshape( 100 , 1 )
X_new_poly = poly_features. transform( X_new)
y_new = lin_reg. predict( X_new_poly)
plt. plot( X, y, "b." )
plt. plot( X_new, y_new, "r-" , linewidth= 2 , label= "Predictions" )
plt. xlabel( "$x_1$" , fontsize= 18 )
plt. ylabel( "$y$" , rotation= 0 , fontsize= 18 )
plt. legend( loc= "upper left" , fontsize= 14 )
plt. axis( [ - 3 , 3 , 0 , 10 ] )
plt. show( )
多項式迴歸能發現特徵與特徵的關係(純線性迴歸做不到)。原因:PolynomialFeatures會在給定的階數下,添加所有特徵組合。舉例:特徵a和b,階數degree=3,除了a,b,擴展的新特徵有a 2 、 a 3 、 b 2 、 b 3 、 a b 、 a 2 b 以 及 a b 2 a^2、a^3、b^2、b^3、ab、a^2b以及ab^2 a 2 、 a 3 、 b 2 、 b 3 、 a b 、 a 2 b 以 及 a b 2 。
所以,要特別小心
:特徵組合數量的爆炸。
4. 學習曲線
學習曲線描述的是預測函數和實際函數的對比,及特徵X和label的關係。個人認爲是預測準確度的體現,還有一種描述性能的學習曲線。
分別畫出300,2,1階的效果。
from sklearn. preprocessing import StandardScaler
from sklearn. pipeline import Pipeline
for style, width, degree in ( ( "g-" , 1 , 300 ) , ( "b--" , 2 , 2 ) , ( "r-+" , 2 , 1 ) ) :
polybig_features = PolynomialFeatures( degree= degree, include_bias= False )
std_scaler = StandardScaler( )
lin_reg = LinearRegression( )
polynomial_regression = Pipeline( [
( "poly_features" , polybig_features) ,
( "std_scaler" , std_scaler) ,
( "lin_reg" , lin_reg) ,
] )
polynomial_regression. fit( X, y)
y_newbig = polynomial_regression. predict( X_new)
plt. plot( X_new, y_newbig, style, label= str ( degree) , linewidth= width)
plt. plot( X, y, "b." , linewidth= 3 )
plt. legend( loc= "upper left" )
plt. xlabel( "$x_1$" , fontsize= 18 )
plt. ylabel( "$y$" , rotation= 0 , fontsize= 18 )
plt. axis( [ - 3 , 3 , 0 , 10 ] )
plt. show( )
另一種學習曲線:模型在訓練集和驗證集上,關於訓練集大小的性能函數。
from sklearn. metrics import mean_squared_error
from sklearn. model_selection import train_test_split
def plot_learning_curves ( model, X, y) :
X_train, X_val, y_train, y_val = train_test_split( X, y, test_size= 0.2 , random_state= 10 )
train_errors, val_errors = [ ] , [ ]
for m in range ( 1 , len ( X_train) ) :
model. fit( X_train[ : m] , y_train[ : m] )
y_train_predict = model. predict( X_train[ : m] )
y_val_predict = model. predict( X_val)
train_errors. append( mean_squared_error( y_train[ : m] , y_train_predict) )
val_errors. append( mean_squared_error( y_val, y_val_predict) )
plt. plot( np. sqrt( train_errors) , "r-+" , linewidth= 2 , label= "train" )
plt. plot( np. sqrt( val_errors) , "b-" , linewidth= 3 , label= "val" )
plt. legend( loc= "upper right" , fontsize= 14 )
plt. xlabel( "Training set size" , fontsize= 14 )
plt. ylabel( "RMSE" , fontsize= 14 )
lin_reg = LinearRegression( )
plot_learning_curves( lin_reg, X, y)
plt. axis( [ 0 , 80 , 0 , 3 ] )
plt. show( )
觀察上圖,
訓練集:當只有一兩個實例時,模型完美擬合。但是,當實例越來越多時,因爲數據有噪聲,所以誤差慢慢爬升,到一定高度就趨於平穩了。
驗證集:模型在一開始數據量小的時候,泛化能力差,所以誤差很大,但是隨着訓練數據的增加,它開始慢慢學習,因此驗證集誤差慢慢下降。
我們再觀察一個10階多項式模型的學習曲線。
from sklearn. pipeline import Pipeline
polynomial_regression = Pipeline( [
( "poly_features" , PolynomialFeatures( degree= 10 , include_bias= False ) ) ,
( "lin_reg" , LinearRegression( ) ) ,
] )
plot_learning_curves( polynomial_regression, X, y)
plt. axis( [ 0 , 80 , 0 , 3 ] )
save_fig( "learning_curves_plot" )
plt. show( )
分析:上圖可以看到,訓練集上表現比驗證集上要好很多,說明過擬合了。
******************小結(重要)******************:
① 學習曲線可以用於選擇模型,可以觀察到是否過擬合等等。
② 偏差/方差的權衡,統計和機器學習領域重要理論結果,:
模型的泛化誤差 = 偏差 + 方差 + 不可避免的誤差
。
偏差:假設函數就沒選對,比如假設函數是線性的,實際數據確是2次的,高偏差
可能造成欠擬合
。
方差:模型對訓練數據的微小變化比較敏感,高度自由的模型很可能有高方差
,所以很容易對訓練數據過擬合
。
不可避免的的誤差: 數據本身所致,比如異常值,缺失值等,需要數據處理。
總結,增加模型的複雜度會顯著提升模型的方差,減小偏差。反之,降低模型的複雜度則會降低模型的方差,提升偏差。所以,一個好的模型要平衡好偏差和方差,才能使得泛化能力更好
。
再貼張大佬圖幫助理解:
5. 正則線性模型
減少過擬合的一個好辦法:對模型正則化 ( 約束他,不要過度自由)。
5.1 嶺迴歸
也叫吉洪諾夫正則化,是線性迴歸的正則化版。加入L2正則項α ∑ i = 1 n θ i 2 \alpha \sum_{i=1}^n \theta_i^2 α ∑ i = 1 n θ i 2 。
嶺迴歸成本函數:
J ( θ ) = M S E ( θ ) + α 1 2 ∑ i = 1 n θ i 2 J(\theta) = MSE(\theta)+\alpha\frac{1}{2} \sum_{i=1}^n \theta_i^2 J ( θ ) = M S E ( θ ) + α 2 1 i = 1 ∑ n θ i 2
注意:這裏偏置項θ 0 \theta_0 θ 0 沒有正則化,求和是從 θ 1 \theta_1 θ 1 開始的。如果我們將w定義爲特徵權重的向量(θ 1 到 θ n \theta_1到\theta_n θ 1 到 θ n ),那麼正則項即等於1 2 ( ∥ w ∥ 2 ) 2 \frac{1}{2}(\left \|w \right \|_2)^2 2 1 ( ∥ w ∥ 2 ) 2 ,其中∥ w ∥ 2 \left \|w \right \|_2 ∥ w ∥ 2 爲權重向量的l2範數。
注意
:在執行嶺迴歸之前,要對特徵進行縮放 (例如StandardScaler),因爲它對輸入特徵的大小非常敏感,大多數正則化模型都是如此。
下面使用多個
α \alpha α 對某線性數據進行訓練的兩種嶺迴歸模型
。左圖爲直接使用嶺迴歸模型,右圖爲嶺正則化後的多項式迴歸。
上圖代碼:
from sklearn. linear_model import Ridge
np. random. seed( 42 )
m = 20
X = 3 * np. random. rand( m, 1 )
y = 1 + 0.5 * X + np. random. randn( m, 1 ) / 1.5
X_new = np. linspace( 0 , 3 , 100 ) . reshape( 100 , 1 )
def plot_model ( model_class, polynomial, alphas, ** model_kargs) :
for alpha, style in zip ( alphas, ( "b-" , "g--" , "r:" ) ) :
model = model_class( alpha, ** model_kargs) if alpha > 0 else LinearRegression( )
if polynomial:
model = Pipeline( [
( "poly_features" , PolynomialFeatures( degree= 10 , include_bias= False ) ) ,
( "std_scaler" , StandardScaler( ) ) ,
( "regul_reg" , model) ,
] )
model. fit( X, y)
y_new_regul = model. predict( X_new)
lw = 2 if alpha > 0 else 1
plt. plot( X_new, y_new_regul, style, linewidth= lw, label= r"$\alpha = {}$" . format ( alpha) )
plt. plot( X, y, "b." , linewidth= 3 )
plt. legend( loc= "upper left" , fontsize= 15 )
plt. xlabel( "$x_1$" , fontsize= 18 )
plt. axis( [ 0 , 3 , 0 , 4 ] )
plt. figure( figsize= ( 8 , 4 ) )
plt. subplot( 121 )
plot_model( Ridge, polynomial= False , alphas= ( 0 , 10 , 100 ) , random_state= 42 )
plt. ylabel( "$y$" , rotation= 0 , fontsize= 18 )
plt. subplot( 122 )
plot_model( Ridge, polynomial= True , alphas= ( 0 , 10 ** - 5 , 1 ) , random_state= 42 )
plt. show( )
嶺迴歸也有閉式解:
θ ^ = ( X T ⋅ X + α A ) − 1 ⋅ X T ⋅ y \hat\theta = (X^T\cdot X+\alpha A)^{-1}\cdot X^T\cdot y θ ^ = ( X T ⋅ X + α A ) − 1 ⋅ X T ⋅ y
使用sklearn執行閉式解的嶺迴歸 ( 利用上面公式的變體:Cholesky的矩陣因式分解法)
from sklearn. linear_model import Ridge
ridge_reg = Ridge( alpha= 1 , solver= "cholesky" , random_state= 42 )
ridge_reg. fit( X, y)
ridge_reg. predict( [ [ 1.5 ] ] )
使用隨機梯度下降
sgd_reg = SGDRegressor( max_iter= 50 , tol= - np. infty, penalty= "l2" , random_state= 42 )
sgd_reg. fit( X, y. ravel( ) )
sgd_reg. predict( [ [ 1.5 ] ] )
上面2種方式都屬於嶺迴歸。
5.2 套索迴歸
線性迴歸的另一種正則化,簡稱Lasso,其中La表示Least Absolute的意思,即最小絕對值。它也是向成本函數增加一個正則項,但是它增加的是權重向量的L1範數。
Lasso迴歸成本函數:
J ( θ ) = M S E ( θ ) + α ∑ i = 1 n ∣ θ i ∣ J(\theta)=MSE(\theta)+ \alpha\sum_{i=1}^n|\theta_i| J ( θ ) = M S E ( θ ) + α i = 1 ∑ n ∣ θ i ∣
Lasso迴歸的一個重要特點:傾向於完全消除掉最不重要的特徵權重(也就是設置爲0)。如下圖,當α = 1 0 − 7 \alpha=10^{-7} α = 1 0 − 7 時,快接近於線性:因爲所有高階多項式的特徵權重都等於0。換句話說,Lasso迴歸會輸出一個稀疏模型,只有很少的特徵有非零權重。
from sklearn. linear_model import Lasso
plt. figure( figsize= ( 8 , 4 ) )
plt. subplot( 121 )
plot_model( Lasso, polynomial= False , alphas= ( 0 , 0.1 , 1 ) , random_state= 42 )
plt. ylabel( "$y$" , rotation= 0 , fontsize= 18 )
plt. subplot( 122 )
plot_model( Lasso, polynomial= True , alphas= ( 0 , 10 ** - 7 , 1 ) , tol= 1 , random_state= 42 )
plt. show( )
sklearn的Lasso實現
from sklearn. linear_model import Lasso
lasso_reg = Lasso( alpha= 0.1 )
lasso_reg. fit( X, y)
lasso_reg. predict( [ [ 1.5 ] ] )
5.3 彈性網絡
顧名思義,所謂彈性,指的就是它位於嶺迴歸和lasso迴歸之間的中間地帶。混合的比例通過r來控制,r=0時,彈性網絡即等於嶺迴歸。
彈性網絡成本函數:
J ( θ ) = M S E ( θ ) + r α ∑ i = 1 n ∣ θ i ∣ + 1 − r 2 α ∑ i = 1 n θ i 2 J(\theta)=MSE(\theta)+ r\alpha\sum_{i=1}^n|\theta_i|+\frac{1-r}{2} \alpha\sum_{i=1}^n \theta_i^2 J ( θ ) = M S E ( θ ) + r α i = 1 ∑ n ∣ θ i ∣ + 2 1 − r α i = 1 ∑ n θ i 2
如何選擇線性迴歸、嶺迴歸、lasso迴歸和彈性網絡呢?
① 通常來說,有正則化總比沒有要好。所以嶺迴歸可以是默認選擇。
② 若實際用到的特徵很少,那就應該更傾向於lasso迴歸或彈性網絡,因爲它們會將無用特徵的權重都降維0.
③ 一般情況下,彈性網絡優於lasso,因爲當特徵數量超過實例數量,或幾個特徵強相關時,lasso可能非常不穩定。
所以,一般來說,默認用嶺迴歸
,第②種情況用彈性網絡
。
sk種ElasticNet小例子:
from sklearn. linear_model import ElasticNet
elastic_net = ElasticNet( alpha= 0.1 , l1_ratio= 0.5 , random_state= 42 )
elastic_net. fit( X, y)
elastic_net. predict( [ [ 1.5 ] ] )
5.4 早期停止法
針對梯度下降這類迭代學習的算法而言,早停是另一種正則化方法:在驗證集誤差達到最小時停止迭代訓練。
上圖代碼
np. random. seed( 42 )
m = 100
X = 6 * np. random. rand( m, 1 ) - 3
y = 2 + X + 0.5 * X** 2 + np. random. randn( m, 1 )
X_train, X_val, y_train, y_val = train_test_split( X[ : 50 ] , y[ : 50 ] . ravel( ) , test_size= 0.5 , random_state= 10 )
poly_scaler = Pipeline( [
( "poly_features" , PolynomialFeatures( degree= 90 , include_bias= False ) ) ,
( "std_scaler" , StandardScaler( ) ) ,
] )
X_train_poly_scaled = poly_scaler. fit_transform( X_train)
X_val_poly_scaled = poly_scaler. transform( X_val)
sgd_reg = SGDRegressor( max_iter= 1 ,
tol= - np. infty,
penalty= None ,
eta0= 0.0005 ,
warm_start= True ,
learning_rate= "constant" ,
random_state= 42 )
n_epochs = 500
train_errors, val_errors = [ ] , [ ]
for epoch in range ( n_epochs) :
sgd_reg. fit( X_train_poly_scaled, y_train)
y_train_predict = sgd_reg. predict( X_train_poly_scaled)
y_val_predict = sgd_reg. predict( X_val_poly_scaled)
train_errors. append( mean_squared_error( y_train, y_train_predict) )
val_errors. append( mean_squared_error( y_val, y_val_predict) )
best_epoch = np. argmin( val_errors)
best_val_rmse = np. sqrt( val_errors[ best_epoch] )
plt. annotate( 'Best model' ,
xy= ( best_epoch, best_val_rmse) ,
xytext= ( best_epoch, best_val_rmse + 1 ) ,
ha= "center" ,
arrowprops= dict ( facecolor= 'black' , shrink= 0.05 ) ,
fontsize= 16 ,
)
best_val_rmse -= 0.03
plt. plot( [ 0 , n_epochs] , [ best_val_rmse, best_val_rmse] , "k:" , linewidth= 2 )
plt. plot( np. sqrt( val_errors) , "b-" , linewidth= 3 , label= "Validation set" )
plt. plot( np. sqrt( train_errors) , "r--" , linewidth= 2 , label= "Training set" )
plt. legend( loc= "upper right" , fontsize= 14 )
plt. xlabel( "Epoch" , fontsize= 14 )
plt. ylabel( "RMSE" , fontsize= 14 )
plt. show( )
早停的基本實現:
from sklearn. base import clone
sgd_reg = SGDRegressor( max_iter= 1 , tol= - np. infty, warm_start= True , penalty= None ,
learning_rate= "constant" , eta0= 0.0005 , random_state= 42 )
minimum_val_error = float ( "inf" )
best_epoch = None
best_model = None
for epoch in range ( 1000 ) :
sgd_reg. fit( X_train_poly_scaled, y_train)
y_val_predict = sgd_reg. predict( X_val_poly_scaled)
val_error = mean_squared_error( y_val, y_val_predict)
if val_error < minimum_val_error:
minimum_val_error = val_error
best_epoch = epoch
best_model = clone( sgd_reg)
注意
:當warm_start=True時,調用fit(),會從停下的地方繼續開始訓練,而不會重新開始。
5.5 邏輯迴歸
其實就是用線性的方式去解決分類問題,更詳細通俗的請參考我之前寫的博客邏輯迴歸 。
邏輯迴歸的成本函數沒有閉式方程,不能直接求出θ \theta θ ,但是它是一個凸函數,可以使用梯度下降等算法求出全局最小值。
5.5.1 概率估算
邏輯迴歸也是計算輸入特徵的加權和,但是不同的是它輸出的是一個概率 (0 - 1之間的數字)。
5.5.2 訓練和成本函數
成本函數爲log損失函數,具體參考之前博客。
5.5.3 決策邊界
略
5.9 Softmax迴歸
也叫多元邏輯迴歸,它是邏輯迴歸的擴展,支持多個類別。
原理
步驟:對於一個給定的實例
① Softmax先計算出每個類別k的分數s k ( x ) s_k(x) s k ( x ) ;
類別k的Softmax分數:s k ( x ) = θ k T ⋅ x s_k(x)=\theta_k^T\cdot x s k ( x ) = θ k T ⋅ x
② 對這些分數應用Softmax函數(也叫歸一化函數),估算出每個類別的概率。
Softmax分數歸一化(除以所有指數的總合)即得到 p ^ k \hat p_k p ^ k :
p ^ k = h ( s ( x ) ) k = e x p ( s k ( x ) ) ∑ j = 1 k e x p ( s j ( x ) ) \hat p_k = h(s(x))_k = \frac {exp(s_k(x))}{\sum_{j=1}^kexp(s_j(x))} p ^ k = h ( s ( x ) ) k = ∑ j = 1 k e x p ( s j ( x ) ) e x p ( s k ( x ) )
K是類別的數量;
s(x)是實例x每個類別的分數的向量;
h ( s ( x ) ) k h(s(x))_k h ( s ( x ) ) k 是實例x屬於類別k的概率。
③ 最終的Softmax分類器預測函數爲:
y ^ = a r g m a x ( k ) h ( s ( x ) ) k = a r g m a x ( k ) ( s k ( x ) ) = a r g m a x ( k ) ( θ k T ⋅ X ) \hat y = argmax_{(k)} \ h(s(x))_k = argmax_{(k)} \ (s_k(x))=argmax_{(k)} \ (\theta_k^T\cdot X) y ^ = a r g m a x ( k ) h ( s ( x ) ) k = a r g m a x ( k ) ( s k ( x ) ) = a r g m a x ( k ) ( θ k T ⋅ X )
argmax返回的是使函數最大化所對應的變量的值。 在上面的等式裏,返回的是使估算概率
h ( x ( x ) ) k h(x(x))_k h ( x ( x ) ) k 最大時
k的值。
注意
:Softmax迴歸分類器一次只會預測一個類別(它是多類別,但不是多輸出,每次只會單輸出衆多類別中的一個),比如,用它識別照片中的多個人是不行的
。
Softmax成本函數(交叉熵),爲啥使用交叉熵?因爲交叉熵經常用於衡量一組估算類別概率和目標類別的匹配程度。公式:
J ( Θ ) = − 1 m ∑ i = 1 m ∑ k = 1 k y k ( i ) l o g ( p ^ k ( i ) ) J(\Theta) = - \frac{1}{m}\sum_{i=1}^{m}\sum_{k=1}^{k}y_k^{(i)}log(\hat p_k^{(i)}) J ( Θ ) = − m 1 i = 1 ∑ m k = 1 ∑ k y k ( i ) l o g ( p ^ k ( i ) )
如果第i個實例的目標類別爲k,則y k ( i ) y_k^{(i)} y k ( i ) = 1,否則爲0。
其中大寫的theta是參數矩陣,存儲的是每個類別自己的參數向量θ k \theta_k θ k 。
注意:
當只有2個類別時(k=2),它等價於邏輯迴歸的成本函數(log損失函數)。
類別k的交叉熵梯度向量(偏導):
∇ θ k J ( Θ ) = 1 m ∑ i = 1 m ( p ^ k ( i ) − y k ( i ) ) x ( i ) \nabla_{\theta_k}J(\Theta)=\frac{1}{m}\sum_{i=1}^m(\hat p_k^{(i)}-y_k^{(i)})x^{(i)} ∇ θ k J ( Θ ) = m 1 i = 1 ∑ m ( p ^ k ( i ) − y k ( i ) ) x ( i )
現在就可以計算每個類別的梯度向量,然後使用梯度下降找到最小化損失的參數矩陣Θ \Theta Θ 了。
下面使用Softmax迴歸將鳶尾花分爲三類。默認情況下,LR是一對多的訓練方式,將超參數multi_class設置爲"multinomial"就可以切換成Softmax迴歸,還要必須指定一個支持Softmax迴歸的求解器,比如lbfgs求解器。默認l2正則,可以通過超參數C進行控制。
from sklearn. linear_model import LogisticRegression
X = iris[ "data" ] [ : , ( 2 , 3 ) ]
y = iris[ "target" ]
softmax_reg = LogisticRegression( multi_class= "multinomial" , solver= "lbfgs" , C= 10 , random_state= 42 )
softmax_reg. fit( X, y)
softmax_reg. predict( [ [ 5 , 2 ] ] )
softmax_reg. predict_proba( [ [ 5 , 2 ] ] )
決策邊界
x0, x1 = np. meshgrid(
np. linspace( 0 , 8 , 500 ) . reshape( - 1 , 1 ) ,
np. linspace( 0 , 3.5 , 200 ) . reshape( - 1 , 1 ) ,
)
X_new = np. c_[ x0. ravel( ) , x1. ravel( ) ]
y_proba = softmax_reg. predict_proba( X_new)
y_predict = softmax_reg. predict( X_new)
zz1 = y_proba[ : , 1 ] . reshape( x0. shape)
zz = y_predict. reshape( x0. shape)
plt. figure( figsize= ( 10 , 4 ) )
plt. plot( X[ y== 2 , 0 ] , X[ y== 2 , 1 ] , "g^" , label= "Iris-Virginica" )
plt. plot( X[ y== 1 , 0 ] , X[ y== 1 , 1 ] , "bs" , label= "Iris-Versicolor" )
plt. plot( X[ y== 0 , 0 ] , X[ y== 0 , 1 ] , "yo" , label= "Iris-Setosa" )
from matplotlib. colors import ListedColormap
custom_cmap = ListedColormap( [ '#fafab0' , '#9898ff' , '#a0faa0' ] )
plt. contourf( x0, x1, zz, cmap= custom_cmap)
contour = plt. contour( x0, x1, zz1, cmap= plt. cm. brg)
plt. clabel( contour, inline= 1 , fontsize= 12 )
plt. xlabel( "Petal length" , fontsize= 14 )
plt. ylabel( "Petal width" , fontsize= 14 )
plt. legend( loc= "center left" , fontsize= 14 )
plt. axis( [ 0 , 7 , 0 , 3.5 ] )
plt. show( )
多分類如何選擇
邏輯迴歸和Softmax迴歸呢? 主要看類別之間是否互斥。互斥的話選Softmax,不互斥選擇多個LR。參考LR和Softmax區別與聯繫