吳恩達機器學習課程-作業5-Bias vs Variance(python實現)

Machine Learning(Andrew) ex4-Regularized Linear Regression and Bias v.s. Variance

椰汁筆記

Regularized Linear Regression

  • 1.1 Visualizing the dataset

對於一個機器學習的數據,通常會被分爲三部分訓練集、交叉驗證集和測試集。訓練集用於訓練參數,交叉驗證集用於選擇模型參數,測試集用於評價模型。
這裏的作業數據,已經給我們劃分好了

    data = sio.loadmat("ex5data1.mat")
    X = data["X"]
    y = data["y"]
    Xval = data["Xval"]
    yval = data["yval"]
    Xtest = data["Xtest"]
    ytest = data["ytest"]

我們使用線性迴歸擬合的是訓練集數據,因此可視化只用可視化訓練集的數據

    plt.subplot(2, 2, 1)
    plt.scatter(X, y, marker='x', c='r')
    plt.xlabel("Change in water level (x)")
    plt.ylabel("Water flowing out of the dam (y)")
    plt.title("linear regression")
    plt.xlim((-50, 40))
    plt.ylim((-10, 40))
    plt.show()

數據看起來並不是那麼符合線性規律hhh,感覺有點二次函數那味
在這裏插入圖片描述

  • 1.2 Regularized linear regression cost function
    線性迴歸在作業1中已經用到了,那裏沒有正規化,可能導致隨着特徵的增多出現過擬合現象。
    J(θ)=12m(i=1mhθ(x(i)y(i))2)+λ2m(j=1nθj2) \mathit{J}(\theta) = \frac{1}{2m} (\sum_{i=1}^{m}h_\theta(x^{(i)}-y^{(i)})^{2})+\frac{\lambda}{2m}(\sum_{j=1}^{n}\theta_j^2)
    直接在之前的損失值計算中加入懲罰項,注意不懲罰theta0
def cost(theta, X, y, l):
    m = X.shape[0]
    part1 = np.mean(np.power(X.dot(theta) - y.ravel(), 2)) / 2
    part2 = (l / (2 * m)) * np.sum(np.delete(theta * theta, 0, axis=0))
    return part1 + part2

將theta全部設置爲1,lambda設置爲1,進行測試

    theta = np.ones((2,))
    X = np.insert(X, 0, 1, axis=1)
    print(cost(theta, X, y, 1))#303.9931922202643
  • 1.3 Regularized linear regression gradient

J(θ)θ0=1mi=1m(hθ(x(i))y(i))xj(i),j=0J(θ)θj=(1mi=1m(hθ(x(i))y(i))xj(i))+λmθj,j1 \frac{\partial J(\theta)}{\partial\theta_0}=\frac{1}{m}\sum_{i=1}^{m}(h_\theta(x^{(i)})-y^{(i)})x_j^{(i)},j=0 \\\frac{\partial J(\theta)}{\partial\theta_j}=(\frac{1}{m}\sum_{i=1}^{m}(h_\theta(x^{(i)})-y^{(i)})x_j^{(i)})+\frac{\lambda}{m}\theta_j,j\ge1
同樣注意對j=0的情況額處理

def gradient(theta, X, y, l):
    m = X.shape[0]
    part1 = X.T.dot(X.dot(theta) - y.ravel()) / m
    part2 = (l / m) * theta
    part2[0] = 0
    return part1 + part2

將theta全部設置爲1,lambda設置爲1,進行測試

   theta = np.ones((2,))
    X = np.insert(X, 0, 1, axis=1)
    print(gradient(theta, X, y, 1))#[-15.30301567 598.25074417]
  • 1.4 Fitting linear regression
    繼續使用scipy.optimize.minimize()去做優化,記得向X添加x0,這裏的懲罰參數lambda取0,因爲這裏沒有高次項。
    theta = np.ones((2,))
    X = np.insert(X, 0, 1, axis=1)
    res = opt.minimize(fun=cost, x0=theta, args=(X, y, 0), method="TNC", jac=gradient)

