Tutorial introductorio de visión artificial basada en palas - Clase 7 Práctica: Reconocimiento de números escritos a mano

dirección del tutorial de la estación B

https://www.bilibili.com/video/BV18b4y1J7a6/

Introducción a la tarea

El reconocimiento de dígitos escritos a mano es un proyecto clásico de la visión por computadora.Debido a la aleatoriedad de los dígitos escritos a mano , es difícil encontrar características comunes de los dígitos utilizando las técnicas tradicionales de visión por computadora . En los primeros días de la visión artificial, el reconocimiento de dígitos escritos a mano se convirtió en un problema importante.

A juzgar por la clasificación de tareas visuales que explicamos anteriormente, el reconocimiento de dígitos escritos a mano es una tarea de clasificación típica , y se ingresa una imagen para la clasificación de diez . En la vida real, el reconocimiento de dígitos escritos a mano también tiene muchos escenarios de aplicación. Como se muestra en la figura a continuación, podemos ver que el reconocimiento de códigos postales puede promover en gran medida la automatización industrial, y la precisión lograda mediante el uso de redes neuronales convolucionales puede incluso superar la de los humanos .

Esta tarea consiste en construir un modelo que pueda generar un resultado de clasificación correcto ingresando una imagen de un número escrito a mano. A través de un proyecto tan práctico, puede ayudarnos a consolidar y comprender las operaciones comunes, como la convolución y la agrupación de las que hemos hablado antes , y también podemos revisar el proceso básico del aprendizaje profundo.

preparación de datos

Existe un conjunto de datos generales MNIST para el reconocimiento de dígitos escritos a mano , que contiene decenas de miles de dígitos escritos a mano que se han marcado y se ha dividido en conjunto de entrenamiento y conjunto de evaluación. Si visualizamos una de las imágenes , podemos ver algo como esto:

La forma de la imagen es **(1, 28, 28) , que es una imagen de un solo canal**. El tamaño de la imagen es solo 28*28 y su etiqueta es 7 .

Por lo general, para proyectos generales, debe escribir un Dataloader usted mismo para cargar datos en secuencia, devolver imágenes y anotaciones, y proporcionar una interfaz de capacitación para la capacitación . Teniendo en cuenta el motivo de nuestra entrada aquí, usamos directamente la API escrita. Los estudiantes interesados ​​pueden intentar descargar el paquete comprimido y escribir Dataloader por sí mismos sin usar la API avanzada.

train_loader = paddle.io.DataLoader(MNIST(mode='train', transform=ToTensor()), batch_size=10, shuffle=True)
valid_loader = paddle.io.DataLoader(MNIST(mode='test', transform=ToTensor()), batch_size=10)

A través de la API empaquetada anteriormente, hemos cargado el conjunto de entrenamiento y el conjunto de evaluación , que pueden ser llamados por la interfaz de entrenamiento.

Construcción de red

Después de preparar los datos, el segundo paso es construir una red neuronal convolucional . La red neuronal convolucional afecta directamente la precisión del modelo. Este paso es también el enlace más crítico . En este combate real, usamos LeNet por defecto . LeNet es una de las primeras redes neuronales convolucionales, nacida en 1998, y ha logrado un gran éxito en tareas de reconocimiento de dígitos escritos a mano .

Su estructura de red también es muy simple, básicamente una capa convolucional seguida de una capa de agrupación , y finalmente se genera una matriz [1,10] a través de dos capas completamente conectadas. No hemos presentado la capa totalmente conectada antes. Por lo general, se usa para ajustar algunos datos por lotes, como muchos puntos dispersos, para ajustar una curva . Su estructura es la siguiente:

Es decir, cada salida está relacionada con todos los parámetros de la capa anterior , y su expresión matemática es en realidad multiplicar una matriz de transformación y sumar un sesgo para obtener la matriz de salida. ¿Por qué las capas convolucionales se usan mucho en las imágenes y las capas completamente conectadas rara vez? Aquí te lo dejo para que pienses por ti mismo después de la clase.

