1. 程式人生 > >【機器學習】為什麼你程式設計學得越久,就越難入門人工智慧?

【機器學習】為什麼你程式設計學得越久,就越難入門人工智慧?

有沒有這種感覺,學習程式設計的過程就像在挖一口井,而這口井你可以挖成“web”的形狀,也可以挖成“app”的形狀,還可以挖出“game”的形狀。突然有一天,別人說:挖出“artificial intelligence”的形狀後的井水才是最甜的。於是,你就開始想辦法在已經挖好的井的基礎上亂挖試圖挖出最甜的井水,然而卻挖了許多彎道。。。

機器學習的演算法是建立在數學理論上的,如果數學學得不好,就算是學習已有的機器學習框架也是十分吃力。假如你曾經數學掌握得不錯,但是在程式設計上難以 get 到該有的數學思維,那麼,有很大的可能性是,你的 if-else 用久了,程式設計思維沒有跟數學思維打通出一條路。

哈?程式設計思維沒有跟數學思維打通一條路?我多年的資料結構 + 演算法白學了?

這條虛無縹緲的“程式設計 <–> 數學”之路並不是說之前的知識白學了,而是機器學習相比於之前的知識更加地依賴於數學這門知識。打個比方,對於大量的“線性”與“非線性”的資料,處理前者需要用到線性代數的知識,後者大多結合了概率論與數理統計的知識,如今大多的監督學習和非監督學習演算法大多基於線性代數和概率論與數理統計的數學理論。

什麼線性,什麼監督,說那麼多,肯定是在轉移話題,好好解釋什麼是打通程式設計跟數學的任督二脈!

額。。那我就先從標題展開吧!為什麼你程式設計學得越久,越難入門人工智慧?

答案是:你的 if-else 用太多了!

以C++舉個例子,“輸入兩個整數,比較兩個數的大小,輸入最大值”,傳統的程式寫法是:

#include <iostream>
using namespace std;

int max(int a, int b){
    if(a > b){
        cout<<a;
    }
    else{
        cout<<b<<endl;  
    }
}

int main(){
    int a, b;
    cin>>a>>b;
    max(a, b);
}

以上這段程式碼,每個初學者的必經之路,現在再回過來看這段程式碼,感覺要吐了,所以十分佩服教程式設計的老師,每一批學生,都要講一遍。。。回到正題,請忽略掉上面那段要看得想吐吐的程式碼,請看下一段,同樣是比較大小的程式,但是卻骨骼驚奇:

#include <iostream>
using namespace std;

//返回一個數的絕對值 
int abs(int num){
    return num>0 ? num : -num;
}

//返回兩個數中的最大值 
int max(int a, int b){
    return ( a + b + abs(a-b) ) / 2; 
}

int main(){
    int a, b;
    cin>>a>>b;
    cout<<max(a, b)<<endl;

    return 0;
}
【注】

1、注意觀察上面程式碼的 max 函式,並沒有用到 if-else 就能實現比較大小,而是用 a+b+|ab|2 公式計算出 a, b 的最大值。

2、同理,通過公式 a+b|ab|2 就能計算最小值。

哎喲,有點意思。但是,這個程式用公式反而更慢了好不好,本來 ALU 算一遍就可以,用了公式反而多算幾倍,這不是多此一舉嗎?什麼打通程式設計跟數學,你這標題是騙點選量的是吧。

寫博文,誰不想多點人看。。。既然上面的例子沒有 get 到點的話,再來一個看看吧。又是早就膩了的題目,寫一個程式,1+2+3+···+100。老樣子,先回顧一下閉著眼睛就能寫的 for 迴圈傳統演算法:

#include <stdio.h>

int sum(int n){
    int num=0;
    for(int i=1; i<=n; i++){
        num += i;
    } 
    return num;
}

int main(){
    printf("求和結果為%d\n\n", sum(100));

    return 0;
} 

再來看看公式法:

#include <stdio.h>

//返回1+2+..+n的計算結果 
int sum(int n){
    return n*(n+1)/2;
}

//輸出1加到100
int main(){
    printf("求和結果為%d\n\n", sum(100));

    return 0;
}

【注】sum 函式中用的是求和公式:n×(n+1)2 (相信很多人早就忘了這個公式,因為 for 迴圈可以算,幹嘛要記公式是吧)

