用python实现BP算法 判别异或

用python实现BP网络 判别异或

小记

2022.3.28

依据该BP神经网络与Python实现 - -Finley- - 博客园 (cnblogs.com) 博客实现的,但是在一些方面有改动

后来用pytorch又实现了一遍,见添加链接描述

与原博客中不同的

  1. 我的代码中是直接设置最大迭代次数来终止训练的。(博客中说好的办法是用损失函数作为终止的依据)
  2. 我使用python的numpy进行矩阵运算,故而没有了博客代码中麻烦的循环。

在原博客中学到的:

  1. 实际应用中我们通常在输入层额外增加一个偏置神经元, 提供一个可控的输入修正;或者为每个隐含层神经元设置一个偏置参数.
  2. λ是一个称为学习率的参数,一般在(0,0.1)区间上取值.

遇见问题

  1. 如何用numpy创建矩阵。我的方法是先创建出numpy的array,再用np.mat转换过去(应该还有更好的办法,请赐教)

  2. **np.multiply() 和 * 和np.dot()有什么区别。**如下:
    对于mat类型来说,*和np.dot()和np.matmul() 一样,都是矩阵乘法(左边的列数要和右边的行数相同),而np.multiply()是对应位置相乘(两矩阵形状要相同)。
    对于array类型来说,np.dot()是矩阵乘法,而*和 np.multiply()是对应位置想乘。

  3. 计算sigmoid的时候是使用对应位置相乘,故而在对sigmoid求导的时候也是对应位置相乘

  4. 偏导相乘的顺序问题,见机器学习中的矩阵向量求导(四) 矩阵向量求导链式法则 - 刘建平Pinard - 博客园 (cnblogs.com)
    z = f ( y ) , y = x w ,   ∂ z ∂ x = ∂ z ∂ y w T , ∂ z ∂ w = x T ∂ z ∂ y , (对左边求导,它的导数放在右边;对右边求导,它的导数放在左边) z=f(y),y=xw,\ \frac{\partial z}{\partial x}=\frac{\partial z}{\partial y}w^T, \\ \frac{\partial z}{\partial w}=x^T\frac{\partial z}{\partial y}, \\(对左边求导,它的导数放在右边;对右边求导,它的导数放在左边) z=f(y),y=xw, xz=yzwT,wz=xTyz,(对左边求导,它的导数放在右边;对右边求导,它的导数放在左边)

  5. 损失函数,以及它的导数
    l o s s = 0.5 ∗ ∑ i = 1 n ( t a r g e t o i − o u t o i ) 2 ∂   t a r g e t o i ∂ o u t o i = 2 ∗ 1 2 ∗ ( t a r g e t o i − o u t o i ) = t a r g e t o i − o u t o i loss=0.5*\sum_{i=1}^n (target_{oi}-out_{oi})^2 \\\frac{\partial\, target_{oi} }{\partial out_{oi}}=2*\frac{1}{2}*(target_{oi}-out_{oi})=target_{oi}-out_{oi} loss=0.5i=1n(targetoioutoi)2outoitargetoi=221(targetoioutoi)=targetoioutoi

该结构

  1. 训练集和测试集使用同一个,输入2维数据(但是代码编写时输入3维,因为通常在输入层额外增加一个偏置神经元, 提供一个可控的输入修正),训练目标是能输出正确的异或结果,如输入[1,0],要能输出0;输入[1,1],要能输出1。
  2. mark

代码步骤

  1. 设计隐层层和输出层的权值 w 1 , w 2 w_1,w_2 w1,w2 为(0,1)之间的随机值

  2. 进入前向传播环节,输入数据x0,得到输出y2,以及损失价值loss

  3. 进入后向传播环节,算出偏差loss与y2,y1的导数, 如此易得到loss与w2,w1的导数接着更新w2,w1

    (这时求偏导的时候,偏导相乘要注意顺序)

代码

import numpy as np
from icecream import ic
from matplotlib import  pyplot as plt
def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))


def sigmoid_derivative(x):
    #是对应位置相乘
    return np.multiply(x,(1-x))


