本文講梯度下降(Gradient Descent)前先看看利用梯度下降法進行監督學習(例如分類、迴歸等)的一般步驟:
1, 定義損失函數(Loss Function)
2, 信息流forward propagation,直到輸出端
3, 誤差信號back propagation。採用“鏈式法則”,求損失函數關於參數Θ的梯度
4, 利用最優化方法(比如梯度下降法),進行參數更新
5, 重複步驟2、3、4,直到收斂爲止
所謂損失函數,就是一個描述實際輸出值和期望輸出值之間落差的函數。有多種損失函數的定義方法,常見的有均方誤差(error of mean square)、最大似然誤差(maximum likelihood estimate)、最大後驗概率(maximum posterior probability)、交叉熵損失函數(cross entropy loss)。本文就以均方誤差作爲損失函數講講梯度下降的算法原理以及用其解決線性迴歸問題。在監督學習下,對於一個樣本,它的特徵記爲x(如果是多個特徵,x表示特徵向量),期望輸出記爲t(t爲target的縮寫),實際輸出記爲o(o爲output的縮寫)。兩者之間的誤差e可用下式表達(爲了節省時間,各種算式就用手寫的了):
前面的係數1/2主要是爲了在求導時消掉差值的平方項2。如果在訓練集中有n個樣本,可用E來表示所有樣本的誤差總和,並用其大小來度量模型的誤差程度,如下式所示:
對於第d個實例的輸出可記爲下式:
對於特定的訓練數據集而言, 只有Θ是變量,所以E就可以表示成Θ的函數,如下式:
所以,對於神經網絡學習的任務,就是求到一系列合適的Θ值,以擬合給定的訓練數據,使實際輸出儘可能接近期望輸出,使得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的選擇很關鍵。