1.基本原理:
使用梯度下降法使用三次函數模型去擬合散點,基本過程是:
- 根據三次函數模型給出損失函數
- 求偏導得到所有迭代式
- 編程實現
- 初始化自變量
- 設定學習速率 α
- 設置精度
- 運行程序得到結果
首先設定用來擬合的三元函數模型爲:
然後給出在訓練集上的損失函數:
m 爲訓練集中的數據個數
爲了編程實現,將帶入損失函數展開得:
接下來求出到編程中需要使用的迭代式:
2.代碼實現:
(完整代碼在最後)
在擬合之前,首先要初始化學習速率 和 (代碼中使用的是
- 對於學習速率 :
- 如果發現迭代過程發散了(溢出),則可以適當調小然後觀察
- 如果發現迭代過程太慢(可以把中間迭代數據間隔性地輸出),則可以調大一點(調整過度會有發散風險)
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.結果分析:
以下是幾組數據擬合的情況,紅色點爲訓練集,藍色曲線爲擬合結果
(爲了繪圖方便,僅從訓練集中間隔選取一部分數據畫出來)
訓練集數據量的分析:
這裏的擬合目標是
/*多組結果數據
各組學習速率 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
顯然,隨着數據量增大,加權誤差總體上來說是在減小的。
但是還能得到一些結論:
-
對於一次和餘數項的擬合,隨着訓練集的增大,擬合的情況反而更差。
-
數據量增大時,只有高次項(三次項和二次項)越來越準確。
有關擬合效果的分析:
對於類似三元函數的散點集,擬合的效果不錯,但是對於其他的情形,擬合效果只能說是差強人意。
原因考慮到可能有以下兩種情況,但是由於才學疏淺目前還不太明白
- 梯度下降時結束位於局部最優附近?
- 三次函數本身侷限?
完整的代碼:
#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;
}