接着可視化擬合結果

	plt.plot([i for i in range(-50, 40, 1)], [res.x[0] + res.x[1] * i for i in range(-50, 40, 1)])

其實效果並不好,後面我們就需要通過構造高次項來增加特徵,實現非線性的更好。
在這裏插入圖片描述


Bias-variance

我們理解Bias和variance只需要記住,bias是欠擬合,variance是過擬合。這樣接着看就不會暈。
在這裏插入圖片描述
這張圖也很好地說明了這兩個問題,當多項式的次數較少,顯然是不可能很好的擬合,因此對於訓練集和交叉驗證集的誤差都很大,這就是bias problem也就是欠擬合問題。當多項式次數較高時,擬合效果會非常好,但是過度擬合導致應用到更廣的數據的效果不好,導致訓練集誤差小但交叉驗證集誤差大,這就是variance problem過擬合問題。這個問題的解決需要用到正則化解決。

  • 2.1 Learning curves

learning curves可以更加直觀的對比不同參數和數據集的選擇的效果,其實質就是畫出隨參數等其他的變化而帶來的訓練集和交叉驗證集的誤差曲線,取兩個誤差都比較小的情況。
我們先實現隨訓練數據量的變化,導致的誤差變化

    Xval = np.insert(Xval, 0, 1, axis=1) #爲了計算誤差
    error_train = []
    error_validation = []
    for i in range(X.shape[0]):
        subX = X[:i + 1]
        suby = y[:i + 1]
        res = opt.minimize(fun=cost, x0=theta, args=(subX, suby, 1), method="TNC", jac=gradient)
        t = res.x
        error_train.append(cost(t, subX, suby, 0))
        error_validation.append(cost(t, Xval, yval, 0))
    plt.subplot(2, 2, 2)
    plt.plot([i for i in range(X.shape[0])], error_train, label="training set error")
    plt.plot([i for i in range(X.shape[0])], error_validation, label="cross validation set error")
    plt.legend(loc="upper right")
    plt.xlabel("m(numbers of training set)")
    plt.title("learning curves")
    plt.show()

首先不要關心曲線的走勢,我們先看兩個誤差的絕對數值,隨着訓練集的增大,兩個誤差都很大,因此是high bias problem。爲什麼說不要看曲線走勢呢,因爲隨着訓練集的增大,肯定擬合難度更大,因此訓練集的誤差逐漸變大。而相應地訓練結果普適性更好,因此交叉驗證集地誤差變小。
在這裏插入圖片描述


Polynomial regression

對於當前問題由於特徵少,無法更好地擬合,因此我們需要通過構造多項式來添加特徵。由於只有一個特徵,我們只需要不斷增加冪就行了。
hθ(x)=θ0+θ1(waterLevel)+θ2(waterLevel)2+++θp(waterLevel)p h_\theta(x)=\theta_0+\theta_1*(waterLevel)+\theta_2*(waterLevel)^2+\dots++\theta_p*(waterLevel)^p

def poly_features(X, power_max):
    """
    添加多次項,增加特徵
    :param X: ndarray,原始特徵
    :param power_max: int,最高次
    :return: ndarray,增加特徵後的特徵
    """
    _X = X.reshape(-1, 1)
    res = np.ones((X.shape[0], 1))
    for i in range(power_max):
        res = np.concatenate((res, np.power(_X, i + 1)), axis=1)
    return res[..., 1:]
  • 3.1 Learning Polynomial Regression

這裏增加了高次特徵,這些高次特徵的數值會非常大,因此需要歸一化。
訓練集、交叉驗證集和測試集都應該使用同樣的值進行歸一化,也就是訓練集歸一化的值,所以要保存下來,爲了後面對交叉驗證集和測試集進行歸一化。

    features = poly_features(data['X'], power_max)
    means = np.mean(features, axis=0)
    stds = np.std(features, axis=0, ddof=1)
    normalized_features = normalize_features(features, means, stds)
    normalized_X = np.insert(normalized_features, 0, 1, axis=1)