LeNet usa Paddle para reproducir el código de la siguiente manera:

import paddle
import numpy as np
from paddle.nn import Conv2D, MaxPool2D, Linear
import paddle.nn.functional as F

# 定义 LeNet 网络结构
class LeNet(paddle.nn.Layer):
    def __init__(self, num_classes=1):
        super(LeNet, self).__init__()
        self.conv1 = Conv2D(in_channels=1, out_channels=6, kernel_size=5)
        self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)
        self.conv2 = Conv2D(in_channels=6, out_channels=16, kernel_size=5)
        self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)
        self.conv3 = Conv2D(in_channels=16, out_channels=120, kernel_size=4)
        self.fc1 = Linear(in_features=120, out_features=64)
        self.fc2 = Linear(in_features=64, out_features=num_classes)
    def forward(self, x):                        #[N,1,28,28] 
        x = self.conv1(x)                        #[N,6,24,24]
        x = F.sigmoid(x)                         #[N,6,24,24]
        x = self.max_pool1(x)                    #[N,6,12,12]
        x = F.sigmoid(x)                         #[N,6,12,12]
        x = self.conv2(x)                        #[N,16,8,8]
        x = self.max_pool2(x)                    #[N,16,4,4]
        x = self.conv3(x)                        #[N,120,1,1]
        x = paddle.reshape(x, [x.shape[0], -1])  #[N,120]
        x = self.fc1(x)                          #[N,64]
        x = F.sigmoid(x)                         #[N,64]
        x = self.fc2(x)                          #[N,10]
        return x

La forma en que Paddle usa gráficos dinámicos es muy clara . Defina un cuerpo de clase y escriba las capas que se usarán en la función de inicialización. Debe prestar especial atención a la cantidad de canales de entrada y salida y al tamaño del kernel de convolución. Errores dimensionales ocurrir _ Después de definir aquí, escribimos la función de avance.La función de avance es la operación real después de pasar la imagen.

Para ayudar a todos a entender, permítanme explicar el proceso de ejecución en detalle. Primero instanciamos el cuerpo de la clase .

model = LeNet(num_classes=10)

Cuando se crea una instancia, el cuerpo de la clase ejecuta automáticamente la función de inicialización init() y la función init() instancia Conv2D y MaxPool2D Estos son en realidad cuerpos de clase Estos cuerpos de clase, como LeNet, también tienen funciones init() y forward . instanciado en consecuencia en la función de inicialización. El proceso de creación de instancias realmente no inicia el cálculo , sino que solo define la capa que quiero usar.

output = model(img)

Cuando vuelvo a ejecutar el código anterior, es equivalente a llamar al cuerpo de la clase e ingresar img.En este momento, el cuerpo de la clase llamará automáticamente a la función call() , entonces, ¿por qué se ejecuta la función de reenvío? La razón es que todas las operaciones heredan la clase principal de paddle.nn.Layer . En la clase principal, la función de reenvío se escribe en call() , que es equivalente a llamar a la función de reenvío automáticamente LeNetcuando se llama al .

Espero que todos escudriñen todo el proceso repetidamente hasta que lo entiendan a fondo. No es difícil encontrar que tal forma de construir una red se puede anidar continuamente.Esta es una forma muy clara, y esta ventaja se verá reflejada cuando expliquemos el modelo complejo más adelante.

entrenamiento modelo

# -*- coding: utf-8 -*-
# LeNet 识别手写数字
import imp
import paddle
import numpy as np
import paddle
from model import LeNet
from paddle.vision.transforms import ToTensor
from paddle.vision.datasets import MNIST


