實質上鼓勵一下吧

感覺上研究這個問題的人都沒有搞得很清楚,畢竟類神經網路是要用到蠻難理解的數學,並且要真正會寫程式才成。這一篇我就重新整理一下,用比較簡單的方法再把神經網路程式做一遍。

首先宣告神經元,並且同時考慮序列化的動作,序列化的重點有以下幾點:
1.在類別之前加上<Serializable()>類別屬性
2.將要保存的值使用Public變數宣告
3.宣告一個Public Sub New

單看屬性很容易懂的,InnerValue就是神經元內值,SynapseValue就是這個神經元與上一層的神經鍵值,它是個陣列,因為與上一層有關係,必須要知道上一層有多大,才有辦法宣告陣列大小。

神經鍵的初始值經過實際上的測試,如果輸入層太大,很容易沒有辦法收斂,如果是拿來做簡單的測試,那麼用1和-1就可以了。

<Serializable()> Public Class PElement
    '序列化時必須要儲存的值,用Public宣告
    Public LayerNo As Integer
    Public UpperSize As Integer
    Public InnerValue As Double = 0
    Public SynapseValue() As Double

    '序列化時不需要儲存的值,用Friend宣告
    Friend Network As PNetwork      '網路參考
    Friend OutValue As Double       '輸出值
    Friend DifValue As Double       '輸出差異值
    Friend InnerFix As Double       '神經元內修正值
    Friend SynapseFix() As Double   '神經鍵修正值

    '爲了能夠序列化必須要有的宣告
    Public Sub New()
    End Sub

    Public Sub New(ByVal LayerNo As Integer, ByVal UpperSize As Integer)
        Init(LayerNo, UpperSize)
    End Sub

    Public Sub Init(ByVal LayerNo As Integer, ByVal UpperSize As Integer)
        Me.LayerNo = LayerNo
        Me.UpperSize = UpperSize
        '為了簡化程式,讓輸入層也使用標準神經元
        If Me.LayerNo > 0 Then
            ReDim SynapseValue(Me.UpperSize - 1)
            ReDim SynapseFix(Me.UpperSize - 1)
            For S As Integer = 0 To Me.UpperSize - 1
                SynapseValue(S) = IIf(S Mod 2 = 0, -0.01, 0.01)
            Next
        End If
    End Sub

End Class

再來宣告神經層,簡單說一個神經層裏面就是有很多的神經元,所以要宣告一個神經元陣列,並且一個個初始化神經元。

<Serializable()> Public Class PLayer
    '序列化時必須要儲存的值,用Public宣告
    Public LayerNo As Integer
    Public LayerSize As Integer
    Public UpperSize As Integer
    Public Elements() As PElement

    '序列化時不需要儲存的值,用Friend宣告
    Friend Network As PNetwork

    Public Sub New()
    End Sub

    Public Sub New(ByVal LayerNo As Integer, ByVal LayerSize As Integer, ByVal UpperSize As Integer)
        Init(LayerNo, LayerSize, UpperSize)
    End Sub

    Public Sub Init(ByVal LayerNo As Integer, ByVal LayerSize As Integer, ByVal UpperSize As Integer)
        Me.LayerNo = LayerNo
        Me.LayerSize = LayerSize
        Me.UpperSize = UpperSize

        ReDim Elements(LayerSize - 1)
        For E As Integer = 0 To LayerSize - 1
            Elements(E) = New PElement(LayerNo, Me.UpperSize)
        Next
    End Sub

End Class

接著是神經網路,這個可能就不好懂了,先看簡單的宣告和運算輸出。同樣的一個網路就是許多神經層所組成的,因此需要有一個Layers陣列來保存神經層,並且一一的初始化。一般而言神經層只要有輸入、隱藏、輸出三層就夠了,如果你的訓練範例比較多,那麼可以考慮使用兩層隱藏層,但是整個執行效率將會變得很慢。輸出值的運算公式請參考神經網路相關書籍,這裡不多做解釋。