進行優化,這裏的最大次項若選取作業上的8,由於優化方法不同,會導致曲線不同。

    # 若選取作業上的8,由於優化方法不同,會導致曲線不同
    power_max = 6
    l = 0  # 參數lambda, 取0時優化報錯但不影響使用(過擬合),取100(欠擬合)
    res = opt.minimize(fun=cost, x0=np.ones((power_max + 1,)), args=(normalized_X, y, l), method="TNC", jac=gradient)

畫出擬合效果

    plt.scatter(data['X'], y, marker='x', c='r')
    plt.xlabel("Change in water level (x)")
    plt.ylabel("Water flowing out of the dam (y)")
    plt.title("polynomial(8) regression")
    plt.xlim((-100, 100))
    plt.ylim((-10, 40))
    X = np.linspace(-100, 100, 50)
    normalized_X = normalize_features(poly_features(X, power_max), means, stds)
    normalized_X = np.insert(normalized_X, 0, 1, axis=1)
    plt.plot(X, normalized_X.dot(res.x))

可以說擬合效果是非常好了
在這裏插入圖片描述
接着我們來畫出learning curves,首先需要將交叉驗證集和測試集進行歸一化,注意坑!!


    # 注意坑!!!
    # 這裏需要直接利用全部訓練集的歸一化參數,直接將訓練集和驗證集數據全部歸一化,以後直接在裏面取即可
    # 而不是對原始訓練集取後,重新選擇歸一化參數
    train_features = poly_features(data["X"], power_max)
    train_normalized_features = normalize_features(train_features, means, stds)
    train_normalized_X = np.insert(train_normalized_features, 0, 1, axis=1)
    val_features = poly_features(data["Xval"], power_max)
    val_normalized_features = normalize_features(val_features, means, stds)
    val_normalized_X = np.insert(val_normalized_features, 0, 1, axis=1)

接下來就是畫出learn curves

    error_train = []
    error_validation = []
    for i in range(1, train_normalized_X.shape[0]):
        subX = train_normalized_X[:i + 1]
        suby = y[:i + 1]
        res = opt.minimize(fun=cost, x0=np.ones((power_max + 1,)), args=(subX, suby, l),
                           method="TNC", jac=gradient)
        t = res.x
        error_train.append(cost(t, subX, suby, 0))  # 計算error時不需要正則化
        error_validation.append(cost(t, val_normalized_X, yval, 0))
    plt.subplot(2, 2, 4)
    plt.plot([i for i in range(1, train_normalized_X.shape[0])], error_train, label="training set error")
    plt.plot([i for i in range(1, train_normalized_X.shape[0])], error_validation, label="cross validation set error")
    plt.legend(loc="upper right")
    plt.xlabel("m(numbers of training set)")
    plt.title("learning curves")
    plt.show()

由於之前提到的優化方式不同,因此和作業上的圖存在誤差。從圖中可以看到,訓練集的誤差一直很小,而交叉驗證集的誤差雖然在變化但是和訓練集的誤差仍然差距較大,這裏就是很明顯的high varaince problem也就是過擬合問題,這需要我們通過正則化解決。
在這裏插入圖片描述

  • 3.2 Optional (ungraded) exercise: Adjusting the regularization parameter

下面我們修改lambda的值,來改變正則化的影響,這裏直接使用上面的代碼即可
當lambda=1,可以看到這個擬合效果就很不錯,而且不存在Bias和varaince問題。
在這裏插入圖片描述
當lambda=100時,可以發現對數據的擬合併不好,存在欠擬合問題(bias problem),這就是懲罰過度的問題。
在這裏插入圖片描述

  • 3.3 Selecting λ using a cross validation set

從上面的例子我們看到,lambda的選擇很重要,選擇不合適會出現欠擬合和過擬合的情況。這裏我們就用到交叉驗證集,量化不同lambda時的誤差,選擇誤差最小的作爲最好的選擇。

    ls = [0, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, 3, 10]#候選的lambda
    error_train = []
    error_validation = []
    for l in ls:
        res = opt.minimize(fun=cost, x0=np.ones((power_max + 1,)), args=(train_normalized_X, y, l),
                           method="TNC", jac=gradient)
        error_train.append(cost(res.x, train_normalized_X, y, 0))
        error_validation.append(cost(res.x, val_normalized_X, yval, 0))
    plt.plot([i for i in ls], error_train, label="training set error")
    plt.plot([i for i in ls], error_validation, label="cross validation set error")
    plt.legend(loc="upper right")
    plt.xlabel("lambda")
    plt.ylabel("error")
    plt.title("Selecting λ using a cross validation set")
    plt.show()

