- 神經網絡與深度學習
- 吳岸城
- 2552字
- 2019-01-04 14:25:25
2.4 用代碼實現一個感知機
了解感知機的運作原理之后,如果你是程序員或者你習慣用代碼去表示,那么現在就可以動手試試了!我們挑一個受眾面比較廣的語言作為基礎語言——Java,構建一個感知機。
2.4.1 Neuroph:一個基于Java的神經網絡框架
在整個第2章中,我們都將用Neuroph作為基礎框架,實現本書提及的神經網絡和相關案例。
下面,我們簡單認識一下Neuroph神經網絡的框架。Neuroph是一個輕量級的開源Java神經網絡框架,結構簡單清晰,非常適合初學者入門,隨著深入學習,可以慢慢了解Neuroph不只是初學者的入門工具,它可以簡化神經網絡的開發和實現,也能實現一些用在生產環境中的算法,圖2-5所示為它的基本類庫。

圖2-5 Neuroph神經網絡的框架
Neuroph由兩部分組成。一部分是由基于Java開發的API組成,你可以方便地利用這部分API創建神經網絡。另一部分是圖形工具,能直接通過簡單的圖形化工具構造一個神經網絡,幫助我們構造多層神經網絡時更加快捷方便。
Java API部分分成三塊,一塊是neuroph.core,這是Neuroph的核心庫,如表2-3所示。
表2-3 Neuroph類庫說明

圖2-6所示最簡化地表示了Neuroph是如何工作的。

圖2-6 Neuroph是如何工作的
(1)神經網絡和學習規則對應,神經網絡按照一定的學習規則訓練相應的數據集。
(2)神經網絡由基礎的層(layer)組成,按照結構分為輸入層、隱藏層和輸出層,我們講神經網絡的時候會具體談到各層的概念。
(3)神經網絡的每層由最基礎的神經元組成。
(4)訓練規則包含一個訓練集,允許有多個訓練集,訓練集由單個訓練元素組成。
接下來,我們看看Neuroph中的類Neuron,它表示單個神經元的構造。如圖2-7所示,這里沒有羅列出Neuron所有的屬性和方法,只展示了與本節相關的部分。

圖2-7 類Neuron
其中inputConnections表示神經元的輸入連接。比如,一個輸入刺激到神經元,就構成一條輸入。由于神經元是可以有多個輸入的,所以神經元和輸入連接是一對多的關系。netInput表示凈輸入,輸入函數的輸出。output表示神經元的輸入。error為神經元的誤差。inputFunction表示輸入函數,通常選擇加權求和。transferFunction表示傳輸函數。
另一個重要的類為Connection。它表示神經元的連接,可以是兩個神經元之間的連接,也可以是輸入信號與神經元之間的連接(實際上,輸入信號可以看作一個輸出永遠等于輸入的簡單神經元), weight表示這個連接的權重。
這個結構已經與之前所述的感知機原理很接近了,下面我們就來構造一個感知機。
2.4.2 代碼實現感知機
回到本節的主題,我們用代碼實現一個感知機,以便更加深入地了解感知機的構造。你也可以選擇跳過所有與代碼相關的章節,不影響閱讀。
首先,我們先想想要構建一個什么樣的感知機:我們將神經元輸入鏈接個數、神經元輸出鏈接個數和傳輸函數類型作為輸入的參數,如圖2-8所示。

