[PyTorch 실습] Cifar10 데이터 세트를 사용하여 LeNet5 네트워크 학습 및 이미지 분류 구현(코드 포함)

0. 서문

국제 관행에 따라 먼저 선언하고 싶습니다: 이 기사는 학습에 대한 나만의 이해일 뿐이며 다른 사람의 귀중한 통찰력을 참조했지만 내용에 부정확한 내용이 포함될 수 있습니다. 만약 기사에 오류가 있다면 비판하고 수정하여 함께 발전할 수 있기를 바랍니다.

본 논문은 PyTorch 프레임워크 기반의 이미지 분류를 구현하기 위해 LeNet5 네트워크를 이용한 실습으로, 학습 데이터 세트는 Cifar10을 사용하며, 실습을 통해 딥러닝, 특히 컨볼루셔널 뉴런 네트워크에 대한 이해를 높이는 것을 목표로 한다.

이 글은 보모 수준의 완벽한 학습 가이드입니다. 가장 기본적인 딥 러닝 지식이 있는 한 이 가이드를 따를 수 있습니다: PyTorch 라이브러리를 사용하여 처음부터 LeNet5 네트워크를 구축한 다음 훈련하고 최종적으로 다음을 수행할 수 있습니다. 실제 이미지에서 물리적인 물체를 인식합니다.

1. Cifar10 데이터 세트

Cifar10 데이터 세트는 1990년대 컴퓨터 과학자 Geoffrey Hinton의 학생인 Alex Krizhevsky와 Ilya Sutskever가 만들었습니다. Cifar10은 10개의 카테고리로 구성된 이미지 분류 데이터세트로, 각 카테고리에는 32x32 픽셀의 6000개의 컬러 이미지가 포함되어 있으며, 총 60000개의 이미지가 포함되어 있으며, 그 중 50000개의 이미지는 네트워크 모델(훈련 그룹) 학습에 사용되고 10000개의 이미지는 검증에 사용됩니다. 네트워크 모델(검증 그룹).

Cifar10이라는 이름은 Canadian Institute for Advanced Research(Canadian Institute for Advanced Research)에서 만든 10개 카테고리 이미지 세트를 의미하며, 다음 Cifar100은 100개 카테고리 이미지 세트를 의미합니다.

1.1 Cifar10 데이터 세트 다운로드

torchvision직접 다운로드 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.CIFAR10매개변수:

  • 루트: 다운로드 파일 경로
  • train: True인 경우 총 50,000개의 이미지가 포함된 학습 그룹 데이터를 다운로드하고, False인 경우 총 10,000개의 이미지가 포함된 검증 그룹 데이터를 다운로드합니다.
  • download: 새로운 데이터를 다운로드할 때 True로 설정해야 하며, 데이터가 다운로드된 경우 False로 설정할 수 있습니다.
  • 변환: 이미지 데이터를 변환합니다. transforms.ToTensor()여기에 지정된 이미지 데이터는 Tensor로 변환되며 데이터 범위는 0~1로 조정되므로 정규화 코드를 한 줄 더 작성할 필요가 없습니다.
1.2 Cifar10 데이터 세트 분석

다운로드 후 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

torchvision.datasets.cifar.CIFAR10Cifar10은 별도의 데이터 타입을 갖고 있으며 , 그 구조가 리스트와 유사하다는 것을 알 수 있다 .

요소 중 하나가 출력되는 경우(예: 첫 번째 요소)에는 cifar10[0]다음이 포함됩니다.

  • 차원이 [3,32,32]인 텐서(위의 Transform이 ToTensor를 지정했기 때문에), 이는 RGB 3채널 이미지 데이터입니다.
  • 스칼라 데이터 레이블은 다음과 같습니다 6. 이 데이터는 이미지의 실제 분류를 나타내며 해당 관계는 다음과 같습니다.
    여기에 이미지 설명을 삽입하세요.

여기서 matplotlib를 사용하여 이미지의 텐서 데이터를 다시 이미지로 변환하여 라벨 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()

출력은 다음과 같습니다.
여기에 이미지 설명을 삽입하세요.
예, 이것은 라벨 6이 있는 개구리입니다. 32×32 픽셀 이미지는 이 작업만 수행할 수 있습니다.

.permute()여기서는 원본 데이터의 차원이 [channel3, H32, W32]이고, .imshow()필요한 입력 차원이 [H, W, 채널]이어야 하므로 원본 데이터의 차원 순서를 조정해야 하기 때문에 사용합니다 .

2. LeNet5 네트워크

LeNet5는 1990년대 초 Yann LeCun이 제안한 고전적인 컨벌루션 신경망입니다. LeNet5는 2개의 컨볼루션 레이어, 2개의 풀링 레이어, 3개의 완전 연결 레이어를 포함하여 7개의 신경망 레이어로 구성됩니다. (시대의 맥락에서) 컨벌루션 레이어와 풀링 레이어를 창의적으로 사용하여 입력에서 특징을 추출하고 매개변수 수를 줄이면서 입력 이미지에 대한 네트워크의 변환 및 회전 불변성을 향상시켰습니다.