class BP():
    def __init__(self):
        self.x0=[]    #输入层
        self.x1=[]    #隐含层
        self.x2=[]    #输出层
        self.y1=[]    #y1=x0*w1
        self.y2=[]    #y2=x1w1
        self.w1=[]    #隐层层的权值
        self.w2=[]    #输出层的权值
        self.loss=[]     #损失函数
        self.label=[]   #真实值
        self.y2_gradient=[]     #e对y2的导数
        self.y1_gradient=[]     #e对y2的倒数
        self.loss_delta=[]      #损失函数对误差值求导

    def setup(self,input_n,hidden_n,ouput_n):
        """
        # 对bp初始化,设置各层维度,以及初始随机权值
        """
        input_n+=1  #通常在输入层额外增加一个偏置神经元, 提供一个可控的输入修正
        self.x0=np.mat(np.ones([1,input_n]))  #1*3
        self.x1=np.mat(np.ones([1,hidden_n]))   #1*5
        self.x2=np.mat(np.ones([1,ouput_n]))    #1*1

        self.w1=np.mat(np.random.rand(input_n,hidden_n))    #3*5
        self.w2=np.mat(np.random.rand(hidden_n,ouput_n))    #5*1

        self.y2_gradient=np.mat(np.ones([1,ouput_n]))
        self.y1_gradient=np.mat(np.ones([1,hidden_n]))

    def forward(self,inputx,label):
        """
        前向传播
        """
        self.x0[0, 0:2] = inputx    #输入层是1*3矩阵,但实际输入是1*2
        self.label=np.mat(label)

        self.x1=sigmoid(self.x0*self.w1)    #省略了y1=x0*w1
        self.x2=sigmoid(self.x1*self.w2)

        #计算损失值
        e=self.label-self.x2
        self.loss = (0.5*sum(e**2) /e.size).getA()[0]
        self.loss_delta=e

    def backward(self,learn):
        """
        后向传播
        """

        #注意乘法顺序,求梯度
        self.y2_gradient=np.multiply(sigmoid_derivative(self.x2),self.loss_delta)
        self.y1_gradient=np.multiply(self.y2_gradient*self.w2.T,sigmoid_derivative(self.x1))

        # 更新权值
        self.w2+=self.x1.T*self.y2_gradient*learn
        self.w1+=self.x0.T*self.y1_gradient*learn

def make_pic():
	#绘制图像
    train_epoch = 500  #迭代总次数
    show_epoch = 100

    def optimizer(learn):
        bp.setup(input_n=2,hidden_n=5,ouput_n=1)
        epoch = []
        loss = []
        for k in range(train_epoch):
            for i in range(len(cases)):
                bp.forward(inputx=cases[i], label=labels[i])
                bp.backward(learn)

            #每隔show_epoch个跌打数的时候,看看损失值
            if k % show_epoch == 0:
                l=0
                for i in range(len(cases)):
                    bp.forward(inputx=cases[i], label=labels[i])
                    l+=bp.loss
                epoch.append(k)
                loss.append(l/4)

        return epoch, loss

    epoch1, loss1 = optimizer(learn=0.5)
    epoch2, loss2 = optimizer(learn=0.1)
    epoch3, loss3 = optimizer(learn=0.05)

    plt.figure()
    plt.subplot(1, 2, 1)
    plt.plot(range(0, len(epoch1) * show_epoch, show_epoch), loss1, label='learn=0.5')
    plt.plot(range(0, len(epoch2) * show_epoch, show_epoch), loss2, label='learn=0.1')
    plt.plot(range(0, len(epoch3) * show_epoch, show_epoch), loss3, label='learn=0.05')

    # 显示标签,如果不加这句,即使在plot中加了label='一些数字'的参数,最终还是不会显示标签
    plt.legend(loc="upper right")
    plt.show()

if __name__=='__main__':
    #数据集
    cases = [
        [0, 0],
        [0, 1],
        [1, 0],
        [1, 1],
    ]
    labels = [[0], [1], [1], [0]]


    bp=BP()
    bp.setup(2,5,1)     #初始化

    #迭代训练
    for _ in range(100):
        for i in range(len(cases)):
            bp.forward(inputx=cases[i],label=labels[i])
            bp.backward(learn=0.1)


    #画图,看不同学习率,迭代次数不同时的误差率
    make_pic()


代码图像

mark

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是Mally呀!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值