手撕神经网络(1)——神经网络的基本组件
1.你玩過樂高積木嗎?——寫這篇文章的目的
隨著深度學習的火熱,越來越多的領域,越來越多的人,開始使用深度學習。當然,你可以使用現有的深度學習框架如tf和torch。然而,這些框架都是高度封裝的,讀者雖然用著好用,但是這對于想深入理解神經網絡的讀者不是最好的選擇。因此,有一些想要深入理解神經網絡的讀者就想要使用源代碼對神經網絡進行復現。
很多讀者在網上尋找手撕神經網絡的源代碼,卻發現很多都是面向過程的:定義一堆函數,函數與函數之間相互交叉調用,然后使用一個非常冗長的函數把他們組合,然后放一堆代碼進行訓練。。。這對新手十分不友好。
如果你對后文中反向傳播和計算圖理解起來有困難,可以看我的這篇文章:https://blog.csdn.net/weixin_57005504/article/details/126159919?spm=1001.2014.3001.5501
我們知道,python是一門面向對象編程的語言,因此,我們希望能夠利用這一點,將神經網絡中的各個組件封裝成一個類,然后將這些類拼裝,就成了我們的神經網絡,就像搭建積木一樣。
2.Affine層
初始化
什么是Affine層?所謂的Affine層就是,接受一個輸入X,輸出XW+BXW+BXW+B的神經網絡層。因此,該層需要具有屬性w,b。
不要忘了,我們上面所討論的僅僅是該層的正向傳播的輸出,該層在反向傳播時,還需要能夠輸出對W和B的梯度,同時,我們在后面的梯度下降的過程中還希望能夠獲取這兩個梯度,以更新權重,所以還要有屬性dW,dB,因此,我們對該類的初始化代碼:
前向傳播
前向傳播的代碼相當簡單,只需要完成XW+BXW+BXW+B的計算即可:
def forward(self, x):"""x是n行 一維行向量組成的批"""self.x = xout = np.dot(x, self.w) + self.breturn out反向傳播
該方法接受一個后面一層反向傳播過來的梯度dout,通過以下公式完成對dW,dB的梯度的計算以及反向傳播:
?Loss?W=XT?dout\frac{\partial Loss}{\partial W}=X^T·dout?W?Loss?=XT?dout
?Loss?B=∑dout\frac{\partial Loss}{\partial B}=\sum dout?B?Loss?=∑dout
所以,代碼如下:
3.Sigmoid層
初始化
首先,需要明確sigmoid函數:
sigmoid(x)=11+e?xsigmoid(x)=\frac{1}{1+e^{-x}}sigmoid(x)=1+e?x1?
該函數有一個很好的性質:
設y=sigmoid(x)y=sigmoid(x)y=sigmoid(x),則有dydx=y(1?y)\frac{dy}{dx}=y(1-y)dxdy?=y(1?y)。
因此,我們發現,該層的正向輸出yyy在計算梯度中也可以巧妙地用到,所以,對于輸出yyy,我們可將其添加到該類的屬性,以便前向傳播的結果在反向傳播中也能方便的用到,故其初始化的代碼:
前向傳播
直接帶入sigmoidsigmoidsigmoid函數計算即可:
def forward(self, x):out = 1 / (1 + np.exp(-x))self.out = outreturn out反向傳播
如前所述,dydx=y(1?y)\frac{dy}{dx}=y(1-y)dxdy?=y(1?y)。則有:
def backward(self, dout):dx = dout * (1.0 - self.out) * self.outreturn dx4.ReLU層
該層和Sigjoid層類似,也是一個激活層,這里對于代碼的講解不再贅述:
class ReLU:def __init__(self, ):self.out = Nonedef forward(self, x):mask = (x < 0)self.mask = maskout = x.copy()out[mask] = 0return outdef backward(self, dout):dout[self.mask] = 0return dout5.MSELoss層
初始化
對于回歸問題,一個經典的LossLossLoss 函數是選用均方誤差計算:
Loss=1n∑i=1n(y^?ylabel)2Loss=\frac{1}{n}\sum_{i=1}^{n} (\hat y-y_{label})^2 Loss=n1?i=1∑n?(y^??ylabel?)2
其中,y^\hat yy^?是網絡的預測值,而ylabely_{label}ylabel?是標簽。
求導可得:
?Loss?y^=1n(y^?ylabel)\frac{\partial Loss}{\partial \hat y}=\frac{1}{n}(\hat y -y_{label})?y^??Loss?=n1?(y^??ylabel?)
你可能好奇,為什么是1n\frac{1}{n}n1?,而不是2n\frac{2}{n}n2?呢?這是因為在后面使用梯度下降法更新權重的時候,還需要乘以一個學習率α\alphaα,所以這里分子是1和2沒有區別,而我們再寫代碼時,我們習慣分子是1.
從上面的分析可知,我們的y^,ylabel\hat y ,y_{label}y^?,ylabel? 在正向傳播和反向傳播中都出現了,所以為了方便傳遞,我們將此二者添加到類的屬性中:
前向傳播
def forward(self, pre, y_true):self.pre = prem, n = self.pre.shapeself.y_true = y_true.reshape(m, n)assert self.pre.shape == self.y_true.shapereturn np.mean(np.power((pre - y_true), 2)) # loss 標量反向傳播
def backward(self, dout, ):return dout * (self.pre-self.y_true)時間和經理有限,對于文章中有的細節,我后面會逐漸更新上去,所以歡迎收藏和關注我的文章!
——by 神采的二舅
總結
以上是生活随笔為你收集整理的手撕神经网络(1)——神经网络的基本组件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 菜鸟和计算机高手的差别
- 下一篇: 谷歌采用神经网络驱动机器翻译,可离线翻译