《機器學習實戰 學習筆記》(六):訓練模型

文章目錄

本章主要是講模型原理,即模型是如何工作的?瞭解這些內容有助於快速選擇合適的模型,正確的訓練算法(GD等),以及一套適當的超參數。

1. 線性迴歸

   預測公式(輸入x,預測y)

y^=θ0+θ1x1+θ2x2+...+θnxn\hat y = \theta_0 + \theta_1x_1 + \theta_2 x_2 + ... + \theta_n x_n

   y^\hat y 是預測值
   nn 是特徵的數量
   xix_i 是第i個特徵值
   θj\theta_j 是第j個模型參數(包括偏置項θ0\theta_0以及特徵權重θ1\theta_1,θ2\theta_2θn\theta_n
   預測公式的向量化表示:

y^=hθ(X)=θTX\hat y = h_\theta(X) = \theta^T\cdot X

   θT\theta^T爲轉置後的行向量,不再是列向量。X爲特徵向量。(二維平面中可理解爲一元一次的直線方程)。
   那麼如何使用這個線性迴歸模型呢?無非就是迭代訓練直到找到最適應訓練集的那個 θ\theta。怎麼纔算是最適應?此時需要一個衡量的標準,比如MSE,MAE等。
   成本函數(也叫損失函數) MSE:

MSE(X,hθ)=1mi=1m(θTX(i)y(i))2MSE(X,h_\theta) = \frac{1}{m}\sum_{i=1}^m(\theta^T\cdot X^{(i)} - y^{(i)} )^2

   所以,我們要求出損失函數最小時的 θ\theta值

1.1 標準方程

   通過上面的分析,要求損失最小時的 θ\theta,有一個閉式解公式可以直接得出:

θ^=(XTX)1XTy\hat \theta = (X^T \cdot X)^{-1}\cdot X^T \cdot y

   驗證公式:
import numpy as np

X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)#高斯噪聲

# 每個實例加入1,原來的X爲100行1列,變爲100行2列,第一列爲1
# np.ones((100, 1)) 生成100行1列的矩陣,值全爲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的值
theta_best

在這裏插入圖片描述

   原函數的參數爲:θ0=4,θ1=3\theta_0=4,\theta_1=3 ,我們訓練出來的參數還比較接近θ0=4.305,θ1=2.943\theta_0=4.305,\theta_1=2.943。如果把噪聲去掉,預測出來的就是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和係數θ1\theta_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 計算複雜度

   求逆矩陣 XTXX^T \cdot X,是n 乘 n矩陣(n是特徵數量)。這種矩陣求逆的計算複雜度通常爲O(n2.4)O(n^{2.4})O(n3)O(n^3)。如果特徵數量n翻倍,比如由1變爲2,那麼計算時間大約爲 22.4=5.32^{2.4} = 5.3 倍到 23=82^3=8倍之間。

2. 梯度下降

   找到最優解 θ\theta 的一種優化算法,通過不斷的迭代不斷調整參數,使損失函數最小化。
   具體做法:通過測量參數向量θ\theta的相關誤差函數的局部梯度,並不斷沿着降低梯度的方向調整,直到梯度降爲0,到到最小值。
   梯度下降中一個重要參數:每一步的步長,取決於超參數學習率。學習率太低,需要大量迭代,學習率太高,容易扯着襠,直接跳過最低點,可能無法找到最小值。
   貼張大神吳恩達的幫助圖理解下:

在這裏插入圖片描述

   注意:應用梯度下降時,需保證所有特徵值的大小比例都差不多(比如使用StandardScaler類),否則收斂時間會長很多。
   訓練模型就是搜尋使成本函數(在訓練集上)最小化的參數組合。

2.1 批量梯度下降

   要實現梯度下降,需要計算關於參數θj\theta_j的成本函數的梯度,即偏導數。(根據複合函數的求導法則得到)在這裏插入圖片描述
   成本函數的偏導數:

θjMSE(θ)=2mi=1m(θTx(i)y(i))xj(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)}

   成本函數的梯度向量:

θMSE(θ)=2mXT(Xθy)\nabla_\theta MSE(\theta) = \frac{2}{m}X^T \cdot (X\cdot \theta - y)

   計算梯度下降的每一步時,都是基於完整的訓練集X。所以叫批量梯度下降。
   得到梯度向量之後,朝反方向下坡。也就是從θ\theta中減去θMSE(θ)\nabla_\theta MSE(\theta)。梯度下降步長:

