1. 程式人生 > >梯度下降法解多元線性迴歸(C++)

梯度下降法解多元線性迴歸(C++)

提供測試

題意:

  已知有資料集包含多個工程師的資訊,而對於每個工程師有engineer -> [y,x1,x2] 表示當其XP的值為x1,解決的題目為x2個時,可以開出y的薪水。請用多元線性迴歸,給出所查詢工程師的薪水。

題解:

  題目比較噁心的是最終引數要保留兩位小數,所以並不是最擬合的就是結果。小心精度就好,不是本次題解的重點。

  說道解線性迴歸,之前CodeFight也有個題,是一元的,直接每個方程求導後用高斯消元解決的。當時嘗試用梯度下降法,但總是不找不到合適的rate而不收斂,十分的費解。

  首先,要確定的是以L2為cost function的線性迴歸是一定可以通過設定合適的步長(這裡就稱作rate),一步步迭代到最有點的。證明很簡單,因為L2是光滑的,所以其最小值一定在某個極小值點,而當我們把L2對每個自變數求二階導,得到的都是一個一元方程,可知對於任意方向都只存在一個駐點,因此用合適步長梯度下降法一定會收斂並且必然可以取到cost function的全域性最小值。

  而梯度下降法如何做呢,網上也有很多資料了,包括吳恩達在Coursera上的公開課都說得很詳細。對於每次迭代,算出當前cost function在各個自變數維度上的斜率(對該 變數求偏導),然後讓引數往負斜率方向迭代,因為全域性只有一個極小值點,所以必然會讓cost function的各個維度偏導從一個大於0的點通過不斷的迭代到一個逼近0的點。

  但問題的關鍵是在開始實現的時候,我發現總是不收斂,cost function隨著每次迭代變化卻越來越大(有種每次都是矯枉過正的感覺)。在想是不是步長rate設定得不夠好,但嘗試了很多不同的步長,都無法得到想要的結果。最終發現是自己漏了一個很重要的步驟,一定要做歸一化

!!如果資料集中每個變數差別很大,或者一個數據的每個特徵取值相差很大,會很難找到合適的步長的。歸一化有很多種方法,我的處理是取測試集的平均值。

double rd(double x){
    char buff[100];
    double a;
    sprintf(buff,"%.2lf",x);
    sscanf(buff,"%lf",&a);
    return a;
}
int compute(vector<vector<int>> engineers, vector<int> candidate) {
    int n=engineers.size();
    double v[3][5],sum[3],E[n][3];
    for (int j=0;j<3;j++){
        sum[j]=1;
        for (int i=0;i<n;i++)
            sum[j]+=engineers[i][j];
        sum[j]/=n; 
    } 
    sum[0]=1;
    for (int i=0;i<n;i++)
        for (int j=0;j<3;j++)
            E[i][j]=engineers[i][j]/sum[j];
    memset(v,0,sizeof(v));
    double h[3],p[3],rate=0.01,pre=1e+50,now;
    for (int i=0;i<3;i++)
        h[i]=0;
    for (int times=0;times<2000000;times++){
        for (int i=0;i<3;i++)
            p[i]=h[i]; 
        for (int i=0;i<n;i++){
            double d = E[i][0] - (p[0] + p[1]*E[i][1] + p[2]* E[i][2]);   
            h[0]+=rate*d;
            for (int j=1;j<3;j++)
                h[j]+=rate*E[i][j]*d;
        }  
    }
    h[1]/=sum[1];
    h[2]/=sum[2]; 
    for (int i=0;i<3;i++)
        h[i]=rd(h[i]); 
    return (int)(h[0] + candidate[0]*h[1] + candidate[1]*h[2]);
}