LeNet5는 필기 숫자 인식에 널리 사용되며 다른 이미지 분류 작업에도 사용할 수 있습니다. 현재의 심층 합성곱 신경망은 LeNet5보다 성능이 우수하지만 LeNet5는 합성곱 신경망의 기본 원리와 방법을 학습하는 데 중요한 교육적 의미를 갖습니다 .

2.1 LeNet5의 네트워크 구조

LeNet5의 네트워크 구조는 다음과 같습니다.
이미지 설명을 추가해주세요

LeNet5의 입력은 32x32 이미지입니다:

  • 첫 번째 레이어는 6개의 5x5 콘볼루션 커널을 포함하는 콘볼루션 레이어이며 출력 기능 맵은 28x28입니다.
  • 두 번째 레이어는 2x2 최대 풀링 레이어로, 기능 맵 크기를 14x14로 절반으로 줄입니다.
  • 세 번째 레이어는 16개의 5x5 콘볼루션 커널을 포함하는 또 다른 콘볼루션 레이어이며 출력 기능 맵은 10x10입니다.
  • 네 번째 레이어는 두 번째 레이어와 동일하며 특징 맵의 크기를 5×5로 절반으로 줄입니다.
  • 다섯 번째 계층은 120개의 뉴런을 포함하는 완전 연결 계층입니다.
  • 여섯 번째 계층은 84개의 뉴런을 포함하는 또 다른 완전 연결 계층입니다.
  • 마지막 레이어는 10개의 뉴런을 포함하는 출력 레이어이며, 각 뉴런은 레이블에 해당합니다.
2.2 PyTorch 기반 LeNet5 네트워크 코딩

위의 LeNet5 네트워크 구조에 따라 다음과 같이 코드를 작성합니다.

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. LeNet5 네트워크 훈련 및 출력 검증

3.1 LeNet5 네트워크 훈련

내 컴퓨터에는 GPU가 없기 때문에 CPU 버전의 PyTorch 데이터로 훈련하는 것이 매우 느립니다. Cifar10의 처음 2000개 데이터만 훈련에 사용했습니다(T_T)

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

훈련 관련 설정은 다음과 같습니다.

  • 손실 함수: 교차 엔트로피 손실 함수nn.CrossEntropyLoss()
  • 최적화 방법: 확률적 경사하강법torch.optim.SGD()
  • Epoch 및 학습률: 번거로운 부분인데 현재로서는 초기 단계에서 epoch와 lr을 더 잘 설정할 수 있는 좋은 방법을 찾지 못하여 차근차근 해볼 수 밖에 없습니다. 각 훈련을 낭비하지 않기 위해 각 훈련의 가중치를 저장할 수 있으며 다음 훈련은 마지막 결과를 기반으로 합니다. 가중치를 저장하고 로드하는 방법은 이전 블로그를 참조하세요. Pytorch에서 Weights.load_state_dict()를 로드하고 예제를 통해 Weights.save()를 저장하는 방법을 알아보세요 . 아래 그림은 제가 탐색하는 과정을 보여줍니다. lr 값은 약 1e-5에서 2e-7로 점차 감소하고 총 epoch 수는 약 3000이었으며 손실 값은 초기 10000에서 100 미만으로 떨어졌습니다.

훈련 과정의 이 부분에서 각 단계의 세부 매개변수(epoch 및 lr)를 완전히 기록하는 것을 잊어버렸습니다. 필요한 경우 이메일을 남겨주시면 훈련된 가중치를 보내드리겠습니다. 독자는 더 나은 훈련 매개변수를 탐색할 수도 있습니다.

여기에 이미지 설명을 삽입하세요.

3.2 LeNet5 네트워크 검증

신나는 시간이 다가오고 있습니다! 이제 훈련된 네트워크가 대상 이미지를 정확하게 식별할 수 있는지 검증해 보겠습니다!

제가 선택한 이미지는 Xpeng Motors가 검증을 위해 2023년에 출시한 G6 모델로, 이미지는 다음과 같습니다:
여기에 이미지 설명을 삽입하세요.
우리가 훈련한 Weight 파일을 로드하고 이미지를 모델에 입력합니다.

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))

최종 출력은 다음과 같습니다.

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>)

이 출력을 해석해 보겠습니다.

  • [1]1번째, 2번째, 3번째 이미지 는 요소의 출력 텐서의 최대값(0부터 계산)에 해당합니다 . 즉 해당 레이블 값은 1이고 실제 분류는 Car이며 예측이 정확합니다.
  • 네 번째 이미지의 출력 예측 오류가 잘못되었으며 요소에 최대값이 있습니다 [0]. LeNet5는 이 이미지를 비행기라고 생각합니다.

비록 정확도가 높지는 않지만 Cifar10의 처음 2000개 데이터만 훈련에 사용했다는 점과 LeNet5 네트워크 입력은 위의 개구리처럼 32×32 이미지라는 점을 잊지 마세요. 구별.과제.

4. 완전한 코드

4.1 훈련 코드
#文件命名为 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 인증코드
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))

추천

출처blog.csdn.net/m0_49963403/article/details/133365347