"Introducción al aprendizaje profundo" Capítulo 5 Combate real: Reconocimiento de números escritos a mano - Propagación inversa de errores

Sugerencia: después de escribir el artículo, la tabla de contenido se puede generar automáticamente. Cómo generarla puede consultar el documento de ayuda a la derecha


prefacio

Recientemente leí el quinto capítulo del libro "Introducción a la teoría e implementación basadas en Python de aprendizaje profundo". Este capítulo explica principalmente el método de propagación hacia atrás del error , un método que puede calcular de manera eficiente el método de gradiente para los parámetros de peso.


1. Una pequeña introducción

El método de retropropagación de errores se utiliza para calcular el gradiente de los parámetros de peso de la red neuronal. Este método tiene como objetivo atravesar la red neuronal de atrás hacia adelante, de modo que se pueda calcular el gradiente de la función de pérdida para todos los parámetros del modelo en la red.

(1) Gráfico de cálculo

El libro utiliza gráficos de cálculo para explicar el método de propagación hacia atrás del error. La principal razón para usar un gráfico computacional es que las derivadas se pueden calcular de manera eficiente a través de la retropropagación. 苹果价格的上涨会在多大程度上影响最终支付金额Por ejemplo, el problema " " se explica en la imagen de abajo , es decir, el problema de "la derivada del monto del pago con respecto al precio de las manzanas".

Entonces, ¿ por qué calcular derivadas mediante retropropagación ? Si se puede calcular la derivada, se puede conocer el gradiente de los parámetros de peso de la red neuronal, de modo que los parámetros se pueden actualizar de acuerdo con el gradiente para encontrar los parámetros óptimos.
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

(2) Propagación hacia atrás

Después de una breve introducción al gráfico de cálculo, el autor también explicó el método de retropropagación, centrándose en la retropropagación del nodo de suma y la retropropagación del nodo de multiplicación.

  • La retropropagación del nodo de suma
    La retropropagación del nodo de suma toma z=x+y como ejemplo. El propósito de este ejemplo es calcular cuánto afecta un cambio en x a z, y cuánto afecta un cambio en y a z, es decir, la derivada de z con respecto a x y la derivada de z con respecto a y.
    inserte la descripción de la imagen aquí
    inserte la descripción de la imagen aquí
    inserte la descripción de la imagen aquí

  • Propagación hacia atrás de nodos de multiplicación
    inserte la descripción de la imagen aquí
    inserte la descripción de la imagen aquí

(3) Representación de código de retropropagación

Anteriormente presentamos el método de retropropagación del nodo de suma y el nodo de multiplicación, e implementaremos su retropropagación en forma de código a continuación.
A continuación, el nodo de multiplicación para realizar el gráfico de cálculo se denomina "capa de multiplicación" y el nodo de suma se denomina "capa de suma".

  • Implementación de código de la capa de multiplicación
class MulLayer:
    def __init__(self):
        self.x = None
        self.y = None
	
	# 这是正向传播
    def forward(self, x, y):
        self.x = x
        self.y = y                
        out = x * y

        return out
	
	# 这里就是反向传播
    def backward(self, dout):
        dx = dout * self.y # 根据(二)中所讲的,乘法节点的反向传播需要把x和y进行翻转
        dy = dout * self.x

        return dx, dy
  • Implementación de código de la capa de adición
class AddLayer:
    def __init__(self):
        pass	# 加法层无需进行初始化,所以直接pass即可

    def forward(self, x, y):
        out = x + y

        return out

    def backward(self, dout):
        dx = dout * 1
        dy = dout * 1

        return dx, dy

Después de leer el concepto, combinado con el código para entenderlo de nuevo, de repente me di cuenta un poco. El libro también explica la derivación de retropropagación de la función de activación relu, función sigmoidea, etc., que no se presentará aquí.

2. Reconocimiento de dígitos escritos a mano usando error backpropagation

En este ejemplo, la red neuronal es una red neuronal de 2 capas implementada mediante una clase TwoLayerNet. Las variables de la clase TwoLayerNet se describen a continuación:

params : variable de tipo diccionario que contiene parámetros de red neuronal. params['W1'] es el peso de la primera capa, params['b1'] es el sesgo de la primera capa, params['W2'] es el peso de la segunda capa, params['b2'] es el segunda capa el sesgo.

capas : guarde las variables de diccionario ordenadas de las capas de la red neuronal y guarde la capa Affine1, la capa ReLu1 y la capa Affine2 a su vez.

lastLayer : La última capa de la red neuronal. En este caso la capa SoftmaxWithLoss.

Hablemos de algunas funciones principales:
La siguiente función es la función de inicialización de la clase TwoLayerNet. Como se puede ver en el código, la red neuronal utiliza una red neuronal de 2 capas. De adelante hacia atrás están la capa Affine, la capa ReLu, la capa Affine, la capa SoftmaxWithLoss.

def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
    # 初始化权重
    self.params = {
    
    }
    self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
    self.params['b1'] = np.zeros(hidden_size)
    self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
    self.params['b2'] = np.zeros(output_size)
    # 生成层
    self.layers = OrderedDict()
    self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
    self.layers['Relu1'] = Relu()
    self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
    self.lastLayer = SoftmaxWithLoss()

La siguiente función def gradient(self, x, t)se utiliza principalmente para calcular el gradiente del parámetro de peso. x son los datos de la imagen y t es la etiqueta de la imagen. self.loss(x, t)es el proceso de propagación directa utilizado para calcular la pérdida prevista. Luego está el proceso de retropropagación hacia atrás. En el proceso de retropropagación, layers.reverse()primero invierta el orden de estas capas, y luego retropropague capa por capa, y finalmente obtenga dout y coloque el valor del parámetro actualizado en grad{}.

    def gradient(self, x, t):
        # forward
        self.loss(x, t)
        # backward
        dout = 1
        dout = self.lastLayer.backward(dout)
        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)
        # 设定
        grads = {
    
    }
        grads['W1'] = self.layers['Affine1'].dW
        grads['b1'] = self.layers['Affine1'].db
        grads['W2'] = self.layers['Affine2'].dW
        grads['b2'] = self.layers['Affine2'].db

La siguiente función def predict(self, x)se utiliza para hacer predicciones. La propagación directa de la red neuronal solo necesita llamar al método directo de cada capa en el orden de agregar elementos para completar el procesamiento.

def predict(self, x):
    for layer in self.layers.values():
        x = layer.forward(x)
    return x

