正則化線性迴歸的方差與偏差(吳恩達課程Octave程式碼用Python實現)
詳細程式碼參考 ofollow,noindex">github
利用正則化線性迴歸模型來了解偏差和方差的特徵
例項:
首先根據資料建立線性迴歸模型,模型能夠根據水庫液位的變化來預測大壩的排水量,然後通過調整引數等方法來學習偏差和方差的一些特性。
1.概念
偏差:度量了學習演算法的期望預測與真實結果的偏離程度,即刻畫了學習演算法本身的擬合能力;
方差:度量了同樣大小的訓練集的變動所導致的學習效能的變化,即刻畫了資料擾動所造成的影響(模型的穩定性)。
2.構建正則化線性模型
(1)載入資料
開啟資料集 ex5data1.mat
,資料集包含了以下內容:
名稱 | 維度 |
---|---|
X | (12, 1) |
y | (12, 1) |
Xtest | (21, 1) |
ytest | (21, 1) |
Xval | (21, 1) |
yval | (21, 1) |
其中:
X,y
是 訓練集 ,用來訓練模型;
Xval,yval
是 交叉驗證集 ,主要用來確定模型中的超引數,如正則化引數 lambda
;
Xtest,ytest
是 測試集 ,主要評估模型的泛化能力。
(2)將 X,y
視覺化
橫軸表示水位的變化,縱軸表示水流量,單純的線性迴歸也能計算出模型,但那樣做偏差特別大,擬合程度很差,即出現 欠擬合 。

Plot X and y
def plotXY(self, x, y): plt.scatter(x, y, marker='x', color='r') plt.xlabel('Change in water level(x)') plt.ylabel('Water flowing out of the dam (y)') plt.show()
(3)計算正則化線性迴歸損失函式J
損失函式公式如下,注意正則化項是 從下標1 開始計算的,lambda就是正則化係數,它控制模型複雜度的"懲罰"力度。

Cost J
(4)計算正則化線性迴歸梯度Gradient
損失函式J對 theta0和theta1 的偏導數定義如下,即要計算的梯度公式。

Gradient
def linearRegCostFunction(self, theta, x, y, lamda): m = x.shape[0] theta = theta.reshape((x.shape[1], 1)) cost = np.sum((x.dot(theta)-y)**2)/(2*m) regular = lamda/(2*m)*np.sum(theta[1::]**2) J = cost + regular return J def linearRegGradient(self, theta, x, y, lamda): m = y.shape[0] theta = theta.reshape((x.shape[1], 1)) grad = np.zeros((x.shape[1], 1)) grad[0] = 1/m*(x[:, 0:1].T.dot(x.dot(theta)-y)) grad[1::] = 1/m*(x[:, 1::].T.dot(x.dot(theta)-y)) + lamda/m*theta[1::] return grad
(5)擬合線性迴歸
當我們根據上述公式計算損失函式和梯度得到正確的結果後,我們利用之前提到的 scipy.optimize
中的 minimize
函式來計算最優的 theta0
和 theta1
,計算得到 theta
的結果後,將 lambda
的值設為 0 ,因為當前模型只有 theta1
和 theta2
兩個值,模型很簡單,沒必要設定正則化項。
將得到的 theta
值同 X
相乘,得到預測的 y_predict
,將結果進行繪製,得到擬合的結果,如下圖。
可以看出得到的結果並不好,在接下來的內容裡,我們逐步討論。

Fit X and y
def plotTrainingLine(self, x, y): theta = self.trainLinearReg(x, y, 0) plt.scatter(self.x, self.y, marker='+', color='r') plt.plot(self.x, x.dot(theta), '--', linewidth=2) plt.xlabel('Change in water level (x)') plt.ylabel('Water flowing out of the dam (y)') plt.legend(['Trained', 'Original']) plt.show()
3.方差和偏差
(1)繪製學習曲線
繪製 訓練集和交叉驗證集的誤差 與 訓練樣本數量 之間的學習曲線, 注意: 訓練集誤差和交叉驗證集誤差都沒有計算正則化項,後面我們單獨討論正則化係數對誤差造成的影響。
觀察下圖,隨著樣本數量逐漸增加,訓練集和交叉驗證集之間的誤差仍然是較大的,這就反應了模型的 高偏差問題 ,即 欠擬合 。
因為模型太簡單了,不能很好的擬合我們的資料,接下來我們將訓練模型修改,建立一個8次多項式。