嗯,我承認,用求和公式確實是比 for 迴圈運算速度快了。看了這兩個程式,難道這篇博文想表達的是少用 for 迴圈,多背數學公式來代替 for 迴圈嗎?

這個倒不是。本文想表達的是,從初學到熟練,程式設計師的程式碼邏輯會能力越來越強,但是,也會慢慢地忽視曾經也花了很多時間才掌握的數學知識也能在程式碼層派上用場。尤其在人工智慧方面,數學是重中之重的基礎。

其實我也知道數學很重要,但是現在很忙,數學的知識又多,又不知道從何入手,怎麼學得過來。。。

如果不喜歡數學,學起來確實痛苦,但是進步的過程總會伴隨著痛苦(突然雞湯了)。在機器學習方面,最簡單的,應該是線性迴歸演算法,乃至 多元線性迴歸演算法 了,我覺得這個演算法是機器學習的基礎中的基礎,所需的知識是線性代數。

接下來,我簡單講一下不需要線性代數的知識也能理解的多元線性迴歸演算法(非原理,僅簡單應用)。

有一天,我漫步在學校教導處門口,偶然撿到了一張紙,難道是期末試卷?好像不是,有點失望,但我還是好奇地看了一下,這是一張表格,是對多個學生的德智體美的綜合評分:

學生 總評
9 7 5 10 80
8 8 8 8 80
7 10 4 5 65
7 6 9 5 68
8 7 8 6 74

[ 教C語言的老師告訴我,看到表格,第一時間就要想到二維陣列 ]

於是我的腦海裡,出現了這麼一副畫面:

//表格資料 
float data[ROW][COL] = {
    {
        9, 7, 5, 10, 80
    },{
        8, 8, 8, 8, 80
    },{
        7, 10, 4, 5, 65
    },{
        7, 6, 9, 5, 68
    },{
        8, 7, 8, 6, 74
    }
};

這個表格的資料,稱之為訓練資料。

表格中的資料突然打斷了我的思緒,“嘿呀,不對啊,鉀同學的體育才5分,總評卻有80分,怎麼可能,不就是成績好嗎,切!”

我隨手就把表格給扔了,然後越想越氣,成績好了不起嗎!就在此刻,我回憶了起來,之前班主任有講過總評是根據德智體美的評分算出來的,但是體育究竟佔總評的比重有多大,跟智力(成績)的比重相差多少,我實在想不起來了,這個計算總評的公式大概是:

x1 × 德 + x2 × 智 + x3 × 體 + x4 × 美 = 總評

其中 x 就是德智體美分別佔總評的比重,這個比重,又稱權重。現在,我要把這四個權重算出來,好好對比對比,擺事實講道理,四個比重應該平均,憑什麼智的比重比其他的大。

不知不覺,下意識地~~,定義了這個總評的計算函式:

//多元線性方程
float fun(float d, float z, float t, float m, float d_w, float z_w, float t_w, float m_w){
    return d*d_w + z*z_w + t*t_w + m*m_w;
}

這時候,我想到了多元線性迴歸,又稱多變數線性迴歸。多元線性迴歸有一個很有用的公式:J(θ)=mi=1(hθ(xi)yi)22×m

有人看掉這條公式,立即放棄數學。那麼這個公式到底什麼意思?簡單理解為為:J(i)=i=1(i×)22×

另外,這個公式不是計算一遍,而是對幾乎所有的權重組合都計算一遍,然後取 J(θ) 最小值對應的 θ 即權重組合。

是不是太複雜了?舉個例子按步驟算你就明白了:

1、假設取一組權重值:1, 2, 1, 2,分別對應德, 智, 體, 美的權重值;

2、查表看第一行: 鉀 | 9 | 7 | 5 | 10 | 80

計算對應權重值的總評猜測值:9×1+7×2+5×1+10×2 = 48 (這個結果是對應假設的權重值求出的猜測值,不是真實的,下一步用這個值與真實的總評值作差再平方)

3、用上一步求出的總評猜測值-在表格中真實的值,然後對結果平方,即(48-80)^2 = 1024 (咦,求出的值好親切)

4、還沒完呢,繼續查看錶格,看第二行,用同樣的這組權重值計算:8×1+8×2+8×1+8×2 = 48,然後又得到1024;

