神經網路-全連接層(1)

本文收錄在無痛的機器學習第一季。

寫在前面:感謝@夏龍對本文的審閱並提出了寶貴的意見。

接下來聊一聊現在大熱的神經網路。最近這幾年深度學習發展十分迅速,感覺已經佔據了整個機器學習的「半壁江山」。各大會議也是被深度學習佔據,引領了一波潮流。深度學習中目前最火熱的兩大類是卷積神經網路(CNN)和遞歸神經網路(RNN),就從這兩個模型開始聊起。

當然,這兩個模型所涉及到概念內容實在太多,要寫的東西也比較多,所以為了能把事情講得更清楚,這裡從一些基本概念聊起,大神們不要覺得無聊啊……

今天扯的是全連接層,也是神經網路中的重要組成部分。關於神經網路是怎麼發明出來的這裡就不說了。全連接層一般由兩個部分組成,為了後面的公式能夠更加清楚的表述,以下的變數名中上標表示所在的層,下標表示一個向量或矩陣內的行列號

  • 線性部分:主要做線性轉換,輸入用X表示,輸出用Z表示

  • 非線性部分:那當然是做非線性變換了,輸入用線性部分的輸出Z表示,輸出用X表示。

線性部分

線性部分的運算方法基本上就是線性加權求和的感覺,如果對於一個輸入向量x=[x_0,x_1,...x_n]^T,線性部分的輸出向量是z=[z_0,z_1,z_2,...z_m]^T,那麼線性部分的參數就可以想像一個m*n的矩陣W,再加上一個偏置項b=[b_0,...b_m]^T,於是有:

W*x+b=z

線性部分做了什麼事情呢?簡單來說就是對輸入數據做不同角度的分析,得出該角度下對整體輸入數據的判斷。

這麼說有點抽象,舉一個實際點的例子,就拿CNN的入門case——MNIST舉例。MNIST的例子在此不多說了,它是一個手寫數字的識別項目,輸入是一張28*28的二值圖,輸出是0-9這是個數字,這裡假設我們採用完全全連接的模型,那麼我們的輸入就是28*28=784個像素點。數據顯示到屏幕上大概是這個樣子:

對於我們來說,這個像素點都太過於抽象了,我們無法判斷這些像素點的取值和最終識別的關係:

他們是正相關還是負相關?

很顯然,像素點之間是存在相關關係的,這個關係具體是什麼我們後面再說,但存在關係這件事是板上釘釘的。所以只給每一個像素點一個權重是解決不了問題的,我們需要多組權重。

我們可以

1)在第一組權重中給第一個像素一個正數,第二個也是正數,

2)在第二組權重中給第一個像素負數,而第二個還是正數……

這樣,我們相當於從多個角度對輸入數據進行分析匯總,得到了多個輸出結果,也就是對數據的多種評價。

非線性部分

非線性部分有一些「套路」函數,這裡只說下其中的一個經典函數——sigmoid。它的函數形式如下所示:

f(x)=frac{1}{1+e^{-x}}

圖像如下所示:

這個函數的輸入正是我們上一步線性部分的輸出z,此時z取值範圍在(-infty ,+infty),經過了這個函數就變成了(0,1)

那非線性部分為什麼要做這個函數轉換呢?以我的粗淺理解,其中的一個作用就是作數據的歸一化。不管前面的線性部分做了怎樣的工作,到了非線性這裡,所有的數值將被限制在一個範圍內,這樣後面的網路層如果要基於前面層的數據繼續計算,這個數值就相對可控了。不然如果每一層的數值大小都不一樣,有的範圍在(0,1),有的在(0,10000),做優化的時候優化步長的設定就會有麻煩。

另外一個作用,就是打破之前的線性映射關係。如果全連接層沒有非線性部分,只有線性部分,我們在模型中疊加多層神經網路是沒有意義的,我們假設有一個2層全連接神經網路,其中沒有非線性層,那麼對於第一層有:

W^0*x^0+b^0=z^1

對於第二層有:

W^1*z^1+b^1=z^2

兩式合併,有

W^1*(W^0*x^0+b^0)+b^1=z^2

W^1*W^0*x^0+(W^1*b^0+b^1)=z^2

所以我們只要令W^{0}=W^1*W^0 , b^{0}=W^1*b^0+b^1,就可以用一層神經網路表示之前的兩層神經網路了。所以非線性層的加入,使得多層神經網路的存在有了意義。

另外還有一個比較有名的非線性函數,叫做雙曲正切函數。它的函數形式如下所示:

f(x)=frac{e^x-e^{-x}}{e^x+e^{-x}}

這個長得很複雜的函數的範圍是(-1,1)。可以看出,它的函數範圍和前面的sigmoid不同,它是有正有負的,而sigmoid是全為正的。

神經網路的模樣

