BP神经网络(Back Propagation Neural Network)算法原理推导与Python实现详解

BP神经网络算法推导

给定训练集:
D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x m , y m ) } , x i R I , y i R O
即数据有 D 个特征,标签为 O 维实值向量。

因此,我们定义一个拥有 I 个输入层神经元、 O 个输出层神经元的神经网络,且设该网络的隐藏层神经元个数为 H

其中,隐藏层第 h 个神经元的阀值用 γ h 表示,输出层第 o 个神经元的阀值用 θ o 表示。

输入层第 i 个神经元与隐藏层第 h 个神经元之间的连接权重为 ν i h ,记隐藏层第 h 个神经元接收到的输入为 α h = i = 1 I ν i h x i ;
隐藏层第 h 个神经元与输出层第 o 个神经元之间的连接权重为 ω h o ,记输出层第 o 个神经元接收到的输入为 β o = h = 1 H ω h o b h ,其中 b h 为隐藏层第 h 个神经元的输出。

假设隐藏层和输出层都使用Sigmoid函数作为激活函数。

可以得到,对于训练集中的一个训练例 k ( x ( k ) , y ( k ) ) ,假设神经网络的输出为 y ^ ( k ) = ( y 1 ( k ) , y 2 ( k ) , . . . , y 3 ( k ) ) ,则有:

y ^ o ( k ) = f ( β 0 θ o )
其中 f ( ) 为Sigmoid函数, y ^ o ( k ) 为训练例 k 在第 o 个输出层神经元上的输出。

由此可以得到,神经网络在训练例 k 上的均方误差为:

E k = 1 2 o = 1 O ( y ^ o ( k ) y o ( k ) )
其中 y ^ o ( k ) 为训练例 k 在第 o 个输出层神经元上的输出, y o ( k ) 为训练例 k 在第 o 个输出层神经元上的实际值。


BP神经网络Python实现

该神经网络被设置为三层:一层输入层、一层隐藏层、一层输出层

样本集:

特征一 特征二 标签
0 0 0
0 1 1
1 0 1
1 1 0

可以看出,这就是一个异或样本集,使用这个样本集可以展现出神经网络与感知机在处理非线性可分问题上的差别。

import math
import random

# 用于设置权重矩阵的大小并给定初始权重
def weight_matrix(row, col, weight=0.0):
    weightMat = []
    for _ in range(row):
        weightMat.append([weight] * col)
    return weightMat

# 用于给权重矩阵内的每元素生成一个初始随机权重
def random_weight(parm_1, parm_2):
    return (parm_1 - 1) * random.random() + parm_2

# Sigmoid激活函数
def sigmoid(x):
    return 1.0 / (1.0 + math.exp(-x))

# Sigmoid激活函数的导函数
def sigmoid_derivate(x):
    return x * (1 - x)

