實質上鼓勵一下吧

前一篇釋出了倒傳遞網路的程式碼,這一篇應用前一篇所寫的網路物件,將學習機器人實做出來。由於寫出這個程式並不容易,需要考慮到執行效率的問題,因此直接使用多執行緒的方式來撰寫。而做出來的神經網路因為可以應用在許多地方,因此使用物件導向的方式製作機器人原始物件,程式很難懂,我儘量詳細說明。

直接列出完整學習機器人的程式碼,請連同前面的程式一起貼在一個檔案裏面即可,如果連該複製什麼都搞不清楚,那麼表示你連VB.NET也搞不清楚,也不用再看下去了。

Imports System.Data
Imports System.Data.Odbc
Imports System.Threading

Public MustInherit Class RobotBase
    Friend mBestNetwork As PNetwork
    Friend mSampleTable As DataTable
    Friend mResultTable As DataTable
    Public LearnSpeed As Double = 5
    Friend SampleCount As Integer
    Public smNoBestCount As Integer = 0
    Public StartSampeNo As Integer = 0

    Private FinishCount As Integer
    Private TotalDiffent As Double

    Public Event ThreadMonitor(ByVal FinishCount As Integer, ByVal ThreadCount As Integer)
    Public Event CycleFinish(ByVal CycleNo As Integer, ByVal TotalDiffent As Double, ByVal NoBest As Integer)
    Public Event FoundBest(ByVal CycleNo As Integer, ByVal BestDiffent As Double, ByVal ResultTable As DataTable)
    Public Event QueryBreak(ByRef IsBreak As Boolean)

    Public MustOverride Sub LoadSample(ByVal FullFileName As String)
    Public MustOverride Sub LoadData(ByVal Network As PNetwork, ByVal SampleNo As Integer)
    Public MustOverride Sub SaveResult(ByVal Network As PNetwork)
    Public MustOverride Sub InitNetwork()

    Public Overridable Sub Summation(ByVal SampleNo As Integer)
        LoadData(mBestNetwork, SampleNo)
        mBestNetwork.Summation()
    End Sub

    Public Overridable Sub Learning(ByVal LearnSpeed As Double, ByVal MaxNoBest As Integer, ByVal ResultSampleNo As Integer, ByVal Precision As Integer)
        mBestNetwork.LearnSpeed = LearnSpeed

        Dim smIsBreak As Boolean = False
        Dim smCycleNo As Integer = 0

        Dim smWorkNetwork As PNetwork = mBestNetwork.Clone
        Dim smBestDiffent As Double = 10000
        Dim ThreadCount As Integer = SampleCount - StartSampeNo
        smNoBestCount = 0
        While smNoBestCount < MaxNoBest And Not smIsBreak And Math.Round(smBestDiffent, Precision) > 0
            smCycleNo += 1
            FinishCount = 0
            smWorkNetwork.TotalDiff = 0
            smWorkNetwork.ClearFix()
            ThreadPool.SetMaxThreads(40, 40)
            For SampleNo As Integer = StartSampeNo To SampleCount - 1
                Dim Arg As New ThreadArg(SampleNo, smWorkNetwork)
                Dim callBack As New WaitCallback(AddressOf LearningThread)
                ThreadPool.QueueUserWorkItem(callBack, Arg)
                'LearningThread(Arg)
            Next
            While FinishCount < ThreadCount
                RaiseEvent ThreadMonitor(FinishCount, ThreadCount)
                Thread.Sleep(1000)
            End While
            TotalDiffent = smWorkNetwork.TotalDiff / ThreadCount
            If smBestDiffent > TotalDiffent Then
                smBestDiffent = TotalDiffent
                smNoBestCount = 0
                mBestNetwork = smWorkNetwork.Clone
                LoadData(mBestNetwork, ResultSampleNo)
                mBestNetwork.Summation()
                SaveResult(mBestNetwork)
                RaiseEvent FoundBest(smCycleNo, smBestDiffent, mResultTable)
            Else
                smNoBestCount += 1
            End If
            smWorkNetwork.FixNetwork(SampleCount)
            RaiseEvent CycleFinish(smCycleNo, TotalDiffent, smNoBestCount)
            RaiseEvent QueryBreak(smIsBreak)
        End While
        SaveNetwork(mBestNetwork, "NetworkXOR.xml")
    End Sub

    Private Sub LearningThread(ByVal Arg As Object)
        Dim CloneNetwork As PNetwork = Arg.Network.Clone
        LoadData(CloneNetwork, Arg.SampleNo)
        CloneNetwork.Summation()
        CloneNetwork.CalcDiffent(Arg.Network)
        SyncLock Me
            Arg.Network.TotalDiff += CloneNetwork.TotalDiff
            FinishCount += 1
        End SyncLock
    End Sub

    Public Overridable Sub LoadNetwork(ByVal FullFileName As String)
        Dim xmlNetwork As String = IO.File.ReadAllText(FullFileName)
        mBestNetwork = Deserialize(GetType(PNetwork), xmlNetwork)
    End Sub

    Public Overridable Sub SaveNetwork(ByVal Network As PNetwork, ByVal FullFileName As String)
        Dim xmlNetwork As String = Serialize(GetType(PNetwork), Network)
        IO.File.WriteAllText(FullFileName, xmlNetwork)
    End Sub

    Public Overridable Sub DumpNetwork()
        Dim xmlNetwork As String = Serialize(GetType(PNetwork), mBestNetwork)
        Debug.Print(xmlNetwork)
    End Sub

