手写BP算法实现异或运算

1.说明

BP算法的理解可以去看这一篇博客,静下心去看绝对可以看懂

深度学习之BP算法通俗易懂篇

2.代码

结合着上面的博客看下面的代码

数学公式都在代码中有体现

如果觉得我写的注释有点看不懂,可以自己放到机器中debug跑一下

代码是《深度学习与TensorFlow实践》这本书第5章的思考与习题的第2题的代码

from math import exp
from random import seed
from random import random


# 初始化神经网络
def initialize_network(n_inputs, n_hidden, n_outputs):
    network = list()
    # 初始化权值
    # 这里只有一个隐含层和一个输出层,
    hidden_layer = [{'weights': [random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
    network.append(hidden_layer)
    output_layer = [{'weights': [random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
    network.append(output_layer)
    print(hidden_layer)
    # 设置网络层
    return network


# # 计算神经元的激活值(加权之和)
def activate(weights, inputs):
    activation = weights[-1]
    for i in range(len(weights) - 1):
        activation += weights[i] * inputs[i]
    return activation


# 定义激活函数
def transfer(activation):
    return 1.0 / (1.0 + exp(-activation))


# 计算神经网络的正向传播
def forward_propagate(network, row):
    inputs = row
    for layer in network:
        new_inputs = []
        for neuron in layer:
            # 计算权值与输入的函数值
            activation = activate(neuron['weights'], inputs)
            # 将函数值激活并保存
            neuron['output'] = transfer(activation)
            new_inputs.append(neuron['output'])
        inputs = new_inputs
    return inputs


# 计算激活函数的导数
def transfer_derivative(output):
    return output * (1.0 - output)


# 反向传播误差信息,并将纠偏责任存储在神经元中
# 反向传播使用的是链式法则 + 梯度下降公式
# BP更新的公式
# 总误差对W的导数  =    总误差对当前层激活函数的导数   *    当前层激活函数的导数对当前层线性函数Z的导数  *  当前层线性函数Z对W的导数
# 第一步:总误差对当前层激活函数的导数
# 第二步:当前层激活函数的导数对当前层线性函数Z的导数
# 第三步:当前层线性函数Z对W的导数

# 输出层与隐含层的第一步不同
# 1.如果更新输出层上的权值W,则总误差就是全网络的误差,即误差函数的值
# 总误差对当前层激活函数的导数  =  总误差对当前层激活函数的导数
# 2.如果更新任意隐含层的权值W,则总误差指的是与当前层节点有联系的下一层节点的误差
# 总误差对当前层激活函数的导数  =  与当前层节点有联系的下一层节点1对当前层激活函数的导数 + 与当前层节点有联系的下一层节点2对当前层激活函数的导数 + ... +与当前层节点有联系的下一层节点n对当前层激活函数的导数
# 由于我们每一层只有两个节点,所以
# 总误差对当前层激活函数的导数  =  与当前层节点有联系的下一层节点1对当前层激活函数的导数 + 与当前层节点有联系的下一层节点2对当前层激活函数的导数
# 与当前层节点有联系的下一层节点1对当前层激活函数的导数 =  与当前层节点有联系的下一层节点1对与当前层节点有联系的下一层节点1的线性函数Z的导数   *   当前层节点有联系的下一层节点1的线性函数Z对当前层激活函数的导数
# 与当前层节点有联系的下一层节点1对与当前层节点有联系的下一层节点1的线性函数Z的导数    已经 在计算输出层权值时计算过并存到 neuron['responsibility'])
# 当前层节点有联系的下一层节点1的线性函数Z对当前层激活函数的导数   就是 下一层节点1的权值


def backward_propagate_error(network, expected):
    for i in reversed(range(len(network))):
        layer = network[i]
        errors = list()
        # 如果是隐含层
        if i != len(network) - 1:
            # 遍历隐含层的一层
            # 开始遍历某一层隐含层的所有神经节点,从第0个开始,取出一个隐含层的神经节点
            for j in range(len(layer)):
                error = 0.0
                # 第一步
                # 遍历当前层节点有联系的下一层节点,因为下一层节点的对应误差已经计算过了(这就是反向传播的真谛!)
                # 计算总误差对当前层激活函数的导数
                for neuron in network[i + 1]:
                    error += (neuron['weights'][j] * neuron['responsibility'])
                errors.append(error)
        # 如果是输出层
        else:
            # 第一步
            for j in range(len(layer)):
                neuron = layer[j]
                errors.append(expected[j] - neuron['output'])
        # 第二步
        # 计算当前层激活函数对当前层线性函数Z的导数,即transfer_derivative
        for j in range(len(layer)):
            neuron = layer[j]
            neuron['responsibility'] = errors[j] * transfer_derivative(neuron['output'])


# 根据误差,更新网络权重
def update_weights(network, row, l_rate):
    for i in range(len(network)):
        inputs = row[:-1]
        if i != 0:
            inputs = [neuron['output'] for neuron in network[i - 1]]
        # 第三步  +  梯度下降
        for neuron in network[i]:
            for j in range(len(inputs)):
                neuron['weights'][j] += l_rate * neuron['responsibility'] * inputs[j]
            neuron['weights'][-1] += l_rate * neuron['responsibility']


# 根据指定的训练周期训练网络
def train_network(network, train, l_rate, n_epoch, n_outputs):
    for epoch in range(n_epoch):
        sum_error = 0
        for row in train:
            outputs = forward_propagate(network, row)
            # 期望数组初始化
            expected = [0 for i in range(n_outputs)]
            # 期望数组的索引0、1就是对应要输出的值,其中哪个索引被置为1,则代表着这个值就是输出值
            # [1, 1, 0],预期输出应该是0,所以期望数组是expected[0] = 1
            expected[row[-1]] = 1
            sum_error += sum([(expected[i] - outputs[i]) ** 2 for i in range(len(expected))])
            backward_propagate_error(network, expected)
            update_weights(network, row, l_rate)
        print('>周期=%d, 误差=%.3f' % (epoch, sum_error))


def predict(network, row):
    outputs = forward_propagate(network, row)
    return outputs.index(max(outputs))


if __name__ == '__main__':
    dataset = [[1, 1, 0],
               [1, 0, 1],
               [0, 1, 1],
               [0, 0, 0]]
    n_inputs = len(dataset[0]) - 1
    n_outputs = len(set([row[-1] for row in dataset]))
    network = initialize_network(n_inputs, 2, n_outputs)
    train_network(network, dataset, 0.5, 20000, n_outputs)
    for layer in network:
        print(layer)
    for row in dataset:
        prediction = predict(network, row)
        print('预期值=%d, 实际输出值=%d' % (row[-1], prediction))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值