θ(nextstep)=θηθMSE(θ)\theta^{(next step)} = \theta - \eta \nabla_\theta MSE(\theta)

   快速實現
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 = []
#迭代的同時畫出梯度下降尋找最優theta的過程。
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)  # random initialization

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  # learning schedule hyperparameters

def learning_schedule(t):
    return t0 / (t + t1)

theta = np.random.randn(2,1)  # random initialization

for epoch in range(n_epochs):
    for i in range(m):
        if epoch == 0 and i < 20:                    # not shown in the book
            y_predict = X_new_b.dot(theta)           # not shown
            style = "b-" if i > 0 else "r--"         # not shown
            plt.plot(X_new, y_predict, style)        # not shown
        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) #求的是單個樣本實例的步長,區別於BGD
        eta = learning_schedule(epoch * m + i)
        theta = theta - eta * gradients
        theta_path_sgd.append(theta)                 # not shown

plt.plot(X, y, "b.")                                 # not shown
plt.xlabel("$x_1$", fontsize=18)                     # not shown
plt.ylabel("$y$", rotation=0, fontsize=18)           # not shown
plt.axis([0, 2, 0, 15])                              # not shown
save_fig("sgd_plot")                                 # not shown
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)  # random initialization

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) #2次,不包含偏置項
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_

在這裏插入圖片描述

#獲取 -3 到 3之間 100個數字,且符合等差數列
X_new=np.linspace(-3, 3, 100).reshape(100, 1) #linspace:返回在指定範圍內的均勻間隔的數字(組成的數組),也即返回一個等差數列
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,擴展的新特徵有a2a3b2b3aba2bab2a^2、a^3、b^2、b^3、ab、a^2b以及ab^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) #訓練模型 (此時X已經是擴展過的訓練集,並進行了標準化處理)
    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)   # not shown in the book
    plt.xlabel("Training set size", fontsize=14) # not shown
    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])           # not shown
save_fig("learning_curves_plot")  # not shown
plt.show()

在這裏插入圖片描述

   分析:上圖可以看到,訓練集上表現比驗證集上要好很多,說明過擬合了。
   ******************小結(重要)******************:
   ① 學習曲線可以用於選擇模型,可以觀察到是否過擬合等等。
   ② 偏差/方差的權衡,統計和機器學習領域重要理論結果,:
      模型的泛化誤差 = 偏差 + 方差 + 不可避免的誤差
      偏差:假設函數就沒選對,比如假設函數是線性的,實際數據確是2次的,高偏差可能造成欠擬合
      方差:模型對訓練數據的微小變化比較敏感,高度自由的模型很可能有高方差,所以很容易對訓練數據過擬合
      不可避免的的誤差: 數據本身所致,比如異常值,缺失值等,需要數據處理。
      總結,增加模型的複雜度會顯著提升模型的方差,減小偏差。反之,降低模型的複雜度則會降低模型的方差,提升偏差。所以,一個好的模型要平衡好偏差和方差,才能使得泛化能力更好
   再貼張大佬圖幫助理解:

在這裏插入圖片描述

5. 正則線性模型

   減少過擬合的一個好辦法:對模型正則化 ( 約束他,不要過度自由)。

5.1 嶺迴歸

   也叫吉洪諾夫正則化,是線性迴歸的正則化版。加入L2正則項αi=1nθi2\alpha \sum_{i=1}^n \theta_i^2
   嶺迴歸成本函數:

J(θ)=MSE(θ)+α12i=1nθi2J(\theta) = MSE(\theta)+\alpha\frac{1}{2} \sum_{i=1}^n \theta_i^2

   注意:這裏偏置項θ0\theta_0沒有正則化,求和是從 θ1\theta_1 開始的。如果我們將w定義爲特徵權重的向量(θ1θn\theta_1到\theta_n),那麼正則項即等於12(w2)2\frac{1}{2}(\left \|w \right \|_2)^2,其中w2\left \|w \right \|_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) #獲取0到3之間的數值,符合等差數列

#繪製模型圖
# model_class 模型
# polynomial 是否高階
# alphas 學習率取值列表
# model_kargs 模型其他參數
def plot_model(model_class, polynomial, alphas, **model_kargs):
    for alpha, style in zip(alphas, ("b-", "g--", "r:")):
        #如果學習率大於0就用正則化模型,否則就線性迴歸模型(alpha = 0時其實就是LinearRegression)
        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()
   嶺迴歸也有閉式解:

θ^=(XTX+αA)1XTy\hat\theta = (X^T\cdot X+\alpha A)^{-1}\cdot X^T\cdot 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(θ)=MSE(θ)+αi=1nθiJ(\theta)=MSE(\theta)+ \alpha\sum_{i=1}^n|\theta_i|

   Lasso迴歸的一個重要特點:傾向於完全消除掉最不重要的特徵權重(也就是設置爲0)。如下圖,當α=107\alpha=10^{-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(θ)=MSE(θ)+rαi=1nθi+1r2αi=1nθi2J(\theta)=MSE(\theta)+ r\alpha\sum_{i=1}^n|\theta_i|+\frac{1-r}{2} \alpha\sum_{i=1}^n \theta_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  # just to make the graph look better
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)  # continues where it left off
    y_val_predict = sgd_reg.predict(X_val_poly_scaled)
    val_error = mean_squared_error(y_val, y_val_predict) # mse得分
    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的分數sk(x)s_k(x)
     類別k的Softmax分數:sk(x)=θkTxs_k(x)=\theta_k^T\cdot x
   ② 對這些分數應用Softmax函數(也叫歸一化函數),估算出每個類別的概率。
     Softmax分數歸一化(除以所有指數的總合)即得到 p^k\hat p_k

p^k=h(s(x))k=exp(sk(x))j=1kexp(sj(x))\hat p_k = h(s(x))_k = \frac {exp(s_k(x))}{\sum_{j=1}^kexp(s_j(x))}

     K是類別的數量;
     s(x)是實例x每個類別的分數的向量;
     h(s(x))kh(s(x))_k是實例x屬於類別k的概率。
   ③ 最終的Softmax分類器預測函數爲:

y^=argmax(k) h(s(x))k=argmax(k) (sk(x))=argmax(k) (θkTX)\hat y = argmax_{(k)} \ h(s(x))_k = argmax_{(k)} \ (s_k(x))=argmax_{(k)} \ (\theta_k^T\cdot X)

     argmax返回的是使函數最大化所對應的變量的值。 在上面的等式裏,返回的是使估算概率h(x(x))kh(x(x))_k最大時 k的值。
   注意:Softmax迴歸分類器一次只會預測一個類別(它是多類別,但不是多輸出,每次只會單輸出衆多類別中的一個),比如,用它識別照片中的多個人是不行的
   Softmax成本函數(交叉熵),爲啥使用交叉熵?因爲交叉熵經常用於衡量一組估算類別概率和目標類別的匹配程度。公式:

J(Θ)=1mi=1mk=1kyk(i)log(p^k(i))J(\Theta) = - \frac{1}{m}\sum_{i=1}^{m}\sum_{k=1}^{k}y_k^{(i)}log(\hat p_k^{(i)})

   如果第i個實例的目標類別爲k,則yk(i)y_k^{(i)} = 1,否則爲0。
   其中大寫的theta是參數矩陣,存儲的是每個類別自己的參數向量θk\theta_k
   注意:當只有2個類別時(k=2),它等價於邏輯迴歸的成本函數(log損失函數)。
   類別k的交叉熵梯度向量(偏導):

θkJ(Θ)=1mi=1m(p^k(i)yk(i))x(i)\nabla_{\theta_k}J(\Theta)=\frac{1}{m}\sum_{i=1}^m(\hat 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)]  # petal length, petal width
y = iris["target"]

softmax_reg = LogisticRegression(multi_class="multinomial",solver="lbfgs", C=10, random_state=42)
softmax_reg.fit(X, y)

# 給你一朵花,花瓣長5  寬2 
# Softmax預測
softmax_reg.predict([[5,2]]) #輸出爲第2類Virginica鳶尾花

softmax_reg.predict_proba([[5,2]]) # 94.2%爲第2類Virginica鳶尾花,5.8%概率爲Versicolor鳶尾花

在這裏插入圖片描述

   決策邊界

在這裏插入圖片描述

x0, x1 = np.meshgrid(#座標矩陣
        np.linspace(0, 8, 500).reshape(-1, 1),# 0-8區間生成500個符合等差數列的數字
        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區別與聯繫
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章