[Ejercicio práctico de PyTorch] Utilice el conjunto de datos Cifar10 para entrenar la red LeNet5 e implementar la clasificación de imágenes (con código)

0. Prefacio

De acuerdo con la práctica internacional, primero me gustaría declarar: este artículo es solo mi propia comprensión del aprendizaje. Aunque me refiero a las valiosas ideas de otros, el contenido puede contener imprecisiones. Si encuentras errores en el artículo, espero criticarlos y corregirlos para que podamos avanzar juntos.

Este artículo es un ejercicio práctico que utiliza la red LeNet5 para implementar la clasificación de imágenes basada en el marco PyTorch. El conjunto de datos de entrenamiento utiliza Cifar10. Su objetivo es mejorar la comprensión del aprendizaje profundo, especialmente las redes neuronales convolucionales, a través de operaciones prácticas.

Este artículo es una guía completa de aprendizaje a nivel de niñera. Siempre que tenga los conocimientos más básicos de aprendizaje profundo, puede seguir esta guía: use la biblioteca PyTorch para construir la red LeNet5 desde cero, luego capacítela y finalmente pueda Reconocer objetos físicos en imágenes reales.

1. Conjunto de datos Cifar10

El conjunto de datos Cifar10 fue creado en la década de 1990 por Alex Krizhevsky e Ilya Sutskever, estudiantes del informático Geoffrey Hinton. Cifar10 es un conjunto de datos de clasificación de imágenes que contiene 10 categorías. Cada categoría contiene 6000 imágenes en color de 32x32 píxeles, con un total de 60000 imágenes, de las cuales 50000 imágenes se utilizan para entrenar el modelo de red (grupo de entrenamiento) y 10000 imágenes se utilizan para la validación. Modelo de red (grupo de validación).

El nombre Cifar10 representa el conjunto de imágenes de 10 categorías creado por el Instituto Canadiense de Investigación Avanzada (Instituto Canadiense de Investigación Avanzada), y el siguiente Cifar100 es el conjunto de imágenes de 100 categorías.

1.1 Descarga del conjunto de datos Cifar10

Utilice torchvisionla descarga directa de Cifar10:

from torchvision import datasets
from torchvision import transforms

data_path = 'CIFAR10/IMG_file'
cifar10 = datasets.CIFAR10(root=data_path, train=True, download=True,transform=transforms.ToTensor())   #首次下载时download设为true

datasets.CIFAR10Parámetros en:

  • raíz: ruta para descargar el archivo
  • train: Si es True descarga los datos del grupo de entrenamiento, con un total de 50.000 imágenes; si es False, descarga los datos del grupo de verificación, con un total de 10.000 imágenes.
  • descargar: debe configurarse en Verdadero al descargar nuevos datos. Si los datos se han descargado, se puede configurar en Falso.
  • Transformar: transforma los datos de la imagen. transforms.ToTensor()Los datos de la imagen especificados aquí se convertirán en tensor y el rango de datos se ajustará a 0 ~ 1, lo que nos ahorrará la necesidad de escribir otra línea de código de normalización.
1.2 Análisis del conjunto de datos Cifar10

Después de la descarga, puede ver el contenido específico del conjunto de datos Cifar10:

print(type(cifar10))
print(cifar10[0])
------------------------输出------------------------------------
<class 'torchvision.datasets.cifar.CIFAR10'>
(tensor([[[0.2314, 0.1686, 0.1961,  ..., 0.6196, 0.5961, 0.5804],
         [0.0627, 0.0000, 0.0706,  ..., 0.4824, 0.4667, 0.4784],
         [0.0980, 0.0627, 0.1922,  ..., 0.4627, 0.4706, 0.4275],
         ...,
         [0.8157, 0.7882, 0.7765,  ..., 0.6275, 0.2196, 0.2078],
         [0.7059, 0.6784, 0.7294,  ..., 0.7216, 0.3804, 0.3255],
         [0.6941, 0.6588, 0.7020,  ..., 0.8471, 0.5922, 0.4824]],

        [[0.2431, 0.1804, 0.1882,  ..., 0.5176, 0.4902, 0.4863],
         [0.0784, 0.0000, 0.0314,  ..., 0.3451, 0.3255, 0.3412],
         [0.0941, 0.0275, 0.1059,  ..., 0.3294, 0.3294, 0.2863],
         ...,
         [0.6667, 0.6000, 0.6314,  ..., 0.5216, 0.1216, 0.1333],
         [0.5451, 0.4824, 0.5647,  ..., 0.5804, 0.2431, 0.2078],
         [0.5647, 0.5059, 0.5569,  ..., 0.7216, 0.4627, 0.3608]],

        [[0.2471, 0.1765, 0.1686,  ..., 0.4235, 0.4000, 0.4039],
         [0.0784, 0.0000, 0.0000,  ..., 0.2157, 0.1961, 0.2235],
         [0.0824, 0.0000, 0.0314,  ..., 0.1961, 0.1961, 0.1647],
         ...,
         [0.3765, 0.1333, 0.1020,  ..., 0.2745, 0.0275, 0.0784],
         [0.3765, 0.1647, 0.1176,  ..., 0.3686, 0.1333, 0.1333],
         [0.4549, 0.3686, 0.3412,  ..., 0.5490, 0.3294, 0.2824]]]), 6)

