反向传播

image-20220327123627307

一、正向传播

正向传播(FP - Forward Propagation)

前一层的输出作为后一层的输入的逻辑结构,每一层神经元仅与下一层的神经元全连接,通过增加神经网络的层数虽然可为其提供更大的灵活性,让网络具有更强的表征能力,也就是说,能解决的问题更多,但随之而来的数量庞大的网络参数的训练,一直是制约多层神经网络发展的一个重要瓶颈。

1564414334950

正向传播推导过程如下:

\[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%,请计算应该支付的金额

image-20220327125454892

问题:Tom在超市买了2个苹果,每个10元,消费税10%,请计算苹果价格上涨会在多大程度上影响支付金额(即求“支付金额关于苹果的价格的导数”)。设苹果的价格为x,支付金额为L,则相当于求 \(\frac{\partial L}{\partial x}\) 。这个导数的值表示当苹果的价格稍微上涨时,支付金额会增加多少。

image-20220327130022356

苹果价格的导数为2.2,同理,苹果个数导数为11,消费税导数为20,可以解释为:苹果价格、苹果个数或消费税增加相同的值,分别对支付金额产生2.2倍、11倍、20倍的影响

考虑函数 y = f(x) , 输出为E,反向传播的计算顺序是,将信号E乘以节点的局部导数(偏导数),传递给前面的节点,这样可以高效地求出导数的值。

image-20220327130356402

加法节点反向传播:

image-20220327131005359

乘法节点反向传播:

image-20220327131036958

链式求导法则:

image-20220327130917136

image-20220327130933313

案例:苹果、橙子价格和个数以及税率如下图所示,利用反向传播算法,在方框处计算填入导数

image-20220327131318071

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)

image-20220329142059881

posted @ 2022-03-27 13:48  王陸  阅读(476)  评论(0编辑  收藏  举报