参考课程:智能计算系统
神经网络中常见的组成部分有:全连接层,激活函数,Softmax层。
全连接层
全连接层输入为一维向量(维度为m),输出为一维向量(维度为n)。前向传播的公式为:
y = W T x + b y=W^Tx+b y=WTx+b
其中,W为二维权重矩阵,维度为 m × n m×n m×n;偏置b是一维向量,维度为n。
以手写数字识别为例,输入的手写数字图像信息,经过第一轮的正向传播后,会产生一个预测结果(结果以概率的形式体现),但这个概率与真实情况的概率(比如数字7的手写图像,理想情况下它为7的概率为1,为其他数字的概率为0)有差别,这样便产生了差值。利用差值来调整网络中的参数(权重、偏置等),得到较为准确的预测结果。
反向传播在理论上通过求偏导的方式来解决。实际应用中通常使用批量随机梯度下降的方法进行反向传播计算。
梯度下降法:通过不断地沿着函数的负梯度方向更新参数,从而逼近函数的局部最小值。(是一种基于一阶导数的优化算法)
代码实现:
# 要引入numpy, os, time等包
class FullyConnectedLayer(object):def __init__(self, num_input, num_output): # 全连接层初始化self.num_input = num_input # 输入的一维向量维度self.num_output = num_output # 输出的一维向量维度print('\tFully connected layer with input %d, output %d.' % (self.num_input, self.num_output))def init_param(self, std=0.01): # 参数初始化# 正态分布随机生成数# loc=0.0 正态分布的均值# scale=std:正态分布的标准差# size:生成随机数的形状,这里是二维数组,行数为self.num_input,列数为self.num_output# 对应于全连接层的权重矩阵self.weight = np.random.normal(loc=0.0, scale=std, size=(self.num_input, self.num_output)) self.bias = np.zeros([1, self.num_output]) # 生成全零数组def forward(self, input): # 前向传播计算start_time = time.time()self.input = input#全连接层的前向传播,计算输出结果#Y = WX + bself.output = np.dot(self.input, self.weight) + self.bias # np.dot:求两个数组的内积return self.outputdef backward(self, top_diff): # 反向传播的计算# 全连接层的反向传播,计算参数梯度和本层损失# top_diff是“从上一层传递下来的梯度”(损失函数关于上一层输出的偏导数,这些偏导数用于计算当前层的参数梯度(即权重和偏置的更新量)以及进一步传递给下一层)self.d_weight = np.dot(self.input.T, top_diff)self.batch_size = top_diff.shape[0]self.d_bias = np.dot(np.ones(shape=(1,self.batch_size)),top_diff)bottom_diff = np.dot(top_diff, self.weight.T)return bottom_diffdef update_param(self, lr): # 参数更新# 对全连接层参数利用参数进行更新self.weight = self.weight - lr * self.d_weightself.bias = self.bias - lr * self.d_biasdef load_param(self, weight, bias): # 参数加载assert self.weight.shape == weight.shapeassert self.bias.shape == bias.shapeself.weight = weightself.bias = biasdef save_param(self): # 参数保存return self.weight, self.bias
激活函数
为什么要使用激活函数:因为神经网络中每一层的输入输出都是一个线性求和的过程,下一层的输出只是承接了上一层输入函数的线性变换,所以如果没有激活函数,那么无论你构造的神经网络多么复杂,有多少层,最后的输出都是输入的线性组合,纯粹的线性组合并不能够解决更为复杂的问题。而引入激活函数之后,我们会发现常见的激活函数都是非线性的,因此也会给神经元引入非线性元素,使得神经网络可以逼近其他的任何非线性函数,这样可以使得神经网络应用到更多非线性模型中。
参考资料:知乎
ReLU激活函数:x<0时y=0,x>0时y=x。
代码实现:
class ReLULayer(object):def __init__(self):print('\tReLU layer.')def forward(self, input): # 前向传播的计算start_time = time.time()self.input = input# ReLU层的前向传播,计算输出结果# y(i) = max(0, x(i))output = np.maximum(0, self.input)return outputdef backward(self, top_diff): # 反向传播的计算# ReLU层的反向传播,计算本层损失(计算损失函数对本层的第i个输入的偏导)# 公式:x(i)>=0时为:根据损失函数对输出的偏导▽yL计算损失函数对输入的偏导▽xL# x(i)<0时取0bottom_diff = np.zeros_like(self.input)mask = self.input >= 0np.putmask(bottom_diff, mask, top_diff)#bottom_diff[mask] = top_diff #TypeError: NumPy boolean array indexing assignment requires a 0 or 1-dimensional input, input has 2 dimensions. changereturn bottom_diff
Softmax损失层
Softmax损失层是目前多分类问题中最常用的损失函数层。