多項(xiàng)式回歸與模型泛化

之前總結(jié)的線(xiàn)性回歸都是在特征集每個(gè)特征項(xiàng)數(shù)為1的情況下進(jìn)行求解,即y=θ0 * X0 + θ1 * X1 + ... + θn * Xn,所有特征都是一次項(xiàng)。實(shí)際情況下,樣本數(shù)據(jù)可能并不能很好的用這種一次項(xiàng)表達(dá)式進(jìn)行回歸。舉個(gè)例子

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

'''創(chuàng)建數(shù)據(jù)'''
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
y = 0.5 * X**2 + X + 2
noise = np.random.normal(0, 1, size=100)
noise = noise.reshape(-1, 1)
y = y + noise

plt.scatter(X, y)
plt.show()

給定這樣的數(shù)據(jù)集,以y = 0.5 * X^2 + X + 2加入噪聲生成,其圖像



當(dāng)時(shí)用線(xiàn)性回歸獲取回歸方程,并用預(yù)測(cè)結(jié)果對(duì)比實(shí)際情況,得到

'''普通線(xiàn)性回歸'''
lin_reg = LinearRegression()
lin_reg.fit(X, y)
y_predict = lin_reg.predict(X)
plt.scatter(X, y)
plt.plot(x, y_predict, color='r')
plt.show()

紅色直線(xiàn)是通過(guò)回歸方程得到的所有樣本預(yù)測(cè)值連成的直線(xiàn),可以看出,一次項(xiàng)式的回歸曲線(xiàn)不能很好的完成預(yù)測(cè)。
此時(shí)在X樣本集中添加X(jué)^2作為另一個(gè)特征,并使用線(xiàn)性回歸獲得回歸函數(shù)。

'''對(duì)原始數(shù)據(jù)增加X(jué)^2項(xiàng)的多項(xiàng)式特征'''
#添加X(jué)^2項(xiàng)作為特征
X_new = np.hstack([X, X**2])
lin_reg2 = LinearRegression()
#將新的特征集合進(jìn)行線(xiàn)性回歸
lin_reg2.fit(X_new, y)
y_predict2 = lin_reg2.predict(X_new)
#繪制回歸曲線(xiàn)
plt.scatter(X, y)
plt.plot(np.sort(x), y_predict2[np.argsort(x)], color='r')
plt.show()

加入特征平方項(xiàng)作為新的特征后,得到新的回歸曲線(xiàn)明顯更好的貼合數(shù)據(jù)。這種向特征集中加入多次項(xiàng)產(chǎn)生新的特征集并訓(xùn)練模型的方式稱(chēng)為多項(xiàng)式回歸。

sklearn中的多項(xiàng)式回歸

線(xiàn)性回歸在sklearn中被封裝在linear_model包下的LinearRegression中。但LinearRegression不支持生成含有多項(xiàng)式的特征集,需要使用preprocessing包下的PolynomialFeatures進(jìn)行生成。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

'''創(chuàng)建數(shù)據(jù)'''
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
noise = np.random.normal(0, 1, size=100)
noise = noise.reshape(-1, 1)
y = 0.5 * X**2 + X + 2 + noise

plt.scatter(X, y)
plt.show()

'''使用sklearn中的PolynomialFeatures進(jìn)行多項(xiàng)式回歸'''
poly = PolynomialFeatures(degree=2)
poly.fit(X)
#獲得PolynomialFeatures得到的多項(xiàng)式
X_new = poly.transform(X)
#使用線(xiàn)性回歸對(duì)X_new進(jìn)行回歸
lin_reg = LinearRegression()
lin_reg.fit(X_new, y)
#根據(jù)線(xiàn)性回歸得到預(yù)測(cè)值
y_predect = lin_reg.predict(X_new)
#畫(huà)圖
plt.scatter(X, y)
plt.plot(np.sort(x), y_predect[np.argsort(x)], color='r')
plt.show()

創(chuàng)建PolynomialFeatures對(duì)象時(shí),傳入一個(gè)degree參數(shù),這個(gè)參數(shù)的意思是特征最高次項(xiàng)的項(xiàng)數(shù),這里傳入2表示最高產(chǎn)生特征數(shù)據(jù)平方項(xiàng)。得到回歸曲線(xiàn)


#查看得到的多項(xiàng)式矩陣
print(X_new)
#查看回歸得到的系數(shù)
print('X0到X2的系數(shù)分別是 %s' % lin_reg.coef_)
print('求得回歸曲線(xiàn)截距為 %s' % lin_reg.intercept_)

coef_表示每一項(xiàng)的系數(shù),intercept_表示截距,即y = ax + b中的b。

X0到X2的系數(shù)分別是 [[0.         1.07913561 0.58108989]]
求得回歸曲線(xiàn)截距為 [1.8117245]