<Serializable()> Public Class PNetwork
    Public LearnSpeed As Double
    Public TotalDiff As Double
    Public Layers() As PLayer

    Friend InpLayerNo As Integer = 0
    Friend OutLayerNo As Integer = 0
    Friend StdValue() As Double

    Public Sub New()
    End Sub

    Public Sub New(ByVal LayerCount As Integer, ByVal InpSize As Integer, ByVal OutSize As Integer, ByVal Speed As Double)
        Init(LayerCount, InpSize, OutSize, Speed)
    End Sub

    Public Sub Init(ByVal LayerCount As Integer, ByVal InpSize As Integer, ByVal OutSize As Integer, ByVal Speed As Double)
        OutLayerNo = LayerCount - 1
        LearnSpeed = Speed

        ReDim Layers(LayerCount - 1)
        For LayerNo As Integer = 0 To Layers.Length - 1
            Dim UpperSize As Integer = 0
            If LayerNo > InpLayerNo Then UpperSize = Layers(LayerNo - 1).LayerSize
            Dim LayerSize = IIf(LayerNo = InpLayerNo, InpSize, OutSize)
            Layers(LayerNo) = New PLayer(LayerNo, LayerSize, UpperSize)
        Next
        ReDim StdValue(OutSize - 1)

        SetNetwork()
    End Sub

    '必須在序列化之後呼叫這個程序,神經元與神經層才能夠參考到網路
    Public Sub SetNetwork()
        For LayerNo As Integer = 0 To Layers.Length - 1
            Layers(LayerNo).Network = Me
            For ElementNo As Integer = 0 To Layers(LayerNo).Elements.Length - 1
                Layers(LayerNo).Elements(ElementNo).Network = Me
            Next
        Next
    End Sub

    Public Property InpValue(ByVal index As Integer) As Double
        Get
            Return Layers(Me.InpLayerNo).Elements(index).OutValue
        End Get
        Set(ByVal value As Double)
            Layers(Me.InpLayerNo).Elements(index).OutValue = value
        End Set
    End Property

    Public ReadOnly Property OutValue(ByVal Index As Integer) As Double
        Get
            Return Layers(Me.OutLayerNo).Elements(Index).OutValue
        End Get
    End Property

    Public Sub Summation()
        For L As Integer = 1 To Layers.Length - 1
            For E As Integer = 0 To Layers(L).LayerSize - 1
                Dim outdbl As Double = -Layers(L).Elements(E).InnerValue
                For S As Integer = 0 To Layers(L).Elements(E).SynapseValue.Length - 1
                    outdbl += Layers(L - 1).Elements(S).OutValue * Layers(L).Elements(E).SynapseValue(S)
                Next
                Layers(L).Elements(E).OutValue = 1 / (1 + Math.Exp(-outdbl))
            Next
        Next
    End Sub

End Class

再來就是複雜的類神經網路學習,倒傳遞網路的學習原理就是根據標準值和實際輸出值得差異,對整個神經網路進行修正,所以首先就是要求得差異值,可是很奇怪,每一本書寫的輸出層差異值一開始都是:
差異值=(StdValue - OutValue)
可是算呀算的到後面就變成另一個公式:
差異值=OutValue * (1 - OutValue) * (StdValue – OutValue)

實際套值進去看:
當輸出值為0.9標準值為0時,輸出層差異值=0.9*(1-0.9)*(0-0.9)=0.081
當輸出值為0.99標準值為0時,輸出層差異值=0.99*(1-0.99)*(0-0.99)-0.009801
也就是說按照公式,0.99比0.9更接近0,這是哪一國的數學呀!

漠哥已經沒有老師可以問,如果還在學校的網友可以請教一下老師這個問題。

知道了一個輸出單元的差異值,那麼全部加起來當然就是整個網路的差異值了,但是差異值有正有負,因此網路差異值應該取絕對值加起來。其他的書上寫的另一套公式當然也是可以,但是這個值只是用來看的,沒有拿來做進一步的運算,所以爲了執行速度公式越簡單越好。

    Public Sub CalcDiffent(ByVal StoreNetwork As PNetwork)
        '計算輸出層的差異值
        TotalDiff = 0
        For E As Integer = 0 To Layers(OutLayerNo).LayerSize - 1
            With Layers(OutLayerNo).Elements(E)
                '.DifValue = .OutValue * (1 - .OutValue) * (StdValue(E) - .OutValue)
                .DifValue = (StdValue(E) - .OutValue)
                TotalDiff += Math.Abs(.DifValue)
            End With
        Next