圖2-8 感知機
private void createNetwork(int inputNeuronsCount, int outputNeuronsCount, TransferFunctionType transferFunctionType) { //設置神經網絡類型,這里我們將類型設置為感知器 this.setNetworkType(NeuralNetworkType.PERCEPTRON); //初始化神經元輸入刺激設置 NeuronProperties inputNeuronProperties = new NeuronProperties(); inputNeuronProperties.setProperty("transferFunction", TransferFunctionType.LINEAR); //創建輸入刺激 Layer inputLayer = LayerFactory.createLayer(inputNeuronsCount, inputNeuronProperties); this.addLayer(inputLayer); NeuronProperties outputNeuronProperties = new NeuronProperties(); outputNeuronProperties.setProperty("neuronType", ThresholdNeuron. class); outputNeuronProperties.setProperty("thresh", new Double(Math.abs (Math.random()))); outputNeuronProperties.setProperty("transferFunction", transferFunctionType); //為sigmoid和tanh傳輸函數設置斜率屬性 outputNeuronProperties.setProperty("transferFunction.slope", new Double(1)); //create一個神經元的輸出 Layer outputLayer = LayerFactory.createLayer(outputNeuronsCount, outputNeuronProperties); this.addLayer(outputLayer); //在輸入和輸出層中建立全鏈接 ConnectionFactory.fullConnect(inputLayer, outputLayer); //為神經網絡設置默認輸入輸出 NeuralNetworkFactory.setDefaultIO(this); this.setLearningRule(new BinaryDeltaRule()); }
至此,一個簡單的感知機已經建立,調用這個感知機可以處理諸如水果分類之類的簡單問題。我們將訓練這個神經網絡,讓它具有記憶和解決簡單問題的能力。
2.4.3 感知機學習一個簡單邏輯運算
我們已經建立了一個簡單的感知機,本節中我們將訓練這個感知機,并讓學習邏輯運算AND。
首先,我們列出邏輯運算AND中的基本規則:
0 and 0=0
0 and 1=0
1 and 0=0
1 and 1=1
也就是說,在感知機接收0、0輸入時,感知機應該響應1;當接收1、1輸入時,應該響應1。當接收0、1或1、0時,應該輸出0。AND主要用在條件判斷中,也就是當兩個子條件都成立時,才往下進行,兩個子條件就用AND連接。
我們先給出完整代碼,再順著代碼的脈絡看看感知機是如何學會AND運算的。
public static void main(String args[]) { //建立訓練集,有兩個輸入一個輸出 DataSet trainingSet = new DataSet(2, 1); trainingSet.addRow(new DataSetRow(new double[]{0, 0}, new double[]{0})); trainingSet.addRow(new DataSetRow(new double[]{0, 1}, new double[]{0})); trainingSet.addRow(new DataSetRow(new double[]{1, 0}, new double[]{0})); trainingSet.addRow(new DataSetRow(new double[]{1, 1}, new double[]{1})); //建立一個感知機,定義輸入刺激是2個,感知機輸出是1個,這里我們調用Neuroph提供 的Perceptron類。 NeuralNetwork myPerceptron = new Perceptron(2, 1); LearningRule lr =myPerceptron.getLearningRule(); lr.addListener(this); //開始學習訓練集 myPerceptron.learn(trainingSet); //測試感知機是否正確輸出,打印 System.out.println("Testing trained perceptron"); testNeuralNetwork(myPerceptron, trainingSet); }
我們運行程序,結果如下:
[main] INFO org.neuroph.core.learning.LearningRule - Learning Started 1. iterate 2. iterate 3. iterate 4. iterate 5. iterate [main] INFO org.neuroph.core.learning.LearningRule - Learning Stoped 5. iterate Testing trained perceptron Input: [0.0, 0.0] Output: [0.0] Input: [0.0, 1.0] Output: [0.0] Input: [1.0, 0.0] Output: [0.0] Input: [1.0, 1.0] Output: [1.0]
結果輸出表明,網絡經過5次迭代后,誤差為0,初始的權值和偏置是隨機數,接著網絡根據輸入的訓練數據迭代學習,不斷調整權值和偏置,并最終記憶AND邏輯運算。從測試數據中可以看到,4個輸出已經完全正確,該網絡已經對AND邏輯操作有正確的響應。
同理,只需要改變訓練數據,就可以讓感知機記憶其他內容,比如OR邏輯運算。
0 or 0=0
0 or 1=1
1 or 0=1
1 or 1=1
我們依據OR邏輯運算的規則,更改上例的訓練數據:
trainingSet.addRow(new DataSetRow(new double[]{0, 0}, new double[]{0})); trainingSet.addRow(new DataSetRow(new double[]{0, 1}, new double[]{1})); trainingSet.addRow(new DataSetRow(new double[]{1, 0}, new double[]{1})); trainingSet.addRow(new DataSetRow(new double[]{1, 1}, new double[]{1}));
然后測試該網絡,給出的輸出可能為:
[main] INFO org.neuroph.core.learning.LearningRule - Learning Started 1. iterate 2. iterate 3. iterate 4. iterate 5. iterate 6. iterate 7. iterate 8. iterate 9. iterate 10. iterate 11. iterate 12. iterate [main] INFO org.neuroph.core.learning.LearningRule - Learning Stoped 12. iterate Testing trained perceptron Input: [0.0, 0.0] Output: [0.0] Input: [0.0, 1.0] Output: [1.0] Input: [1.0, 0.0] Output: [1.0] Input: [1.0, 1.0] Output: [1.0]
可以看到,經過12次迭代,網絡已經正確記憶了OR邏輯運算。到這里,細心的讀者可以發現,在學習AND邏輯運算的時候,網絡進行了5次迭代,但在學習OR邏輯運算的時候迭代了12次。這種結果是完全隨機的,實際上在網絡建立的時候,初始權值是隨機產生的(絕對值小于1),因此迭代幾次才能使網絡給出正確輸出是不確定的,但不論初始值如何,經過有限次迭代,網絡一定能完全記住訓練數據。
2.4.4 XOR問題
大家還記得羅森布拉特嗎?他是我們在第0章提到的那位神經網絡中感知機奠基人之一,并且還是利用計算機模擬了感知機模型的天才,他是怎么被自己的中學同學明斯基找到感知機漏洞,郁悶至死的呢?對!就是因為XOR問題,我們來看看XOR能不能在我們的感知機模型中得到解決。
老規矩,先列出XOR的運算規則:
0 xor 0=0
0 xor 1=1
1 xor 0=1
1 xor 1=0
可以看到,當輸入的兩個值相等時,XOR返回0,否則得到1。
依然使用給出的感知機學習XOR會得到什么結果呢?感知機還可以正常工作嗎?
我們將訓練數據替換成如下代碼:
trainingSet.addRow(new DataSetRow(new double[]{0, 0}, new double[]{0})); trainingSet.addRow(new DataSetRow(new double[]{0, 1}, new double[]{1})); trainingSet.addRow(new DataSetRow(new double[]{1, 0}, new double[]{1})); trainingSet.addRow(new DataSetRow(new double[]{1, 1}, new double[]{0}));
再來運行一下程序:
…... 573884. iterate 573885. iterate 573886. iterate 573887. iterate 573888. iterate 573889. iterate 573890. iterate 573891. iterate 573892. iterate 573893. iterate 573894. iterate ……
我暫停了,已經計算了60w次仍然沒有得到結果。
如果不手動終止程序,它將永遠運行下去。讀者如果執行此程序,一定也會得到一樣的結果。這是什么原因呢?為什么感知機可以輕松地記憶AND和OR運算,但是無法學習XOR呢?
先不解答此問題,我們先來構造一個神經網絡,大家且往下看。