動機
一直以來,使用機器學習的算法都是用他人寫好的類庫,總覺得雲裏霧裏的,弄不清楚到底怎麼回事。今天實現了一個最簡單的線性迴歸分析,覺得收貨很大。紙上得來終覺淺,絕知此事要躬行。
迴歸分析
數據
假設有一組數據,知道自變量和因變量的值,如下例:
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');
可以看到:
模型假設
通過觀察我們發現,自變量和因變量大體位於一條線上,這樣我們就假設他們遵循一個線性函數。
因爲現在的自變量x是一個數字(或者看做1維的向量),所以w和b也是一個數字。
現在的情況是,我們知道自變量和變量,但不知道這個w和b。所以我們的目的就是要估計出一個最合理的w和b。
補充
* 入門讀者 這個部分可以不看 *
在實際問題中,x可能是一個長度爲n的向量,那麼w的維度要和x一致。
這種寫法非常的繁瑣,可以用線性代數的寫法,實際上這種寫法也是一般所用的寫法。
這裏w是一個橫向量,x是一個列向量,b 是一個標量(就是一個數字)。
誤差分析
爲了刻畫我們的模型的好壞,我們需要給出一個衡量標準。那麼怎麼衡量的。
通過一定的算法,我們得到了w和b的估計值
那麼 y的估計值 ypred:
這個y的估計值 ypred 和y 的差的絕對值
在數學上,計算絕對值不是很方便,因爲絕對值函數在0點不可導。我們希望把他給換成一個功能類似的可導函數。
爲什麼需要可導呢? 後面會看到。
我們使用誤差平方和函數來代替絕對值函數。這個又叫做二階範式。
note: 爲什麼要加二分之一?
這個函數是可導的。
誤差最小化
爲了能得到最好的w和b 的估計,我們希望上面表示出的誤差能最小。那麼如何求一個函數的最小值呢? 微積分告訴我們,當一個函數導數爲0的時候,它去極大值或極小值。 那麼函數的最小值就在導數爲0的地方。
我們把誤差函數看成是w的函數,對w進行求導,可以得到:
如果忘記了怎麼求導,可以看看我這篇文章複習一下。
http://blog.csdn.net/taiji1985/article/details/72857554
誤差函數對b求導,可以得到:
數學方法求誤差最小
上面得到了L關於w和b的導數,另導數等於0,可以得到極值。
帶入導數公式得到
求解可得最小二乘法公式,這裏就不求了。
數值方法求解
上面的方法可以通過讓導數爲0得到結果,但對於一切比較複雜的模型,求解導數爲0的公式是很困難的。這樣就需要一些數值方法來學習。
數值方法一般使用迭代的方法來逼近最終答案,最常用的爲梯度下降法。梯度下降法的原理是這樣的。 隨便選擇一個x的初始點
具體做法是,在每次迭代時,計算w的新值
其中a稱爲學習因子,就是梯度每一步走多長。。如果學習因子太大,容易錯過極值點,如果比較小,則收斂變慢。 學習因子一般通過經驗給出。 可以先設置一個較小的數,逐漸調大,知道找到一個合適的值。
對於我們這個例子,直接讓
算法
有了核心的思路就可以寫算法了。先給出一個僞代碼
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畫出的函數曲線。