對於如今流行的深度學習,保持學習精神是必要的——程序員尤爲是架構師永遠都要對核心技術和關鍵算法保持關注和敏感,必要時要動手寫一寫掌握下來,先不用關心何時用到——用不用是政治問題,會不會寫是技術問題,就像軍人不關心打不打的問題,而要關心如何打贏的問題。php
程序員如何學習機器學習對程序員來講,機器學習是有必定門檻的(這個門檻也是其核心競爭力),相信不少人在學習機器學習時都會爲盡是數學公式的英文論文而頭疼,甚至可能 知難而退。但實際上機器學習算法落地程序並不難寫,下面是70行代碼實現的反向多層(BP)神經網絡算法,也就是深度學習。其實不光是神經網絡,邏輯回 歸、決策樹C45/ID三、隨機森林、貝葉斯、協同過濾、圖計算、Kmeans、PageRank等大部分機器學習算法都能在100行單機程序內實現(以 後考慮分享出來)。java
機器學習的真正難度在於它爲何要這麼計算,它背後的數學原理是什麼,怎麼推導得來的公式,網上大部分的資料都在介紹這部分理論知識,卻不多告訴 你該算法的計算過程和程序落地是怎麼樣的,對於程序員來講,你須要作的僅是工程化應用,而不須要證實出一項新的數學計算方法。實際大部分機器學習工程師都 是利用別人寫好的開源包或者工具軟件,輸入數據和調整計算係數來訓練結果,甚至不多本身實現算法過程。可是掌握每一個算法的計算過程仍然很是重要,這樣你才 能理解該算法讓數據產生了什麼樣的變化,理解算法的目的是爲了達到什麼樣的效果。程序員
本文重點探討反向神經網絡的單機實現,關於神經網絡的多機並行化, Fourinone 提供很是靈活完善的並行計算框架,咱們只須要理解透單機程序實現,就能構思和設計出分佈式並行化方案,若是不理解算法計算過程,一切思路將沒法展開。另外,還有卷積神經網絡,主要是一種降維思想,用於圖像處理,不在本文討論範圍。算法
延伸閱讀:shell
神經網絡的計算過程神經網絡結構以下圖所示,最左邊的是輸入層,最右邊的是輸出層,中間是多個隱含層,隱含層和輸出層的每一個神經節點,都是由上一層節點乘以其權重累 加獲得,標上「+1」的圓圈爲截距項b,對輸入層外每一個節點:Y=w0*x0+w1*x1+…+wn*xn+b,由此咱們能夠知道神經網絡至關於一個多層 邏輯迴歸的結構。數據庫
(圖片來自 UFLDL Tutorial )數組
算法計算過程:輸入層開始,從左往右計算,逐層往前直到輸出層產生結果。若是結果值和目標值有差距,再從右往左算,逐層向後計算每一個節點的偏差, 而且調整每一個節點的全部權重,反向到達輸入層後,又從新向前計算,重複迭代以上步驟,直到全部權重參數收斂到一個合理值。因爲計算機程序求解方程參數和數 學求法不同,通常是先隨機選取參數,而後不斷調整參數減小偏差直到逼近正確值,因此大部分的機器學習都是在不斷迭代訓練,下面咱們從程序上詳細看看該過 程實現就清楚了。網絡
神經網絡的算法程序實現神經網絡的算法程序實現分爲初始化、向前計算結果,反向修改權重三個過程。架構
1. 初始化過程框架
因爲是n層神經網絡,咱們用二維數組layer記錄節點值,第一維爲層數,第二維爲該層節點位置,數組的值爲節點值;一樣,節點偏差值 layerErr也是類似方式記錄。用三維數組layer_weight記錄各節點權重,第一維爲層數,第二維爲該層節點位置,第三維爲下層節點位置,數 組的值爲某節點到達下層某節點的權重值,初始值爲0-1之間的隨機數。爲了優化收斂速度,這裏採用動量法權值調整,須要記錄上一次權值調整量,用三維數組 layer_weight_delta來記錄,截距項處理:程序裏將截距的值設置爲1,這樣只須要計算它的權重就能夠了,
2. 向前計算結果
採用S函數1/(1+Math.exp(-z))將每一個節點的值統一到0-1之間,再逐層向前計算直到輸出層,對於輸出層,其實是不須要再用S函數的,咱們這裏將輸出結果視爲0到1之間的機率值,因此也採用了S函數,這樣也有利於程序實現的統一性。
3. 反向修改權重
神經網絡如何計算偏差,通常採用平方型偏差函數E,以下:
也就是將多個輸出項和對應目標值的偏差的平方累加起來,再除以2。實際上邏輯迴歸的偏差函數也是這個,至於爲何要用這個函數來計算偏差,它從數 學上的合理性是什麼,怎麼得來的,這個我建議程序員們不想當數學家的話,先不去深究了,如今咱們要作的是如何把這個函數E偏差取它的最小值,須要對其進行 求導,若是有些求導數學基礎的話,倒能夠嘗試去推導下如何從函數E對權重求導獲得下面這個公式的:
不會推導也沒有關係,咱們只須要運用結果公式就能夠了,在咱們的程序裏用layerErr記錄了E對權重求導後的最小化偏差,再根據最小化偏差去調整權重。
注意這裏採用動量法調整,將上一次調整的經驗考慮進來,避免陷入局部最小值,下面的k表明迭代次數,mobp爲動量項,rate爲學習步長:
Δw(k+1) = mobp*Δw(k)+rate*Err*Layer
也有不少使用下面的公式,效果上的差異不是太大:
Δw(k+1) = mobp*Δw(k)+(1-mobp)rate*Err*Layer
爲了提高性能,注意程序實現是在一個while裏面同時計算偏差和調整權重,先將位置定位到倒數第二層(也就是最後一層隱含層)上,而後逐層反向 調整,根據L+1層算好的偏差來調整L層的權重,同時計算好L層的偏差,用於下一次循環到L-1層時計算權重,以此循環下去直到倒數第一層(輸入層)結 束。
小結在整個計算過程當中,節點的值是每次計算都在變化的,不須要保存,而權重參數和偏差參數是須要保存的,須要爲下一次迭代提供支持,所以,若是咱們構思一個分佈式的多機並行計算方案,就能理解其餘框架中爲何會有一個Parameter Server的概念。
多層神經網絡完整程序實現下面的實現程序BpDeep.java能夠直接拿去使用,也很容易修改成C、C#、Python等其餘任何語言實現,由於都是使用的基本語句,沒有用到其餘Java庫(除了Random函數)。如下爲原創程序,轉載引用時請註明做者和出處。
import java.util.Random; public class BpDeep{ public double[][] layer;//神經網絡各層節點 public double[][] layerErr;//神經網絡各節點偏差 public double[][][] layer_weight;//各層節點權重 public double[][][] layer_weight_delta;//各層節點權重動量 public double mobp;//動量係數 public double rate;//學習係數 public BpDeep(int[] layernum, double rate, double mobp){ this.mobp = mobp; this.rate = rate; layer = new double[layernum.length][]; layerErr = new double[layernum.length][]; layer_weight = new double[layernum.length][][]; layer_weight_delta = new double[layernum.length][][]; Random random = new Random(); for(int l=0;l<layernum.length;l++){ layer[l]=new double[layernum[l]]; layerErr[l]=new double[layernum[l]]; if(l+1<layernum.length){ layer_weight[l]=new double[layernum[l]+1][layernum[l+1]]; layer_weight_delta[l]=new double[layernum[l]+1][layernum[l+1]]; for(int j=0;j<layernum[l]+1;j++) for(int i=0;i<layernum[l+1];i++) layer_weight[l][j][i]=random.nextDouble();//隨機初始化權重 } } } //逐層向前計算輸出 public double[] computeOut(double[] in){ for(int l=1;l<layer.length;l++){ for(int j=0;j<layer[l].length;j++){ double z=layer_weight[l-1][layer[l-1].length][j]; for(int i=0;i<layer[l-1].length;i++){ layer[l-1][i]=l==1?in[i]:layer[l-1][i]; z+=layer_weight[l-1][i][j]*layer[l-1][i]; } layer[l][j]=1/(1+Math.exp(-z)); } } return layer[layer.length-1]; } //逐層反向計算偏差並修改權重 public void updateWeight(double[] tar){ int l=layer.length-1; for(int j=0;j<layerErr[l].length;j++) layerErr[l][j]=layer[l][j]*(1-layer[l][j])*(tar[j]-layer[l][j]); while(l-->0){ for(int j=0;j<layerErr[l].length;j++){ double z = 0.0; for(int i=0;i<layerErr[l+1].length;i++){ z=z+l>0?layerErr[l+1][i]*layer_weight[l][j][i]:0; layer_weight_delta[l][j][i]= mobp*layer_weight_delta[l][j][i]+rate*layerErr[l+1][i]*layer[l][j];//隱含層動量調整 layer_weight[l][j][i]+=layer_weight_delta[l][j][i];//隱含層權重調整 if(j==layerErr[l].length-1){ layer_weight_delta[l][j+1][i]= mobp*layer_weight_delta[l][j+1][i]+rate*layerErr[l+1][i];//截距動量調整 layer_weight[l][j+1][i]+=layer_weight_delta[l][j+1][i];//截距權重調整 } } layerErr[l][j]=z*layer[l][j]*(1-layer[l][j]);//記錄偏差 } } } public void train(double[] in, double[] tar){ double[] out = computeOut(in); updateWeight(tar); } }一個運用神經網絡的例子
最後咱們找個簡單例子來看看神經網絡神奇的效果。爲了方便觀察數據分佈,咱們選用一個二維座標的數據,下面共有4個數據,方塊表明數據的類型爲 1,三角表明數據的類型爲0,能夠看到屬於方塊類型的數據有(1,2)和(2,1),屬於三角類型的數據有(1,1),(2,2),如今問題是須要在平面 上將4個數據分紅1和0兩類,並以此來預測新的數據的類型。
咱們能夠運用邏輯迴歸算法來解決上面的分類問題,可是邏輯迴歸獲得一個線性的直線作爲分界線,能夠看到上面的紅線不管怎麼擺放,老是有一個樣本被 錯誤地劃分到不一樣類型中,因此對於上面的數據,僅僅一條直線不能很正確地劃分他們的分類,若是咱們運用神經網絡算法,能夠獲得下圖的分類效果,至關於多條 直線求並集來劃分空間,這樣準確性更高。
下面是這個測試程序BpDeepTest.java的源碼:
import java.util.Arrays; public class BpDeepTest{ public static void main(String[] args){ //初始化神經網絡的基本配置 //第一個參數是一個整型數組,表示神經網絡的層數和每層節點數,好比{3,10,10,10,10,2}表示輸入層是3個節點,輸出層是2個節點,中間有4層隱含層,每層10個節點 //第二個參數是學習步長,第三個參數是動量係數 BpDeep bp = new BpDeep(new int[]{2,10,2}, 0.15, 0.8); //設置樣本數據,對應上面的4個二維座標數據 double[][] data = new double[][]{{1,2},{2,2},{1,1},{2,1}}; //設置目標數據,對應4個座標數據的分類 double[][] target = new double[][]{{1,0},{0,1},{0,1},{1,0}}; //迭代訓練5000次 for(int n=0;n<5000;n++) for(int i=0;i<data.length;i++) bp.train(data[i], target[i]); //根據訓練結果來檢驗樣本數據 for(int j=0;j<data.length;j++){ double[] result = bp.computeOut(data[j]); System.out.println(Arrays.toString(data[j])+":"+Arrays.toString(result)); } //根據訓練結果來預測一條新數據的分類 double[] x = new double[]{3,1}; double[] result = bp.computeOut(x); System.out.println(Arrays.toString(x)+":"+Arrays.toString(result)); } }小結
以上測試程序顯示神經網絡有很神奇的分類效果,實際上神經網絡有必定優點,但也不是接近人腦的萬能算法,不少時候它可能會讓咱們失望,還須要結合 各類場景的數據大量運用去觀察其效果。咱們能夠把1層隱含層改爲n層,並調整每層節點數、迭代次數、學習步長和動量係數,以得到一個最優化的結果。可是很 多時候n層隱含層的效果並不比1層有明顯提高,反而計算更復雜耗時,咱們對神經網絡的認識還須要多實踐多體會。
做者簡介:彭淵,在Java技術領域從業十多年,曾撰寫多款開源軟件,歷任淘寶高級專家和華爲中間件首席架構師。開源表明做有Fourinone(四不像)分佈式核心技術框架、CoolHash並行數據庫引擎等,曾出版書籍《大規模分佈式系統架構與設計實戰》。
責編:周建丁(zhoujd@csdn.net)
(轉載)http://www.open-open.com/code/view/1455771789339