梯度下降與delta法則
delta法則
儘管當訓練樣例線性可分時,感知器法則可以成功地找到一個權向量,但如果樣例不是線性可分時它將不能收斂。因此,人們設計了另一個訓練法則來克服這個不足,稱為 delta 法則(delta rule)。如果訓練樣本不是線性可分的,那麼 delta 法則會收斂到目標概念的最佳 近似。
delta 法則的關鍵思想是使用梯度下降(gradient descent)來搜尋可能權向量的假設空間, 以找到最佳擬合訓練樣例的權向量。
梯度下降
利用感知器法則的要求是必須訓練樣本是線性可分的,當樣例不滿足這條件時,就不能再收斂,為了克服這個要求,引出了delta法則,它會收斂到目標概念的最佳近似!
delta法則的關鍵思想是利用梯度下降(gradient descent)來搜尋可能的權向量的假設空間,以找到最佳擬合訓練樣例的權向量。
簡單的理解,就是訓練一個無閾值的感知器,也就是一個線性單元。它的輸出o如下:
先指定一個度量標準來衡量假設(權向量)相對於訓練樣例的訓練誤差(training error)。
其中D是訓練樣例集合,td 是訓練樣例d的目標輸出,od是線性單元對訓練樣例d的輸出。E(w)是目標輸出td和線性單元輸出od的差異的平方在所有的訓練樣例上求和後的一半。我們定義E為w的函式,是因為線性單元的輸出o依賴於這個權向量。
在這裡,我們對於給定的訓練資料使E最小化的假設也就是H中最可能的假設,也就是找到一組權向量能使E最小化。
直觀感覺,E函式就是為了讓目標輸出td 與線性輸出od的差距也來越小,也就是越來越接近目標概念。
上圖中,紅點和綠點是線性不可分的(不能找到一條直線完全分開兩類點),但是找到一條線能把兩類點儘可能的分開。使錯分的點儘可能地少。
為什麼感知器不能分開呢?
就在於它是帶閾值的,>0 為1,<0為-1。正是這種強制性,使函式輸出帶有跳躍性(不是可微的),用上圖中的影象來表示就是,在訓練過程中,線會一直順時針或著逆時針旋轉,而不會收斂到最佳值。
上圖中,兩個座標軸表示一個簡單的線性單元中兩個權可能的取值,而圓圈大小代表訓練誤差E值的大小。
為了確定一個使E最小化的權向量,梯度下降搜尋從一個任意的初始向量開始,然後以很小的步伐反覆修改這個向量。每一步都沿誤差曲線產生最陡峭的下降方向修改權向量(見藍線),繼續這個過程直到得到全域性的最小誤差點。
這個最陡峭的下降方向是什麼呢?
可以通過計算E相對向量w的的每個分量的導數來得到這個方向。這個向量導數被稱為E對於W的梯度(gradient),記作ΔE(w).
ΔE(w)本身是一個向量,它的成員是E對每個wi的偏導數。當梯度被解釋為權空間的一個向量時,它確定了使E最陡峭上升的方向。
既然確定了方向,那梯度下降法則就是:
其中:
這裡的η是一個正的常數叫做學習速率,它決定梯度下降搜尋中的步長。公式中的符號是想讓權向量E下降的方向移動。
這個訓練法則也可以寫成它的分量形式:
其中:
(公式1)
最陡峭的下降可以按照比例改變中的每一分量來實現。
可以通過前面的訓練誤差公式中計算E的微分,從而得到組成這個梯度向量的分量。
推導過程略去。
最後得到:
其中xid表示訓練樣例d的一個輸入分量xi。現在我們有了一個公式,能夠用線性單元的輸入xid,輸出od以及訓練樣例的目標值td表示。
把次此公式帶入公式(1)得到了梯度下降權值更新法則。
(公式2)
因此,訓練線性單元的梯度下降演算法如下:選取一個初始的隨機權向量;應用線性單元到所有的訓練樣例,然後根據公式2計算每個權值的Δwi;通過加上Δwi來更新每個權值,然後重複這個過程。
因為這個誤差曲面僅包含一個全域性的最小值,所以無論訓練樣例是否線性可分,這個演算法都會收斂到具有最小誤差的權向量。條件是必須使用一個足夠小的學習速率η。
如果η太大,梯度下降搜素就有越過誤差面最小值而不是停留在那一點的危險。因此常有的改進方法是隨著梯度下降步數的增加逐漸減小η的值。
梯度下降演算法的虛擬碼:
要實現梯度下降的隨機近似,刪除(T4.2),並把(T4.1)替換為 。
隨機梯度下降演算法
梯度下降是一種重要的通用學習範型。它是搜尋龐大假設空間或無限假設空間的一種策略,它可以滿足以下條件的任何情況:
(1)假設空間包含連續引數化的假設。
(2)誤差對於這些假設引數可微。
在應用梯度下降的主要實踐問題是:
(1)有時收斂過程可能非常慢;
(2)如果在誤差曲面上有多個區域性極小值,那麼不能保證這個過程會找到全域性最小值。
緩解這些困難的一個常見的梯度下降變體被稱為增量梯度下降演算法(incremental gradient descent)或者隨機梯度下降(stochastic gradient descent)。
鑑於公式2給出的梯度下降訓練法則在對D中的所有訓練樣例求和後計算權值更新,隨機梯度下降的思想是根據每個單獨樣例的誤差增量計算權值更新,得到近似的梯度下降搜尋.
修改後的訓練法則與公式2相似,只是在迭代計算每個訓練樣例時根據下面的公式來更新權值:
公式3
其中,t、o和xi分別是目標值、單元輸出和第i個訓練樣例的輸入。
隨機梯度下降可被看作為每個單獨的訓練樣例d定義不同的誤差函式:
其中,td和od是訓練樣例d的目標輸出值和單元輸出值。
隨機梯度下降迭代計算訓練樣例集D的每個樣例d,在每次迭代過程中按照關於的梯度來改變權值。在迭代所有訓練樣例時,這些權值更新的序列給出了對於原來的誤差函式的梯度下降的一個合理近似。
標準的梯度下降和隨機的梯度下降之間的關鍵區別:
(1)標準的梯度下降是在權值更新前對所有的樣例彙總誤差,而隨機梯度下降的權值是通過考查每個訓練樣例來更新的。
(2)在標準的梯度下降中,權值更新的每一步對多個樣例求和,這需要大量的計算。
(3)如果有多個區域性極小值,隨機的梯度下降有時可能避免陷入這些區域性極小值中,因為它使用不同的而不是來引導搜尋。
注意:
公式3的增量法則與之前感知器法則訓練法則相似。但是它們是不同的,在增量法則中o是值線性單元的輸出,而對於感知器法則,o是指閾值輸出,在公式中。
樣例:
輸入x1、x2,輸出為o,訓練w0,w1,w2
滿足 w1+x1*w1+x2*x2=o
訓練樣例為:
x1 x2 o 1 4 19 2 5 26 5 1 19 4 2 20
標頭檔案
#ifndef HEAD_H_INCLUDED #define HEAD_H_INCLUDED #include <iostream> #include <fstream> #include <vector> #include <cstdio> #include <cstdlib> #include <cmath> using namespace std; const int DataRow=4; const int DataColumn=3; const double learning_rate=.01; extern double DataTable[DataRow][DataColumn]; extern double Theta[DataColumn-1]; const double loss_theta=0.001; const int iterator_n =100; #endif // HEAD_H_INCLUDED
原始碼
#include "head.h" double DataTable[DataRow][DataColumn]; double Theta[DataColumn-1]; void Init() { ifstream fin("data.txt"); for(int i=0;i<DataRow;i++) { for(int j=0;j<DataColumn;j++) { fin>>DataTable[i][j]; } } if(!fin) { cout<<"fin error"; exit(1); } fin.close(); for(int i=0;i<DataColumn-1;i++) { Theta[i]=0.5; } } void batch_grandient() //標準梯度下降 { double loss=1000; for(int i=0;i<iterator_n&&loss>=loss_theta;i++) { double Thetasum[DataColumn-1]={0}; for(int j=0;j<DataRow;j++) { double error=0; for(int k=0;k<DataColumn-1;k++) { error+=DataTable[j][k]*Theta[k]; } error=DataTable[j][DataColumn-1]-error; for(int k=0;k<DataColumn-1;k++) { Thetasum[k]+=learning_rate*error*DataTable[j][k]; } } double a=0; for(int k=0;k<DataColumn-1;k++) { Theta[k]+=Thetasum[k]; a+=abs(Thetasum[k]); } loss=a/(DataColumn-1); } } void stochastic_gradient() //隨即梯度下降 { double loss=1000; for(int i=0;i<iterator_n&&loss>=loss_theta;i++) { double Thetasum[DataColumn-1]={0}; for(int j=0;j<DataRow;j++) { double error=0; for(int k=0;k<DataColumn-1;k++) { error+=DataTable[j][k]*Theta[k]; } error=DataTable[j][DataColumn-1]-error; double a=0; for(int k=0;k<DataColumn-1;k++) { Theta[k]+=learning_rate*error*DataTable[j][k]; a+=abs(learning_rate*error*DataTable[j][k]); } loss=a/(DataColumn-1); if(loss<=loss_theta) break; } } } void printTheta() { for(int i=0;i<DataColumn-1;i++) cout<<Theta[i]<<" "; cout<<endl; } int main() { Init(); //batch_grandient(); stochastic_gradient(); printTheta(); return 0; }