梯度下降的一個簡單應用實例:三次函數擬合散點

1.基本原理:

使用梯度下降法使用三次函數模型去擬合散點,基本過程是:

  • 根據三次函數模型給出損失函數
  • 求偏導得到所有迭代式
  • 編程實現
    • 初始化自變量
    • 設定學習速率 α
    • 設置精度
    • 運行程序得到結果

首先設定用來擬合的三元函數模型爲:

y=h(x)=k3x3+k2x2+k1x+k0y=h(x)=k_{3}x^{3}+k_{2}x^{2}+k_{1}x+k_{0}

然後給出在訓練集上的損失函數

J(k3,k2,k1,k0)=12mi=1m[h(xi)yi]2J(k_{3},k_{2},k_{1},k_{0})=\frac{1}{2m}\sum_{i=1}^m[h(x_{i})-y_{i}]^{2}

m 爲訓練集中的數據個數

爲了編程實現,將h(x)h(x)帶入損失函數展開得:

J(k3,k2,k1,k0)=12mi=1m[k3x3+k2x2+k1x+k0yi]2J(k_{3},k_{2},k_{1},k_{0})=\frac{1}{2m}\sum_{i=1}^m[k_{3}x^{3}+k_{2}x^{2}+k_{1}x+k_{0}-y_{i}]^{2}

接下來求出到編程中需要使用的迭代式:

  • 要使用梯度下降算法對損失函數 J(k3,k2,k1,k0)J(k_{3},k_{2},k_{1},k_{0}) 進行最小化,就要分別對 k3k2k1k0k_{3} k_{2} k_{1} k_{0}進行求偏導。

  • 梯度下降的原理式:

    • ki=kiαJ(k3,k2,k1,k0)kik_{i}=k_{i}-\alpha\frac{\partial J(k_{3},k_{2},k_{1},k_{0})}{\partial k_{i}}

  • 結合損失函數J(k3,k2,k1,k0)J(k_{3},k_{2},k_{1},k_{0})和梯度下降原理式,得到迭代式:

k3=k3αmi=1m(k3xi3+k2xi2+k1xi+k0yi)xi3k2=k2αmi=1m(k3xi3+k2xi2+k1xi+k0yi)xi2k1=k1αmi=1m(k3xi3+k2xi2+k1xi+k0yi)xik0=k0αmi=1m(k3xi3+k2xi2+k1xi+k0yi) k3=k3-\frac{\alpha}{m}\sum_{i=1}^{m}(k_{3}x_{i}^{3}+k_{2}x_{i}^{2}+k_{1}x_{i}+k_{0}-y_{i})x_{i}^{3} \\ k2=k2-\frac{\alpha}{m}\sum_{i=1}^{m}(k_{3}x_{i}^{3}+k_{2}x_{i}^{2}+k_{1}x_{i}+k_{0}-y_{i})x_{i}^{2} \\ k1=k1-\frac{\alpha}{m}\sum_{i=1}^{m}(k_{3}x_{i}^{3}+k_{2}x_{i}^{2}+k_{1}x_{i}+k_{0}-y_{i})x_{i}^{} \\ k0=k0-\frac{\alpha}{m}\sum_{i=1}^{m}(k_{3}x_{i}^{3}+k_{2}x_{i}^{2}+k_{1}x_{i}+k_{0}-y_{i})

2.代碼實現:

(完整代碼在最後)

在擬合之前,首先要初始化學習速率 α\alphak3,k2,k1,k0k_{3},k_{2},k_{1},k_{0} (代碼中使用的是 x3,x2,x1,x0)x_{3},x_{2},x_{1},x_{0})

  • 對於學習速率 α\alpha :
    • 如果發現迭代過程發散了(溢出),則可以適當調小然後觀察
    • 如果發現迭代過程太慢(可以把中間迭代數據間隔性地輸出),則可以調大一點(調整過度會有發散風險)
double alpha = 0.0000000000000008;
x3 = 0;
x2 = 0;
x1 = 0;
x0 = 0;

然後是迭代部分:

根據原理部分的迭代式進行計算和更新,直到全部滿足一定的精度後停止迭代,得到擬合結果。

相關代碼:

//首先計算和式
for(int i = 0;i < m;i++){
	sum0 += (x3 * pow(x[i], 3) + x2 * pow(x[i], 2) + x1 * x[i] + x0 - y[i]);
	sum1 += (x3 * pow(x[i], 3) + x2 * pow(x[i], 2) + x1 * x[i] + x0 - y[i]) * x[i];
	sum2 += (x3 * pow(x[i], 3) + x2 * pow(x[i], 2) + x1 * x[i] + x0 - y[i]) * pow(x[i], 2);
	sum3 += (x3 * pow(x[i], 3) + x2 * pow(x[i], 2) + x1 * x[i] + x0 - y[i]) * pow(x[i], 3);
}

//按照迭代公式進行迭代,這裏要注意要保持k1234同時更新,如果提前更新了k1便會影響到k234的計算
temp0 = x0 - (alpha / m) * sum0;
temp1 = x1 - (alpha / m) * sum1;
temp2 = x2 - (alpha / m) * sum2;
temp3 = x3 - (alpha / m) * sum3;