可以看到平方項(xiàng)的系數(shù)0.58接近0.5,一次項(xiàng)的系數(shù)約為1,截距因?yàn)榧尤朐肼暭s為1.8117245,基本符合創(chuàng)造數(shù)據(jù)的方式。
假設(shè)數(shù)據(jù)有兩個(gè)特征時(shí),對(duì)其進(jìn)行多項(xiàng)式轉(zhuǎn)換得到的新的數(shù)據(jù)集什么樣?

#假設(shè)數(shù)據(jù)有兩個(gè)特征,查看PolynomialFeature生成的多項(xiàng)式矩陣
#創(chuàng)建特征數(shù)據(jù)集,一個(gè)5 * 2的矩陣
x = np.arange(1, 11)
X = x.reshape(-1, 2)
poly = PolynomialFeatures(degree=2)
X_new = poly.fit_transform(X)
print('X_new的大小為: ', X_new.shape)
print(X_new)

這里創(chuàng)建一個(gè)5 * 2的矩陣,PolynomialFeatures的degree傳入2.打印得到的新特征矩陣。

X_new的大小為:  (5, 6)
[[  1.   1.   2.   1.   2.   4.]
 [  1.   3.   4.   9.  12.  16.]
 [  1.   5.   6.  25.  30.  36.]
 [  1.   7.   8.  49.  56.  64.]
 [  1.   9.  10.  81.  90. 100.]]

新矩陣的大小是5 * 6,多了4列。第一列全是1,這是線(xiàn)性回歸的默認(rèn)操作將數(shù)據(jù)集第一列全部設(shè)為1,作為數(shù)據(jù)的X0。第2、3列是原始數(shù)據(jù)。第4列是第2列每個(gè)數(shù)的平方值,第5列是第2列和第3列的乘積,第6列則是第3列的平方值。
如果是degree取3的情況呢?

poly = PolynomialFeatures(degree=3)
X_new = poly.fit_transform(X)
print('X_new的大小為: ', X_new.shape)
print(X_new)

得到

X_new的大小為:  (5, 10)
[[   1.    1.    2.    1.    2.    4.    1.    2.    4.    8.]
 [   1.    3.    4.    9.   12.   16.   27.   36.   48.   64.]
 [   1.    5.    6.   25.   30.   36.  125.  150.  180.  216.]
 [   1.    7.    8.   49.   56.   64.  343.  392.  448.  512.]
 [   1.    9.   10.   81.   90.  100.  729.  810.  900. 1000.]]

第1列不用管,記第2列為X1,第3列為X2,第4列可以看出是X1^2,第5列X1 * X2,第6列X2^2,第7列 X1^3,第8列 X1^2 * X2,第9列X2^2 * X1,第10列X2^3。
可以看出,PolynomialFeatures會(huì)生成新特征矩陣的方式是生成每個(gè)特征從二次項(xiàng)到最高次項(xiàng)表達(dá)式和所有特征項(xiàng)數(shù)相加為最高次項(xiàng)的表達(dá)式。這樣可以表達(dá)各個(gè)特征在不同次項(xiàng)上的系數(shù),但可能造成模型的過(guò)擬合問(wèn)題。

使用PipeLine簡(jiǎn)化過(guò)程

之前的步驟中,依次使用PolynomialFeatures對(duì)原始數(shù)據(jù)進(jìn)行多項(xiàng)式擴(kuò)充,再使用LinearRegression進(jìn)行回歸。sklearn提供了管道——PipeLine可以很方便的將所有步驟簡(jiǎn)化為一步進(jìn)行。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

'''創(chuàng)建數(shù)據(jù)'''
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
noise = np.random.normal(0, 1, size=100)
noise = noise.reshape(-1, 1)
y = 0.5 * X**2 + X + 2 + noise

'''創(chuàng)建管道進(jìn)行多項(xiàng)式回歸'''
pipe = Pipeline(
    [
        ('poly', PolynomialFeatures(degree=2)), #傳入一個(gè)PolynomialFeatures
        ('std', StandardScaler()),  #StandardScaler對(duì)數(shù)據(jù)歸一化
        ('lin_reg', LinearRegression()) #傳入LinearRegression對(duì)象
    ]
)

print(pipe.fit(X, y))
pipe.fit(X, y)
y_predict = pipe.predict(X)

plt.scatter(X, y)
plt.plot(np.sort(x), y_predict[np.argsort(x)], color='r')
plt.show()

創(chuàng)造一個(gè)Pipeline實(shí)例,一次傳入PolynomialFeatures等需要使用的對(duì)象,之后調(diào)用fit即可完成訓(xùn)練。也能得到相應(yīng)的圖像,類(lèi)似之前的曲線(xiàn)。

