接下來聊一聊如今大熱的神經網絡。最近這幾年深度學習發展十分迅速,感受已經佔據了整個機器學習的「半壁江山」。各大會議也是被深度學習佔據,引領了一波潮流。深度學習中目前最火熱的兩大類是卷積神經網絡(CNN)和遞歸神經網絡(RNN),就從這兩個模型開始聊起。git
固然,這兩個模型所涉及到概念內容實在太多,要寫的東西也比較多,因此爲了能把事情講得更清楚,這裏從一些基本概念聊起,大神們不要以爲無聊啊……github
今天扯的是全鏈接層,也是神經網絡中的重要組成部分。關於神經網絡是怎麼發明出來的這裏就不說了。全鏈接層通常由兩個部分組成,爲了後面的公式可以更加清楚的表述,如下的變量名中上標表示所在的層,下標表示一個向量或矩陣內的行列號:網絡
-
線性部分:主要作線性轉換,輸入用X表示,輸出用Z表示dom
-
非線性部分:那固然是作非線性變換了,輸入用線性部分的輸出Z表示,輸出用X表示。機器學習
線性部分函數
線性部分作了什麼事情呢?簡單來講就是對輸入數據作不一樣角度的分析,得出該角度下對總體輸入數據的判斷。性能
這麼說有點抽象,舉一個實際點的例子,就拿CNN的入門case——MNIST舉例。MNIST的例子在此很少說了,它是一個手寫數字的識別項目,輸入是一張28*28的二值圖,輸出是0-9這是個數字,這裏假設咱們採用徹底全鏈接的模型,那麼咱們的輸入就是28*28=784個像素點。數據顯示到屏幕上大概是這個樣子:學習
對於咱們來講,這個像素點都太過於抽象了,咱們沒法判斷這些像素點的取值和最終識別的關係:測試
他們是正相關仍是負相關?優化
很顯然,像素點之間是存在相關關係的,這個關係具體是什麼咱們後面再說,但存在關係這件事是板上釘釘的。因此只給每個像素點一個權重是解決不了問題的,咱們須要多組權重。
咱們能夠:
1)在第一組權重中給第一個像素一個正數,第二個也是正數,
2)在第二組權重中給第一個像素負數,而第二個仍是正數……
這樣,咱們至關於從多個角度對輸入數據進行分析彙總,獲得了多個輸出結果,也就是對數據的多種評價。
非線性部分
非線性部分有一些「套路」函數,這裏只說下其中的一個經典函數——sigmoid。它的函數形式以下所示:
這個函數的輸入正是咱們上一步線性部分的輸出z,此時z取值範圍在,通過了這個函數就變成了。
那非線性部分爲何要作這個函數轉換呢?以個人粗淺理解,其中的一個做用就是做數據的歸一化。無論前面的線性部分作了怎樣的工做,到了非線性這裏,全部的數值將被限制在一個範圍內,這樣後面的網絡層若是要基於前面層的數據繼續計算,這個數值就相對可控了。否則若是每一層的數值大小都不同,有的範圍在(0,1),有的在(0,10000),作優化的時候優化步長的設定就會有麻煩。
另一個做用,就是打破以前的線性映射關係。若是全鏈接層沒有非線性部分,只有線性部分,咱們在模型中疊加多層神經網絡是沒有意義的,咱們假設有一個2層全鏈接神經網絡,其中沒有非線性層,那麼對於第一層有:
這個長得很複雜的函數的範圍是(-1,1)。能夠看出,它的函數範圍和前面的sigmoid不一樣,它是有正有負的,而sigmoid是全爲正的。
神經網絡的模樣
實際上對於只有一層且只有一個輸出的神經網絡,若是它的非線性部分還使用sigmoid函數,那麼它的形式和邏輯斯特迴歸(logistic regression)是同樣的。因此能夠想象神經網絡模型從概念上來看比邏輯斯特迴歸要複雜。那麼它的複雜的樣子是什麼樣呢?下面給出一段全鏈接層的代碼,開始作實驗:
class FC:
def __init__(self, in_num, out_num, lr = 0.01):
self._in_num = in_num
self._out_num = out_num
self.w = np.random.randn(out_num, in_num) * 10
self.b = np.zeros(out_num)
def _sigmoid(self, in_data):
return 1 / (1 + np.exp(-in_data))
def forward(self, in_data):
return self._sigmoid(np.dot(self.w, in_data) + self.b)
從代碼上看東西並很少嘛,注意到咱們會對參數中的w進行隨機初始化,有時咱們會讓老天隨機一個神經網絡給咱們,咱們也能夠看看隨機大帝的旨意。
爲了方即可視化,這裏只作輸入爲2,輸出爲1的數據。好了,先來看1號選手:
x = np.linspace(-10,10,100)
y = np.linspace(-10,10,100)
X, Y = np.meshgrid(x,y)
X_f = X.flatten()
Y_f = Y.flatten()
data = zip(X_f, Y_f)
fc = FC(2, 1)
Z1 = np.array([fc.forward(d) for d in data])
Z1 = Z1.reshape((100,100))
draw3D(X, Y, Z1)
定睛一看這其實就是一個標準的Logistic Regression。他的圖像以下所示:
通過屢次隨機測試,基本上它都是這個形狀,只不過隨着權重隨機的數值變化,這個「臺階」對旋轉到不一樣的方向,但歸根結底仍是一個臺階。
這也說明1層神經網絡是沒有出路的,它本質上仍是個線性分類器的實力,那麼小夥伴還給它加一層吧:
fc = FC(2, 3)
fc.w = np.array([[0.4, 0.6],[0.3,0.7],[0.2,0.8]])
fc.b = np.array([0.5,0.5,0.5])
fc2 = FC(3, 1)
fc2.w = np.array([0.3, 0.2, 0.1])
fc2.b = np.array([0.5])
Z1 = np.array([fc.forward(d) for d in data])
Z2 = np.array([fc2.forward(d) for d in Z1])
Z2 = Z2.reshape((100,100))
draw3D(X, Y, Z2)
此次咱們暫時不用隨機權重,而是本身設置了幾個數值,能夠看出,參數設置得很用心。兩層全都是正數……,那麼圖像呢?
看上去比以前的臺階「柔軟」了一些,但歸根結底仍是很像一個臺階……好吧,那咱們加點負權重,讓咱們從兩個方面分析輸入數據:
fc = FC(2, 3)
fc.w = np.array([[-0.4, 1.6],[-0.3,0.7],[0.2,-0.8]])
fc.b = np.array([-0.5,0.5,0.5])
fc2 = FC(3, 1)
fc2.w = np.array([-3, 2, -1])
fc2.b = np.array([0.5])
Z1 = np.array([fc.forward(d) for d in data])
Z2 = np.array([fc2.forward(d) for d in Z1])
Z2 = Z2.reshape((100,100))
draw3D(X, Y, Z2)
趕忙上圖:
加了負權重後,看上去終於不那麼像臺階了,這時候2層神經網絡的非線性能力開始顯現出來了。下面把權重交給隨機大帝:
fc = FC(2, 100)
fc2 = FC(100, 1)
Z1 = np.array([fc.forward(d) for d in data])
Z2 = np.array([fc2.forward(d) for d in Z1])
Z2 = Z2.reshape((100,100))
draw3D(X, Y, Z2,(75,80))
上圖:
這時候的非線性已經很是明顯了,咱們不妨繼續加幾層看看DNN的厲害:
fc = FC(2, 10)
fc2 = FC(10, 20)
fc3 = FC(20, 40)
fc4 = FC(40, 80)
fc5 = FC(80, 1)
Z1 = np.array([fc.forward(d) for d in data])
Z2 = np.array([fc2.forward(d) for d in Z1])
Z3 = np.array([fc3.forward(d) for d in Z2])
Z4 = np.array([fc4.forward(d) for d in Z3])
Z5 = np.array([fc5.forward(d) for d in Z4])
Z5 = Z5.reshape((100,100))
draw3D(X, Y, Z5,(75,80))
這個圖看上去又複雜了許多……
從上面的實驗中能夠看出,層數越高,非線性的「能力」確實越強,腦洞開得也越大。
知道了他的厲害,下回咱們將詳細聊下它的求解方法——反向傳播(Back Propagation)。
文章代碼能夠在hsmyy/zhihuzhuanlan (http://link.zhihu.com/?target=https%3A//github.com/hsmyy/zhihuzhuanlan/blob/master/FCLayer.ipynb)找到