[筆記] Golang小試實現神經網絡框架

國慶節宅在家裏,看完了40集的電視劇,也刷了4K代碼。最近看看Golang,然後想擺弄一下神經網絡。雖然如今都是第三方庫氾濫,開源代碼拿來即用,而這次對自己的要求是沒有第三方庫,代碼自成閉包。這樣更有自主控制性,加深對神經網絡實現的理解,也省去了學習各種三方包的用法,順帶着熟練下Golang這門新語言。

搜索引擎可以找到各種神經網絡的入門文章。
A Neural Network in 11 lines of Python
Anyone Can Learn To Code an LSTM-RNN in Python
這兩篇算是不錯的入門級文章,雖然第二篇直接跳到比較新的循環神經網絡。寫文章的前輩代碼質量雖然不是特別高,但是也是一行一行用盡洪荒之力來分析代碼。第一篇的11行代碼的神經網絡很清晰,讓人一看就知道如何繼續修改自己玩。一句話總結,神經網絡三步曲:Predict,Learn和Update。Predict就是系統嘗試給出結果,Learn就是根據系統的自我判斷或者參照外部結果得到系統偏差,Update則是根據偏差更新系統內部結構。

在GitHub上找Golang的機器學習包,還不是很多。這年頭,機器學習都被Matlab,R和python佔去了風頭吧。尤其,Python有着NumPy社區,Theano和TensorFlow等優秀框架。

不多說,直接開始寫代碼吧:GitHub/hotpot.seafood
其中有三個例子,分別是最基礎的xor,循環神經網絡訓練256內加法add,卷積神經網絡識別手寫數字mnist。

首先,就想到最基礎的矩陣乘法。那得有Matrix類和一些基礎方法。這個就不必搜索了,根據以數學知識實現就好。期間開小差,編譯了一下LAPACK和OpenBLAS庫,發現Fortron的代碼是編譯成.o文件的,這樣其他語言接extension很容易,只是document太少,不知道api要學到什麼時候。

本來學習前輩文章中的Python代碼,照貓畫虎寫好了循環神經網絡的add,發現沒什麼規律。直到看到 nnet 才發現原來TensorFlow,theano等框架都是往程序Graph的結構去的,就連2015年PyCon大會上的Lazy Expression估計也是爲機器學習框架設計的。nnet的代碼十分易讀,建立一個神經網絡,裏面包含的層也看得很清楚。

    nn = nnet.NeuralNetwork(
        layers=[
            nnet.Conv(
                n_feats=12,
                filter_shape=(5, 5),
                strides=(1, 1),
                weight_scale=0.1,
                weight_decay=0.001,
            ),
            nnet.Activation('relu'),
            nnet.Pool(
                pool_shape=(2, 2),
                strides=(2, 2),
                mode='max',
            ),
            nnet.Conv(
                n_feats=16,
                filter_shape=(5, 5),
                strides=(1, 1),
                weight_scale=0.1,
                weight_decay=0.001,
            ),
            nnet.Activation('relu'),
            nnet.Flatten(),
            nnet.Linear(
                n_out=n_classes,
                weight_scale=0.1,
                weight_decay=0.02,
            ),
            nnet.LogRegression(),
        ],
    )

於是照着nnet的結構,寫起了神經網絡計算框架。對於網絡的每一層,都會有計算Forward和Backward,然後Update參數。一個神經網絡先把所有的Forward計算完,得到的結果就完成了Predict任務。按自己聚類或外部提供的正確結果,再倒着把誤差傳遞一遍運行所有層的Backward過程,能完成Learn任務。最後的Update任務當然就是Update所有層的參數了。

Forward過程其實很容易理解,比如一個線性函數啦y=kx+b,不過這裏是用矩陣表示就是了:Y = X.W + B,所以這種類型就是LayerLinear了。當然還有形如11+ex\frac{1}{1+e^{-x} } sigmoid一類的叫作激活函數的層,就是矩陣每個元素都按這個元素計算一遍,相當於matrix.elements.map(sigmoid),激活函數層對於神經網絡收斂有不可或缺的作用,如果不用它們,很可能導致一個線性層參數瞬間刷爆到Inf。

