一、正向传播
正向传播(FP - Forward Propagation)
前一层的输出作为后一层的输入的逻辑结构,每一层神经元仅与下一层的神经元全连接,通过增加神经网络的层数虽然可为其提供更大的灵活性,让网络具有更强的表征能力,也就是说,能解决的问题更多,但随之而来的数量庞大的网络参数的训练,一直是制约多层神经网络发展的一个重要瓶颈。
正向传播推导过程如下:
\[layer_0 = X \\
\]
根据第一层权重计算第一层结果:
\[layer_1 = sigmoid(layer_0 \times W_1)
\]
根据第二层权重计算当前样本的预测输出:
\[layer_2(out) = sigmoid(layer_1 \times W_2)) = y'
\]
二、反向传播
反向传播(Backpropagation algorithm)全称“误差反向传播”,是在深度神经网络中,根据输出层输出值,来反向调整隐藏层权重的一种方法。
- 为什么不直接使用梯度下降而使用反向传播方式更新权重呢?
梯度下降应用于有明确求导函数的情况,或者可以求出误差的情况(比如线性回归),我们可以把它看做没有隐藏层的网络。但对于多个隐藏层的神经网络,输出层可以直接求出误差来更新参数,但隐藏层的误差是不存在的,因此不能对它直接应用梯度下降,而是先将误差反向传播至隐藏层,然后再应用梯度下降。
2.1 图解反向传播
问题:Tom在超市买了2个苹果,3个橙子,其中苹果每个10元,橙子每个15元,消费税10%,请计算应该支付的金额
问题:Tom在超市买了2个苹果,每个10元,消费税10%,请计算苹果价格上涨会在多大程度上影响支付金额(即求“支付金额关于苹果的价格的导数”)。设苹果的价格为x,支付金额为L,则相当于求 \(\frac{\partial L}{\partial x}\) 。这个导数的值表示当苹果的价格稍微上涨时,支付金额会增加多少。
苹果价格的导数为2.2,同理,苹果个数导数为11,消费税导数为20,可以解释为:苹果价格、苹果个数或消费税增加相同的值,分别对支付金额产生2.2倍、11倍、20倍的影响
考虑函数 y = f(x) , 输出为E,反向传播的计算顺序是,将信号E乘以节点的局部导数(偏导数),传递给前面的节点,这样可以高效地求出导数的值。
加法节点反向传播:
乘法节点反向传播:
链式求导法则:
案例:苹果、橙子价格和个数以及税率如下图所示,利用反向传播算法,在方框处计算填入导数
2.2 代码实现
from __future__ import unicode_literals
import numpy as np
import matplotlib.pyplot as mp
class ANNModel():
def __init__(self):
# 随机初始化权重[-1 1)
self.w0 = 2 * np.random.random((2, 4)) - 1 # 2*4
self.w1 = 2 * np.random.random((4, 1)) - 1 # 4*1
self.lrate = 0.1
# sigmiod 函数
def active(self, x):
return 1 / (1 + np.exp(-x))
# sigmoid函数导函数
def backward(self, x):
return x * (1 - x)
# 单层网路前向传播
def forward(self, x, w):
return np.dot(x, w)
def fit(self, x):
for j in range(10000):
# 前向传播
l0 = x
l1 = self.active(self.forward(l0, self.w0))
l2 = self.active(self.forward(l1, self.w1))
# 损失
l2_error = y - l2
if (j % 100) == 0:
print("Error:" + str(np.mean(np.abs(l2_error))))
# 反向传播
l2_delta = l2_error * self.backward(l2)
self.w1 += l1.T.dot(l2_delta * self.lrate)
l1_error = l2_delta.dot(self.w1.T)
l1_delta = l1_error * self.backward(l1)
self.w0 += l0.T.dot(l1_delta * self.lrate)
def predict(self, x):
l0 = x
l1 = self.active(self.forward(l0, self.w0))
l2 = self.active(self.forward(l1, self.w1))
result = np.zeros_like(l2)
result[l2 > 0.5] = 1
return result
x = np.array([
[3, 1],
[2, 5],
[1, 8],
[6, 4],
[5, 2],
[3, 5],
[4, 7],
[4, -1]])
y = np.array([0, 1, 1, 0, 0, 1, 1, 0]).reshape(-1, 1)
n = 500
l, r = x[:, 0].min() - 1, x[:, 0].max() + 1
b, t = x[:, 1].min() - 1, x[:, 1].max() + 1
grid_x = np.meshgrid(np.linspace(l, r, n),
np.linspace(b, t, n))
flat_x = np.column_stack((grid_x[0].ravel(), grid_x[1].ravel()))
model = ANNModel()
model.fit(x)
flat_y = model.predict(flat_x)
grid_y = flat_y.reshape(grid_x[0].shape)
mp.figure('Classification', facecolor='lightgray')
mp.title('Classification', fontsize=20)
mp.xlabel('x', fontsize=14)
mp.ylabel('y', fontsize=14)
mp.tick_params(labelsize=10)
mp.pcolormesh(grid_x[0], grid_x[1], grid_y, cmap='gray')
mp.scatter(x[:, 0], x[:, 1], c=y.ravel(), cmap='brg', s=80)
mp.show()
2.3 使用反向传播神经网络逼近函数
import math
import numpy as np
import matplotlib.pyplot as plt
# 神经网络结构
class ApproachNetwork:
learning_rate = 0.05 # 学习率
# W1:输入层与隐层之间的权重
# B1:隐含层神经元的阈值
# W2:隐含层与输出层之间的权重
# B2:输出层神经元的阈值
def __init__(self, hidden_size=100, output_size=1):
self.params = {'W1': np.random.random((1, hidden_size)),
'B1': np.zeros(hidden_size),
'W2': np.random.random((hidden_size, output_size)),
'B2': np.zeros(output_size)}
# 增加产生数据的函数
@staticmethod
def generate_data(fun, is_noise=True, axis=np.array([-1, 1, 100])):
"""
产生数据集
:param fun: 这个是你希望逼近的函数功能定义,在外面定义一个函数功能方法,把功能方法名传入即可
:param is_noise: 是否需要加上噪点,True是加,False表示不加
:param axis: 这个是产生数据的起点,终点,以及产生多少个数据
:return: 返回数据的x, y
"""
np.random.seed(0)
x = np.linspace(axis[0], axis[1], axis[2])[:, np.newaxis]
x_size = x.size
y = np.zeros((x_size, 1))
if is_noise:
noise = np.random.normal(0, 0.1, x_size)
else:
noise = None
for i in range(x_size):
if is_noise:
y[i] = math.sin(x[i]) + noise[i]
else:
y[i] = math.sin(x[i])
return x, y
@staticmethod
# sigmiod函数作为激活函数
def sigmoid(x_):
return 1 / (1 + np.exp(-x_))
# sigmoid函数导函数
def sigmoid_grad(self, x_):
return (1.0 - self.sigmoid(x_)) * self.sigmoid(x_)
# 输出层predict
def predict(self, x_):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['B1'], self.params['B2']
a1 = np.dot(x_, W1) + b1
z1 = self.sigmoid(a1)
a2 = np.dot(z1, W2) + b2
return a2
# 均方差MSE作为损失函数
def loss(self, x_, t):
y_ = self.predict(x_)
return y_, np.mean((t - y_) ** 2)
# 计算BP网络梯度和更新权重
def gradient(self, x, t):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['B1'], self.params['B2']
grads = {}
batch_num = x.shape[0]
# forward 前向传播
a1 = np.dot(x, W1) + b1
z1 = self.sigmoid(a1)
a2 = np.dot(z1, W2) + b2
# backward 反向传播
dy = (a2 - t) / batch_num
grads['W2'] = np.dot(z1.T, dy)
grads['B2'] = np.sum(dy, axis=0)
dz1 = np.dot(dy, W2.T)
da1 = self.sigmoid_grad(a1) * dz1
grads['W1'] = np.dot(x.T, da1)
grads['B1'] = np.sum(da1, axis=0)
return grads
# 训练主函数
def train_with_own(self, x_, y_, max_steps=100):
for k in range(max_steps):
grad = self.gradient(x_, y_)
for key in ('W1', 'B1', 'W2', 'B2'):
self.params[key] -= self.learning_rate * grad[key]
pred, loss = network.loss(x_, y_)
if k % 500 == 0: # 每500步,刷新输出当前图形
# 动态绘制结果图,可以看到训练过程如何慢慢的拟合数据点
plt.cla()
plt.scatter(x, y)
plt.plot(x, pred, 'r-', lw=5)
plt.text(0.5, 0, 'Loss=%.4f' % abs(loss), fontdict={'size': 20, 'color': 'red'})
plt.pause(0.1)
# 关闭动态绘制模式
plt.ioff()
plt.show()
if __name__ == '__main__':
network = ApproachNetwork()
x, y = network.generate_data(False, axis=np.array([-3, 3, 100]))
# 使用 自编代码 训练
# 最大循环步骤为3000
network.train_with_own(x, y, 3000)