機器學習中梯度降低法原理及用其解決線性迴歸問題的C語言實現

本文講梯度降低(Gradient Descent)前先看看利用梯度降低法進行監督學習(例如分類、迴歸等)的通常步驟:算法

1, 定義損失函數(Loss Function)網絡

2, 信息流forward propagation,直到輸出端函數

3, 偏差信號back propagation。採用「鏈式法則」,求損失函數關於參數Θ的梯度post

4, 利用最優化方法(好比梯度降低法),進行參數更新學習

5, 重複步驟二、三、4,直到收斂爲止優化

所謂損失函數,就是一個描述實際輸出值和指望輸出值之間落差的函數。有多種損失函數的定義方法,常見的有均方偏差(error of mean square)、最大似然偏差(maximum likelihood estimate)、最大後驗機率(maximum posterior probability)、交叉熵損失函數(cross entropy loss)。本文就以均方偏差做爲損失函數講講梯度降低的算法原理以及用其解決線性迴歸問題。在監督學習下,對於一個樣本,它的特徵記爲x(若是是多個特徵,x表示特徵向量),指望輸出記爲t(t爲target的縮寫),實際輸出記爲o(o爲output的縮寫)。二者之間的偏差e可用下式表達(爲了節省時間,各類算式就用手寫的了):spa

前面的係數1/2主要是爲了在求導時消掉差值的平方項2。若是在訓練集中有n個樣本,可用E來表示全部樣本的偏差總和,並用其大小來度量模型的偏差程度,以下式所示:3d

 對於第d個實例的輸出可記爲下式:code

對於特定的訓練數據集而言, 只有Θ是變量,因此E就能夠表示成Θ的函數,以下式:blog

因此,對於神經網絡學習的任務,就是求到一系列合適的Θ值,以擬合給定的訓練數據,使實際輸出儘量接近指望輸出,使得E取得最小值。

 

再來看梯度降低。上式中損失函數E對權值向量Θ的梯度以下式所示:

它肯定了E最快上升的方向。在梯度前面加上負號「-」,就表示E最快降低的方向。因此梯度降低的訓練法則以下式所示:

, 其中

這裏的負號「-」表示和梯度相反的方向。η表示學習率。下面給出各個權值梯度計算的數學推導:

因此最終的梯度降低的訓練法則以下式:

    

 這個式子就是用於程序中計算參數Θ的。

下面看怎麼用梯度降低法解決線性迴歸問題。線性迴歸就是可以用一個直線較爲精確地描述數據之間的關係。這樣當出現新的數據的時候,就可以預測出一個簡單的值。線性迴歸函數可寫成 。線性迴歸問題經常使用最小二乘法解決,這裏用梯度降低法解決主要是經過實例加深對梯度降低法的理解。先假設Y = 2X + 3=2*X + 3*1,取X的四個值分別爲1,4,5,8,相應的Y爲5,11,13,19。這樣就能夠描述爲有四個樣本分別爲(1,1),(4,1),(5,1),(8,1),對應的指望值是5,11,13,19.5(這個值作了微調,從19變成了19.5,是爲了讓四個樣本不在一根直線上)。經過梯度降低法求Θ值(最終Θ逼近2和3)。C語言實現的代碼以下:

#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char *argv[]) { double matrix[4][2]={{1,1},{4,1},{5,1},{8,1}}; //樣本 double result[4]={5,11,13,19.5}; //指望值 double err_sum[4] = {0,0,0,0}; //各個樣本的偏差 double theta[2] = {1,6}; //Θ,初始值隨機 double err_square_total = 0.0; //方差和 double learning_rate = 0.01; //學習率 int ite_num; //迭代次數 for(ite_num = 0; ite_num <= 10000; ite_num++) { int i,j,k; err_square_total = 0.0; for(i = 0; i < 4; i++) { double h = 0; for(j = 0; j < 2; j++) h += theta[j]*matrix[i][j]; err_sum[i] = result[i] - h; err_square_total += 0.5*err_sum[i]*err_sum[i]; } if(err_square_total < 0.05) //0.05表示精度 break; for(j = 0; j < 2; j++) { double sum = 0; for(k = 0; k < 4; k++) //全部樣本都參與計算 sum += err_sum[k]*matrix[k][j]; theta[j] = theta[j] + learning_rate*sum; //根據上面的公式計算新的Θ } } printf(" @@@ Finish, ite_number:%d, err_square_total:%lf, theta[0]:%lf, theta[1]:%lf\n", ite_num, err_square_total, theta[0], theta[1]); return 0; }

