这章节我们来解决的问题是:如何使用神经网络实现逻辑电路中的“异或门”模型?
如下图:
根据第2章我们知道,单层感知器是能够解决“与门”、“或门”、“非门”这些简单的线性问题,但是不能解决“异或门”这类非线性问题。
1.1 解决异或门问题的思路
如果在单层感知器上增加一层,能够很好的解决异或问题。
例如网络结构如下:
我们把每一个感知器节点代表了一个分类器,那么几个节点学习到的分割线如下:
以上3个节点的组合就可以解决异或门问题。
1.2 sigmoid激活函数 -- 线性转为非线性
多层感知器中,我们期望分割面是非线性的,故选择为sigmoid为激活函数。
sigmoid表达式和曲线:
sigmoid函数也叫 Logistic 函数,取值范围为(0,1),它可以将一个实数映射到(0,1)的区间。
求导如下:
这个公式在计算梯度的时候用到。
那么采样非线性激活函数,神经网络就有可能学习到平滑的曲线来分割平面,从线性到非线性。
1.3 新的问题
按照单层感知器的权重更新,节点3的权重更新可以轻松求得;但是节点1和节点2权重的更新如何求解呢?
按照公式如下:
要求得w111必须要知道节点1的期望值,但是节点1,2的期望值不存在,不能直接求其梯度。
解决方案:误差反向传播算法(Backpropagation,BP算法)
1.4 误差反向传播算法 -- BP算法
神经网络的训练关键在于如何调整权值和阈值,采用的算法:
(1)单层感知机的算法:梯度下降算法;
(2)多层感知器的算法:误差反向传播算法(error Back Propagation),简称BP算法。
反向传播过程如下:
在多层神经网络中,从最后一层开始,逐层的反向进行误差信息传递,并更新相应权重。使用BP算法的多层前馈(向前传播方式没有回路或者环路)网络又称之为BP神经网络;
BP神经网络学习过程:
以下图来源:http://galaxy.agh.edu.pl/~vlsi/AI/backp_t_en/backprop.html
(1)以一个3层全连接网络来为例子
下面通过一个3层全连接网络来讲解,包含2个输入、2个隐藏层(第1层3个节点、第2层2个节点)、一个输出(第3层1个节点),节点计算如右图:
(2)前向传播
输入层到隐藏层的计算,即计算第一层输出y1,y2,y3
隐藏层到隐藏层的计算,即计算第2层输出y4,y5。
隐藏层到输出层,即计算输出y
(3)反向传播
反向传播,根据网络的连接路径,从输出层反向把误差项前向传递,并更新权重和偏置参数的过程。
误差项计算:将网络输出信号y与训练数据集的输出值z(期望值)进行比较,得到的差异被称为输出层神经元的误差信号d(不是均方误差,是计算梯度的某一项)。
内部神经元的误差项的计算,有2种情况:
<1>误差权重:即前向传播的权重;
<2>如果传播的误差来自多个关联神经元,它们将被叠加。
(4)权重更新
权重更新,根据网络的连接路径,从输出层反向把误差反向传递并更新权重和偏置的过程。
权重更新公式:
(5)总结
来源:http://neuralnetworksanddeeplearning.com/chap2.html
1.5 多层感知器 -- 异或门例子
异或门数据集定义:
import numpy as np
def get_xy_data():
""" XOR的训练集 -- 真值表 """
x = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])
return x,y
多层感知器模型定义:
import numpy as np
""" 定义感知器模型结构 """
class XORGateModel():
def __init__(self):
""" 初始化权重、偏置 """
self.w = np.random.normal(size=6).reshape((3, 2)) # 权重
self.b = np.random.normal(size=3) # 偏置
self.lr = 0.1 # 学习率(超参数)
def sigmoid(self, x):
return 1/(1 + np.exp(-x))
def dsigmoid(self, x):
y = self.sigmoid(x)
return y * (1 - y)
def forward(self, x):
""" 前向计算 """
y1 = self.sigmoid((x[0]*self.w[0][0]) + (x[1]*self.w[0][1]) + self.b[0])
y2 = self.sigmoid((x[0]*self.w[1][0]) + (x[1]*self.w[1][1]) + self.b[1])
y3 = self.sigmoid((y1*self.w[2][0]) + (y2*self.w[2][1]) + self.b[2])
return y3
def update_weight(self, x, n, d):
e = (x[0] * self.w[n][0]) + (x[1] * self.w[n][1]) + self.b[n]
self.w[n][0] = self.w[n][0] + (self.lr * d * self.dsigmoid(e) * x[0])
self.w[n][1] = self.w[n][1] + (self.lr * d * self.dsigmoid(e) * x[1])
self.b[n] = self.b[n] + (self.lr * d * self.dsigmoid(e))
def train(self, X, Y):
"""
X,Y -- 训练集
"""
for i in range(100000): # 迭代
j = 0
C = 0
for xi in X:
yi = self.forward(xi) # 前向计算
Ci = np.power((Y[j] - yi), 2) / 2 # 计算误差
C += Ci
# 更新权重跟偏置
if Ci > 0:
d = Y[j] - yi
d1 = self.w[2][0] * d
d2 = self.w[2][1] * d
self.update_weight(xi, 0, d1) # 节点1
self.update_weight(xi, 1, d2) # 节点2
self.update_weight(xi, 2, d) # 节点3
j += 1
print("epoch{} 误差:{}, 权重:{}".format(i, C, self.w))
# 什么时候退出?
if C <= 0.05:
print("=== 与门的4数据都正确了, 退出迭代")
break
训练并保存参数:
# 训练
import data_manager
X, Y = data_manager.get_xy_data() # 读取数据集
model = XORGateModel() # 实例化类对象
model.train(X, Y) # 执行训练
# 验证训练结果是否正确
for xi in X:
print("验证 输入:{} 模型的前向计算结果:{}".format(xi, model.forward(xi)))
# 保存模型参数
np.savez("./params", model.w, model.b)
读取参数并预测:
import numpy as np
if __name__ == "__main__":
# 加载参数
r = np.load("./params.npz")
print("权重", r["arr_0"])
print("偏置", r["arr_1"])
# 设置参数到网络结构
import model
and_model = model.AndGateModel()
and_model.w = r["arr_0"]
and_model.b = r["arr_1"]
# 预测
X = [[0,0], [0, 1], [1, 0], [1, 1]]
for xi in X:
print("预测 输入:{} 结果:{}".format(xi, and_model.forward(xi)))
有帮助,一键三连哦~~~~~~~~~~~~~~~~~~~~~~~~