Process finished with exit code 0

Se puede ver que Cifar10 tiene su propio tipo de datos separado torchvision.datasets.cifar.CIFAR10y su estructura es similar a una lista.

Si se genera uno de los elementos, como el primero cifar10[0], contiene:

  • Un tensor con dimensiones [3,32,32] (porque Transform ha especificado ToTensor arriba), estos son los datos de imagen RGB de tres canales
  • Una etiqueta de datos escalares, aquí está 6, estos datos representan la verdadera clasificación de la imagen y su relación correspondiente es la siguiente:
    Insertar descripción de la imagen aquí

Aquí también podemos usar matplotlib para convertir los datos del tensor de la imagen nuevamente a la imagen para ver cómo se ve la imagen con la etiqueta 6:

from torchvision import datasets
import matplotlib.pyplot as plt
from torchvision import transforms

data_path = 'CIFAR10/IMG_file'
cifar10 = datasets.CIFAR10(root=data_path, train=True, download=False,transform=transforms.ToTensor())   #首次下载时download设为true

# print(type(cifar10))
# print(cifar10[0])

img,label = cifar10[0]
plt.imshow(img.permute(1,2,0))
plt.show()

El resultado es:
Insertar descripción de la imagen aquí
Sí, esta es una rana con la etiqueta 6. Una imagen de 32 × 32 píxeles solo puede hacer esto.

Se utiliza aquí .permute()porque las dimensiones de los datos originales son [canal3, H32, W32] y las .imshow()dimensiones de entrada requeridas deben ser [H, W, canal]. Es necesario ajustar el orden de las dimensiones de los datos originales.

2. Red LeNet5

LeNet5 fue propuesto por Yann LeCun a principios de la década de 1990 y es una red neuronal convolucional clásica. LeNet5 consta de 7 capas de red neuronal, incluidas 2 capas convolucionales, 2 capas de agrupación y 3 capas completamente conectadas. (En el contexto de la época) utilizó creativamente capas convolucionales y capas de agrupación para extraer características de la entrada, reduciendo la cantidad de parámetros y mejorando al mismo tiempo la invariancia de traducción y rotación de la red a la imagen de entrada.

LeNet5 se usa ampliamente en el reconocimiento de dígitos escritos a mano y también puede usarse para otras tareas de clasificación de imágenes. Aunque la red neuronal convolucional profunda actual tiene un mejor rendimiento que LeNet5, LeNet5 tiene una importancia educativa importante para aprender los principios y métodos básicos de las redes neuronales convolucionales .

2.1 Estructura de red de LeNet5

La estructura de red de LeNet5 es la siguiente:
Por favor agregue la descripción de la imagen.

La entrada de LeNet5 es una imagen de 32x32:

  • La primera capa es una capa convolucional, que incluye 6 núcleos de convolución de 5x5 y el mapa de características de salida es 28x28.
  • La segunda capa es una capa de agrupación máxima de 2x2, que reduce el tamaño del mapa de características a la mitad a 14×14.
  • La tercera capa es otra capa convolucional, que incluye 16 núcleos de convolución de 5x5 y el mapa de características de salida es 10x10.
  • La cuarta capa es la misma que la segunda capa, reduciendo el tamaño del mapa de características a la mitad a 5×5
  • La quinta capa es una capa completamente conectada que contiene 120 neuronas.
  • La sexta capa es otra capa completamente conectada y contiene 84 neuronas.
  • La última capa es la capa de salida, que contiene 10 neuronas y cada neurona corresponde a una etiqueta.
2.2 Codificación de red LeNet5 basada en PyTorch

De acuerdo con la estructura de red LeNet5 anterior, escriba el código de la siguiente manera:

import torch.nn as nn

class LeNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=6, kernel_size=5),  # 由于图片为RGB彩图,channel_in = 3
            #输出张量为 Batch(1)*Channel(6)*H(28)*W(28)
            nn.Sigmoid(),
            nn.MaxPool2d(kernel_size=2, stride=2),
            # 输出张量为 Batch(1)*Channel(6)*H(14)*W(14)
            nn.Conv2d(in_channels=6,out_channels= 16,kernel_size= 5),
            # 输出张量为 Batch(1)*Channel(16)*H(10)*W(10)
            nn.Sigmoid(),
            nn.MaxPool2d(kernel_size=2, stride=2),
            # 输出张量为 Batch(1)*Channel(16)*H(5)*W(5)
            nn.Conv2d(in_channels=16, out_channels=120,kernel_size=5),
            # 输出张量为 Batch(1)*Channel(120)*H(1)*W(1)
            nn.Flatten(),
            # 将输出一维化,用于后面的全连接网络输入
            nn.Linear(120, 84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )

    def forward(self, x):
        return self.net(x)

3. Capacitación de la red LeNet5 y verificación de salida.

3.1 Entrenamiento de la red LeNet5

Como mi computadora no tiene GPU, el entrenamiento con la versión CPU de los datos de PyTorch es muy lento, solo tomé los primeros 2000 datos de Cifar10 para entrenar (T_T)

small_cifar10 = []
for i in range(2000):
    small_cifar10.append(cifar10[i])

Las configuraciones relacionadas con la capacitación son las siguientes:

  • Función de pérdida: función de pérdida de entropía cruzadann.CrossEntropyLoss()
  • Método de optimización: descenso de gradiente estocásticotorch.optim.SGD()
  • Época y tasa de aprendizaje: esta es una parte problemática. En la actualidad, no he encontrado una buena manera de establecer la época y lr mejor en la etapa inicial. Solo puedo intentarlo paso a paso. Para no desperdiciar cada entrenamiento, podemos guardar los pesos de cada entrenamiento, y el siguiente entrenamiento se basará en el último resultado. Para conocer los métodos para guardar y cargar pesos, consulte los blogs anteriores: Aprenda a cargar con Pytorch pesos.load_state_dict() y guardar pesos.save() mediante ejemplos . La siguiente figura muestra mi proceso de exploración: el valor de lr disminuyó gradualmente de aproximadamente 1e-5 a 2e-7, el número total de épocas fue de aproximadamente 3000 y el valor de pérdida cayó de los 10000 iniciales a menos de 100.

En esta parte del proceso de entrenamiento me olvidé de registrar completamente los parámetros detallados (época y lr) de cada paso, si lo necesitas puedes dejar tu correo electrónico y te enviaré los pesos entrenados. Los lectores también pueden explorar mejores parámetros de entrenamiento.

Insertar descripción de la imagen aquí

3.2 Verificación de la red LeNet5

¡Se acerca el momento emocionante! ¡Ahora verifiquemos si nuestra red entrenada puede identificar con precisión la imagen de destino!

La imagen que elegí es el modelo G6 lanzado por Xpeng Motors en 2023 para verificación. La imagen es la siguiente:
Insertar descripción de la imagen aquí
cargue el archivo de peso que entrenamos e ingrese la imagen en el modelo:

def img_totensor(img_file):
    img = Image.open(img_file)
    transform = transforms.Compose([transforms.ToTensor(), transforms.Resize((32, 32))])
    img_tensor = transform(img).unsqueeze(0)  #这里要升维,对应增加batch维度

    return img_tensor

test_model = LeNet()
test_model.load_state_dict(torch.load('CIFAR10/small2000_8.pth'))

img1 = img_totensor('1.jpg')
img2 = img_totensor('2.jpg')
img3 = img_totensor('3.jpg')
img4 = img_totensor('4.jpg')

print(test_model(img1))
print(test_model(img2))
print(test_model(img3))
print(test_model(img4))

El resultado final es el siguiente:

tensor([[ 8.4051, 12.0952, -7.9274,  0.3868, -3.0866, -4.7883, -1.6089, -3.6484,
         -1.1387,  4.7348]], grad_fn=<AddmmBackward0>)
tensor([[-1.1992, 17.4531, -2.7929, -6.0410, -1.7589, -2.6942, -3.6753, -2.6800,
          3.6378,  2.4267]], grad_fn=<AddmmBackward0>)