5、接著,計算表格第三行,7×1+10×2+4×1+5×2 = 41,然後(41-65)^2 = 576;

6、接著繼續下去,把對應同樣的權重值求出總評猜測值 - 真實的總評值再把結果平方;

7、直到把所有行都算完後,把之前算好的所有猜測值與總評值的差的平方全部加起來,然後得到的結果再除以2倍的表格行數;

8、重新取新的權重組合,又把以上七步又計算一遍。把所有的權重組合對應結果計算一遍後,取結果值最小的對應的權重組合作為資料訓練成果。

根據多元線性迴歸公式,寫出的演算法如下:

//資料訓練完成後的德智體美的權重
float d_w = 0;
float z_w = 0;
float t_w = 0;
float m_w = 0;

//各權重的取值區間估計 
float w_min = 0;
float w_max = 4;

//多元線性迴歸估計權重演算法
void train( float data[ROW][COL] ){

    //迴歸函式的最小值
    float Linear_value_min = -1;     

    //每次計算迴歸函式的結果值
    float Linear_value = 0;

    //德智體美值
    float d = 0;
    float z = 0;
    float t = 0;
    float m = 0; 

    //計算權重的精度(精度越小,計算時間越久) 
    float w_precise = 0.5; 

    //遍歷
    for(float d_g = w_min; d_g <= w_max; d_g += w_precise){
        for(float z_g = w_min; z_g <= w_max; z_g += w_precise){
            for(float t_g = w_min; t_g <= w_max; t_g += w_precise){
                for(float m_g = w_min; m_g <= w_max; m_g += w_precise){

                    //核心演算法 
                    for(int i=0; i<ROW; i++){
                        for(int j=0; j<COL-1; j++){

                            //賦值德智體美 
                            switch(j){
                                case 0 : d = data[i][j];
                                    break;
                                case 1 : z = data[i][j];
                                    break;
                                case 2 : t = data[i][j];
                                    break;
                                case 3 : m = data[i][j];
                                    break;
                            }
                        }

                        /* 以上準備好了引數,以下開始計算迴歸函式 */
                        float truth_score = data[i][COL-1];     //真實總評 
                        float guess_score = fun( d,z,t,m, d_g, z_g, t_g, m_g);  //猜測總評

                        //求和
                        Linear_value += (guess_score - truth_score)*(guess_score - truth_score);


                        /* 檢視已遍歷情況 */       
                        cout<<"正在訓練:  "<<d_g<<"  "<<z_g<<"  "<<t_g<<"  "<<m_g<<"  "
                            <<truth_score<<"  "<<guess_score<<"  "<<truth_score<<"  "
                            <<Linear_value<<"  "<<Linear_value_min<<endl;
                    }

                    //求出迴歸函式結果
                    Linear_value = Linear_value/2*ROW;

                    //如果求出的結果是最小值 
                    if( Linear_value_min == -1 || Linear_value < Linear_value_min ){
                        //替換原本最小值 
                        Linear_value_min = Linear_value;

                        //儲存求得的權重 
                        d_w = d_g;
                        z_w = z_g;
                        t_w = t_g;
                        m_w = m_g;
                    }
                    if( Linear_value_min == 0){     //找到最小,直接返回 
                        return;
                    }

                    //重置 
                    Linear_value = 0;
                }               
                //system("cls");
            } 
        }    
    }               
} 

注意1】看到這段程式碼發現問題沒有,for 迴圈太多了。由於我的線性代數還在學習中,暫時做不到用矩陣的計算來簡化程式碼。有興趣的童鞋可以圍觀 《線性迴歸之向量化 linear regression – vectorization》

注意2】計算權重的思想是機器學習演算法的基礎,還對機器學習懵懵懂懂的可以看這篇文章《機器學習到底學到了什麼》

多元線性迴歸完整程式碼如下:

#include <iostream>
#define ROW 5
#define COL 5
using namespace std;

//得到資料,德智體美評分 + 總評 
float data[ROW][COL] = {
    {
        9, 7, 5, 10, 80
    },{
        8, 8, 8, 8, 80
    },{
        7, 10, 4, 5, 65
    },{
        7, 6, 9, 5, 68
    },{
        8, 7, 8, 6, 74
    }
};

//資料訓練完成後的德智體美的權重
float d_w = 0;
float z_w = 0;
float t_w = 0;
float m_w = 0;