過(guò)擬合和欠擬合

使用多項(xiàng)式回歸可以使模型更加復(fù)雜,提高模型的準(zhǔn)確度,但如果模型過(guò)于復(fù)雜,過(guò)于貼近訓(xùn)練數(shù)據(jù),導(dǎo)致對(duì)真實(shí)數(shù)據(jù)的預(yù)測(cè)準(zhǔn)確度并不高。通常使用學(xué)習(xí)曲線(xiàn)分別對(duì)訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)進(jìn)行擬合,使用得到的預(yù)測(cè)數(shù)據(jù)均值平方差作為衡量標(biāo)準(zhǔn),均值平方差越小預(yù)測(cè)越準(zhǔn)確。舉個(gè)例子:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

'''創(chuàng)建數(shù)據(jù)集'''
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
noise = np.random.normal(0, 1,size=100)
noise = noise.reshape(-1, 1)
y = 0.5 * X**2 + X + noise
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

def plot_learning_curve(algo, X_train, X_test, y_train, y_test):
    '''
    繪制學(xué)習(xí)曲線(xiàn)函數(shù)
    :param algo:
    :param X_train:
    :param X_test:
    :param y_train:
    :param y_test:
    :return:
    '''
    #train_score記錄不同項(xiàng)數(shù)下,模型對(duì)訓(xùn)練數(shù)據(jù)的準(zhǔn)確度
    #test_score記錄不同項(xiàng)數(shù)下,模型對(duì)測(cè)試數(shù)據(jù)的準(zhǔn)確度
    train_score = []
    test_score = []
    #訓(xùn)練數(shù)據(jù)有75個(gè),for循環(huán)通過(guò)訓(xùn)練數(shù)據(jù)個(gè)數(shù)從1到75,測(cè)試訓(xùn)練樣本數(shù)對(duì)準(zhǔn)確度的影響
    for i in range(1, 76):
        algo.fit(X_train[:i], y_train[:i])

        #分別對(duì)訓(xùn)練集和測(cè)試集預(yù)測(cè)
        y_train_predict = algo.predict(X_train[:i])
        y_test_predict = algo.predict(X_test)

        #記錄不同i值下的準(zhǔn)確度,使用mean_squared_error均值平方差衡量
        train_score.append(mean_squared_error(y_train[:i], y_train_predict))
        test_score.append(mean_squared_error(y_test, y_test_predict))

    #繪制學(xué)習(xí)曲線(xiàn)
    plt.plot([i for i in range(1, 76)], np.sqrt(train_score), label='train')
    plt.plot([i for i in range(1, 76)], np.sqrt(test_score), label='test')
    plt.legend()
    plt.axis([0, len(X_train) + 1, 0, 4])
    plt.show()

#單項(xiàng)式回歸時(shí)的學(xué)習(xí)曲線(xiàn)
lin_reg = LinearRegression()
plot_learning_curve(lin_reg, X_train, X_test, y_train, y_test)

使用單項(xiàng)式回歸得到的曲線(xiàn)



可以看到當(dāng)訓(xùn)練樣本數(shù)少時(shí),預(yù)測(cè)的準(zhǔn)確度會(huì)很差,方差很大,隨著訓(xùn)練樣本數(shù)增加,訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)的擬合程度趨于穩(wěn)定,方差在1.5上下,且訓(xùn)練樣本準(zhǔn)確度好于測(cè)試樣本。
接下來(lái)使用多項(xiàng)式回歸進(jìn)行測(cè)試,設(shè)degree為2

#degree=2時(shí)的學(xué)習(xí)曲線(xiàn)
pipe = Pipeline(
    [
        ('poly', PolynomialFeatures(degree=2)),
        ('std', StandardScaler()),
        ('lin_reg', LinearRegression()),
    ]
)
plot_learning_curve(pipe, X_train, X_test, y_train, y_test)

得到學(xué)習(xí)曲線(xiàn)



從均值方差上看,二項(xiàng)式回歸的方差明顯低于單項(xiàng)式回歸,說(shuō)明這個(gè)模型好于上一個(gè)模型,因?yàn)閯?chuàng)建的數(shù)據(jù)集就是一個(gè)二項(xiàng)式的基礎(chǔ)上添加噪聲而成的。
如果將項(xiàng)數(shù)提高會(huì)怎樣,設(shè)degree=10進(jìn)行測(cè)試。

#degree=10時(shí)的學(xué)習(xí)曲線(xiàn)
pipe2 = Pipeline(
    [
        ('poly', PolynomialFeatures(degree=10)),
        ('std', StandardScaler()),
        ('lin_reg', LinearRegression()),
    ]
)
plot_learning_curve(pipe2, X_train, X_test, y_train, y_test)