Learning curve for Linear Reg
def learningCurve(self, x, y, xval, yval, lamda): m = x.shape[0] error_train = np.zeros((m, 1)) error_val = np.zeros((m, 1)) print("Training Examples\tTrain Error\tCross Validation Error\n") for i in range(m): theta = self.trainLinearReg(x[:1+i, :], y[:1+i], lamda) error_train[i] = self.linearRegCostFunction(theta, x[:1+i, :], y[:1+i], 0) error_val[i] = self.linearRegCostFunction(theta, xval, yval, 0) print("\t\t%d\t\t\t%f\t\t%f\n"%(i, error_train[i], error_val[i])) return [error_train, error_val] def plotLinerRCurve(self): error_train, error_val = self.learningCurve(self.x_plus_one, self.y, self.xval_plus_one, self.yval, 0) plt.xlim([0, 13]) plt.ylim([0, 150]) plt.plot([i for i in range(12)], error_train, 'r') plt.plot([i for i in range(12)], error_val, 'b') plt.title('Learning curve for linear regression') plt.xlabel('Number of training examples') plt.ylabel('Error') plt.legend(['Train', 'Cross Validation']) plt.show()
(2)建立多項式迴歸模型
由於一次線性模型太簡單,導致欠擬合,我們新增更多的"特徵",做一個 八 次多項式。 具體最高項應該設定成幾次這個問題,也是沒有定式,只能說多嘗試,找到較為合適的最高次數。
參考程式碼:
def polyFeatures(self, x, p): x_ploy = np.zeros((np.size(x), p), np.float32)# (12, 8) m = np.size(x)# 12 for i in range(m): for j in range(p): x_ploy[i, j] = x[i]**(j+1) return x_ploy
(3)繪製多項式迴歸模型擬合曲線
根據上一部分得到的多項式,因為同樣是解決一個線性迴歸的問題,所以之前的損失函式和梯度計算函式仍可使用。
接下來利用優化函式計算得到最優的 theta
值,繪製擬合曲線,當設定正則化係數 lambda=0 的時候,意味這對模型複雜度沒有任何處理,得到下圖。
可以看出,我們的模型訓練的非常好,基本穿過了每一個點,訓練誤差肯定很小,但是在一些極值外,函式迅速的上升和下降,這就意味這我們設計的模型 過擬合 了,雖然對訓練資料擬合很好,但是不具有很好的泛化能力,也意味著模型不穩定。

Polynomial Reg Fit with lambda=0
觀察下 lambda=0 的情況下,訓練誤差和交叉驗證誤差的曲線,非常明顯,訓練誤差幾乎為0,而交叉驗證誤差卻很大,訓練集和交叉驗證集之間的空隙充分反應了模型的 高方差問題 。

Polynomial Reg Learning curve with lambda=0
此時我們讓正則化係數為1,再重複上面的試驗,我們再來看下你和曲線和學習曲線的圖形。
一目瞭然,多項式模型很好的擬合了資料,訓練誤差和交叉驗證集誤差曲線都隨著樣本數量的增加都 收斂於一個較小的值 ,因此我們認為當 lambda=1
時,得到的模型沒有 高偏差和高方差的問題 ,實際上也是模型在方差和偏差之間進行了很好的 折中 。

Polynomial Reg Fit with lambda=1

Learning curve with lambda=1
def plotFit(self, x, mu, sigma, theta, p): x = np.arange(np.min(x) - 15, np.max(x) + 25, 0.05) x = x.reshape((-1, 1)) x_poly = self.polyFeatures(x, p) x_poly = x_poly - mu x_poly = x_poly/sigma x_poly = np.hstack([np.ones((x_poly.shape[0], 1)), x_poly]) plt.plot(x, x_poly.dot(theta), '--', linewidth=2) plt.title('Polynomial Regression Fit (lambda=0.00)') plt.xlabel('Change in water level (x)') plt.ylabel('Water flowing out of the dam (y)') plt.show()
(4)利用交叉驗證集選擇lambda
通過上面的試驗,我們可以知道,lambda明顯的影響著多項式正則化迴歸的訓練誤差和交叉驗證誤差。 當lambda為0甚至很小的時候,模型可以很好的擬合訓練集,不具備好的泛化能力,當lambda很大的時候,模型又不能很好的擬合數據,出現欠擬合問題 ,那麼到底怎樣選擇一個lambda的值呢?
我們根據上面的例項,選擇10個lambda的值,分別繪製每個lambda對應的訓練誤差和交叉驗證集誤差,
lambda_vec = {0; 0:001; 0:003; 0:01; 0:03; 0:1; 0:3; 1; 3; 10}
繪製得到下圖, 從圖中我們可以看出最好的lambda值出現在 3 附近,此時測試誤差值大約3.8599,結果已經相當不錯了。

Find the best lambda
def validationCurveForLamdas(self, x, y, xval, yval, lamda_vec): error_train = np.zeros((len(lamda_vec), 1)) error_val = np.zeros((len(lamda_vec), 1)) print("Lambda\t\tTrain Error\tValidation Error\n") for i in range(len(lamda_vec)): lamda = lamda_vec[i] theta = self.trainLinearReg(x, y, lamda) error_train[i] = self.linearRegCostFunction(theta, x, y, 0) error_val[i] = self.linearRegCostFunction(theta, xval, yval, 0) print("\t\t%d\t\t\t%f\t\t%f\n" % (i, error_train[i], error_val[i])) return [error_train, error_val]