tensor([[ 1.7580, 10.6321, -5.3922, -0.4557, -2.0147, -0.5974, -0.5785, -4.7977,
         -1.2916,  5.4786]], grad_fn=<AddmmBackward0>)
tensor([[10.5689,  6.2413, -0.9554, -4.4162,  1.0807, -7.9541, -5.3185, -6.0609,
          5.1129,  4.2243]], grad_fn=<AddmmBackward0>)

Interpretemos este resultado:

  • Las imágenes 1.ª, 2.ª y 3.ª corresponden al valor máximo del tensor de salida en [1]el elemento (contando desde 0), es decir, el valor de etiqueta correspondiente es 1, la clasificación verdadera es Car y la predicción es correcta.
  • El error de predicción de salida de la cuarta imagen es incorrecto y el valor máximo está en el [0]elemento. LeNet5 cree que esta imagen es un avión.

Aunque esta precisión no es alta, no olvide que solo utilicé los primeros 2000 datos de Cifar10 para el entrenamiento, y la entrada de la red LeNet5 es una imagen de 32 × 32, como la rana de arriba, lo cual es muy difícil incluso para las personas. distinguir tarea.

4. Código completo

4.1 Código de entrenamiento
#文件命名为 CIFAR10_main.py 后面验证时需要调用
from torchvision import datasets
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
from torchvision import transforms
from tqdm import tqdm


data_path = 'CIFAR10/IMG_file'
cifar10 = datasets.CIFAR10(data_path, train=True, download=False,transform=transforms.ToTensor())   #首次下载时download设为true


class LeNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=6, kernel_size=5),  # 由于图片为RGB彩图,channel_in = 3
            #输出张量为 Batch(1)*Channel(6)*H(28)*W(28)
            nn.Sigmoid(),
            nn.MaxPool2d(kernel_size=2, stride=2),
            # 输出张量为 Batch(1)*Channel(6)*H(14)*W(14)
            nn.Conv2d(in_channels=6,out_channels= 16,kernel_size= 5),
            # 输出张量为 Batch(1)*Channel(16)*H(10)*W(10)
            nn.Sigmoid(),
            nn.MaxPool2d(kernel_size=2, stride=2),
            # 输出张量为 Batch(1)*Channel(16)*H(5)*W(5)
            nn.Conv2d(in_channels=16, out_channels=120,kernel_size=5),
            # 输出张量为 Batch(1)*Channel(120)*H(1)*W(1)
            nn.Flatten(),
            # 将输出一维化,用于后面的全连接网络输入
            nn.Linear(120, 84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )

    def forward(self, x):
        return self.net(x)

if __name__ == '__main__':
    model = LeNet()
    model.load_state_dict(torch.load('CIFAR10/small2000_7.pth'))

    loss = nn.CrossEntropyLoss()
    opt = torch.optim.SGD(model.parameters(),lr=2e-7)


    small_cifar10 = []
    for i in range(2000):
        small_cifar10.append(cifar10[i])

    for epoch in range(1000):
        opt.zero_grad()
        total_loss = torch.tensor([0])
        for img,label in tqdm(small_cifar10):
            output = model(img.unsqueeze(0))
            label = torch.tensor([label])
            LeNet_loss = loss(output, label)
            total_loss = total_loss + LeNet_loss
            LeNet_loss.backward()
            opt.step()

        total_loss_numpy = total_loss.detach().numpy()
        plt.scatter(epoch,total_loss_numpy,c='b')
        print(total_loss)
        print("epoch=",epoch)


    torch.save(model.state_dict(),'CIFAR10/small2000_8.pth')
    plt.show()

4.1 Código de verificación
import torch
from torchvision import transforms
from PIL import Image
from CIFAR10_main import LeNet

def img_totensor(img_file):
    img = Image.open(img_file)
    transform = transforms.Compose([transforms.ToTensor(), transforms.Resize((32, 32))])
    img_tensor = transform(img).unsqueeze(0)  #这里要升维,对应增加batch维度

    return img_tensor

test_model = LeNet()
test_model.load_state_dict(torch.load('CIFAR10/small2000_8.pth'))

img1 = img_totensor('1.jpg')
img2 = img_totensor('2.jpg')
img3 = img_totensor('3.jpg')
img4 = img_totensor('4.jpg')

print(test_model(img1))
print(test_model(img2))
print(test_model(img3))
print(test_model(img4))

Supongo que te gusta

Origin blog.csdn.net/m0_49963403/article/details/133365347
Recomendado
Clasificación