# 定义BP神经网络类
class BPNeuralNetwork:
    def __init__(self):
        # 定义输入层、隐藏层、输出层,所有层的神经元个数都初始化为0
        self.input_num, self.hidden_num, self.output_num = 0, 0, 0
        # 定义输入层、隐藏层、输出层的值矩阵,并在setup函数中初始化
        self.input_values, self.hidden_values, self.output_values = [], [], []
        # 定义输入-隐藏层、隐藏-输出层权重矩阵,并在setup函数中设置大小并初始化
        self.input_hidden_weights, self.hidden_output_weights = [], []

    # 神经网络的初始化函数
    # 四个参数分别代表:对象自身、输入层神经元个数、隐藏层神经元个数、输出层神经元个数
    def setup(self, input_num, hidden_num, output_num):
        # 设置输入层、隐藏层、输出层的神经元个数,其中输入层包含偏置项因此数量+1
        self.input_num, self.hidden_num, self.output_num = input_num + 1, hidden_num, output_num
        # 初始化输入层、隐藏层、输出层的值矩阵,均初始化为1
        self.input_values = [1.0] * self.input_num
        self.hidden_values = [1.0] * self.hidden_num
        self.output_values = [1.0] * self.output_num
        # 设置输入-隐藏层、隐藏-输出层权重矩阵的大小
        self.input_hidden_weights = weight_matrix(self.input_num, self.hidden_num)
        self.hidden_output_weights = weight_matrix(self.hidden_num, self.output_num)
        # 初始化输入-隐藏层、隐藏-输出层的权重矩阵
        for i in range(self.input_num):
            for h in range(self.hidden_num):
                self.input_hidden_weights[i][h] = random_weight(-0.2, 0.2)
        for h in range(self.hidden_num):
            for o in range(self.output_num):
                self.hidden_output_weights[h][0] = random_weight(-0.2, 0.2)

    # 神经网络的前向预测
    # 两个参数分别代表:对象自身、单个数据
    def predict(self, data):
        # 将数据放入输入层,-1是由于输入层中的偏置项不需要接收数据
        for i in range(self.input_num - 1):
            self.input_values[i] = data[i]
        # 隐藏层计算
        for h in range(self.hidden_num):
            # 激活函数的参数
            total = 0.0
            # 激活函数的参数值由输入层权重和输入层的值确定
            for i in range(self.input_num):
                total += self.input_values[i] * self.input_hidden_weights[i][h]
            # 将经过激活函数处理的输入层的值赋给隐藏层
            self.hidden_values[h] = sigmoid(total - 0)
        # 输出层计算
        for o in range(self.output_num):
            total = 0.0
            for h in range(self.hidden_num):
                total += self.hidden_values[h] * self.hidden_output_weights[h][o]
            self.output_values[o] = sigmoid(total - 0)
        return self.output_values[:]

    # 神经网络的反向传播
    # 四个参数分别代表:对象自身、单个数据、数据对应的标签、学习率(步长)
    # 本函数皆为数学推导的实现
    def back_propagate(self, data, label, learn):
        # 反向传播前先进行前向预测
        self.predict(data)
        # 计算输出层的误差
        output_datas = [0.0] * self.output_num
        for o in range(self.output_num):
            error = label[o] - self.output_values[o]
            output_datas[o] = sigmoid_derivate(self.output_values[o]) * error
        # 计算隐藏层的误差
        hidden_datas = [0.0] * self.hidden_num
        for h in range(self.hidden_num):
            error = 0.0
            for o in range(self.output_num):
                error += output_datas[o] * self.hidden_output_weights[h][o]
            hidden_datas[h] = sigmoid_derivate(self.hidden_values[h]) * error
        # 更新隐藏-输出层权重
        for h in range(self.hidden_num):
            for o in range(self.output_num):
                self.hidden_output_weights[h][o] += learn * output_datas[o] * self.hidden_values[h]
        # 更新输入-隐藏层权重
        for i in range(self.input_num):
            for h in range(self.hidden_num):
                self.input_hidden_weights[i][h] += learn * hidden_datas[h] * self.input_values[i]
        # 计算样本的均方误差
        error = 0
        for o in range(len(label)):
            error += 0.5 * (label[o] - self.output_values[o]) ** 2
        return error

    # 神经网络训练函数
    # 四个参数分别代表:对象自身、数据集、标签、最大循环次数、学习率、终止误差
    def train(self, datas, labels, limit=50000, learn=0.05, stop_error=0.02):
        for i in range(limit):
            error = 0
            for i in range(len(datas)):
                data = datas[i]
                label = labels[i]
                error += self.back_propagate(data, label, learn)
            if error <= stop_error:
                break

    # 神经网络验证函数
    def test(self):
        # 数据集及其标签
        datas = [[0, 0], [0, 1], [1, 0], [1, 1]]
        labels = [[0], [1], [1], [0]]
        # 调用神经网络的初始化函数并传入参数作为输入层、隐藏层、输出层的神经元个数
        # 其中输入层的神经元个数应与数据集的特征数保持一致
        self.setup(2, 5, 1)
        self.train(datas, labels)
        for data in datas:
            print(self.predict(data))

# 定义BP神经网络对象并调用其进行预测
if __name__ == '__main__':
    nn = BPNeuralNetwork()
    nn.test()

神经网络训练结果:

[0.018648283776391633]
[0.9754998553712237]
[0.9806999914518663]
[0.02997622156919269]

该结果与真实值labels[0, 1, 1, 0]基本类似,可以认为神经网络在预测异或这类非线性可分问题上是有效的。

猜你喜欢

转载自blog.csdn.net/baishuiniyaonulia/article/details/81713256