PyTorch발행일 2024. 12. 12.원본 https://blog.naver.com/jword_/223691540958 ↗

합성곱 신경망 코드 분석(파이토치)

합성곱 신경망 코드 분석(파이토치) — #합성곱신경망 #파이토치 #합성곱신경망코드 #개발자의도구들 컴퓨터공학과 학사과정 중 공부한 내용을 정...

#pytorch#Naver Blog

#합성곱신경망 #파이토치 #합성곱신경망코드 #개발자의도구들

​

컴퓨터공학과 학사과정 중 공부한 내용을 정리하였습니다.

\* 본글은 PC버전에 최적화 되어있습니다.

​

CNN

이전글에서 기본적인 파이토치 모듈과 기능들을 알아보았습니다. 이번에는 기본적인 모듈을 활용해서 실제 데이터를 불러와, 합성곱 신경망을 적용하는 코드를 가져왔습니다.

​

해당 코드는 제가 구현한 것이 아니며, 저는 단지 코드 분석만 수행합니다.

INDEX

  • 전체코드
  • 코드의 기본적인 틀
  • 코드분석
  • 학습데이터 로드
  • 모델정의
  • 상속
  • 레이어층
  • forward()
  • 학습시작
  • 알고리즘 순서
  • 테스트
  • 후기

​

전체코드 (skim)

python 코드 예제
                                    import torch
import torchvision # torchvision for ... what?
import torchvision.transforms as transforms
from google.colab import drive # drive

drive.mount('/content/gdrive') # mount with my gdrive folder

