pytorch的官网上有一段教程,是使用python的numpy工具实现一个简单的神经网络的bp算法。下面先贴上自己的代码:
import numpy as npN,D_in,H,D_out = 4,10,8,5x = np.random.randn(N,D_in)#4x10
y = np.random.randn(N,D_out)#4x5
#print(x)
#print(y)w1 = np.random.randn(D_in,H)#10x8
w2 = np.random.randn(H,D_out)#8x5lr = 1e-6h = x.dot(w1)#h=x*w1,4x8
h_relu = np.maximum(h,0)#h_relu=relu(h),4x8
y_pred = h_relu.dot(w2)#y_pred=h_relu*w2,4x5
print(h)loss = np.square(y_pred - y).sum()#loss=sum((y_pred-y)^2)
print("loss: %f" %loss)#grad_y_pred=d(loss)/d(y_pred),4x5
grad_y_pred = 2.0*(y_pred - y)
#grad_w2=d(loss)/d(w2)=grad_y_pred * d(y_pred)/d(w2),8x4 * 4x5=>8x5
grad_w2 = h_relu.T.dot(grad_y_pred)#grad_h_relu=d(loss)/d(h_relu)=grad_y_pred * d(y_pred)/d(h_relu),4x5 * 5x8
grad_h_relu = grad_y_pred.dot(w2.T)
#grad_h=(relu())'
grad_h = grad_h_relu.copy()
grad_h[h<0] = 0
#grad_w1=d(loss)/d(w1)
# =d(loss)/d(y_pred) * d(y_pred)/d(h_relu) * d(h_relu)/d(h) * d(h)/d(w1)
# =grad_y_pred * w2 * (h<0 ? 0 : 1) * x
grad_w1 = x.T.dot(grad_h)#10x4 * 4x5 * 5x8w1 -= lr*grad_w1
w2 -= lr*grad_w2
首先声明此代码中没有进行循环迭代的重复操作,只是进行了一次前向传播与反向传播。
需要注意的知识点有:
1.反向传播算法中如何更新权值w1 w2。
在这个过程中最重要的是求取权值的变化对损失函数的影响,而这个影响一般用梯度值来表示。因此这个问题变成了求损失函数相对于权值的梯度。得到了梯度grad_w之后,根据bp算法更新权值的方式:w=w-lr*grad_w,其中lr是学习率,就可以更新权值w1,w2了。更新完了权值w1,w2之后,完成了一次整个的运算过程,将这整个的过程循环迭代多次,即可逐渐最小化损失函数loss的值。
求取grad_w的方式用到了求导的链式法则,具体公式在上述代码的注释中,代码实现的过程中,需要注意矩阵的转置是为了保证运算结果的矩阵维度正确。
还需注意的是,因为上述代码中实际上是包含了一个隐含层的全连接的神经网络,因此权值w1,w2矩阵中的值,会影响多个输出层的y的值,同样地,每一个输出的y值也会影响与之相连的每一个权值w的梯度,因此在实际求偏导进而求梯度的时候,对于某个w值,所求取的应当是所有y相对于这个w的偏导之和,这也就对应了代码中进行运算时,是对两个矩阵进行乘法运算(而不是矩阵中对应元素的相乘这么简单)。
此外,对于每个层的激活函数,在求梯度的过程中也要记得求其导数。
2.神经网络中为什么要用到激活函数
一般激活函数都是非线性的,如果不用激活函数,网络中的每一层的输出相对于输入都是线性的(相当于y=x),引入激活函数之后很容易验证,无论你神经网络有多少层,输出都是输入的线性组合,与没有隐藏层效果相当,这种情况就是最原始的感知机(Perceptron)了。这样深度神经网络的“深度”就没有了意义。具体到某些分类问题,对于非线性可分的样本则无法进行分类,而采用了非线性的激活函数之后,才能实现非线性问题的分类。
3.sigmoid函数的缺点
根据该函数的图像可知,sigmoid函数容易饱和,当输入非常大或者非常小的时候,函数的梯度就接近于0了,很容易就会出现梯度消失的情况。从图中可以看出梯度的趋势。这就使得我们在反向传播算法中反向传播接近于0的梯度,导致最终权重基本没什么更新。
4.ReLU函数相对于sigmoid函数的优点
(1)使用 ReLU 得到的SGD的收敛速度会比 sigmoid/tanh 快很多(如上图右)。有人说这是因为它是linear,而且梯度不会饱和。为什么线性不饱和,就会收敛的快?反向传播算法中,下降梯度等于敏感度乘以前一层的输出值,所以前一层输出越大,下降的梯度越多。该优点解决了sigmod的梯度消失问题。
(2)sigmoid/tanh需要计算指数等,计算复杂度高,求梯度时涉及到除法运算。ReLU只需要一个阈值就可以得到激活值。
(3)ReLU会使一部分神经元的输出为0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生。
与此同时,该函数的缺点为:
ReLU在训练的时候很”脆弱”,一不小心有可能导致神经元”坏死”。举个例子:由于ReLU在x<0时梯度为0,这样就导致负的梯度在这个ReLU被置零,而且这个神经元有可能再也不会被任何数据激活。如果这个情况发生了,那么这个神经元之后的梯度就永远是0了,也就是ReLU神经元坏死了,不再对任何数据有所响应。实际操作中,如果你的learning rate 很大,那么很有可能你网络中的40%的神经元都坏死了。 当然,如果你设置了一个合适的较小的learning rate,这个问题发生的情况其实也不会太频繁。
结语:
实际上此代码并未使用pytorch工具。官网教程地址:
https://pytorch.org/tutorials/beginner/pytorch_with_examples.html