[SSH] 서버에서 PyTorch로 모델 정의, 훈련, 평가하기(CNN 기반의 신경망 모델, 활성화 함수 및 드롭아웃)

김보현·2024년 7월 15일
0

Server

목록 보기
3/5

FashionMNIST 데이터셋을 사용하여 신경망 모델을 훈련시키고 평가했다.
활성화 함수와 정규화는 모델의 정의 부분에서 추가해서 모델의 학습 성능을 개선시킬 수 있다.

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torch
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))  # 평균 0.5, 표준편차 0.5로 정규화
])

train_dataset = datasets.FashionMNIST(root='./data', train=True, download=False, transform=transform)
test_dataset = datasets.FashionMNIST(root='./data', train=False, download=False, transform=transform)

train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, 3, 1)
        self.conv2 = nn.Conv2d(32, 64, 3, 1)
        self.fc1 = nn.Linear(12*12*64, 128)
        self.fc2 = nn.Linear(128, 10)
        self.dropout = nn.Dropout(0.25)
        
    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2)
        x = self.dropout(x)
        x = torch.flatten(x, 1)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.dropout(x)
        x = self.fc2(x)
        output = F.log_softmax(x, dim=1)
        return output

model = Net()

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

def train(model, device, train_loader, optimizer, epoch):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % 100 == 0:
            print(f'Train Epoch: {epoch} [{batch_idx*len(data)}/{len(train_loader.dataset)} ({100.*batch_idx/len(train_loader):.0f}%)]\tLoss: {loss.item():.6f}')

num_epochs = 10
for epoch in range(1, num_epochs + 1):
    train(model, device, train_loader, optimizer, epoch)