隱藏層的差異值 = 隱藏層輸出值 * (1 - 隱藏層輸出值) * SUM(輸出層的差異值 * 對應神經鍵值)
因此算出隱藏層的差異值程式如下:

        '計算各層的差異值
        For L As Integer = OutLayerNo - 1 To 1 Step -1
            For E As Integer = 0 To Layers(L).LayerSize - 1
                Dim DifSum As Double = 0
                For NextE As Integer = 0 To Layers(L + 1).LayerSize - 1
                    With Layers(L + 1).Elements(NextE)
                        DifSum += .SynapseValue(E) * .DifValue
                    End With
                Next
                With Layers(L).Elements(E)
                    .DifValue = .OutValue * (1 - .OutValue) * DifSum
                End With
            Next
        Next

接著就是要算出修正值,程式如下:

        '計算神經元與神經鍵修正值
        For L As Integer = OutLayerNo To 1 Step -1
            For E As Integer = 0 To Layers(L).LayerSize - 1
                Layers(L).Elements(E).InnerFix += _
                -LearnSpeed * Layers(L).Elements(E).DifValue
                For UpperE As Integer = 0 To Layers(L - 1).LayerSize - 1
                    Layers(L).Elements(E).SynapseFix(UpperE) += _
                        LearnSpeed * Layers(L).Elements(E).DifValue * _
                        Layers(L - 1).Elements(UpperE).OutValue
                    Next
            Next
        Next
    End Sub

在取得修正值之後就是實際上去修正網路,當然可以在計算的時候就直接修正,但是考慮到批次學習那麼最好還是另外寫一個修正程式。

    Public Sub FixNetwork(ByVal SampleCount As Integer)
        For L As Integer = 1 To Layers.Length - 1
            For E As Integer = 0 To Layers(L).Elements.Length - 1
                Layers(L).Elements(E).InnerValue += _
                    Layers(L).Elements(E).InnerFix / SampleCount
                For S As Integer = 0 To Layers(L).Elements(E).SynapseValue.Length - 1
                    Layers(L).Elements(E).SynapseValue(S) += _
                       Layers(L).Elements(E).SynapseFix(S) / SampleCount
                Next
            Next
        Next
    End Sub

其實前面這些程式已經是可以運作的神經網路程式了,但是實際上學習範例比較多得網路應用都會採用批次學習方法,那麼就有需要對網路進行複製,也就是說新造一個網路,然後所有的值全部複製過去,因此另外寫一段程序如下:

    Public Function Clone() As PNetwork
        Dim MyClone As New PNetwork(Me.Layers.Length, _
           Me.Layers(InpLayerNo).LayerSize, _
           Me.Layers(OutLayerNo).LayerSize, LearnSpeed)
        For L As Integer = 1 To Layers.Length - 1
            For E As Integer = 0 To Layers(L).LayerSize - 1
                MyClone.Layers(L).Elements(E).InnerValue = _
                    Me.Layers(L).Elements(E).InnerValue
                For S As Integer = 0 To Layers(L).Elements(E).SynapseValue.Length - 1
                    MyClone.Layers(L).Elements(E).SynapseValue(S) = _
                        Me.Layers(L).Elements(E).SynapseValue(S)
                Next
            Next
        Next
        Return MyClone
    End Function

寫到這裡其實已經把程式內容都交代了,接著就是怎麼使用這個網路了,如果讀者可以看得懂程式,那麼當然可以直接拿來應用,不過最好還是能用比較好的方法去寫,這樣寫出來的程式就能夠很容易的重複利用。這一篇有點長了,留到下一篇寫。

創作者介紹

人生四十宅開始 二號宅

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


留言列表 (5)

