最近有个项目,需要用到深度学习,为此学了一点神经网络,而反向传播算法为神经网络中最基础也最重要的算法,特此编辑出来,记录自己的学习经历。
从最早的模式识别(Pattern Recongnition)时期开始,研究者的目标就是用可训练的多层网络取代人工特征工程。但该解决方案并没有得到广泛认可,直到上世纪80年代中期,多层架构可以通过SGD训练。只要模块是其输入金额内部权值的相对平滑函数,就可以使用反向传播步骤计算梯度。在20世纪七八十年代,几个不同的研究小组分别独立发现该思路可行且的确可用。如下图所示
从输入单元到第一个隐层H1计算如下:
对H1层的每个单元 ,其值 ,其中 取值遍历所有输入层节点, 是对前一层所有节点的加权和,这里省略了偏置项。网络中使用非线性函数 对 进行非线性变换,得到改成输出
从H1到H2计算如下:
对H2层的每个单元 ,其值 ,其中 遍历去所有H1层节点
从H2层到输出层计算如下
对输出层的每个单元 ,其值 ,其中k取值遍历所有H2层节点
每层只取其中一个节点进行演算
每层首先计算相对于改成输出节点的误差梯度,即所有来自相对于后一层输入节点的误差梯度的加权和。之后使用链式法则将误差梯度传递至盖层输入节点,输出单元的误差梯度通过对代价函数的求导得到,假设输出单元
对应的代价函数项为
,其中
为期望输出值。可计算相对于
的偏导数为
。由于
,所以代价函数相对于
的偏导数为:
从输出单元到第二个隐层H2计算如下:
对H2层的每个单元 ,其误差梯度为 ,其中 取值遍历所有输出层节点
同理,可得出H1层的误差梯度为:
,其中 取遍H2层所有节点。
输入层的误差梯度为:
,其中 取遍H1层所有节点。
通过上面的公式,可以了解到,反向传播算法的关键一点就是代价函数相对于一个模块输入的导数(或梯度),可以通过目标函数相对于该模块输出的导数反向传播求得。反向传播公式可以重复应用,将梯度从顶层输出(网络产生预测的位置)通过所有模块传递到底层(输入层)。所有这些中间梯度被计算出来后,再计算代价目标函数相对于每个模块内部权值的梯度就非常容易了。以输入层到H1层权值为例,其误差梯度为:
# 创建神经网络类
# nodes为1*n矩阵,表示每一层有多少个节点
class nn():
def __init__(self, nodes):
self.layers = len(nodes)
self.nodes = nodes;
# 学习率
self.u = 1.0;
# 权值
self.W = list();
# 偏差值
self.B = list()
# 层值
self.values = list();
# 误差
self.error = 0;
# 损失
self.loss = 0;
for i in range(self.layers - 1):
# 权值初始化,权重范围-0.5~0.5
self.W.append(np.random.random((self.nodes[i], self.nodes[i + 1])) - 0.5)
# B值初始化
self.B.append(0)
for j in range(self.layers):
# values值初始化
self.values.append(0)
###############################################
# 激活函数
def sigmod(x):
return 1.0 / (1.0 + np.exp(-x))
# 前馈函数
def nnff(nn, x, y):
layers = nn.layers
numbers = x.shape[0]
# 赋予初值
nn.values[0] = x
for i in range(1, layers):
nn.values[i] = sigmod(np.dot(nn.values[i - 1], nn.W[i - 1]) + nn.B[i - 1])
# 最后一层与实际的误差
nn.error = y - nn.values[layers - 1]
nn.loss = 1.0 / 2.0 * (nn.error ** 2).sum() / numbers
return nn
# BP函数
def nnbp(nn):
layers = nn.layers;
# 初始化delta
deltas = list();
for i in range(layers):
deltas.append(0)
# 最后一层的delta为
deltas[layers - 1] = -nn.error * nn.values[layers - 1] * (1 - nn.values[layers - 1])
# 其他层的delta为
for j in range(1, layers - 1)[::-1]: # 倒过来
deltas[j] = np.dot(deltas[j + 1], nn.W[j].T) * nn.values[j] * (1 - nn.values[j])
# 更新W值
for k in range(layers - 1):
nn.W[k] -= nn.u * np.dot(nn.values[k].T, deltas[k + 1]) / (deltas[k + 1].shape[0])
nn.B[k] -= nn.u * deltas[k + 1] / (deltas[k + 1].shape[0])
return nn
# 对神经网络进行训练
def nntrain(nn, x, y, iterations):
for i in range(iterations):
nnff(nn, x, y)
nnbp(nn)
return nn