def test(model, device, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            test_loss += criterion(output, target).item() 
            pred = output.argmax(dim=1, keepdim=True) 
            correct += pred.eq(target.view_as(pred)).sum().item()
    
    test_loss /= len(test_loader.dataset)
    print(f'\nTest set: Average loss: {test_loss:.4f}, Accuracy: {correct}/{len(test_loader.dataset)} ({100.*correct/len(test_loader.dataset):.0f}%)\n')

test(model, device, test_loader)

훈련 과정에서는 train함수가 반복적으로 호출되며, 각 epoch 동안 훈련 데이터를 사용하여 모델을 학습시킨다.
batch에 대해 loss를 계산하고, backpropagation을 통해 모델의 가중치를 업데이트한다.

  • Train Epoch: X: 현재 epoch 번호입니다.
  • [N/M (P%)]: 현재 batch index N, 총 batch 수 M, 현재 진행 상황 P%입니다.
  • Loss: X.XXXXXX: 현재 배치의 손실 값입니다.
Train Epoch: 1 [0/60000 (0%)]   Loss: 2.299489
Train Epoch: 1 [6400/60000 (11%)]       Loss: 0.451712
Train Epoch: 1 [12800/60000 (21%)]      Loss: 0.256184
Train Epoch: 1 [19200/60000 (32%)]      Loss: 0.356389
Train Epoch: 1 [25600/60000 (43%)]      Loss: 0.277357
Train Epoch: 1 [32000/60000 (53%)]      Loss: 0.317639
Train Epoch: 1 [38400/60000 (64%)]      Loss: 0.442633
Train Epoch: 1 [44800/60000 (75%)]      Loss: 0.296921
Train Epoch: 1 [51200/60000 (85%)]      Loss: 0.490320
Train Epoch: 1 [57600/60000 (96%)]      Loss: 0.302867
Train Epoch: 2 [0/60000 (0%)]   Loss: 0.245094
Train Epoch: 2 [6400/60000 (11%)]       Loss: 0.259174
Train Epoch: 2 [12800/60000 (21%)]      Loss: 0.331606
Train Epoch: 2 [19200/60000 (32%)]      Loss: 0.326106
Train Epoch: 2 [25600/60000 (43%)]      Loss: 0.202231
Train Epoch: 2 [32000/60000 (53%)]      Loss: 0.273806
Train Epoch: 2 [38400/60000 (64%)]      Loss: 0.167239
Train Epoch: 2 [44800/60000 (75%)]      Loss: 0.205895
Train Epoch: 2 [51200/60000 (85%)]      Loss: 0.423255
Train Epoch: 2 [57600/60000 (96%)]      Loss: 0.235607
Train Epoch: 3 [0/60000 (0%)]   Loss: 0.173490
Train Epoch: 3 [6400/60000 (11%)]       Loss: 0.147664
Train Epoch: 3 [12800/60000 (21%)]      Loss: 0.293266
Train Epoch: 3 [19200/60000 (32%)]      Loss: 0.125321
Train Epoch: 3 [25600/60000 (43%)]      Loss: 0.167285
Train Epoch: 3 [32000/60000 (53%)]      Loss: 0.199895
Train Epoch: 3 [38400/60000 (64%)]      Loss: 0.319456
Train Epoch: 3 [44800/60000 (75%)]      Loss: 0.393176
Train Epoch: 3 [51200/60000 (85%)]      Loss: 0.130113
Train Epoch: 3 [57600/60000 (96%)]      Loss: 0.232381
Train Epoch: 4 [0/60000 (0%)]   Loss: 0.198897
Train Epoch: 4 [6400/60000 (11%)]       Loss: 0.303667
Train Epoch: 4 [12800/60000 (21%)]      Loss: 0.096893
Train Epoch: 4 [19200/60000 (32%)]      Loss: 0.093401
Train Epoch: 4 [25600/60000 (43%)]      Loss: 0.108086
Train Epoch: 4 [32000/60000 (53%)]      Loss: 0.255551
Train Epoch: 4 [38400/60000 (64%)]      Loss: 0.235716
Train Epoch: 4 [44800/60000 (75%)]      Loss: 0.143834
Train Epoch: 4 [51200/60000 (85%)]      Loss: 0.386011
Train Epoch: 4 [57600/60000 (96%)]      Loss: 0.049070
Train Epoch: 5 [0/60000 (0%)]   Loss: 0.170773
Train Epoch: 5 [6400/60000 (11%)]       Loss: 0.213930
Train Epoch: 5 [12800/60000 (21%)]      Loss: 0.160695
Train Epoch: 5 [19200/60000 (32%)]      Loss: 0.170513
Train Epoch: 5 [25600/60000 (43%)]      Loss: 0.060371
Train Epoch: 5 [32000/60000 (53%)]      Loss: 0.220566
Train Epoch: 5 [38400/60000 (64%)]      Loss: 0.131082
Train Epoch: 5 [44800/60000 (75%)]      Loss: 0.160562
Train Epoch: 5 [51200/60000 (85%)]      Loss: 0.185677
Train Epoch: 5 [57600/60000 (96%)]      Loss: 0.142417
Train Epoch: 6 [0/60000 (0%)]   Loss: 0.123884
Train Epoch: 6 [6400/60000 (11%)]       Loss: 0.079526
Train Epoch: 6 [12800/60000 (21%)]      Loss: 0.201342
Train Epoch: 6 [19200/60000 (32%)]      Loss: 0.064522
Train Epoch: 6 [25600/60000 (43%)]      Loss: 0.166178
Train Epoch: 6 [32000/60000 (53%)]      Loss: 0.120792
Train Epoch: 6 [38400/60000 (64%)]      Loss: 0.161126
Train Epoch: 6 [44800/60000 (75%)]      Loss: 0.107098
Train Epoch: 6 [51200/60000 (85%)]      Loss: 0.137737
Train Epoch: 6 [57600/60000 (96%)]      Loss: 0.109077
Train Epoch: 7 [0/60000 (0%)]   Loss: 0.072777
Train Epoch: 7 [6400/60000 (11%)]       Loss: 0.064303
Train Epoch: 7 [12800/60000 (21%)]      Loss: 0.142835
Train Epoch: 7 [19200/60000 (32%)]      Loss: 0.060505
Train Epoch: 7 [25600/60000 (43%)]      Loss: 0.175739
Train Epoch: 7 [32000/60000 (53%)]      Loss: 0.182543
Train Epoch: 7 [38400/60000 (64%)]      Loss: 0.096359
Train Epoch: 7 [44800/60000 (75%)]      Loss: 0.228986
Train Epoch: 7 [51200/60000 (85%)]      Loss: 0.090245
Train Epoch: 7 [57600/60000 (96%)]      Loss: 0.057390
Train Epoch: 8 [0/60000 (0%)]   Loss: 0.134924
Train Epoch: 8 [6400/60000 (11%)]       Loss: 0.237328
Train Epoch: 8 [12800/60000 (21%)]      Loss: 0.107030
Train Epoch: 8 [19200/60000 (32%)]      Loss: 0.102582
Train Epoch: 8 [25600/60000 (43%)]      Loss: 0.195219
Train Epoch: 8 [32000/60000 (53%)]      Loss: 0.076944
Train Epoch: 8 [38400/60000 (64%)]      Loss: 0.102075
Train Epoch: 8 [44800/60000 (75%)]      Loss: 0.065227
Train Epoch: 8 [51200/60000 (85%)]      Loss: 0.149001
Train Epoch: 8 [57600/60000 (96%)]      Loss: 0.095124
Train Epoch: 9 [0/60000 (0%)]   Loss: 0.107940
Train Epoch: 9 [6400/60000 (11%)]       Loss: 0.072973
Train Epoch: 9 [12800/60000 (21%)]      Loss: 0.155759
Train Epoch: 9 [19200/60000 (32%)]      Loss: 0.175524
Train Epoch: 9 [25600/60000 (43%)]      Loss: 0.151510
Train Epoch: 9 [32000/60000 (53%)]      Loss: 0.172344
Train Epoch: 9 [38400/60000 (64%)]      Loss: 0.200090
Train Epoch: 9 [44800/60000 (75%)]      Loss: 0.099178
Train Epoch: 9 [51200/60000 (85%)]      Loss: 0.074452
Train Epoch: 9 [57600/60000 (96%)]      Loss: 0.062576
Train Epoch: 10 [0/60000 (0%)]  Loss: 0.150953
Train Epoch: 10 [6400/60000 (11%)]      Loss: 0.054729
Train Epoch: 10 [12800/60000 (21%)]     Loss: 0.109157
Train Epoch: 10 [19200/60000 (32%)]     Loss: 0.060900
Train Epoch: 10 [25600/60000 (43%)]     Loss: 0.199112
Train Epoch: 10 [32000/60000 (53%)]     Loss: 0.121605
Train Epoch: 10 [38400/60000 (64%)]     Loss: 0.091149
Train Epoch: 10 [44800/60000 (75%)]     Loss: 0.146826
Train Epoch: 10 [51200/60000 (85%)]     Loss: 0.070407
Train Epoch: 10 [57600/60000 (96%)]     Loss: 0.176750

Test set: Average loss: 0.0038, Accuracy: 9263/10000 (93%)

모델 평가는 test 함수에서 수행되며, 훈련이 끝난 후 검증 데이터셋을 사용하여 모델의 성능을 평가한다. 평가 과정에서는 모델이 예측한 결과와 실제 레이블을 비교하여 손실과 정확도를 계산한다.

  • Average loss: 0.0038: 검증 데이터셋에 대한 평균 손실 값이다.
  • Accuracy: 9263/10000 (93%): 검증 데이터셋에서 정확히 예측한 샘플의 수와 전체 샘플 수, 그리고 정확도 비율이다.

모델 정의

Conv layer: 입력 이미지를 처리하여 특성 맵을 추출
ReLU 활성화 함수: 비선형성을 추가하여 모델이 더 복잡한 패턴을 학습할 수 있도록 한다.
Max Pooling: 특성 맵의 크기를 줄여서 계산량을 감소시키고 중요한 특성만 남긴다.
Dropout: 과적합을 방지하기 위해 일부 뉴런을 랜덤하게 끈다.
fc layer: 추출된 특성 맵을 클래스 점수로 변환한다.
softmax: 각 클래스의 확률을 계산하여 최종 출력을 생성한다.

  • class Net(nn.Module): nn.Module을 상속받아 새로운 신경망 모델 클래스를 정의한다.
  • def __init__(self): 클래스 초기화 메서드이다. 모델의 계층을 정의한다.
  1. super(Net, self).__init__(): 부모 클래스인 nn.Module의 초기화 메서드를 호출한다.
  2. self.conv1: 첫 번째 합성곱 층이다. 입력 채널 수 1, 출력 채널 수 32, 커널 크기 3x3, 스트라이드 1이다. nn.Conv2d(1, 32, 3, 1)
  3. self.conv2: 두 번째 합성곱 층이다. 입력 채널 수 32, 출력 채널 수 64, 커널 크기 3x3, 스트라이드 1이다. nn.Conv2d(32, 64, 3, 1)
  4. self.fc1: 첫 번째 fc 층이다. 입력 노드 수 121264, 출력 노드 수 128이다. (입력 크기는 Max Pooling에 의해 12x12로 줄어든다.)
  5. self.fc2: 두 번째 fc 층이다. 입력 노드 수는 128, 출력 노드 수는 10입니다.
  6. self.dropout: 드롭아웃 계층으로, 드롭아웃 확률은 0.25입니다.

def forward(self, x): 입력 데이터를 네트워크를 통해 전달하여 최종 출력을 계산한다.
x = self.conv1(x): 입력 데이터 x를 첫 번째 합성곱 층에 통과시킨다.
x = F.relu(x): conv 층의 출력에 ReLU 활성화 함수를 통해 비선형성을 추가한다.
x = self.conv2(x): 두 번째 합성곱 층에 데이터를 통과시킨다.
x = F.relu(x): 두 번째 conv 층의 출력에 ReLU 활성화 함수를 통해 비선형성을 추가한다.
x = F.max_pool2d(x, 2): Max Pooling을 통해 feature map의 크기를 절반으로 줄인다. (2x2 윈도우, stride 2)
x = self.dropout(x): dropout을 적용하여 일부 뉴런을 랜덤하게 끄고 과적합을 방지한다.
x = torch.flatten(x, 1): feature map을 1차원 벡터로 펼친다. 배치 차원은 유지한다.
x = self.fc1(x): 첫 번째 fc 층에 데이터를 통과시킨다.
x = F.relu(x): fc 층의 출력에 ReLU 활성화 함수를 통해 비선형성을 추가한다.
x = self.dropout(x): 다시 drop out을 적용한다.
x = self.fc2(x): 두 번째 fc 층에 데이터를 통과시킨다.
output = F.log_softmax(x, dim=1): 마지막 출력에 softmax 함수를 적용하여 클래스별 확률을 계산한다.
return output: 최종 출력을 반환한다.

모델 훈련

손실 함수 및 옵티마이저 설정: 손실 함수와 옵티마이저를 정의한다.
디바이스 설정: 모델을 실행할 디바이스를 설정하고, 모델을 해당 디바이스로 이동시킨다.
훈련 함수 정의: 훈련 과정을 정의한다. 각 배치마다 손실을 계산하고 역전파를 통해 파라미터를 업데이트한다.
모델 훈련: 정의한 훈련 함수를 에포크 수만큼 반복하여 모델을 훈련시킨다.

손실 함수 및 옵티마이저 설정

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

criterion: 손실 함수이다. nn.CrossEntropyLoss를 사용하여 다중 클래스 분류 문제의 손실을 계산한다. CrossEntropyLoss는 소프트맥스 함수와 음의 로그 가능도를 결합한 것이다.
optimizer: Adam을 사용하여 파라미터를 업데이트한다. 학습률은 0.001로 설정한다.

디바이스 설정

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

device: CUDA가 사용 가능하면 GPU를 사용하고, 그렇지 않으면 CPU를 사용한다.
model.to(device): 모델을 설정한 디바이스로 이동시킨다.

훈련 함수 정의

def train(model, device, train_loader, optimizer, epoch):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % 100 == 0:
            print(f'Train Epoch: {epoch} [{batch_idx*len(data)}/{len(train_loader.dataset)} ({100.*batch_idx/len(train_loader):.0f}%)]\tLoss: {loss.item():.6f}')

model.train(): 모델을 훈련 모드로 설정한다. (드롭아웃과 배치 정규화)
enumerate(train_loader): train_loader에서 배치를 하나씩 가져온다. batch_idx는 배치 인덱스, data는 입력 데이터, target은 레이블이다.
data.to(device), target.to(device): 데이터를 설정한 디바이스로 이동시킨다.
optimizer.zero_grad(): 옵티마이저의 모든 변화도를 0으로 초기화한다. 이전 배치의 변화도가 다음 배치에 누적되지 않게 한다.
output = model(data): 모델에 데이터를 입력하여 출력을 계산한다.
loss = criterion(output, target): 출력과 실제 레이블을 비교하여 손실을 계산한다.
loss.backward(): 역전파를 통해 손실에 대한 그래디언트를 계산한다.
optimizer.step(): 옵티마이저를 통해 모델의 파라미터를 업데이트한다.
if batch_idx % 100 == 0: 매 100번째 배치마다 현재 epoch, 진행 상황, 손실 값을 출력합니다.

모델 훈련

num_epochs = 10
for epoch in range(1, num_epochs + 1):
    train(model, device, train_loader, optimizer, epoch)

num_epochs: 전체 훈련 에포크 수를 설정한다.
for epoch in range(1, num_epochs + 1): 각 에포크마다 훈련 함수를 호출한다. 각 에포크는 전체 훈련 데이터를 한 번씩 처리한다.

모델 평가

평가 함수 정의

def test(model, device, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            test_loss += criterion(output, target).item()
            pred = output.argmax(dim=1, keepdim=True)
            correct += pred.eq(target.view_as(pred)).sum().item()
    
    test_loss /= len(test_loader.dataset)
    print(f'\nTest set: Average loss: {test_loss:.4f}, Accuracy: {correct}/{len(test_loader.dataset)} ({100.*correct/len(test_loader.dataset):.0f}%)\n')

model.eval(): 모델을 평가 모드로 설정합니다. 이는 드롭아웃이나 배치 정규화 계층이 평가 모드로 작동하도록 합니다.
torch.no_grad(): 평가 시에는 그래디언트를 계산할 필요가 없으므로, 이 컨텍스트 내에서는 그래디언트 계산을 비활성화한다.
enumerate(test_loader): test_loader에서 배치 데이터를 하나씩 가져온다.
data.to(device), target.to(device): 데이터를 설정한 디바이스로 이동시킨다.
output = model(data): 모델에 데이터를 입력하여 예측 출력을 계산한다.
test_loss += criterion(output, target).item(): 모델의 예측 출력과 실제 레이블을 비교하여 손실 값을 누적한다.
pred = output.argmax(dim=1, keepdim=True): 예측 출력에서 가장 높은 확률을 가지는 클래스를 선택한다.
correct += pred.eq(target.view_as(pred)).sum().item(): 예측이 맞은 경우를 누적한다.
test_loss /= len(test_loader.dataset): 전체 데이터셋에 대한 평균 손실 값을 계산한다.

모델 평가

test(model, device, test_loader)

훈련이 완료된 모델을 사용하여 테스트 데이터셋에 대한 평가를 수행한다.
평가 결과를 바탕으로 모델의 성능(평균 손실 값과 정확도)을 출력한다.

profile
Fall in love with Computer Vision

0개의 댓글