transform = transforms.Compose([ # for what?
    transforms.toTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

# CIFAR-10 학습 데이터 셋 로드
trainset = torchvision.datasets.CIFAR10(
    root='./data',      # 데이터 저장 경로
    train=True,        # 학습용 데이터 셋임을 명시
    download=True,      # 데이터가 없으면 다운로드 하라
    transform=transform # 데이터 변환 적용
)

trainloader = torch.utils.data.DataLoader(
    trainset,
    batch_size=4,   # batch
    shuffle=True,   # 데이터 무작위 적용
    num_workers=2   # 데이터 로딩에 사용할 스레드 수
)

# 테스트용 데이터 set 로드
testset = torchvision.datasets.CIFAR10(
    root = './data',
    train = False, # 테스트용 데이터 셋
    download=True,
    transform=transform
)

# 테스트 데이터 로더 설정
testloader = torch.utils.data.DataLodaer(
    testset,
    batch_size=4,
    shuggle=False,
    num_workers=2
)

classes = ('plane', 'car', 'bird', 'cat', 'deer',
           'dog', 'frog', 'horse', 'ship', 'truck')

# matplotlib, numpy
import matplotlib.pyplot as plt
import numpy as np

def imshow(img):
  img = img / 2 + 0.5 # 정규화 해제
  npimg = img.numpy()   # tensor to numpy array
  plt.imshow(np.transpose(npimg, (1, 2, 0))) # 이미지 차원 변환 후 출력
  plt.show()

dataiter = iter(trainloader)
imgaes, labels = next(dataiter)

imshow(torchvision.utils.make_grid(images))

import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
  def __init__(self):
    super(Net, self).__init__()

    self.conv = nn.Conv2d(3, 6, 5)

    self.pool = nn.MaxPool2d(2, 2)

    self.conv2 = nn.Conv2d(6, 16, 5)

    self.fc1 = nn.Linear(16 * 5 * 5, 120)

    self.fc2= nn.Linear(120, 84)

    self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
      # 첫 번째 합성곱 층 + ReLu 활서오하 + 풀링
      x = self.pool(F.relu(self.conv1(x)))
      # 두 번째 합서곱 층 + ReLu 활성화 + 풀링
      x = self.pool(F.relu(self.conv2(x)))
      # 텐서 1차원으로 변환
      x = x.view(-1, 16 * 5 * 5)
      # 첫 번째 완전 연결 츨 + ReLu
      x = F.relu(self.fc1(x))
      # 두 번째 완전 연결 층 + ReLU
      x = F.relu(self.fc2(x))
      # 세 번째 완전 연결층 (출력)
      x = self.fc3(x)

      return x

net = Net()

# 손실함수와 옵티마이저 설정
import torch.optim as optim

# 교차 엔트로피 손실 함수 사용
criterion = nn.CorssEntropyLoss()
# Adam 옵티마이저 사용, 합습률 0.001
optimizer = optim.Adam(net.paramters(), lr=0.001)

for epoch in range(2):
  running_loss = 0.0
  for i, data in enumerate(trainloader, 0):
    inputs, labels = data

    # 옵티마이저 기울기 초기화
    optimizer.zero_grad()

    # 순전파: 신경망에 입력 데이터를 통과시켜출력 계산
    outputs = net(inputs)
    # 손실 함수 계산
    loss = criterion(outputs, lables)
    # 역전파 : 손실을 기준으로 기울기 계산
    loss.backword()
    #옵티마이저를 사용하여 가중치 업데이트
    optimizer.step()

    running_loss += loss.item()
    if i % 2000 == 1999: # 2000 번째 배치마다 출력
        print(f'[{epoch + 1}, {i +1:5d}]loss: {running_loss / 2000: .3f}')
        running_loss = 0.0 # 손실 초기화

  print('Finished Training') # 학습 완료 메세지

# 테스트 데이터에서 한 배치 가져오기
dataiter = iter(testloader)
images, labels = next(dataiter)

# 테스트용 이미지 출력
imshow(torchivision.utils.make_gird(imgaes))
# 실제 라벨 출력
print('GroundTruth: ', ' '.join(f'{classes[labels[j]]:5}' for j in range(4)))

코드의 기본적인 틀

딥러닝 모델을 코드는 다음과 같은 단계를 따르는게 일반적입니다.

​

  1. 학습할 데이터 로드
  2. 모델정의
  3. 학습 시작
  4. 테스트
  5. 테스트 결과 출력

​

위 코드도 이런 원칙을 아주 잘 반영하고 있습니다.

코드분석

\* 전체 코드를 알아보기 편리하게 변수명을 변경하여 분석을 진행하였습니다.

🚌 학습 데이터 로드
python 코드 예제
                                    import torch
import torchvision
import transforms from torch
from google.colab import drive

우선 기본적으로 필요한 모듈을 불러옵니다.

python 코드 예제
                                    custom_transform = transforms.Compose([
    transforms.toTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

불러온 데이터를 전처리할 transform을 정의합니다.

python 코드 예제
                                    train_data_set = torchvision.datasets.CIFAR10(
    root = './data',
    train = True,
    download = True,
    trainsform = custom_transform
)

모델에 적용시킬 데이터 셋을 불러옵니다. torchvision에서 제공하는 CIFAR10 데이터를 활용하겠습니다.

python 코드 예제
                                    custom_data_loader = torch.utils.data.DataLoader(
    dataset = custom_transform,
    shuffle = True,
    batch_size = 4,
    num_workers = 2
)

모델의 데이터 로드를 설정합니다. 배치 사이즈 4, 셔플 적용 등

📦모델 정의 (모델 상속)

학습 데이터는 불러왔으니 본격적으로 모델을 정의해야 합니다.

python 코드 예제
                                    import torch.nn as nn
import torch.nn.functional as F

모델을 custom 할 수 있도록 nn을 불러옵니다.

python 코드 예제
                                    class MyCNNModel(nn.Module):
    def __init__(self):
        super(MYCNNModel, self).__init__()

    def forward(self, x)

모델을 Cutomizing 하려면 위 코드를 필수적으로 입력해야합니다.

​

  1. nn.Module을 상속받습니다.
  1. super().\_\_init\_\_()을 실행합니다.
  1. forward를 override 해야합니다. 이때 parameter는 입력 텐서입니다.

​

📦모델 정의 (레이어층 구성)

CNN은 다층 퍼셉트론의 한 종류로, 여러 레이어를 통해 학습이 이루어집니다. 다음과 같은 레이어 층이 있습니다.

text 코드 예제
                                    1. Convolution layer + Activation func

2. Pooling layer
   : 이미지나 특징 맵의 높이 너비를 줄여 연산량을 감소시킴
   : 과적합 방지 효과 - 노이즈 제거

3. Fully Connected layer(FC)
   : 최종 예측을 출력하는 층
python 코드 예제
                                    class MyCNNModel(nn.Module):
    def __init__(self):
        # 중략
        self.conv = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 15, 5)
        self.fc1 = nn.Linear(16 *5 *5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

각 층을 정의합니다.

​

\* method 정리

  • conv2d(in\_channels, out\_channels, kernel\_size, stride = 1, padding = 0)
  • 2D 합성곱 연산을 수행
  • 파라미터 정리
  • in\_channels : 입력 데이터의 채널 수
  • RGB이미지 -> in\_channel = 3이다.
  • 이전 레이어의 출력 채널 수와 연결된다.
  • out\_channels
  • 합성곱 층에서 생성할 출력 특징 맵의 채널 수를 나타낸다.
  • kernal\_size
  • 합성곱 커널의 크기를 나타냄
  • size = 5라면 5x5 크기의 필터를 사용
  • stride
  • 필터를 이동시키는 간겨이다. 기본값은 1로 1씩 이동
  • 슬라이딩 윈도우 생각하면 편함
  • padding
  • 입력의 가장자리에 추가되는 0의 개수, 기본값은 패딩이 없음
  • 출력 크기를 조정하며, 필터가 입력의 모든 영역에서 동등하게 작동하도록 보장

​

​

  • MaxPool2d
  • nxn 영역에서 가장 큰 값을 선택해서 출력

​

  • Linear(in\_features, out\_features)
  • 입력 벡터를 가중치와 편향을 사용하여 선형 변환 수행
  • in\_features: 입력 벡터의 크기, 이전 레이어 또는 데이터의 출력 크기
  • out\_features: 출력 벡터의 크기(차원), 이 중에서 원하는 출력 크기 지정
  • 완전 연결층은 각 입력 특징을 모든 출력 노드와 연결한다.
  • 사용 이유?
  • 합성곱 or 풀링을 거친 2d 형태의 특징 맵을 1D로 벡터로 변환하여 Linear() 층을 통해 입력 벡터를 선형 변환하여 최종 출력값을 계산할 수 있다.
📦모델 정의 (forward)

층을 정의한 후 각 층을 적절한 순서로 배치하여 예측을 수행합니다.

​

  • 입력과 출력 모두 Tensor입니다.

​

python 코드 예제
                                    def forward(self, x):
    x = self.pool(F.relu(self.conv1(x)))
    x = self.pool(F.relu(self.conv2(x)))
    x = x.view(-1, 16 * 5 * 5)
    x = F.relu(self.fc1(x))
    x = F.relu(self.fc2(x))
    x = self.fc3(x)

    return x

각 층은 activation fucntion으로 relu를 사용합니다.

✏️ 학습시작
python 코드 예제
                                    import torch.optim as optim

criterion = nn.CorseEntropyLoss()
optimizer = optim.Adam(customModel.parameters(), lr=0.001) # 학습률 0.001

이건 옵티마이저 적용하는 부분인데, 좀 깊게 들어가면 학습할 내용이 많아져서 생략하겠습니다. (암기하기)

​

python 코드 예제
                                    for epoch in range(2): # epoch 2로 설정 - 전체 데이를 총 두번 학습한다.
    running_loss = 0.0
    for i, data in enumerate(custom_data_loader, 0):
        inputs, labels = data

        optimizer.zero_grad() #손실함수의 기울기를 초기화 - 이전 배치와 독립적이게 계산

        output = net(inputs)  # 실제코드에서는 모듈을 net으로 정의 - forward를 수행하는 것을 의미

        loss = criterion(outputs, labels)
        # criterion은 손실 함수로, 모델 출려과 실제 정답(label)의 차이를 계산
        # loss는 object이다.

        loss.backward() # loss가 나오면 이를 역전파함 - 모델 가중치 편햐 업데이트

        optimizer.step() # 각 파라미터를 업데이트 하는 함수

        running_loss += loss.item()

        if i % 2000 == 1999:  #2000번 배치마다 출력, 손실 초기화
            print(f'[{epoch + 1}, {i +1:5d}]loss: {running_loss / 2000: .3f}')
            running_loss = 0.0 # 손실 초기화

우선 epoch는 딥러닝 모델 학습시킬 때 항상 등장하는 키워드인데, 이는 학습 데이터를 epoch만큼 학습시키는 것을 의미합니다. 잘 보시면 epoch for문 내부에, loader를 통해 데이터를 가져오고 있습니다.

​

데이터의 경우 inputs와 labels로 나눠지는데, inputs은 4D Tesnor 형태로 데이터가 들어있고, labels의 경우 실제 클래스 정보를 나타내는 라벨로 정답이 되겠습니다

python 코드 예제
                                    classes = ('plane', 'car', 'bird', 'cat', 'deer',
           'dog', 'frog', 'horse', 'ship', 'truck')

전체 코드를 잘 보심면 classes를 정의하고 있는데 label의 클래스가 튜플로 정의되어 있는데, 해당 인덱스를 의미합니다.

​

🍃 알고리즘 흐름
text 코드 예제
                                    1. 데이터 로드: transloader에서 배치 단위로 입력 이미지와 정답 라벨을 가져옴

2. 기울기 초기화: 이전 배치에서 계산된 기울기를 초기화함

3. 순전파 시행

4. 손실 계산

5. 역전파 시행

6. 파라미터 업데이트

7. 손실 기록

8. 다음 배치로 반복
✏️ 테스트

이제 모듈을 학습시켰으니, 해당 모듈이 얼마나 잘 예측하는지 알아보기 위해 테스트 데이터를 통해 평가를 해봐야 합니다. 이를 위해 테스트용 데이터를 로드하고, 모델에 입력 해보겠습니다.

python 코드 예제
                                    test_data = torchvision.datasets.CIFAR10(
    root = './data',
    download = true,
    train = False,
    transform = transform
)

test_loader = torch.utils.data.DataLoader(
    test_data,
    batch_size = 4,
    shuffle = False,
    num_workers = 2
)

테스트 데이터를 불러옵니다.

python 코드 예제
                                    def imshow(img):
  img = img / 2 + 0.5 # 정규화 해제
  npimg = img.numpy()   # tensor to numpy array
  plt.imshow(np.transpose(npimg, (1, 2, 0))) # 이미지 차원 변환 후 출력
  plt.show()

정규화된 이미지 데이터를 정규화 해제 후, 텐서를 이미지로 복구합니다.

text 코드 예제
                                    dataiter = iter(testloader)
images, labels = next(dataiter)

# 테스트용 이미지 출력
imshow(torchivision.utils.make_gird(imgaes))

테스트 이미지를 불러와서 출력해보면 얼마나 모듈이 예츢을 잘하는지 알 수 잇습니다.

후기

처음에는 너무 막연한 내용이고 어려웠는데, 하나씩 차근 차근 이해하다보닌깐 왜 이런걸 사용하는지 잘 알게된 것 같습니다. 또한 기존에 이론으로 배웠던 내용들이 거의 다 모듈로 구현되어있고, 거의 다 실제로 사용하는 것임을 알게되니 좀 더 와닿게 되는 것 같습니다.