最近团队在搞DNN,用的都是别人搭好的框架,因此想着自己来实现一下简单的神经网络模型,更好的理解其中foward和back propagation的过程。
原理
最简单的神经网络基本上可以分为两个步骤:前向传播和后向误差传递。
前向传播
定义input layer与hidden layer之间的weights为$w_1$,hidden layer与output layer之间的weights为$w_2$。输入向量$x$与$w_1$相乘累加后通过一个非线性变换函数activation function (sigmoid, tanh, relu等)得到hidden layer中neuron的输出结果,然后hidden layer与$w_2$相乘累加后再通过activation function得到output layer中nueron的输出结果。如果中间有多个hidden layer,就不断重复这一过程(这里为了简化处理省略了bias项)。
其中$\sigma$即为activation function,如果为sigmoid函数,
其导数具有良好的性质,
而最终的output就是我们期待的输出y。
后向误差传递
后向传播的目地在于将预测结果与真实结果进行对比,并根据预测误差来对每层的weights进行更新,最简单的更新方法就是梯度下降,也就是每次迭代时在误差梯度方向移动一小步,这样在经过若干步后参数逐渐更新,误差逐渐收敛,以达到训练效果。如果我们如果将问题视为一个regression问题来处理,那么一种常见的平方误差损失函数可以表示为
求梯度,简单来讲就是求导数,在参数更新时,由于误差是在output layer才得到的,如果想对前若干层的weights求导,我们需要采取的是链式求导方法,也就是
这样误差一层一层向前传递,最终每一层的weights都会得到相对于loss的变化$\Delta$,然后再减掉$\Delta * \eta$ (学习率),这样就完成了一次所有参数的更新
针对$w_2$而言,运用链式求导法则,则有
$w_1$由于需要再向后反向传播一层,求导显得更麻烦一点,不过依然是运用链式求导法则,
如果中间隐层增加,依然遵循这一法则向后传播误差,可以看到每向后跨越一层,就需要计算一次activation fuction的导数,这个导数最大值在0处为0.25,也就是说如果隐层为N层,最后误差向后传播到第一个隐层,误差信息最多为$\frac{\partial loss} {\partial output}*0.25^N$,已经所剩无几,这也解释了为什么sigmoid函数容易出现gradient vanishing(梯度消失现象),因此在实际应用中我们大多数是使用ReLU这种激活函数的。
值得注意的是这里也要遵循维数相容原则,简单来讲就是链式求导时的前后相乘项需要维度互相匹配,并最终相乘后维度等于输出空间的维度。
- “将他们当做一维实数然后使用链式法则,最后做维数相容的调整,使之符合矩阵乘法原则并且维数相容即可”,这种方式是一种最快速准确的策略
- “对单个元素求导,再整理成矩阵形式”这种方式是困难、过程缓慢的
完成一次向前预测和向后传递误差的过程称为一次epoch,如此反复的进行多轮迭代到一定次数或误差小于一定的阈值,即可完成模型的训练。
实现
假设现在测试数据共4个样本,每个样本有3维特征,输出为1维,因此input layer为(4, 3),output layer为(4, 1)。设置1层4个neuron的hidden layer,其中$w_1$为(3, 4),$w_2$为(4, 1),代码十分简洁。
|
|
Loss随着epoch不断增加而减小,输出结果:
|
|
加了nueron上的bias项、ReLU和learning rate,更加通用
|
|