用Java開源項目JOONE實現人工智能編程
訓練神經網絡
為實現本文的目的,我們將指導JOONE去識別一個很簡單的模式。在這種模式中,我們將考察一個二進制的布爾操作,例如XOR。這個XOR操作的真值表列舉如下:
X | Y | X XOR Y |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
正如你從上表中看到的,XOR運算的結果是只有當X和Y具有不同值時,結果才為真(1)。其它情況下,XOR運算結果均為假(0)。默認地,JOONE從存儲在你的系統中的文本文件中取得輸入。這些文本文件通過使用一種稱為FileInputSynapse的特殊觸角來讀取。為了訓練XOR運算問題,你必須建立一個輸入文件-該文件包含上面顯示的數據。該文件顯示在列表1中。
列表1:為解決XOR問題的輸入文件的內容
0.0;0.0;0.0
0.0;1.0;1.0
1.0;0.0;1.0
1.0;1.0;0.0
我們現在分析一個簡單的程序,它指導JOONE來識別XOR運算并產生正確的結果。我們現在分析訓練該神經網絡必須被處理的過程。訓練過程包括把XOR問題提交給神經網絡,然后觀察結果。如果這個結果不是所預期的,該訓練算法將調整存儲在觸角中的重量。在神經網絡的實際輸出和預料的輸出之間的差距稱為誤差。訓練將繼續到誤差小于一個可接受值為止。這個級別通常是一個百分數,如10%。我們現在分析必須用于訓練一個神經網絡的代碼。
訓練過程通過建立神經網絡開始,同時也必須創建隱蔽的輸入層和輸出層。
| // 首先,創造這三個層 input = new SigmoidLayer(); hidden = new SigmoidLayer(); output = new SigmoidLayer(); |
每個層被使用JOONE對象SigmoidLayer創建。Sigmoidlayer基于自然對數生成一個輸出。JOONE還包含另外的層,而不是你可能選擇使用的S形的層類型。
下一步,每一層被賦于一個名字。這些名字將有助于后面在調試期間識別該層。
| input.setLayerName("input"); hidden.setLayerName("hidden"); output.setLayerName("output"); |
現在必須定義每個層。我們將指定在每一層中的"行"號。該"行"號對應于這一層中的神經原的數目。
| input.setRows(2); hidden.setRows(3); output.setRows(1); |
從上面的代碼看出,輸入層有兩個神經原,隱蔽層有三個隱蔽神經原,輸出層包含一個神經原。這對于神經網絡包含兩個輸入神經原和一個輸出神經原是具有重要意義的,因為XOR運算符接收兩個參數而產生一個結果。
為使用該神經原層,我們也必須創建觸角。在本例中,我們要使用多個觸角。這些觸角用下面的代碼實現。
| // 輸入-> 隱蔽的連接。 FullSynapse synapse_IH = new FullSynapse(); // 隱蔽-> 輸出連接。 FullSynapse synapse_HO = new FullSynapse(); |
就象神經原層的情況一樣,觸角也可能命名以有助于程序的調試。下面的代碼命名新建的觸角。
| synapse_IH.setName("IH"); synapse_HO.setName("HO"); |
最后,我們必須把觸角聯接到適當神經原層。下面的代碼實現這一點。
| // 聯接輸入層到隱蔽層 input.addOutputSynapse(synapse_IH); hidden.addInputSynapse(synapse_IH); // 聯接隱蔽層到輸出層 hidden.addOutputSynapse(synapse_HO); output.addInputSynapse(synapse_HO); |
現在既然神經網絡已被創建,我們必須創建一個用于調節該神經網絡的監視器對象。下面的代碼創建監視器對象。
| //創建監視器對象并且設置學習參數 monitor = new Monitor(); monitor.setLearningRate(0.8); monitor.setMomentum(0.3); |
學習速度和動力作為參數以用于指定訓練的產生方式。JOONE利用backpropagation學習算法。要更多了解關于學習速度或者動力的信息,你應該參考backpropagation算法。
這個監視器對象應該被賦值給每一個神經原層。下面的代碼實現這一點。
| input.setMonitor(monitor); hidden.setMonitor(monitor); output.setMonitor(monitor); |
就象許多Java對象本身一樣,JOONE監視器允許聽者可以添加到它上面去。隨著訓練的進行,JOONE將通知聽者有關訓練進程的信息。在這個簡單的例子中,我們使用:
| monitor.addNeuralNetListener(this); |
我們現在必須建立輸入觸角。如前所述,我們將使用一個FileInputSynapse來讀取一個磁盤文件。磁盤文件不是JOONE唯一能夠接受的輸入種類。JOONE對于不同的輸入源具有很強的靈活性。為使JOONE能夠接收其它輸入類型,你只需創建一個新的觸角來接受輸入。在本例中,我們將簡單地使用FileInputSynapse。FileInputSynapse首先被實例化。
| inputStream = new FileInputSynapse(); |
然后,必須通知FileInputSynapse要使用哪些列。列表1中顯示的文件使用了輸入數據的前兩列。下面代碼建立起前兩列用于輸入到神經網絡。
| // 前兩列包含輸入值 inputStream.setFirstCol(1); inputStream.setLastCol(2); |
然后,我們必須提供輸入文件的名字,這個名字直接來源于用戶接口。然后,提供一個編輯控件用于收集輸入文件的名字。下面代碼為FileInputSynapse設置文件名。
| // 這是包含輸入數據的文件名 inputStream.setFileName(inputFile.getText()); |
如前所述,一個觸角僅是一個神經原層之間的數據導管。FileInputSynapse正是這里的數據導管,通過它數據進入到神經網絡。為了更容易實現這點,我們必須要把FileInputSynapse加到神經網絡的輸入層。這由下面一行實現。
| input.addInputSynapse(inputStream); |
現在既然已經建立起神經網絡,我們必須創建一個訓練員和一個監視器。訓練員用于訓練該神經網絡,因為該監視器通過一個事先設置好的訓練重復數來運行這個神經網絡。對于每一次訓練重復,數據被提供到神經網絡,然后就可以觀察到結果。該神經網絡的權重(存儲在穿梭在神經原層之間的觸角連接中)將根據誤差作適當調整。隨著訓練的進行,誤差級將下降。下列代碼建立訓練員并把它依附到監視器。
| trainer = new TeachingSynapse(); trainer.setMonitor(monitor); |
你會記得列表1中提供的輸入文件包含三個列。到目前為止,我們僅僅使用了第一、二列,它們指定了到神經網絡的輸入。第三列包含當提供給神經網絡第一列中的數字時的期盼的輸出值。我們必須使得訓練員能夠存取該列以便能確定誤差。該錯誤是神經網絡的實際輸出和期盼的輸出之間的差距。下列代碼創建另外一個FileInputSynapse并作好準備以讀取與前面相同的輸入文件。
| // 設置包含期望的響應值的文件,這由FileInputSynapse來提供 samples = new FileInputSynapse(); samples.setFileName(inputFile.getText()); |
這時,我們想指向在第三列的FileInputSynapse。下列代碼實現了這一點,然后讓訓練員使用這個FileInputSynapse。
| //輸出值在文件中的第三列上 samples.setFirstCol(3); samples.setLastCol(3); trainer.setDesired(samples); |
最后,訓練員被連結到神經網絡輸出層,這將使訓練員接收神經網絡的輸出。
| // 連接訓練員到網絡的最后一層 output.addOutputSynapse(trainer); |
我們現在已為所有的層準備好后臺線程,包括訓練員。
| input.start(); hidden.start(); output.start(); trainer.start(); |
最后,我們為訓練設置一些參數。我們指定在輸入文件中共有四行,而且想訓練20,000個周期,而且還在不段學習。如果你設置學習參數為false,該神經網絡將簡單地處理輸入并不進行學習。我們將在下一節討論輸入處理。
| monitor.setPatterns(4); monitor.setTotCicles(20000); monitor.setLearning(true); |
現在我們已經為訓練過程作好準備。調用監視器的Go方法將在后臺啟動訓練過程。
| monitor.Go(); |
神經網絡現在將要被訓練20,000個周期。當神經網絡訓練完成,誤差層應該在一個合理的低級別上。一般低于10%的誤差級是可接受的。
運行神經網絡
現在,既然神經網絡已經訓練開始,我們可以通過提供輸入模式給神經網絡來測試它并觀察結果。用于運行神經網絡的方法必須首先為神經網絡處理數據作好準備。目前,該神經網絡處理一種訓練方式中。首先,我們將從輸出層刪除訓練員。然后,我們將用FileOutputSynapse替換訓練員,以便于我們能記錄來自于神經網絡的輸出。下面的代碼實現這一點:
| output.removeOutputSynapse(trainer); FileOutputSynapse results = new FileOutputSynapse(); results.setFileName(resultFile.getText()); |
現在我們必須重置輸入流--我們將使用與訓練期間一樣的文件輸入流。這將填入在神經網絡訓練期間使用的相同的輸入。
| inputStream.resetInput(); samples.resetInput(); results.setMonitor(monitor); output.addOutputSynapse(results); |
然后,我們必須重啟響應于該神經網絡所有的線程。
| input.start(); hidden.start(); output.start(); trainer.start(); |
現在,既然線程已被重新啟動,我們必須為下一步的識別設置一些基本配置信息。下面代碼完成這些工作。
| monitor.setPatterns(4); monitor.setTotCicles(1); monitor.setLearning(false); |
首先,輸入模式數要置為4,這是因為我們想要神經網絡處理所有這四種輸入模式(你以前為訓練該神經網絡提供的)。最后,置為學習方式。這些完成后,我們可以調用監視器的"Go"方法。
| monitor.Go(); |
當訓練完成,你將看到輸出文件產生的結果類似列表2。
列表2:來自于神經網絡的輸出
0.012549763955262739
0.9854631848890223
0.9853159647305264
0.01783622084836082
可以看出,上面列表中的第一行中的數字合理地接近于零。這是很好的,因為輸入訓練文件的第一行,如列表1所示,被預期結果是為零的。同樣,第二行合理地接近于1,這也很好,因為訓練文件的第二行被預期結果也是為1的。
結論
JOONE引擎封裝了神經網絡編程中的具有相當多的復雜性,本文的例子顯示了神經網絡使用的基本過程。盡管真實世界中神經網絡的實現將復雜得多,但是其基本處理過程是一樣的--數據被提供到神經網絡用于訓練,然后生成新的模式用于識別。這里的例程為用JOONE進行探索提供了一個良好的開端。