得到學(xué)習(xí)曲線(xiàn)



最終的方差確實(shí)降低了,但從訓(xùn)練集個(gè)數(shù)上看,只有當(dāng)訓(xùn)練樣本的個(gè)數(shù)到30+時(shí)才趨于穩(wěn)定,相較前兩幅圖,觀(guān)察訓(xùn)練樣本的個(gè)數(shù)和訓(xùn)練樣本的個(gè)數(shù)少的情況下測(cè)試集的預(yù)測(cè)準(zhǔn)確度,明顯不如二項(xiàng)式回歸,因此,對(duì)于訓(xùn)練數(shù)據(jù)而言,這個(gè)模型可以比較好的擬合,但對(duì)于測(cè)試樣本,這個(gè)擬合效果不如項(xiàng)數(shù)稍低時(shí)的效果。這就是所謂的過(guò)擬合狀態(tài)。需要對(duì)模型進(jìn)行范化。

交叉驗(yàn)證

通過(guò)訓(xùn)練數(shù)據(jù)得到的模型,如果在測(cè)試數(shù)據(jù)上偏差較大,這種情況稱(chēng)為對(duì)訓(xùn)練數(shù)據(jù)過(guò)擬合,但如果模型能很好的擬合測(cè)試樣本,也不能說(shuō)模型是準(zhǔn)確的,因?yàn)榭赡艽嬖跍y(cè)試數(shù)據(jù)的過(guò)擬合。為了得到可靠的模型,常用以下方法。
之前只是將數(shù)據(jù)分為訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)兩部分,改進(jìn)一下可以將數(shù)據(jù)分為三份,分別是訓(xùn)練數(shù)據(jù)、驗(yàn)證數(shù)據(jù)和測(cè)試數(shù)據(jù)。訓(xùn)練數(shù)據(jù)訓(xùn)練出最初的模型,之后使用驗(yàn)證數(shù)據(jù)進(jìn)行驗(yàn)證和調(diào)參,最后使用測(cè)試數(shù)據(jù)測(cè)試。注意,只有測(cè)試數(shù)據(jù)不參與模型訓(xùn)練。 但這樣的方式也存在一個(gè)問(wèn)題,數(shù)據(jù)的劃分存在隨機(jī)性,怎么能夠?qū)㈦S機(jī)性造成的影響降到最低,應(yīng)當(dāng)使用交叉驗(yàn)證的方式。



如圖給出交叉驗(yàn)證的原理,將訓(xùn)練數(shù)據(jù)(可以理解為訓(xùn)練數(shù)據(jù)和驗(yàn)證數(shù)據(jù)的總稱(chēng))分為k份,通過(guò)不同的組合方式訓(xùn)練出多個(gè)模型。通過(guò)對(duì)每個(gè)模型進(jìn)行測(cè)試,計(jì)算當(dāng)前參數(shù)下模型準(zhǔn)確度的最高的進(jìn)行作為最優(yōu)模型,這個(gè)衡量標(biāo)準(zhǔn)通常選用準(zhǔn)確度均值作為標(biāo)準(zhǔn)進(jìn)行衡量。
加入現(xiàn)在訓(xùn)練一個(gè)KNN模型,通過(guò)for循環(huán)對(duì)參數(shù)進(jìn)行調(diào)整

import numpy as np
import sklearn.datasets as dataset
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score

'''加載數(shù)據(jù)'''
data = dataset.load_digits()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4,random_state=666)

'''通過(guò)for循環(huán)進(jìn)行調(diào)試'''
best_score = -1
best_k = 0
best_p = 0
for k in range(2, 10):
    for p in range(1, 6):
        knn_clf = KNeighborsClassifier(n_neighbors=k, p=p, weights='distance')
        knn_clf.fit(X_train, y_train)
        score = knn_clf.score(X_test, y_test)

        if score > best_score:
            best_score = score
            best_k = k
            best_p = p
print('best K is ', best_k)
print('best p is ', best_p)
print('best score is ', best_score)

得到的最佳的k,p和準(zhǔn)確度為

best K is  3
best p is  4
best score is  0.9860917941585535

使用交叉驗(yàn)證獲取最佳參數(shù)

'''通過(guò)交叉驗(yàn)證進(jìn)行調(diào)參'''
knn_clf = KNeighborsClassifier()
scores = cross_val_score(knn_clf, X_train, y_train)
print(scores)

#放入for循環(huán)中獲取最佳參數(shù)
best_score = -1
best_k = 0
best_p = 0
for k in range(2, 10):
    for p in range(1, 6):
        knn_clf = KNeighborsClassifier(n_neighbors=k, p=p, weights='distance')
        knn_clf.fit(X_train, y_train)
        scores = cross_val_score(knn_clf, X_train, y_train)
        score = np.mean(scores)

        if score > best_score:
            best_score = score
            best_k = k
            best_p = p