//各權重的取值區間估計 
float w_min = 0;
float w_max = 4;

//多元線性方程
float fun(float d, float z, float t, float m, float d_w, float z_w, float t_w, float m_w){
    return d*d_w + z*z_w + t*t_w + m*m_w;
}

//多元線性迴歸估計權重演算法
void train( float data[ROW][COL] ){

    //迴歸函式的最小值
    float Linear_value_min = -1;     

    //每次計算迴歸函式的結果值
    float Linear_value = 0;

    //德智體美值
    float d = 0;
    float z = 0;
    float t = 0;
    float m = 0; 

    //計算權重的精度(精度越小,計算時間越久) 
    float w_precise = 0.5; 

    //遍歷
    for(float d_g = w_min; d_g <= w_max; d_g += w_precise){
        for(float z_g = w_min; z_g <= w_max; z_g += w_precise){
            for(float t_g = w_min; t_g <= w_max; t_g += w_precise){
                for(float m_g = w_min; m_g <= w_max; m_g += w_precise){

                    //核心演算法 
                    for(int i=0; i<ROW; i++){
                        for(int j=0; j<COL-1; j++){

                            //賦值德智體美 
                            switch(j){
                                case 0 : d = data[i][j];
                                    break;
                                case 1 : z = data[i][j];
                                    break;
                                case 2 : t = data[i][j];
                                    break;
                                case 3 : m = data[i][j];
                                    break;
                            }
                        }

                        /* 以上準備好了引數,以下開始計算迴歸函式 */
                        float truth_score = data[i][COL-1];     //真實總評 
                        float guess_score = fun( d,z,t,m, d_g, z_g, t_g, m_g);  //猜測總評

                        //求和
                        Linear_value += (guess_score - truth_score)*(guess_score - truth_score);


                        /* 檢視已遍歷情況 */       
                        cout<<"正在訓練:  "<<d_g<<"  "<<z_g<<"  "<<t_g<<"  "<<m_g<<"  "
                            <<truth_score<<"  "<<guess_score<<"  "<<truth_score<<"  "
                            <<Linear_value<<"  "<<Linear_value_min<<endl;
                    }

                    //求出迴歸函式結果
                    Linear_value = Linear_value/2*ROW;

                    //如果求出的結果是最小值 
                    if( Linear_value_min == -1 || Linear_value < Linear_value_min ){
                        //替換原本最小值 
                        Linear_value_min = Linear_value;

                        //儲存求得的權重 
                        d_w = d_g;
                        z_w = z_g;
                        t_w = t_g;
                        m_w = m_g;
                    }
                    if( Linear_value_min == 0){     //找到最小,直接返回 
                        return;
                    }

                    //重置 
                    Linear_value = 0;
                }               
                //system("cls");
            } 
        }    
    }               
} 

//開始訓練 
int main(){

    //訓練資料 
    train(data);    

    //訓練結束 
    cout<<endl;
    cout<<"訓練結束!"<<endl<<endl;
    cout<<"德的權重為:"<<d_w<<endl; 
    cout<<"智的權重為:"<<z_w<<endl;
    cout<<"體的權重為:"<<t_w<<endl;
    cout<<"美的權重為:"<<m_w<<endl;
} 

執行結果如下

這裡寫圖片描述

運算程式後,算出的權重組為:4, 2, 2, 2。帶入表格隨便一行比如第二行計算得:8×4+8×2+8×2+8×2 = 80,驗證成功!(細心的童鞋會發現,前面我猜測智力的權重比較大,計算結果顯示智力跟體育的權重一樣大,證明我對學習好的人過於偏見了)

總結

如果想要更好地學習和掌握機器學習演算法,數學是少不了的,Python 也是要學的(不然用C寫特別痛苦)。在有了不錯的數學功底和熟練使用蟒蛇語言後,選擇一款機器學習框架就可以輕鬆上手和靈活使用。

如果數學基礎紮實,語言也熟練運用還是對學習機器學習演算法感到吃力的話,不妨轉化一下思維,將數學思維與程式設計思維結合起來(學習MATLAB或許有好處)。因為程式設計思維講究確定性、步驟性;而數學思維有關係性、抽象性。兩者結合,即人工智慧。

(以上僅為博主自己的感想與理解,有誤之處,請多多指出,謝謝閱讀)