發表留言
  • Grace
  • 漠大哥,看了你的類神經網路好多次了,但因自己是初學者,很難輕易看懂~不知大哥,可以把實作的範例Mail給小妹學習學習嗎?給大哥大加油!多寫一些,給新人學習的機會~
  • Mr.趙
  • 請問漠大,有用C++寫過嗎?
    感謝漠大的分享!
    得到這難得的資訊,謝謝!
  • 發佈在網上是為了讓比較多人看得懂,所以使用VB.NET,實際上運作效率比較好的版本是用C#寫的。
    其實原理搞懂了,用什麼語言寫都差不多,差別只有在執行效率吧。

    漠哥 於 2009/07/14 17:17 回覆

  • Mr.趙
  • 請問漠大,那原理要怎麼搞懂呢?
    有推薦什麼書嗎?
    我是看:類神經網路與模糊控制理論入門
  • 類神經網路的模型很多種,首先要先找出適合你的應用的那一種,然後針對特定的一種來進行研究,倒傳遞網路是目前應用最為廣泛的,如果沒有目標就以他為準。
    原理可以參考我的第一、二篇內容,應該可以讓你明白到底是怎麼運作的,然後再詳細看書,找出幾個公式:計算輸出值、計算輸出層誤差值、計算隱藏層誤差值、加權值修正。
    先別用物件導向的方式去做,那只會讓你頭昏眼花,而是用最沒有效率的陣列方式去實踐出來,讓整個程式的複雜度降到最低。
    程式應該包含計算輸出值、修正網路、驗證所有範例等三個部份。
    並且先以XOR當做範例去驗證程式是否能夠收斂到可以接受的值,事實上我在學習速率10的狀況下,只要經過9次的學習就能夠得到相當精確的網路值。
    驗證時不要期待能夠完全精確,例如XOR的輸出值只要該為0的達到0.1以下,該為1的達到0.9以上其實就可以了。
    也就是說整體誤差值在0.1應該就是可以接受的範圍了,重點是驗證出程式是可行的,精密度可以另外用參數傳入。
    等到你的網路確實對XOR運作無誤,再拿實際案例套用進網路運算。此時要注意的就是隱藏層的層數、隱藏層寬度、案例資料的合理化。
    要注意的是類神經網路對於資料時間發生的先後順序沒有觀念,所以你的資料必須包含時間變化的資料,例如股票的5日線、10日線等的向量資訊,重點在向量。
    當然套用實際案例時就有效率上的問題,如果再用單一執行緒的方式運行可能很久都得不到答案,必須考慮批次學習的程式寫法。
    大概整個程式發展的過程就是這樣子了。

    漠哥 於 2009/07/15 16:01 回覆

  • yamato
  • 小弟剛接觸類神經網路這門領域
    很多知識幸賴漠哥才能窺知一二
    以下是關於漠哥困惑的點小小表示一下自己的看法
    煩請漠哥指教

    輸出層通常選用雙彎曲函數作為轉換函數
    而雙彎曲函數又寫成 y = 1/(1+exp(-x))
    對雙彎曲函數作微分 y' = exp(-x)/(1+exp(-x))^2
    函數的一次微分相當於是曲線的斜率
    斜率可以想成是 (Y方向的變化量)/(X方向的變化量)
    在這裡 X方向的變化量 = StdValue - OutValue
    將之代回微分式
    可以得知 Y方向的變化量 = y'*(StdValue - OutValue)
    又很剛好y'可以用y來表示
    y' = exp(-x)/(1+exp(-x))^2
    = 1/(1+exp(-x)) * [1-1/(1+exp(-x))]
    = y * [1-y]
    所以
    Y方向的變化量 = y * (1-y) * (StdValue - OutValue)

    實際套值進去看的話:
    當輸出值為0.9標準值為0時,輸出層差異值 = 1/(1+exp(-0.9)) * [1-1/(1+exp(-0.9))] * (0-0.9) = -0.185
    當輸出值為0.99標準值為0時,輸出層差異值 = 1/(1+exp(-0.99)) * [1-1/(1+exp(-0.99))] * (0-0.99) = -0.196

    所以0.9確實比0.99更接近0

    P.S X方向的變化量 就是輸出值與標準值的差
    P.S Y方向的變化量 就是"轉換後的"輸出值與"轉換後的"標準值的差
  • 智慧工人
  • 很久以前就拜讀漠哥這系列大作,
    最近才開始學著實作,
    做到輸出層差異值確實一頭霧水,
    感謝 yamato 大大及時解惑!!