print('best K is ', best_k)
print('best p is ', best_p)
print('best score is ', best_score)

交叉驗(yàn)證在sklearn中被封裝在model_selection包下的cross_val_score中,傳入?yún)?shù)有,通過(guò)第一個(gè)print()函數(shù),得到的輸出

[0.98895028 0.97777778 0.96629213]

可見(jiàn)當(dāng)前數(shù)據(jù)被分為三份,通過(guò)交叉驗(yàn)證的方式得到的每個(gè)組合的準(zhǔn)確度為0.98895028、0.97777778和0.96629213。3是默認(rèn)的分割分?jǐn)?shù),可以通過(guò)cv參數(shù)調(diào)整數(shù)據(jù)被分割的份數(shù)。
之后通過(guò)for循環(huán)的方式指定KNN參數(shù)并使用交叉驗(yàn)證獲取準(zhǔn)確度平均值最高者為最優(yōu)的模型得到結(jié)果

best K is  2
best p is  2
best score is  0.9823599874006478

和之前單純用for循環(huán)的結(jié)果不同了,best score不如之前高,但由于交叉驗(yàn)證的準(zhǔn)確度計(jì)算方式的不同和使用交叉驗(yàn)證盡可能避免隨機(jī)性帶來(lái)的影響,可能該模型的準(zhǔn)確性在實(shí)際情況下會(huì)更好。

偏差與方差的權(quán)衡

在訓(xùn)練模型的過(guò)程中,存在兩種誤差,一種是偏差,一種是方差。



偏差表示實(shí)際數(shù)據(jù)和預(yù)測(cè)數(shù)據(jù)之間的差值,方差則反映了數(shù)據(jù)的離散程度。以這張圖為例,紅點(diǎn)表示預(yù)測(cè)值,左上角明顯的樣本比較集中,而且分布在預(yù)測(cè)值上,這種情況可以視為方差和偏差都很小,而右上的數(shù)據(jù)雖然圍繞在預(yù)測(cè)值,但彼此之間并不集中,屬于低偏差高方差的情況。左下角雖然集體偏離預(yù)測(cè)值,但樣本集中,屬于高偏差低方差。而右下距離預(yù)測(cè)值又遠(yuǎn)還又分散,就是偏差和方差都很高的情況。
訓(xùn)練模型的誤差來(lái)源:


偏差

偏差的根本原因是對(duì)問(wèn)題本身的假設(shè)不正確,比如對(duì)非線(xiàn)性的數(shù)據(jù)進(jìn)行線(xiàn)性回歸,訓(xùn)練出的模型多半不會(huì)有很好的效果。
這種情況也就是欠擬合。

方差

對(duì)于方差而言,一點(diǎn)點(diǎn)數(shù)據(jù)擾動(dòng)可能會(huì)造成模型的巨大變化,根本原因就是模型過(guò)度復(fù)雜,太依賴(lài)數(shù)據(jù),也就是所謂的過(guò)擬合。常見(jiàn)的比如KNN算法就是一種對(duì)方差特別敏感的模型。
在機(jī)器學(xué)習(xí)中,有一些算法本質(zhì)上就是高方差的算法,非參數(shù)學(xué)習(xí)算法通常都是高方差算法,因?yàn)榉菂?shù)學(xué)習(xí)不對(duì)數(shù)據(jù)樣本進(jìn)行假設(shè)。常見(jiàn)的KNN、決策樹(shù)。
參數(shù)學(xué)習(xí)算法通常是高偏差的算法,對(duì)數(shù)據(jù)樣本有較強(qiáng)的假設(shè),例如線(xiàn)性回歸等。
訓(xùn)練模型調(diào)參的過(guò)程實(shí)際上就是調(diào)整偏差和方差的過(guò)程。以KNN調(diào)整k個(gè)數(shù)為例,k越小說(shuō)明模型越復(fù)雜,對(duì)應(yīng)的方差也就越大,偏差越小,比如當(dāng)k=1時(shí),實(shí)際上就是比那種類(lèi)型的個(gè)數(shù)多而已,但當(dāng)數(shù)據(jù)發(fā)生很小的變化,分類(lèi)模型可能就會(huì)變動(dòng)。當(dāng)k大時(shí),這種情況就會(huì)明顯減少。

方差和偏差是矛盾的,一般而言降低方差的同時(shí),模型偏差會(huì)變大。而降低偏差的同時(shí),方差會(huì)變大。
機(jī)器學(xué)習(xí)的主要挑戰(zhàn)來(lái)自于方差。
解決高方差的常用手段

①降低模型復(fù)雜度
②減少數(shù)據(jù)維度和噪音
③增加樣本數(shù)
④使用驗(yàn)證集