//滿足一定的精度後停止迭代
if((fabs(temp0-x0)<0.00000001) && (fabs(temp1-x1)<0.00000001) 
   && (fabs(temp2-x2)<0.00000001) && (fabs(temp3-x3)<0.00000001)){
	break;
}
//將迭代更新
x0 = temp0;
x1 = temp1;
x2 = temp2;
x3 = temp3;

3.結果分析:

以下是幾組數據擬合的情況,紅色點爲訓練集,藍色曲線爲擬合結果

(爲了繪圖方便,僅從訓練集中間隔選取一部分數據畫出來)

訓練集數據量的分析:

這裏的擬合目標是 12x3+2x2+x+1\frac{1}{2}x^{3}+2x^{2}+x+1

/*多組結果數據
各組學習速率 alpha 不同,所以循環次數的比較無意義
*/
數據量 10 循環 649669 次 結果 : y = 0.726796x^3+0.0862042x^2+0.0107911x+0.00147778
數據量 30 循環 9402416 次 結果 : y = 0.498747x^3+2.0656x^2+0.205435x+0.0173887
數據量 90 循環 3168008 次 結果 : y = 0.49982x^3+2.02649x^2+0.067502x+0.00188925
數據量 100 循環 1237039 次 結果 : y = 0.499818x^3+2.02702x^2+0.0608376x+0.00153463
數據量 170 循環 2616705 次 結果 : y = 0.499958x^3+2.01298x^2+0.035513x+0.000524415
數據量 250 循環 4922034 次 結果 : y = 0.500009x^3+2.00268x^2+0.0240199x+0.000240722

計算對應的加權誤差:							   加權誤差      數據量
0.00147778 0.0107911 0.0862042 0.726796 	4290.661912		10
0.0173887 0.205435 2.0656 0.498747 			167.412613		30
0.00188925 0.06750 2.02649 0.49982  		131.521108		90
0.00153463 0.0608376 2.02702 0.499818 		132.740894		100
0.000524415 0.035513 2.01298 0.499958 		119.843456		170
0.000240722 0.0240199 2.00268 0.500009 		110.365603		250

在這裏插入圖片描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zz36UoJR-1582723720180)(K:\DesktopFile\DeepLearing\筆記\筆記圖片資源\figure2.PNG)]

顯然,隨着數據量增大,加權誤差總體上來說是在減小的。

但是還能得到一些結論:

  • 對於一次和餘數項的擬合,隨着訓練集的增大,擬合的情況反而更差。

  • 數據量增大時,只有高次項(三次項和二次項)越來越準確。

有關擬合效果的分析:

對於類似三元函數的散點集,擬合的效果不錯,但是對於其他的情形,擬合效果只能說是差強人意。

原因考慮到可能有以下兩種情況,但是由於才學疏淺目前還不太明白

  • 梯度下降時結束位於局部最優附近?
  • 三次函數本身侷限?

完整的代碼:

#include<iostream> 
#include<cmath>

using namespace std;

int main(void){
	
	double x[1000];
	double y[1000];
	int m = 0;
	int count = 0;
	cout << "輸入訓練用例數 (<1000): ";
	cin >> m;
	cout << "分別輸入 x y" <<endl;
	for(int i = 0;i < m;i++){
		cin >> x[i] >> y[i];
	}
	double sum0, sum1, sum2, sum3;
	double x3, x2, x1, x0;
	double temp0, temp1, temp2, temp3;
	double alpha = 0.0000000000000008;
	x3 = 0;
	x2 = 0;
	x1 = 0;
	x0 = 0;
	cout <<"開始計算"<<endl;
	for(;;count++){
		sum0 = 0;
		sum1 = 0;
		sum2 = 0;
		sum3 = 0;
		//計算累加和 
		for(int i = 0;i < m;i++){
			sum0 += (x3 * pow(x[i], 3) + x2 * pow(x[i], 2) + x1 * x[i] + x0 - y[i]);
			sum1 += (x3 * pow(x[i], 3) + x2 * pow(x[i], 2) + x1 * x[i] + x0 - y[i]) * x[i];
			sum2 += (x3 * pow(x[i], 3) + x2 * pow(x[i], 2) + x1 * x[i] + x0 - y[i]) * pow(x[i], 2);
			sum3 += (x3 * pow(x[i], 3) + x2 * pow(x[i], 2) + x1 * x[i] + x0 - y[i]) * pow(x[i], 3);
		}
		temp0 = x0 - (alpha / m) * sum0;
		temp1 = x1 - (alpha / m) * sum1;
		temp2 = x2 - (alpha / m) * sum2;
		temp3 = x3 - (alpha / m) * sum3;
		
		if((fabs(temp0-x0)<0.00000001) && (fabs(temp1-x1)<0.00000001) && (fabs(temp2-x2)<0.00000001) && (fabs(temp3-x3)<0.00000001)){
			break;
		}
		
		x0 = temp0;
		x1 = temp1;
		x2 = temp2;
		x3 = temp3;
		//cout<<count<< "結果 : y = "<<x3<<"x^3+"<<x2<<"x^2+"<<x1<<"x+"<<x0<<endl;
		if(count % 100000 == 0){
			cout<<count<< "結果 : y = "<<x3<<"x^3+"<<x2<<"x^2+"<<x1<<"x+"<<x0<<endl;
		}
	}
	
	cout << "循環 " << count << " 次" << endl;
	cout << "結果 : y = "<<x3<<"x^3+"<<x2<<"x^2+"<<x1<<"x+"<<x0<<endl;
	
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章