實際上對於只有一層且只有一個輸出的神經網路,如果它的非線性部分還使用sigmoid函數,那麼它的形式和邏輯斯特回歸(logistic regression)是一樣的。所以可以想像神經網路模型從概念上來看比邏輯斯特回歸要複雜。那麼它的複雜的樣子是什麼樣呢?下面給出一段全連接層的代碼,開始做實驗:

class FC:n def __init__(self, in_num, out_num, lr = 0.01):n self._in_num = in_numn self._out_num = out_numn self.w = np.random.randn(out_num, in_num) * 10n self.b = np.zeros(out_num)n def _sigmoid(self, in_data):n return 1 / (1 + np.exp(-in_data))n def forward(self, in_data):n return self._sigmoid(np.dot(self.w, in_data) + self.b)n

從代碼上看東西並不多嘛,注意到我們會對參數中的w進行隨機初始化,有時我們會讓老天隨機一個神經網路給我們,我們也可以看看隨機大帝的旨意。

為了方便可視化,這裡只做輸入為2,輸出為1的數據。好了,先來看1號選手:

x = np.linspace(-10,10,100)ny = np.linspace(-10,10,100)nX, Y = np.meshgrid(x,y)nX_f = X.flatten()nY_f = Y.flatten()ndata = zip(X_f, Y_f)nnfc = FC(2, 1)nZ1 = np.array([fc.forward(d) for d in data])nZ1 = Z1.reshape((100,100))ndraw3D(X, Y, Z1)n

定睛一看這其實就是一個標準的Logistic Regression。他的圖像如下所示:

經過多次隨機測試,基本上它都是這個形狀,只不過隨著權重隨機的數值變化,這個「台階」對旋轉到不同的方向,但歸根結底還是一個台階。

這也說明1層神經網路是沒有出路的,它本質上還是個線性分類器的實力,那麼小夥伴還給它加一層吧:

fc = FC(2, 3)nfc.w = np.array([[0.4, 0.6],[0.3,0.7],[0.2,0.8]])nfc.b = np.array([0.5,0.5,0.5])nnfc2 = FC(3, 1)nfc2.w = np.array([0.3, 0.2, 0.1])nfc2.b = np.array([0.5])nnZ1 = np.array([fc.forward(d) for d in data])nZ2 = np.array([fc2.forward(d) for d in Z1])nZ2 = Z2.reshape((100,100))nndraw3D(X, Y, Z2)n

這次我們暫時不用隨機權重,而是自己設置了幾個數值,可以看出,參數設置得很用心。兩層全都是正數……,那麼圖像呢?

看上去比之前的台階「柔軟」了一些,但歸根結底還是很像一個台階……好吧,那我們加點負權重,讓我們從兩個方面分析輸入數據:

fc = FC(2, 3)nfc.w = np.array([[-0.4, 1.6],[-0.3,0.7],[0.2,-0.8]])nfc.b = np.array([-0.5,0.5,0.5])nnfc2 = FC(3, 1)nfc2.w = np.array([-3, 2, -1])nfc2.b = np.array([0.5])nnZ1 = np.array([fc.forward(d) for d in data])nZ2 = np.array([fc2.forward(d) for d in Z1])nZ2 = Z2.reshape((100,100))nndraw3D(X, Y, Z2)n

趕緊上圖:

加了負權重後,看上去終於不那麼像台階了,這時候2層神經網路的非線性能力開始顯現出來了。下面把權重交給隨機大帝:

fc = FC(2, 100)nfc2 = FC(100, 1)nnZ1 = np.array([fc.forward(d) for d in data])nZ2 = np.array([fc2.forward(d) for d in Z1])nZ2 = Z2.reshape((100,100))ndraw3D(X, Y, Z2,(75,80))n

上圖:

這時候的非線性已經非常明顯了,我們不妨繼續加幾層看看DNN的厲害:

fc = FC(2, 10)nfc2 = FC(10, 20)nfc3 = FC(20, 40)nfc4 = FC(40, 80)nfc5 = FC(80, 1)nnZ1 = np.array([fc.forward(d) for d in data])nZ2 = np.array([fc2.forward(d) for d in Z1])nZ3 = np.array([fc3.forward(d) for d in Z2])nZ4 = np.array([fc4.forward(d) for d in Z3])nZ5 = np.array([fc5.forward(d) for d in Z4])nZ5 = Z5.reshape((100,100))ndraw3D(X, Y, Z5,(75,80))n

這個圖看上去又複雜了許多……

從上面的實驗中可以看出,層數越高,非線性的「能力」確實越強,腦洞開得也越大。

知道了他的厲害,下回我們將詳細聊下它的求解方法——反向傳播(Back Propagation)。

文章代碼可以在hsmyy/zhihuzhuanlan 找到


推薦閱讀:

大家心目中的這些「優質」論文,你讀過幾篇?| PaperDaily #01
微軟愛上Linux:當PowerShell來到Linux時
科學式家|第四範式陳雨強:萬字深析工業界機器學習最新黑科技

TAG:机器学习 | 深度学习DeepLearning |