模型范化

模型泛化也叫正則化,目的是降低模型的復(fù)雜度。常見(jiàn)的方法有嶺回歸、Lasso正則化和Elastic Net正則化。
正則化項(xiàng)可以理解為是接在損失函數(shù)后的額外項(xiàng),可以看做是損失函數(shù)的懲罰項(xiàng),懲罰項(xiàng)對(duì)損失函數(shù)的某些參數(shù)進(jìn)行限制。
回想線(xiàn)性回歸中常見(jiàn)的以誤差平方和的平均值作為代價(jià)函數(shù)的情景。



通過(guò)上面的代價(jià)函數(shù)(有時(shí)會(huì)使用1/2m作為系數(shù))可以最終求得一個(gè)代價(jià)函數(shù)值最小的模型。為了提升模型的范化能力,線(xiàn)性回歸中常用嶺回歸和Lasso回歸對(duì)線(xiàn)性回歸進(jìn)行正則化操作。給出嶺回歸和Lasso回歸的損失函數(shù)。



λ是正則化系數(shù),它控制正則化項(xiàng)的占比,對(duì)模型范化而言很重要。
嶺回歸

為了更好的理解,自建創(chuàng)建一個(gè)數(shù)據(jù)集,并使用普通的多項(xiàng)式回歸和嶺回歸進(jìn)行對(duì)比。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import  train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.linear_model import Ridge

'''創(chuàng)建數(shù)據(jù)集'''
np.random.seed(42)
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
noise = np.random.normal(0, 1,size=100)
noise = noise.reshape(-1, 1)
y = 0.5 * X + 3 + noise
np.random.seed(666)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

創(chuàng)建一個(gè)數(shù)據(jù)集,y = 0.5x + 3并加入噪聲。接下來(lái)封裝幾個(gè)需要用到的函數(shù)。

def PolynomialRegression(degree):
    '''
    多項(xiàng)式回歸,返回PipeLine對(duì)象
    :param degree: 
    :return: 
    '''
    return Pipeline(
        [
            ('poly', PolynomialFeatures(degree=degree)),
            ('std', StandardScaler()),
            ('lin_reg', LinearRegression())
        ]
    )

def plot_model(model):
    '''
    畫(huà)出回歸模型預(yù)測(cè)值形成的模型曲線(xiàn)
    :param model: 
    :return: 
    '''
    X_plot = np.linspace(-3, 3, 100).reshape(-1, 1)
    y_plot = model.predict(X_plot)
    plt.scatter(x, y)
    plt.plot(X_plot[:, 0], y_plot, color='r')
    plt.axis([-3, 3, 0, 6])
    plt.show()

'''使用degree=20的多項(xiàng)式回歸'''
poly = PolynomialRegression(degree=20)
poly.fit(X_train, y_train)
y_test_predict = poly.predict(X_test)
mse = mean_squared_error(y_test, y_test_predict)
print('with linear regression the mse is ', mse)
plot_model(poly)

先使用degree=20的多項(xiàng)式回歸,得到的模型必然是個(gè)過(guò)擬合的模型,因此對(duì)測(cè)試樣本的預(yù)測(cè)效果必然不好,根據(jù)得到的結(jié)果也能看出來(lái)。

with linear regression the mse is  167.9401086326601

接下來(lái)使用嶺回歸進(jìn)行模型范化,在sklearn的linear_model下的Ridge類(lèi)中已經(jīng)對(duì)嶺回歸進(jìn)行封裝。對(duì)之前定義的回歸函數(shù)進(jìn)行修改,得到嶺回歸適合的函數(shù)

'''嶺回歸'''
def RidgeRegression(degree, alpha):
    return Pipeline(
        [
            ('poly', PolynomialFeatures(degree=degree)),
            ('std', StandardScaler()),
            ('lin_reg', Ridge(alpha=alpha))
        ]
    )

多傳入一個(gè)alpha參數(shù),對(duì)應(yīng)的就是之前嶺回歸損失函數(shù)的λ正則化系數(shù)。測(cè)試下λ取0.0001時(shí)的嶺回歸模型。

ridge1 = RidgeRegression(degree=20, alpha=0.0001)
ridge1.fit(X_train, y_train)
y_test_predict = ridge1.predict(X_test)
mse = mean_squared_error(y_test_predict, y_test)
print('with ridge regression and alpha = 0.0001 the mse is ', mse)
plot_model(ridge1)

得到的MSE和回歸曲線(xiàn)

with ridge regression and alpha = 0.0001 the mse is  1.3233492753942047

從測(cè)試數(shù)據(jù)預(yù)測(cè)值的MSE可以看出,模型的準(zhǔn)確度大幅提升,而且回歸曲線(xiàn)也不那么突兀了。
如果調(diào)大正則化系數(shù)會(huì)有什么影響?