def train(model, opt, train_loader, valid_loader):
    use_gpu = True
    paddle.device.set_device('gpu:0') if use_gpu else paddle.device.set_device('cpu')
    print('start training ... ')
    model.train()
    for epoch in range(EPOCH_NUM):
        for batch_id, data in enumerate(train_loader()):
            img = data[0]              #[10,1,28,28]
            label = data[1]            #[10,1]
            # 计算模型输出
            logits = model(img)
            # 计算损失函数
            loss_func = paddle.nn.CrossEntropyLoss(reduction='none')
            loss = loss_func(logits, label)
            avg_loss = paddle.mean(loss)

            if batch_id % 500 == 0:
                print("epoch: {}, batch_id: {}, loss is: {:.4f}".format(epoch+1, batch_id, float(avg_loss.numpy())))
            avg_loss.backward()
            opt.step()
            opt.clear_grad()

        model.eval()
        accuracies = []
        losses = []
        for batch_id, data in enumerate(valid_loader()):
            img = data[0]
            label = data[1] 
            # 计算模型输出
            logits = model(img)
            # 计算损失函数
            loss_func = paddle.nn.CrossEntropyLoss(reduction='none')
            loss = loss_func(logits, label)
            acc = paddle.metric.accuracy(logits, label)
            accuracies.append(acc.numpy())
            losses.append(loss.numpy())
        print("[validation] accuracy/loss: {:.4f}/{:.4f}".format(np.mean(accuracies), np.mean(losses)))
        model.train()

    # 保存模型参数
    paddle.save(model.state_dict(), 'mnist.pdparams')


model = LeNet(num_classes=10)
EPOCH_NUM = 5
opt = paddle.optimizer.Momentum(learning_rate=0.001, parameters=model.parameters())
train_loader = paddle.io.DataLoader(MNIST(mode='train', transform=ToTensor()), batch_size=10, shuffle=True)
valid_loader = paddle.io.DataLoader(MNIST(mode='test', transform=ToTensor()), batch_size=10)
train(model, opt, train_loader, valid_loader)

El código de entrenamiento es muy claro basado en lo que hemos aprendido. Obtenga el conjunto de datos de la interfaz del conjunto de datos , ingrese la imagen en el modelo, el modelo obtiene un valor predicho, use la función de pérdida CrossEntropyLoss para calcular la pérdida entre el valor predicho y el valor real de la etiqueta , invierta la pérdida al Parámetros de red y, finalmente, utilice el optimizador para corregir el parámetro, reducir la pérdida .

Cabe señalar que la función de pérdida CrossEntropyLoss viene con softmax Al final del problema de clasificación, se requiere una función de activación softmax para devolver la matriz de salida [1,10] a [0,1] y la suma de 10 números es 1, lo que significa que la probabilidad de que esta imagen sea 0-9 .

modelo de predicción

import numpy as np
import paddle
from model import LeNet
from paddle.vision.datasets import MNIST
from paddle.vision.transforms import ToTensor
import paddle.nn.functional as F

valid_loader = MNIST(mode='test', transform=ToTensor())
img = np.array(valid_loader[0][0])

# import matplotlib.pyplot as plt
# plt.imshow(img.squeeze(), cmap='gray')
# plt.show()

model = LeNet(num_classes=10)
model_dict = paddle.load("mnist.pdparams")
model.set_state_dict(model_dict)
model.eval()
x = valid_loader[0][0].reshape((1,1,28,28)).astype('float32')
result = F.softmax(model(x))
print(result.numpy()[0])

Después de entrenar el modelo, necesitamos cargar el modelo y hacer una predicción Aquí seleccionamos una predicción de imagen en el conjunto de evaluación para ver si el resultado de salida es correcto.

model = LeNet(num_classes=10)
model_dict = paddle.load("mnist.pdparams")
model.set_state_dict(model_dict)

Cargamos el modelo usando este método y finalmente predecimos el resultado:

[7.3181213e-06 1.4578840e-05 3.3818762e-04 2.1557527e-04 2.6723552e-05 
 6.7271581e-06 1.3456239e-08 9.9840504e-01 4.1231990e-05 9.4459485e-04]

¡Esto también representa la probabilidad de 0-9 respectivamente , la probabilidad de 7 es tan alta como 99.84% y la salida del modelo es correcta!

Referencias

https://www.paddlepaddle.org.cn/tutorials/projectdetail/2227103

Supongo que te gusta

Origin blog.csdn.net/weixin_45747759/article/details/122636048
Recomendado
Clasificación