End Class

開始說明最複雜的程式核心,也就是Public Overridable Sub Learning()這段程式碼。

類神經網路的學習過程分為單次學習和批次學習兩種,單次學習就是每次學習一個範例,並且立刻將網路輸出值和標準輸出值比較,來修正整個網路的神經鍵和神經元。批次學習就是要讓網路算出所有的學習範例,並且記錄每一個神經鍵和神經元的修正值,但是不立刻修正,而是等到所有的範例修正值都得到以後,取每個神經鍵和神經元修正值的平均值來修正網路。

單次學習的好處是程式碼簡單,對於交作業的簡單應用,單次學習就够了,但是執行的效率卻是很差的,因為只能夠一個範例一個範例的做,我還沒有想出有什麼辦法做成多執行緒。

批次學習就可以使用多執行緒的方式來提升執行效率了,因為修正網路是在所有範例都取得修正值之後才進行,不必等待前一個範例執行完成,因此比較適合用在執行範例多的應用當中。

我在類別中宣告了一個mBestNetwork神經網路,就是經過學習所得到的最佳網路組態。網路可能在學習過程中發散,也就是偏離了能夠得到最佳結果的值,因此並不是每一次的學習都是新的最佳值。

        mBestNetwork.LearnSpeed = LearnSpeed

因為其他網路都是複製mBestNetwork,所以只要將學習速率設置進最佳網路即可。

        Dim smIsBreak As Boolean = False

由於學習過程會很花時間,因此使用一個變數來控制什麽時候要脫離回圈,可以讓使用者介面來進行控制。

        Dim smCycleNo As Integer = 0

這個變數用來記錄進行了多少個學習回圈,是給人看的,會使用事件的方式將這個值傳出去。

        Dim smWorkNetwork As PNetwork = mBestNetwork.Clone

複製最佳網路至工作網路物件,這樣才不會破壞之前所算出來的最佳值。

        Dim smBestDiffent As Double = 10000

學習過程所得到得最佳平均誤差值,誤差越小越好,因此初始值是個合理的大值。

        Dim ThreadCount As Integer = SampleCount – StartSampeNo

使用TrheadCount來記錄到底總共有多少範例需要執行,當然可以直接是SampleCount,因為我的程式會拿來做更多的應用,所以不見得就是等於SampleCount。

        smNoBestCount = 0

由於過程中可能會偏離,所以要記錄沒有找到最佳值的次數,只要次數過多就可以停止,不再學習了。

        While smNoBestCount < MaxNoBest And Not smIsBreak And Math.Round(smBestDiffent, Precision) > 0

用while回圈來讓學習的過程不斷重複,一直到找不到最佳值、使用者中斷、誤差值過小的時候才結束學習。

            smCycleNo += 1
            FinishCount = 0
            smWorkNetwork.TotalDiff = 0
            smWorkNetwork.ClearFix()

smWorkNetwork這個物件將用來記錄所有的修正值,並且記錄所有範例的總誤差值,因此在每一次的學習過程都要清除乾淨。

            ThreadPool.SetMaxThreads(40, 40)

使用多執行緒的方式來進行,主要設置是第一個數字,是告訴程式同一個時間有多少執行緒可以一起執行。

            For SampleNo As Integer = StartSampeNo To SampleCount - 1
                Dim Arg As New ThreadArg(SampleNo, smWorkNetwork)
                Dim callBack As New WaitCallback(AddressOf LearningThread)
                ThreadPool.QueueUserWorkItem(callBack, Arg)
                'LearningThread(Arg)
            Next

這段程式就是讓神經網路學習每一個範例,使用多執行緒的方式以提高執行效率。

    Private Sub LearningThread(ByVal Arg As Object)
        Dim CloneNetwork As PNetwork = Arg.Network.Clone
        LoadData(CloneNetwork, Arg.SampleNo)
        CloneNetwork.Summation()
        CloneNetwork.CalcDiffent(Arg.Network)
        SyncLock Me
            Arg.Network.TotalDiff += CloneNetwork.TotalDiff
            FinishCount += 1
        End SyncLock
    End Sub

每一個範例所要做的事情如上面這個方法所做的,載入範例和標準值、計算網路輸出值、計算網路修正值、將修正值存入工作物件smWorkNetwork,告訴主程式完成一個範例。

            While FinishCount < ThreadCount
                RaiseEvent ThreadMonitor(FinishCount, ThreadCount)
                Thread.Sleep(1000)
            End While

這一段程式等待所有的範例學習完成,並且使用事件的方式將進度傳送給使用者端。

            TotalDiffent = smWorkNetwork.TotalDiff / ThreadCount