ridge2 = RidgeRegression(20, 1)
ridge2.fit(X_train, y_train)
y_test_predict = ridge2.predict(X_test)
mse = mean_squared_error(y_test_predict, y_test)
print('with ridge regression and alpha = 1 the mse is ', mse)
plot_model(ridge2)

將正則化系數(shù)改為1進(jìn)行測(cè)試,得到

with ridge regression and alpha = 1 the mse is  1.1888759304218468

曲線(xiàn)上看又平滑了一些,但預(yù)測(cè)的效果不如之前但也相差不大,因此,正則化系數(shù)的變動(dòng)對(duì)曲線(xiàn)的影響從形成的圖像上就能看出來(lái)。

嶺回歸的原理

嶺回歸也稱(chēng)L2正則化。一般認(rèn)為參數(shù)較小的模型簡(jiǎn)單,適應(yīng)不同數(shù)據(jù)集的效果更好,可以一定程度上避免過(guò)擬合。
證明過(guò)程:


Lasso回歸

另一種常用的模型范化方式稱(chēng)為L(zhǎng)asso回歸,也稱(chēng)L1正則化。從之前給的損失函數(shù)看,它和嶺回歸的不同就是損失函數(shù)后面額外項(xiàng)不同。它不僅可以解決過(guò)擬合問(wèn)題,而且可以在參數(shù)縮減過(guò)程中,將一些重復(fù)的沒(méi)必要的參數(shù)直接縮減為零,也就是完全減掉了。這可以達(dá)到提取有用特征的作用。
記原始的代價(jià)函數(shù)為J0,L1正則化的代價(jià)函數(shù)J = J0 + λ∑|θi|,令L= λ∑|θi|,則代價(jià)函數(shù)J = J0 + L。此時(shí)回歸的任務(wù)變?yōu)樵贚的約束下求出代價(jià)函數(shù)J的最小值對(duì)應(yīng)的解。
假設(shè)在二維的情況下,L = |θ1| + |θ2|,將J0和L分別體現(xiàn)在坐標(biāo)軸。



圖中等值線(xiàn)就是J0,方形對(duì)應(yīng)L。二維情況下,L有四個(gè)角,當(dāng)維數(shù)增加,L將會(huì)升級(jí)為體,產(chǎn)生更多的角,J0與這些角接觸的概率遠(yuǎn)遠(yuǎn)大于與L其他部分接觸的概率。在角的位置上,會(huì)有系數(shù)為0,例如在這張圖上,β1為0時(shí)可以與等值線(xiàn)有交點(diǎn),這樣的交點(diǎn)就是Lasso代價(jià)函數(shù)的最小值對(duì)應(yīng)的解。因?yàn)棣?為零,二維情況下,模型只需要考慮β2即可,簡(jiǎn)化了模型的難度。
當(dāng)這種思想用在特征提取時(shí),部分特征權(quán)重為0,即特征將不被考慮,以此達(dá)到特征提取的目的。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import  train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.linear_model import Lasso

'''創(chuàng)建數(shù)據(jù)集'''
np.random.seed(42)
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
noise = np.random.normal(0, 1,size=100)
noise = noise.reshape(-1, 1)
y = 0.5 * X + 3 + noise
np.random.seed(666)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

def PolynomialRegression(degree):
    '''
    普通多項(xiàng)式回歸
    :param degree: 
    :return: 
    '''
    return Pipeline(
        [
            ('poly', PolynomialFeatures(degree=degree)),
            ('std', StandardScaler()),
            ('lin_reg', LinearRegression())
        ]
    )

def plot_model(model):
    X_plot = np.linspace(-3, 3, 100).reshape(-1, 1)
    y_plot = model.predict(X_plot)
    plt.scatter(x, y)
    plt.plot(X_plot[:, 0], y_plot, color='r')
    plt.axis([-3, 3, 0, 6])
    plt.show()

'''普通的多項(xiàng)式回歸測(cè)試模型'''
poly = PolynomialRegression(degree=20)
poly.fit(X_train, y_train)
y_test_predict = poly.predict(X_test)
mse = mean_squared_error(y_test, y_test_predict)
print('with linear regression the mse is ', mse)
plot_model(poly)

還是嶺回歸的數(shù)據(jù),首先使用普通的多項(xiàng)式回歸進(jìn)行測(cè)試。在degree=20的情況下,還是過(guò)擬合嚴(yán)重。

with linear regression the mse is  167.9401086326601

接下來(lái)引入Lasso回歸,首先定義Lasso回歸的函數(shù)