程序運行後的結果爲:@@@ Finish, ite_number:308, err_square_total:0.049916, theta[0]:2.037090, theta[1]:3.002130。發現迭代了308次,最終的線性方程爲Y=2.037090X + 3.002130,是逼近2和3的。當再有一個新的X時就能夠預測出Y了。學習率是一個經驗值,通常是0.01--0.001,當我把它改成0.04再運行時就再也不收斂了。

 

上面的梯度降低叫批量梯度降低法(Batch Gradient Descent, BGD), 它是指在每一次迭代時使用全部樣原本進行梯度的更新當樣本數目很大時,每迭代一步都須要對全部樣本計算,訓練過程會很慢。因而人們想出了隨機梯度降低法(Stochastic Gradient Descent, SGD),每次只隨機取一個樣本計算梯度,訓練速度變快了,可是迭代次數變多了(表示不是一直向最快方向降低,但整體上仍是向最低點逼近)。仍是上面的例子,只不過每次只從四個樣本中隨機取一個計算梯度。C語言實現的代碼以下:

#include <stdio.h>
#include <stdlib.h>


int
main(int argc, char *argv[]) { double matrix[4][2]={{1,1},{4,1},{5,1},{8,1}}; //樣本 double result[4]={5,11,13,19.5}; //指望值 double err_sum[4] = {0,0,0,0}; //各個樣本的偏差 double theta[2] = {1,6}; //Θ,初始值隨機 double err_square_total = 0.0; //方差和 double learning_rate = 0.01; //學習率 int ite_num; //迭代次數 for(ite_num = 0; ite_num <= 10000; ite_num++) { int i,j,seed; err_square_total = 0.0; for(i = 0; i < 4; i++) { double h = 0;
for(j = 0; j < 2; j++) h += theta[j]*matrix[i][j];
err_sum[i]
= result[i] - h; err_square_total += 0.5*err_sum[i]*err_sum[i]; } if(err_square_total < 0.05) break; seed = rand()%4; for(j = 0; j < 2; j++) theta[j] = theta[j] + learning_rate*err_sum[seed]*matrix[seed][j]; //隨機選一個樣本參與計算 } printf(" @@@ Finish, ite_number:%d, err_square_total:%lf, theta[0]:%lf, theta[1]:%lf\n", ite_num, err_square_total, theta[0], theta[1]); return 0; }

程序運行後的結果爲:@@@ Finish, ite_number:1228, err_square_total:0.049573, theta[0]:2.037240, theta[1]:3.000183。發現迭代了1228次(迭代次數變多了),最終的線性方程爲Y=2.037240X + 3.000183,也是逼近2和3的。

 

後來人們又想出了在BGD和SGD之間的一個折中方法,即mini-batch SGD方法,即每次隨機的取一組樣原本計算梯度。mini-batch SGD是實際使用中用的最多的。仍是上面的例子,只不過每次只從四個樣本中隨機取兩個做爲一組個計算梯度。C語言實現的代碼以下:

#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char *argv[]) { double matrix[4][2]={{1,1},{4,1},{5,1},{8,1}}; double result[4]={5,11,13,19.5}; double err_sum[4] = {0,0,0,0}; double theta[2] = {1,6}; double err_square_total = 0.0; double learning_rate = 0.01; int ite_num; for(ite_num = 0; ite_num <= 10000; ite_num++) { int i,j,k,seed; err_square_total = 0.0;
for(i = 0;i<4;i++) { double h = 0;
for(j = 0; j < 2; j++) h += theta[j]*matrix[i][j]; err_sum[i] = result[i] - h; err_square_total += 0.5*err_sum[i]*err_sum[i]; } if(err_square_total < 0.05) break; seed = rand()%4; k = (seed +1)%4; for(j = 0; j < 2; j++) { double sum = 0; sum += err_sum[seed]*matrix[seed][j]; //隨機取兩個做爲一組計算梯度 sum += err_sum[k]*matrix[k][j];
theta[j]
= theta[j] + learning_rate*sum; } } printf(" @@@ Finish, ite_number:%d, err_square_total:%lf, theta[0]:%lf, theta[1]:%lf\n", ite_num, err_square_total, theta[0], theta[1]); return 0; }

程序運行後的結果爲: @@@ Finish, ite_number:615, err_square_total:0.047383, theta[0]:2.039000, theta[1]:2.987382。發現迭代了615次,最終的線性方程爲Y=2.039000X + 2.987382,也是逼近2和3的。迭代次數介於BGD和SGD中間。在用mini-batch SGD時batch size的選擇很關鍵。

相關文章
相關標籤/搜索