Vayamos a la lógica de la "función principal" de Kang Yikang. Las instrucciones están escritas en los comentarios.

	# 加载数据
	(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
    network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

    # 超参数
    iters_num = 10000	# 随机抽数据,抽10000次
    train_size = x_train.shape[0]	# 训练集的大小
    batch_size = 100	# 随机抽数据,每次抽100个数据
    learning_rate = 0.1	# 学习率
    train_loss_list = []# 记录训练集的损失值
    train_acc_list = []	# 记录训练集的准确度
    test_acc_list = []	# 记录测试集的准确度

    # 平均每个epoch的重复次数
    iter_per_epoch = max(train_size / batch_size, 1)
    for i in range(iters_num):
        # 获取mini-batch
        batch_mask = np.random.choice(train_size, batch_size)
        x_batch = x_train[batch_mask]
        t_batch = t_train[batch_mask]

        # 计算梯度
        grad = network.gradient(x_batch, t_batch)
        # 更新参数
        for key in ('W1', 'b1', 'W2', 'b2'):
            network.params[key] -= learning_rate * grad[key]

        # 记录学习过程
        loss = network.loss(x_batch, t_batch)
        train_loss_list.append(loss)

        # 计算每个epoch的识别精度
        if i % iter_per_epoch == 0:
            train_acc = network.accuracy(x_train, t_train)
            test_acc = network.accuracy(x_test, t_test)
            train_acc_list.append(train_acc)
            test_acc_list.append(test_acc)

todos los códigos

PD: Escribí el siguiente código basado en el código del libro. Antes de ejecutar el código, no olvide descargar los paquetes correspondientes common y dataset en el sitio web oficial, y ponerlos en la ruta del proyecto venv\Lib\site- paquetes
Enlace del sitio web oficial: http://www.ituring.com.cn/book/1921
Luego, primero haga clic en "Descargar con libro" a la derecha y luego haga clic en el segundo "Descargar".
inserte la descripción de la imagen aquí
Ponlo así:
inserte la descripción de la imagen aquí
mira el código:

import sys, os

sys.path.append(os.pardir)
import numpy as np
import matplotlib.pyplot as plt
from common.functions import *
from common.gradient import numerical_gradient
from common.layers import *
from collections import OrderedDict
from dataset.mnist import load_mnist
from dataset.two_layer_net import TwoLayerNet


def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
        batch_size = y.shape[0]
        return -np.sum(t * np.log(y + 1e-7)) / batch_size


class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # 初始化权重
        self.params = {
    
    }
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
        self.params['b2'] = np.zeros(output_size)
        # 生成层
        self.layers = OrderedDict()
        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
        self.layers['Relu1'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
        self.lastLayer = SoftmaxWithLoss()

    def sigmoid(a):
        return 1 / (1 + np.exp(-a))

    def softmax(a):
        exp_a = np.exp(a)
        sum = np.sum(exp_a)
        y = exp_a / sum
        return y

    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
        return x

    # x是输入数据,t是标签
    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)

    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        if t.ndim != 1:
            t = np.argmax(t, axis=1)
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy

    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)
        grads = {
    
    }
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        return grads

    def gradient(self, x, t):
        # forward
        self.loss(x, t)
        # backward
        dout = 1
        dout = self.lastLayer.backward(dout)
        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)
        # 设定
        grads = {
    
    }
        grads['W1'] = self.layers['Affine1'].dW
        grads['b1'] = self.layers['Affine1'].db
        grads['W2'] = self.layers['Affine2'].dW
        grads['b2'] = self.layers['Affine2'].db

        return grads

    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
    network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

    # 超参数
    iters_num = 10000
    train_size = x_train.shape[0]
    batch_size = 100
    learning_rate = 0.1
    train_loss_list = []
    train_acc_list = []
    test_acc_list = []

    # 平均每个epoch的重复次数
    iter_per_epoch = max(train_size / batch_size, 1)
    for i in range(iters_num):
        # 获取mini-batch
        batch_mask = np.random.choice(train_size, batch_size)
        x_batch = x_train[batch_mask]
        t_batch = t_train[batch_mask]

        # 计算梯度
        grad = network.gradient(x_batch, t_batch)
        # 更新参数
        for key in ('W1', 'b1', 'W2', 'b2'):
            network.params[key] -= learning_rate * grad[key]

        # 记录学习过程
        loss = network.loss(x_batch, t_batch)
        train_loss_list.append(loss)

        # 计算每个epoch的识别精度
        if i % iter_per_epoch == 0:
            train_acc = network.accuracy(x_train, t_train)
            test_acc = network.accuracy(x_test, t_test)
            train_acc_list.append(train_acc)
            test_acc_list.append(test_acc)

    # 导入数据
    m = list(np.arange(1, iters_num + 1))
    n = list(np.arange(1, len(train_acc_list) + 1))
    t = list(np.arange(1, len(test_acc_list) + 1))
    # 绘图命令
    print(train_loss_list)
    print(train_acc_list)
    print(test_acc_list)
    # 画第一个图
    plt.subplot(221)
    plt.title("Train Loss List")
    plt.plot(m, train_loss_list)
    # 画第二个图
    plt.subplot(222)
    plt.title("Train Acc List")
    plt.plot(n, train_acc_list)
    # 画第三个图
    plt.subplot(223)
    plt.title("Test Acc List")
    plt.plot(t, test_acc_list)
    # show出图形
    plt.show()

resultado de la operación

inserte la descripción de la imagen aquí
¡Tengo que decir que la velocidad de entrenamiento de la retropropagación de errores es realmente mucho más rápida que la diferenciación numérica!

Supongo que te gusta

Origin blog.csdn.net/rellvera/article/details/128038383
Recomendado
Clasificación