def LassoRegression(degree, alpha):
    return Pipeline(
        [
            ('poly', PolynomialFeatures(degree=degree)),
            ('std', StandardScaler()),
            ('lin_reg', Lasso(alpha=alpha))
        ]
    )

'''正則化系數(shù)0.01'''
lasso1 = LassoRegression(20, 0.01)
lasso1.fit(X_train, y_train)
y_predict = lasso1.predict(X_test)
mse = mean_squared_error(y_predict, y_test)
print('with lasso regression and alpha = 0.01 the mse is ', mse)
plot_model(lasso1)

sklearn的Lasso在linear_model下,先使用正則化系數(shù)0.01,得到的回歸結(jié)果

with lasso regression and alpha = 0.01 the mse is  1.1496080843259968

從預(yù)測(cè)的準(zhǔn)確性和生成的圖像可以看出,模型已經(jīng)得到很大的優(yōu)化。
接下來(lái)測(cè)試正則化系數(shù)過(guò)大造成模型欠擬合。

'''正則化系數(shù)過(guò)大'''
lasso2 = LassoRegression(20, 1)
lasso2.fit(X_train, y_train)
y_predict = lasso2.predict(X_test)
mse = mean_squared_error(y_predict, y_test)
print('with lasso regression and alpha = 1 the mse is ', mse)
plot_model(lasso2)

得到結(jié)果

with lasso regression and alpha = 1 the mse is  1.8408939659515595

當(dāng)正則化系數(shù)過(guò)小,會(huì)導(dǎo)致模型擬合程度升高,范化能力下降。

'''正則化系數(shù)0.0001'''
lasso1 = LassoRegression(20, 0.0001)
lasso1.fit(X_train, y_train)
y_predict = lasso1.predict(X_test)
mse = mean_squared_error(y_predict, y_test)
print('with lasso regression and alpha = 0.0001 the mse is ', mse)
plot_model(lasso1)

得到結(jié)果

with lasso regression and alpha = 0.0001 the mse is  1.3773861509225418

從預(yù)測(cè)準(zhǔn)確度到曲線(xiàn)的復(fù)雜程度都有所降低。
因此得出正則化系數(shù)與Lasso回歸的關(guān)系。正則化系數(shù)越大,范化能力越強(qiáng),正則化系數(shù)越小,范化能力越弱。

ElasticNet回歸

ElasticNet回歸結(jié)合了嶺回歸和Lasso回歸。



r是一個(gè)0到1之間的權(quán)值,表示了嶺回歸和Lasso回歸各自占的比重。r=0即為L(zhǎng)1正則,r=1即為L(zhǎng)2正則。
sklearn中在linear_model的ElasticNet類(lèi)中對(duì)其進(jìn)行了封裝。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import  train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.linear_model import ElasticNet

'''創(chuàng)建數(shù)據(jù)集'''
np.random.seed(42)
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
noise = np.random.normal(0, 1,size=100)
noise = noise.reshape(-1, 1)
y = 0.5 * X + 3 + noise
np.random.seed(666)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

def PolynomialRegression(degree):
    '''
    普通多項(xiàng)式回歸
    :param degree:
    :return:
    '''
    return Pipeline(
        [
            ('poly', PolynomialFeatures(degree=degree)),
            ('std', StandardScaler()),
            ('lin_reg', LinearRegression())
        ]
    )

def plot_model(model):
    X_plot = np.linspace(-3, 3, 100).reshape(-1, 1)
    y_plot = model.predict(X_plot)
    plt.scatter(x, y)
    plt.plot(X_plot[:, 0], y_plot, color='r')
    plt.axis([-3, 3, 0, 6])
    plt.show()

'''普通的多項(xiàng)式回歸測(cè)試模型'''
poly = PolynomialRegression(degree=20)
poly.fit(X_train, y_train)
y_test_predict = poly.predict(X_test)
mse = mean_squared_error(y_test, y_test_predict)
print('with linear regression the mse is ', mse)
plot_model(poly)

def ElasticNetRegression(degree, alpha,l1_ratio):
    return Pipeline(
        [
            ('poly', PolynomialFeatures(degree=degree)),
            ('std', StandardScaler()),
            ('lin_reg', ElasticNet(alpha=alpha, l1_ratio=l1_ratio))
        ]
    )

'''ElasticNet回歸'''
elastic_net = ElasticNetRegression(degree=20, alpha=0.01, l1_ratio=0.2)
elastic_net.fit(X_train, y_train)
y_test_predict = elastic_net.predict(X_test)
mse = mean_squared_error(y_test_predict, y_test)
print('with degree = 20, alpha = 0.01 and l1_ratio = 0.2 mse is ', mse)
plot_model(elastic_net)

得到

with degree = 20, alpha = 0.01 and l1_ratio = 0.2 mse is  1.2011937623442832
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容