smWorkNetwork.TotalDiff記錄了所有範例的誤差值,如果只是拿來計算,可以不用除以範例數,反正就是個數字,若是要拿來顯示,除上範例數可以比較容易觀察。

            If smBestDiffent > TotalDiffent Then
                smBestDiffent = TotalDiffent
                smNoBestCount = 0
                mBestNetwork = smWorkNetwork.Clone
                LoadData(mBestNetwork, ResultSampleNo)
                mBestNetwork.Summation()
                SaveResult(mBestNetwork)
                RaiseEvent FoundBest(smCycleNo, smBestDiffent, mResultTable)
            Else
                smNoBestCount += 1
            End If

當新執行完的誤差值比上一次的小,就是找到新的網路值,所以這個時候要將新的網路值存入最佳網路物件mBestNetwork,並且可以載入樣本範例,讓前端顯示一下。當然這會影響一點點速度,但如果範例很多,這一點點時間是值得的。

            smWorkNetwork.FixNetwork(SampleCount)
            RaiseEvent CycleFinish(smCycleNo, TotalDiffent, smNoBestCount)
            RaiseEvent QueryBreak(smIsBreak)
        End While
        SaveNetwork(mBestNetwork, "NetworkXOR.xml")
    End Sub

最後修正網路,並且用事件讓使用者端可以知道進行的結果。

前面所作出來的是學習機器人的基類,因為輸入值和標準值的載入每個案例會有所不同,必須要經由繼承才能夠執行,以XOR為範例來使用這個基類。

Public Class RobotXOR
    Inherits RobotBase

    Public Overrides Sub InitNetwork()
        MyBase.mBestNetwork = New PNetwork(3, 2, 2, 1, LearnSpeed)
    End Sub

    Public Overrides Sub LoadData(ByVal Network As PNetwork, ByVal SampleNo As Integer)
        Select Case SampleNo
            Case 0
                Network.InpValue(0) = -1
                Network.InpValue(1) = -1
                Network.StdValue(0) = 0
            Case 1
                Network.InpValue(0) = -1
                Network.InpValue(1) = 1
                Network.StdValue(0) = 1
            Case 2
                Network.InpValue(0) = 1
                Network.InpValue(1) = -1
                Network.StdValue(0) = 1
            Case 3
                Network.InpValue(0) = 1
                Network.InpValue(1) = 1
                Network.StdValue(0) = 0
        End Select
    End Sub

    Public Overrides Sub LoadSample(ByVal FullFileName As String)
        MyBase.SampleCount = 4

        MyBase.mResultTable = New DataTable
        MyBase.mResultTable.Columns.Add("Rate", GetType(Double))
        Dim drResult As DataRow = MyBase.mResultTable.NewRow
        MyBase.mResultTable.Rows.Add(drResult)
    End Sub

    Public Overrides Sub SaveResult(ByVal Network As PNetwork)
        MyBase.mResultTable.Rows(0)("Rate") = Network.OutValue(0)
    End Sub
End Class

使用者介面可以用,下面的程式碼來使用RobotXOR類別。

Dim WithEvents Robot As New RobotXOR

用類似以下的程式碼來處理物件所發生地事件。

Public Sub Robot_ThreadMonitor(ByVal FinishCount As Integer, ByVal ThreadCount As Integer) Handles Robot.ThreadMonitor

類神經網路的倒傳遞網路程式碼大致上已經全部列出來了,當然理論與實際一定要切合才行,實際上我用了許多案例來測試這個網路,XOR、七節顯示器都可以得到很不錯的結果,大約執行一百多次學習過程就能夠收斂到非常精准的程度。

請注意,要一百多次的學習,也就是說,還需要不算短的時間,但是只要經過學習,最後的網路值就可以存起來,以後沒有新增範例就可以不用重新學習。

既然類神經網路有預測的能力,當然也要拿一些特殊的案例來測試才能驗證它的能力,股票、樂透因為資料取得比較容易,漠哥都測試過了。這麼說吧,因為範例數量龐大,而且每天都在增加,網路的深度、寬度都需要調整才能適應。而寬度、深度的增加就表示計算過程更加耗時,除非你有臺超級電腦,否則想要找到最佳網路值,恐怕難比登天。

創作者介紹
創作者 漠哥 的頭像
漠哥

人生四十宅開始 二號宅

漠哥 發表在 痞客邦 留言(2) 人氣()


留言列表 (2)

發表留言
  • AI
  • ThreadArg,ClearFix未定義?

    漠哥您好..測試您PO上來的程式碼出現兩個地方未定義?
    smWorkNetwork.ClearFix()
    Dim Arg As New ThreadArg(SampleNo, smWorkNetwork)
  • sorry,雖然這些都是從我的原始碼複製出來的,但是爲了讓更多人看懂,有些多餘的碼就沒有貼出來了。
    ClearFix的這個函數就是把修訂值全部清空,反正就是繞回圈全部設定成0。
    另一個是多執行緒的寫法,你可以參考我的另一篇多執行緒的程式調整。

    漠哥 於 2009/12/14 10:06 回覆

  • da
  • 請問如果以算出有數職的氣動力參數要用類神經運算要怎做呢