機器學習通俗入門-使用梯度下降法解決最簡單的線性迴歸問題

動機

一直以來,使用機器學習的算法都是用他人寫好的類庫,總覺得雲裏霧裏的,弄不清楚到底怎麼回事。今天實現了一個最簡單的線性迴歸分析,覺得收貨很大。紙上得來終覺淺,絕知此事要躬行。

迴歸分析

數據

假設有一組數據,知道自變量和因變量的值,如下例:

3.0000   10.0000
3.1000   10.3000
3.2000   10.6000
3.3000   10.9000
3.4000   11.2000
3.5000   11.5000
3.6000   11.8000
3.7000   12.1000
3.8000   12.4000
3.9000   12.7000
4.0000   13.0000
4.1000   13.3000
4.2000   13.6000
4.3000   13.9000
4.4000   14.2000
4.5000   14.5000
4.6000   14.8000
4.7000   15.1000
4.8000   15.4000
4.9000   15.7000
5.0000   16.0000

第一列爲自變量,第二列爲因變量。 使用matlab繪製出一個圖像。

plot(x,y,'o');

可以看到:

數據散點圖

模型假設

通過觀察我們發現,自變量和因變量大體位於一條線上,這樣我們就假設他們遵循一個線性函數。

f(x)=wx+b

因爲現在的自變量x是一個數字(或者看做1維的向量),所以w和b也是一個數字。

現在的情況是,我們知道自變量和變量,但不知道這個w和b。所以我們的目的就是要估計出一個最合理的w和b。

補充

* 入門讀者 這個部分可以不看 *

在實際問題中,x可能是一個長度爲n的向量,那麼w的維度要和x一致。

f(x)=w1x1+w2x2+...+wnxn+b)

這種寫法非常的繁瑣,可以用線性代數的寫法,實際上這種寫法也是一般所用的寫法。

f(x)=w˙x+b

這裏w是一個橫向量,x是一個列向量,b 是一個標量(就是一個數字)。

誤差分析

爲了刻畫我們的模型的好壞,我們需要給出一個衡量標準。那麼怎麼衡量的。
通過一定的算法,我們得到了w和b的估計值 wˆbˆ
那麼 y的估計值 ypred:

ypred=wˆx+bˆ

這個y的估計值 ypred 和y 的差的絕對值 |ypredy| 越小,說明估計的參數越準確。我們將所有的樣本(x,y) 的誤差加起來就得到了總的誤差 :

L=(x,y)|ypredy|

在數學上,計算絕對值不是很方便,因爲絕對值函數在0點不可導。我們希望把他給換成一個功能類似的可導函數。
絕對值函數

爲什麼需要可導呢? 後面會看到。

我們使用誤差平方和函數來代替絕對值函數。這個又叫做二階範式。

L=12(ypredy)2=12(wx+by)2

note: 爲什麼要加二分之一?

這個函數是可導的。

y=x22
, 其圖像如下:

$y= x^2 / 2 $

誤差最小化

爲了能得到最好的w和b 的估計,我們希望上面表示出的誤差能最小。那麼如何求一個函數的最小值呢? 微積分告訴我們,當一個函數導數爲0的時候,它去極大值或極小值。 那麼函數的最小值就在導數爲0的地方。

我們把誤差函數看成是w的函數,對w進行求導,可以得到:

Lw=(x,y)(wx+by)x

如果忘記了怎麼求導,可以看看我這篇文章複習一下。
http://blog.csdn.net/taiji1985/article/details/72857554

誤差函數對b求導,可以得到:

Lb=(x,y)(wx+by)

數學方法求誤差最小

上面得到了L關於w和b的導數,另導數等於0,可以得到極值。

Lb=0

Lw=0

帶入導數公式得到

(wx+by)=0

(wx+by)x=0

求解可得最小二乘法公式,這裏就不求了。

數值方法求解

上面的方法可以通過讓導數爲0得到結果,但對於一切比較複雜的模型,求解導數爲0的公式是很困難的。這樣就需要一些數值方法來學習。

數值方法一般使用迭代的方法來逼近最終答案,最常用的爲梯度下降法。梯度下降法的原理是這樣的。 隨便選擇一個x的初始點x0 ,在該點沿着其導數的相反方向運動一小段,就可以靠近極小值點。(如果是沿着導數的方向移動就會得到極大值)。

梯度下降法演示

具體做法是,在每次迭代時,計算w的新值

w=waLw=wa(x,y)(wx+by)x

b=baLb=ba(x,y)(wx+by)

其中a稱爲學習因子,就是梯度每一步走多長。。如果學習因子太大,容易錯過極值點,如果比較小,則收斂變慢。 學習因子一般通過經驗給出。 可以先設置一個較小的數,逐漸調大,知道找到一個合適的值。

對於我們這個例子,直接讓a=0.001

算法

有了核心的思路就可以寫算法了。先給出一個僞代碼

    while(還沒有收斂){
        計算誤差
        計算誤差和上一次誤差的差
        如果這個差小於某個值,認爲是收斂,退出。
        根據公式,更新w和b
    }

matlab代碼如下:

function [w,b] =  predict(x,y)
    d = 10;
    n = length(x);
    w = rand(1,1);  % 隨機生成 y = wx+b 中的w和b 做爲初始值
    b = rand(1,1); 
    olde = 0;       %上一次得到的誤差
    i = 0;          %迭代計數器
    rate = 0.001;
    while d>0.001    % d表示兩次迭代的誤差的差
        i= i+1;
        %計算 
        yp = w*x+b;  %根據公式計算,當前w和b下,計算出的y的估計值
        e = sum((yp-y).*(yp-y)); %將估計值和真實值的差的平方和作爲誤差評估函數
        d = abs(olde  - e);     %計算上一次循環和這一次之間的誤差的差
        fprintf('%d iter e = %f , d = %f \n',i,e,d);  
        if d<0.01
            break;
        end
        olde = e;

        %修改w和b
        w = w - rate*sum((yp-y).*x);
        b = b - rate*sum(yp-y);
    end
    hold off;
    plot(x,y,'o');
    hold on;
    xp = min(x):0.01:max(x);
    yp = w*xp+b;
    plot(xp,yp);

end

寫一個函數來調用這個學習函數


x = (3:0.1:5)'; 
w = 3; % 這是真實的參數值
b = 1;
y = w*x+b;
seed = 333;  % 隨機數種子,爲了讓這個實驗在重新運行時能重現所有現在的結果
rand('seed',seed)
y = y+rand(size(x))*0.7;
[wp,bp] = predict(x,y)

運行結果

1 iter e = 3335.957691 , d = 3335.957691 
2 iter e = 1348.893476 , d = 1987.064216 
3 iter e = 545.781076 , d = 803.112400 
4 iter e = 221.186873 , d = 324.594203 
5 iter e = 89.995526 , d = 131.191346 
6 iter e = 36.971877 , d = 53.023650 
7 iter e = 15.541291 , d = 21.430586 
8 iter e = 6.879684 , d = 8.661607 
9 iter e = 3.378919 , d = 3.500765 
10 iter e = 1.964014 , d = 1.414905 
11 iter e = 1.392151 , d = 0.571863 
12 iter e = 1.161021 , d = 0.231130 
13 iter e = 1.067605 , d = 0.093416 
14 iter e = 1.029850 , d = 0.037756 
15 iter e = 1.014590 , d = 0.015260 
16 iter e = 1.008422 , d = 0.006168 

wp =

    2.9827


bp =

    1.4161

運行結果

圖中直線爲根據估計出的w和b畫出的函數曲線。

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