学习反向传播算法

最近有个项目,需要用到深度学习,为此学了一点神经网络,而反向传播算法为神经网络中最基础也最重要的算法,特此编辑出来,记录自己的学习经历。
从最早的模式识别(Pattern Recongnition)时期开始,研究者的目标就是用可训练的多层网络取代人工特征工程。但该解决方案并没有得到广泛认可,直到上世纪80年代中期,多层架构可以通过SGD训练。只要模块是其输入金额内部权值的相对平滑函数,就可以使用反向传播步骤计算梯度。在20世纪七八十年代,几个不同的研究小组分别独立发现该思路可行且的确可用。如下图所示
这里写图片描述

从输入单元到第一个隐层H1计算如下:

对H1层的每个单元 j ,其值 y j = f ( z j ) , z j = i ( w i j x i ) ,其中 i 取值遍历所有输入层节点, z j 是对前一层所有节点的加权和,这里省略了偏置项。网络中使用非线性函数 f z j 进行非线性变换,得到改成输出 y j

从H1到H2计算如下:

对H2层的每个单元 k ,其值 y k = f ( z k ) , z k = j w j k y j ,其中 j 遍历去所有H1层节点

从H2层到输出层计算如下

对输出层的每个单元 l ,其值 y l = f ( z l ) , z l = k w k l y k ,其中k取值遍历所有H2层节点

每层只取其中一个节点进行演算
每层首先计算相对于改成输出节点的误差梯度,即所有来自相对于后一层输入节点的误差梯度的加权和。之后使用链式法则将误差梯度传递至盖层输入节点,输出单元的误差梯度通过对代价函数的求导得到,假设输出单元 l 对应的代价函数项为 E = ( y l t l ) 2 ,其中 t l 为期望输出值。可计算相对于 y l 的偏导数为 y l t l 。由于 y l = f ( z l ) ,所以代价函数相对于 z l 的偏导数为:

E z l = E y l y l z l = ( y l t l ) f ( z l )

从输出单元到第二个隐层H2计算如下:

对H2层的每个单元 k ,其误差梯度为 E y k = l E z l z l y k = l w k l E z l ,其中 l 取值遍历所有输出层节点

同理,可得出H1层的误差梯度为:

E y j = k E y k y k z k z k y j = k E y k y k z k w j k ,其中 k 取遍H2层所有节点。

输入层的误差梯度为:

E x i = j E y j y j z j z j x i = j E y j y j z j w i j ,其中 j 取遍H1层所有节点。

通过上面的公式,可以了解到,反向传播算法的关键一点就是代价函数相对于一个模块输入的导数(或梯度),可以通过目标函数相对于该模块输出的导数反向传播求得。反向传播公式可以重复应用,将梯度从顶层输出(网络产生预测的位置)通过所有模块传递到底层(输入层)。所有这些中间梯度被计算出来后,再计算代价目标函数相对于每个模块内部权值的梯度就非常容易了。以输入层到H1层权值为例,其误差梯度为:

E w i j = E y j y j z j z j w i j = E y j y j z j x i

# 创建神经网络类
# 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

猜你喜欢

转载自blog.csdn.net/czp_374/article/details/80908814
今日推荐