합성곱 신경망 코드 분석(파이토치)
합성곱 신경망 코드 분석(파이토치) — #합성곱신경망 #파이토치 #합성곱신경망코드 #개발자의도구들 컴퓨터공학과 학사과정 중 공부한 내용을 정...
#합성곱신경망 #파이토치 #합성곱신경망코드 #개발자의도구들
컴퓨터공학과 학사과정 중 공부한 내용을 정리하였습니다.
\* 본글은 PC버전에 최적화 되어있습니다.
CNN
이전글에서 기본적인 파이토치 모듈과 기능들을 알아보았습니다. 이번에는 기본적인 모듈을 활용해서 실제 데이터를 불러와, 합성곱 신경망을 적용하는 코드를 가져왔습니다.
해당 코드는 제가 구현한 것이 아니며, 저는 단지 코드 분석만 수행합니다.
INDEX
- 전체코드
- 코드의 기본적인 틀
- 코드분석
- 학습데이터 로드
- 모델정의
- 상속
- 레이어층
- forward()
- 학습시작
- 알고리즘 순서
- 테스트
- 후기
전체코드 (skim)
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)))
코드의 기본적인 틀
딥러닝 모델을 코드는 다음과 같은 단계를 따르는게 일반적입니다.
- 학습할 데이터 로드
- 모델정의
- 학습 시작
- 테스트
- 테스트 결과 출력
위 코드도 이런 원칙을 아주 잘 반영하고 있습니다.
코드분석
\* 전체 코드를 알아보기 편리하게 변수명을 변경하여 분석을 진행하였습니다.
| 🚌 학습 데이터 로드 |
|---|
import torch
import torchvision
import transforms from torch
from google.colab import drive
우선 기본적으로 필요한 모듈을 불러옵니다.
custom_transform = transforms.Compose([
transforms.toTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
불러온 데이터를 전처리할 transform을 정의합니다.
train_data_set = torchvision.datasets.CIFAR10(
root = './data',
train = True,
download = True,
trainsform = custom_transform
)
모델에 적용시킬 데이터 셋을 불러옵니다. torchvision에서 제공하는 CIFAR10 데이터를 활용하겠습니다.
custom_data_loader = torch.utils.data.DataLoader(
dataset = custom_transform,
shuffle = True,
batch_size = 4,
num_workers = 2
)
모델의 데이터 로드를 설정합니다. 배치 사이즈 4, 셔플 적용 등
| 📦모델 정의 (모델 상속) |
|---|
학습 데이터는 불러왔으니 본격적으로 모델을 정의해야 합니다.
import torch.nn as nn
import torch.nn.functional as F
모델을 custom 할 수 있도록 nn을 불러옵니다.
class MyCNNModel(nn.Module):
def __init__(self):
super(MYCNNModel, self).__init__()
def forward(self, x)
모델을 Cutomizing 하려면 위 코드를 필수적으로 입력해야합니다.
- nn.Module을 상속받습니다.
- super().\_\_init\_\_()을 실행합니다.
- forward를 override 해야합니다. 이때 parameter는 입력 텐서입니다.
| 📦모델 정의 (레이어층 구성) |
|---|
CNN은 다층 퍼셉트론의 한 종류로, 여러 레이어를 통해 학습이 이루어집니다. 다음과 같은 레이어 층이 있습니다.
1. Convolution layer + Activation func
2. Pooling layer
: 이미지나 특징 맵의 높이 너비를 줄여 연산량을 감소시킴
: 과적합 방지 효과 - 노이즈 제거
3. Fully Connected layer(FC)
: 최종 예측을 출력하는 층
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입니다.
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를 사용합니다.
| ✏️ 학습시작 |
|---|
import torch.optim as optim
criterion = nn.CorseEntropyLoss()
optimizer = optim.Adam(customModel.parameters(), lr=0.001) # 학습률 0.001
이건 옵티마이저 적용하는 부분인데, 좀 깊게 들어가면 학습할 내용이 많아져서 생략하겠습니다. (암기하기)
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의 경우 실제 클래스 정보를 나타내는 라벨로 정답이 되겠습니다
classes = ('plane', 'car', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck')
전체 코드를 잘 보심면 classes를 정의하고 있는데 label의 클래스가 튜플로 정의되어 있는데, 해당 인덱스를 의미합니다.
| 🍃 알고리즘 흐름 |
|---|
1. 데이터 로드: transloader에서 배치 단위로 입력 이미지와 정답 라벨을 가져옴
2. 기울기 초기화: 이전 배치에서 계산된 기울기를 초기화함
3. 순전파 시행
4. 손실 계산
5. 역전파 시행
6. 파라미터 업데이트
7. 손실 기록
8. 다음 배치로 반복
| ✏️ 테스트 |
|---|
이제 모듈을 학습시켰으니, 해당 모듈이 얼마나 잘 예측하는지 알아보기 위해 테스트 데이터를 통해 평가를 해봐야 합니다. 이를 위해 테스트용 데이터를 로드하고, 모델에 입력 해보겠습니다.
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
)
테스트 데이터를 불러옵니다.
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(testloader)
images, labels = next(dataiter)
# 테스트용 이미지 출력
imshow(torchivision.utils.make_gird(imgaes))
테스트 이미지를 불러와서 출력해보면 얼마나 모듈이 예츢을 잘하는지 알 수 잇습니다.
후기
처음에는 너무 막연한 내용이고 어려웠는데, 하나씩 차근 차근 이해하다보닌깐 왜 이런걸 사용하는지 잘 알게된 것 같습니다. 또한 기존에 이론으로 배웠던 내용들이 거의 다 모듈로 구현되어있고, 거의 다 실제로 사용하는 것임을 알게되니 좀 더 와닿게 되는 것 같습니다.