全连接层 时间复杂度_神经网络全连接层(3)
CNN網(wǎng)絡(luò)基礎(chǔ)結(jié)構(gòu)
神經(jīng)網(wǎng)絡(luò)-全連接層(3)
上一回我們聊完了算法,這回我們正式開始寫代碼。上回在做公式推導(dǎo)的時候,我們實際上只是針對一個數(shù)據(jù)樣本進(jìn)行推導(dǎo),而實際中,計算和訓(xùn)練都是一批一批完成的。大多數(shù)機(jī)器學(xué)習(xí)訓(xùn)練都有batch的概念,而訓(xùn)練中batch的計算不是一個一個地算,而是一批數(shù)據(jù)集中算,那么就需要用上矩陣了。
首先給出Loss的代碼,這里y和t都是按列存儲的,每一列都是一個樣本:
class SquareLoss:
? ?def forward(self, y, t):
? ? ? ?self.loss = y - t
? ? ? ?return np.sum(self.loss * self.loss) / ?self.loss.shape[1] / 2
? ?def backward(self):
? ? ? ?return self.loss
為了代碼的簡潔,我們在前向運算的時候就把一些后向計算的信息都保存起來,這樣在后向計算的時候就能簡單點。這樣這個類就不能具備多線程的特性了,不過想支持多線程的功能還有別的辦法。后面的全連接層也會采用同樣的思路——前向為后向準(zhǔn)備運算數(shù)據(jù)。
上一節(jié)我們講了1個例子,輸入有2個元素,第一層有4個輸出,第2層有1個輸出。我們假設(shè)訓(xùn)練數(shù)據(jù)有N個,我們對所有相關(guān)的訓(xùn)練數(shù)據(jù)和參數(shù)做以下的約定:
所有的訓(xùn)練數(shù)據(jù)按列存儲,也就是說如果把N個數(shù)據(jù)組成一個矩陣,那個矩陣的行等于數(shù)據(jù)特征的數(shù)目,矩陣的列等于N
線性部分的權(quán)值w由一個矩陣構(gòu)成,它的行數(shù)為該層的輸入個數(shù),列數(shù)為該層的輸出個數(shù)。如果該層的輸入為2,輸出為4,那么這個權(quán)值w的矩陣就是一個2*4的矩陣。
線性部分的權(quán)值b是一個行數(shù)等于輸出個數(shù),列數(shù)為1的矩陣。
基于上面的規(guī)則,我們把上一節(jié)的例子以批量數(shù)據(jù)的形式畫成了下面一張圖:
這張圖從左往右有三個部分:
最左邊是神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)圖,可以看出里面的數(shù)據(jù)x,z和參數(shù)w,b都符合我們剛才對數(shù)據(jù)組織的定義。
中間是神經(jīng)網(wǎng)絡(luò)前向的過程。一共分為5步,其中最后一步用來計算Loss。
最右邊是神經(jīng)網(wǎng)絡(luò)反向的過程。這里需要仔細(xì)看一下。為了表達(dá)上的簡潔,我們用殘差符號表達(dá)Loss對指定變量的偏導(dǎo)數(shù)。同時為了更加簡潔地表達(dá)梯度計算的過程,在這個過程中我們對其中一個矩陣做了矩陣轉(zhuǎn)置,這樣可以確保最終輸出維度的正確。
對于上圖右邊的部分,需要認(rèn)真地看幾遍,最好能仔細(xì)地推導(dǎo)一遍,才能更好地掌握這個推導(dǎo)的過程,尤其是為了維度對矩陣做轉(zhuǎn)置這部分。
看懂了上面的圖,接下來要做的就是對上面的內(nèi)容進(jìn)行總結(jié),寫出最終的矩陣版后向傳播算法:
class FC:
? ?def __init__(self, in_num, out_num, lr = 0.1):
? ? ? ?self._in_num = in_num
? ? ? ?self._out_num = out_num
? ? ? ?self.w = np.random.randn(in_num, out_num)
? ? ? ?self.b = np.zeros((out_num, 1))
? ? ? ?self.lr = lr
? ?def _sigmoid(self, in_data):
? ? ? ?return 1 / (1 + np.exp(-in_data))
? ?def forward(self, in_data):
? ? ? ?self.topVal = self._sigmoid(np.dot(self.w.T, in_data) + self.b)
? ? ? ?self.bottomVal = in_data
? ? ? ?return self.topVal
? ?def backward(self, loss):
? ? ? ?residual_z = loss * self.topVal * (1 - self.topVal)
? ? ? ?grad_w = np.dot(self.bottomVal, residual_z.T)
? ? ? ?grad_b = np.sum(residual_z)
? ? ? ?self.w -= self.lr * grad_w
? ? ? ?self.b -= self.lr * grad_b
? ? ? ?residual_x = np.dot(self.w, residual_z)
? ? ? ?return residual_x
好了,現(xiàn)在我們有了Loss類和全連接類,我們還需要一個類把上面兩個類串聯(lián)起來,這里為了后面的內(nèi)容我們定義了許多默認(rèn)變量:
class Net:
? ?def __init__(self, input_num=2, hidden_num=4, out_num=1, lr=0.1):
? ? ? ?self.fc1 = FC(input_num, hidden_num, lr)
? ? ? ?self.fc2 = FC(hidden_num, out_num, lr)
? ? ? ?self.loss = SquareLoss()
? ?def train(self, X, y): # X are arranged by col
? ? ? ?for i in range(10000):
? ? ? ? ? ?# forward step
? ? ? ? ? ?layer1out = self.fc1.forward(X)
? ? ? ? ? ?layer2out = self.fc2.forward(layer1out)
? ? ? ? ? ?loss = self.loss.forward(layer2out, y)
? ? ? ? ? ?# backward step
? ? ? ? ? ?layer2loss = self.loss.backward()
? ? ? ? ? ?layer1loss = self.fc2.backward(layer2loss)
? ? ? ? ? ?saliency = self.fc1.backward(layer1loss)
? ? ? ?layer1out = self.fc1.forward(X)
? ? ? ?layer2out = self.fc2.forward(layer1out)
? ? ? ?print 'X={0}'.format(X)
? ? ? ?print 't={0}'.format(y)
? ? ? ?print 'y={0}'.format(layer2out)
代碼是寫完了,可是我們還需要驗證一下自己的代碼是不是正確的。一般來說我們會采用一些近似方法計算驗證梯度是否正確,而現(xiàn)在,有一個博客為我們做了這件事情:
A Step by Step Backpropagation Example
http://link.zhihu.com/?target=https%3A//mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/
把我們的代碼用博客上數(shù)據(jù)和結(jié)果做一下驗證,就可以幫助我們修正代碼做好debug。其實上面的代碼本來也不多,可能犯錯的地方也不多。
一些具體的例子
一個經(jīng)典的例子就是用神經(jīng)網(wǎng)絡(luò)做邏輯運算。我們可以用一個兩層神經(jīng)網(wǎng)絡(luò)來模擬模擬與運算。下面就是具體的代碼:
# and operation
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]).T
y = np.array([[0],[0],[0],[1]]).T
net = Net(2,4,1,0.1)
net.train(X,y)
以下是調(diào)用代碼給出的結(jié)果,可以看出最終的結(jié)果效果還不錯,經(jīng)過10000輪的迭代,最終模型給出的結(jié)果和我們的期望結(jié)果十分相近,實際上如果我們繼續(xù)進(jìn)行迭代,這個算法的精度還可以進(jìn)一步地提高,Loss可以進(jìn)一步地減少:
iter = 0, loss =0.105256639066
=== Label vs Prediction ===
t=[[0 0 0 1]]
y=[[ 0.40930536 ?0.4617139 ? 0.36923076 ?0.4299025 ]]
iter = 1000, loss =0.0229368486589
=== Label vs Prediction ===
t=[[0 0 0 1]]
y=[[ 0.04445123 ?0.22684496 ?0.17747671 ?0.68605373]]
iter = 2000, loss =0.00657594469044
=== Label vs Prediction ===
t=[[0 0 0 1]]
y=[[ 0.01057127 ?0.11332809 ?0.11016211 ?0.83411794]]
iter = 3000, loss =0.00322081318498
=== Label vs Prediction ===
t=[[0 0 0 1]]
y=[[ 0.00517544 ?0.07831654 ?0.07871461 ?0.88419737]]
iter = 4000, loss =0.00201059297485
=== Label vs Prediction ===
t=[[0 0 0 1]]
y=[[ 0.00336374 ?0.06171018 ?0.0624756 ? 0.90855558]]
iter = 5000, loss =0.00142205310651
=== Label vs Prediction ===
t=[[0 0 0 1]]
y=[[ 0.00249895 ?0.05189239 ?0.05257126 ?0.92309992]]
iter = 6000, loss =0.00108341055769
=== Label vs Prediction ===
t=[[0 0 0 1]]
y=[[ 0.00200067 ?0.04532728 ?0.04585262 ?0.93287134]]
iter = 7000, loss =0.000866734887908
=== Label vs Prediction ===
t=[[0 0 0 1]]
y=[[ 0.00167856 ?0.04058314 ?0.04096262 ?0.9399489 ]]
iter = 8000, loss =0.000717647908313
=== Label vs Prediction ===
t=[[0 0 0 1]]
y=[[ 0.00145369 ?0.03696819 ?0.0372232 ? 0.94534786]]
iter = 9000, loss =0.000609513241467
=== Label vs Prediction ===
t=[[0 0 0 1]]
y=[[ 0.00128784 ?0.03410575 ?0.03425751 ?0.94962473]]
=== Final ===
X=[[0 0 1 1]
[0 1 0 1]]
t=[[0 0 0 1]]
y=[[ 0.00116042 ?0.03177232 ?0.03183889 ?0.95311123]]
記得初始化
初始化是神經(jīng)網(wǎng)絡(luò)一個十分重要的事情,我就不說三遍了,來個實驗,如果我們把所有的參數(shù)初始化成0,會發(fā)生一個可怕的事情:
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]).T
y = np.array([[0],[0],[0],[1]]).T
net = Net(2,4,1,0.1)
net.fc1.w.fill(0)
net.fc2.w.fill(0)
net.train(X,y)
print "=== w1 ==="
print net.fc1.w
print "=== w2 ==="
print net.fc2.w
直接看結(jié)果:
=== Final ===X=[[0 0 1 1][0 1 0 1]]t=[[0 0 0 1]]y=[[ 3.22480024e-04 2.22335711e-02 2.22335711e-02 9.57711099e-01]]=== w1 ===[[-2.49072772 -2.49072772 -2.49072772 -2.49072772][-2.49072772 -2.49072772 -2.49072772 -2.49072772]]=== w2 ===[[-3.373125][-3.373125][-3.373125][-3.373125]]
不但沒有訓(xùn)練出合理的結(jié)果,而且每一層的參數(shù)還都是一樣的。
但是如果把每層參數(shù)設(shè)為不同的固定值呢?
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]).T
y = np.array([[0],[0],[0],[1]]).T
net = Net(2,4,1,0.1)
net.fc1.w.fill(1)
net.fc2.w.fill(0)
net.train(X,y)
print "=== w1 ==="
print net.fc1.w
print "=== w2 ==="
print net.fc2.w
結(jié)果竟然也不錯:
=== Final ===
X=[[0 0 1 1]
[0 1 0 1]]
t=[[0 0 0 1]]
y=[[ 0.00399349 ?0.02830098 ?0.02830098 ?0.96924181]]
=== w1 ===
[[ 2.48265841 ?2.48265841 ?2.48265841 ?2.48265841]
[ 2.48265841 ?2.48265841 ?2.48265841 ?2.48265841]]
=== w2 ===
[[ 3.231811]
[ 3.231811]
[ 3.231811]
[ 3.231811]]
雖然每層的參數(shù)依然相同,但是訓(xùn)練得到了收斂。這又說明了什么呢?關(guān)于這個問題有機(jī)會再說。
全連接層就這樣聊了三期,下回可以換個口味了。
雷課:
? ? ? ?讓教育更有質(zhì)量,
? ? ? ?讓教育更有想象!
總結(jié)
以上是生活随笔為你收集整理的全连接层 时间复杂度_神经网络全连接层(3)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 银狐犬的优缺点是什么(银狐犬忠心耿耿黏人
- 下一篇: 硫酸铁是沉淀吗 硫酸铁是不是沉淀