用python实现BP网络 判别异或
小记
2022.3.28
依据该BP神经网络与Python实现 - -Finley- - 博客园 (cnblogs.com) 博客实现的,但是在一些方面有改动
后来用pytorch又实现了一遍,见添加链接描述
与原博客中不同的:
- 我的代码中是直接设置最大迭代次数来终止训练的。(博客中说好的办法是用损失函数作为终止的依据)
- 我使用python的numpy进行矩阵运算,故而没有了博客代码中麻烦的循环。
在原博客中学到的:
- 实际应用中我们通常在输入层额外增加一个偏置神经元, 提供一个可控的输入修正;或者为每个隐含层神经元设置一个偏置参数.
- λ是一个称为学习率的参数,一般在(0,0.1)区间上取值.
遇见问题
-
如何用numpy创建矩阵。我的方法是先创建出numpy的array,再用np.mat转换过去(应该还有更好的办法,请赐教)
-
**np.multiply() 和 * 和np.dot()有什么区别。**如下:
对于mat类型来说,*和np.dot()和np.matmul() 一样,都是矩阵乘法(左边的列数要和右边的行数相同),而np.multiply()是对应位置相乘(两矩阵形状要相同)。
对于array类型来说,np.dot()是矩阵乘法,而*和 np.multiply()是对应位置想乘。 -
计算sigmoid的时候是使用对应位置相乘,故而在对sigmoid求导的时候也是对应位置相乘
-
偏导相乘的顺序问题,见机器学习中的矩阵向量求导(四) 矩阵向量求导链式法则 - 刘建平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, ∂x∂z=∂y∂zwT,∂w∂z=xT∂y∂z,(对左边求导,它的导数放在右边;对右边求导,它的导数放在左边) -
损失函数,以及它的导数
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.5∗i=1∑n(targetoi−outoi)2∂outoi∂targetoi=2∗21∗(targetoi−outoi)=targetoi−outoi
该结构
- 训练集和测试集使用同一个,输入2维数据(但是代码编写时输入3维,因为通常在输入层额外增加一个偏置神经元, 提供一个可控的输入修正),训练目标是能输出正确的异或结果,如输入[1,0],要能输出0;输入[1,1],要能输出1。
代码步骤
-
设计隐层层和输出层的权值 w 1 , w 2 w_1,w_2 w1,w2 为(0,1)之间的随机值
-
进入前向传播环节,输入数据x0,得到输出y2,以及损失价值loss
-
进入后向传播环节,算出偏差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()