從圖中可以看到,error(cv)最小值時的lambda最好,大概在3左右
在這裏插入圖片描述

  • 3.4 Optional (ungraded) exercise: Computing test set error

利用剛纔選擇的最好的lambda 3,計算測試集誤差,評價模型。
這裏的結果也和作業存在誤差,原因是之前爲了是曲線符合所以最高次取得6,若取8結果與作業相同爲3.8598753028610098。

    test_features = poly_features(Xtest, power_max)
    test_normalized_features = normalize_features(test_features, means, stds)
    test_normalized_X = np.insert(test_normalized_features, 0, 1, axis=1)
    res = opt.minimize(fun=cost, x0=np.ones((power_max + 1,)), args=(train_normalized_X, y, 3),
                       method="TNC", jac=gradient)
    print(cost(res.x, test_normalized_X, ytest, 0))#4.7552615904740145
  • 3.5 Optional (ungraded) exercise: Plotting learning curves with randomly selected examples

繪製學習曲線以調試算法時,通常對多組隨機選擇的樣本取平均值來確定訓練誤差和交叉驗證誤差通常會很有幫助。首先從訓練集中隨機選擇i個示例,從交叉驗證集中隨機選擇i個示例。然後,您將使用隨機選擇的訓練集學習參數θ,並在隨機選擇的訓練集和交叉驗證集上評估參數θ。然後,應將上述步驟重複多次(例如50次),並且應使用平均誤差來確定示例的訓練誤差和交叉驗證誤差。

這裏我們僅實現簡單隨機選擇數據

def randomly_select(data, n):
    """
    從數據集中隨機取出n組
    :param data: ndarray,數據
    :param n: int,選擇數量
    :return: ndarray,隨機選擇的數據
    """
    res = np.array(data)
    m = data.shape[0]
    for i in range(m - n):
        index = np.random.randint(0, res.shape[0] - 1)
        res = np.delete(res, index, axis=0)
    return res

畫出圖像

    error_train = []
    error_validation = []
    for i in range(X.shape[0]):
    	# 隨機選擇訓練集
        Xy = randomly_select(np.concatenate((train_normalized_X, y), axis=1), i + 1)
        subtrainX = Xy[..., :-1]
        subtrainy = Xy[..., -1]
        res = opt.minimize(fun=cost, x0=np.ones((power_max + 1,)), args=(subtrainX, subtrainy, 0.01), method="TNC",
                           jac=gradient)
        t = res.x
        error_train.append(cost(t, subtrainX, subtrainy, 0.01))
        # 隨機選擇交叉驗證集
        Xy = randomly_select(np.concatenate((val_normalized_X, yval), axis=1), i + 1)
        subvalX = Xy[..., :-1]
        subvaly = Xy[..., -1]
        error_validation.append(cost(t, subvalX, subvaly, 0.01))
    plt.plot([i for i in range(X.shape[0])], error_train, label="training set error")
    plt.plot([i for i in range(X.shape[0])], error_validation, label="cross validation set error")
    plt.legend(loc="upper right")
    plt.xlabel("m(numbers of training set)")
    plt.title("learning curves(randomly select)")
    plt.show()

由於是隨機選擇,圖像是不固定的
在這裏插入圖片描述
在這裏插入圖片描述
最後總結一下,學習bias和variance的作用就是更好理解當前模型存在問題,對症下藥。而不是一味地去增加數據量。根據learn curves我們分析其中地問題,解決參考方法如下:

high bias problem high variance problem
嘗試增加額外的特徵 獲取更多地數據
增加多項式特徵 將特徵數量減少
降低lambda 提高lambda

完整的代碼會同步 在我的github

歡迎指正錯誤

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