Backward是計算誤差,每層可以從下一層pop上來這一層結果的誤差在y=f(x)y = f(x)中就相當於Δy\Delta y。然後就要使用這個誤差去推算輸入的誤差,即上一層輸出的誤差,並計算這一層參數更新的量。舉個例子就是買了股票預測下一時刻的股價,然後到時候看實際價格和預測差,然後調整對這支股票的期待。在控制論的PID算法裏,這裏就是去求D。Backward比較基礎的可以參考http://cs231n.github.io/optimization-2/#backprop,裏面有一些例子,在流程圖表上標出Forward和Backward的計算結果。

最後的Update就比較傻瓜,學習速率和參數更新量相乘後再累加到參數裏就搞定了。不過這裏的坑也有很多,因爲牽涉到神經網絡這門學科我覺得最坑爹的點,就是收斂。如果參數更新控制得不好,那麼你可能發現新世界新學科:混沌與分形十分典型(x(t+dt)=4x(t)(1x(t))x(t+dt) = 4 * x(t) * (1 - x(t))是經典,它可以製作0-1之間的隨機數生成器)。如果神經網絡不收斂,或者達不到穩定收斂,訓練數據就很蠻煩。比如本來訓練100個樣本數據,準確度已經達到98%了,要是收斂不穩定再來100個樣本,可能結果就變成正確率2%了。當然,穩定收斂也有弊端,那就是,穩定收斂的地方可能是函數的一個溝,可能在遙遠的某個地方還有比這個溝更深的溝,所謂“坑的爹“。所以,請“敬請“懷疑圖靈機這一模型是否能夠構造真正的智能吧。

實現xor很順利,因爲是最基礎的神經網絡調用,LayerLinear和sigmoid層只要不出問題就okay。線性層不帶初中學的記得叫截距b的話,只要記住Forward是Y=XWY = XW,Backward是dX=dYWTdX = dY W^TdW=XTdYdW = X^TdY (後記:這裏應該是dX=dYWGdX = dY W^GdW=XGdYdW = X^GdY, AGA^G是廣義逆矩陣;但是實驗發現錯誤的Backward仍然可以達到正確的收斂…),Update就W+αdWW + \alpha dW

在實現add的時候,循環神經網絡確實比較複雜,要記錄之前的狀態。就拿線性層來說,本來Yi=XWY_i=XW,現在要多出來一項Yi=XW+Yi1HY_i =XW+Y_{i-1}H。所以剛算完的Y要存到一個地方,下次算Y還得用上。這裏還遇到Golang的特性坑。一直以OOP的思路寫程序,結果遇到了Golang的繼承問題:

type I interface {
   Public () int
   Abstract () int
}

type A struct {
   I
   x int
}

func (a *A) Public () int {
   return a.Abstract()
}
func (a *A) Abstract () int {
   a.x = 1
   return a.x
}

type B struct {
   A
}

func (b *B) Abstract () int {
   b.x = 2
   return b.x
}

雖然是自己傻逼,程序明明在A的Public裏寫的是a.Abstract(),當然會執行A的Abstract。解決方案有兩種,一個是在A的Public裏,將a強制轉化爲I,然後調用Abstract;另一種是再寫一個interface,定義一個函數,這個函數有一個參數是傳自己進去。

最後是循環神經網絡,是耗時最多在調試上的。MNIST識別0-9的手寫體數字,因爲最初寫得程序設置層或參數導致不收斂,或者收斂成輸出固定值正確率10%,一度都想還是直接用Python吧。最終偶然把relu函數換成tanh函數,decay參數調小,程序學習的時候就開始比較好得收斂起來。用Python讀MNIST數據源,輸出了一個減量版的json數據集。這麼Golang就好讀了,Marshal一下搞定。至此國慶九天假,結束了,該要上班了,懶腰…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章