Skip to main content
Summary

학습이 끝났다고 바로 믿지 않기

모델을 학습 데이터에 잘 맞추는 것만으로는 충분하지 않습니다. 중요한 것은 학습에 쓰지 않은 데이터에서도 잘 동작하는지 확인하는 일입니다. 학습 데이터에서만 성능이 좋고 새로운 데이터에서 틀린다면, 모델은 문제를 이해한 것이 아니라 학습 데이터를 외운 것에 가깝습니다.

그래서 보통 데이터는 학습용과 검증 또는 테스트용으로 나눕니다. 학습 루프에서는 모델의 파라미터를 바꾸고, 평가 루프에서는 파라미터를 바꾸지 않은 채 성능만 확인합니다.

평가 루프와 추론 코드 작성하기

평가 모드로 바꾸기

평가할 때는 먼저 모델을 평가 모드로 전환합니다.

model.eval()

model.eval()은 모델 안의 일부 계층이 평가 상황에 맞게 동작하도록 바꿉니다. 예를 들어 드롭아웃은 학습 중 일부 뉴런을 임의로 끄지만, 평가할 때는 그렇게 하면 안 됩니다. 배치 정규화도 학습 때와 평가 때 사용하는 통계가 다릅니다.

또 하나 중요한 것은 기울기 계산을 끄는 일입니다.

with torch.no_grad():
    pred = model(x)

평가에서는 loss.backward()를 호출하지 않습니다. 즉, 파라미터를 업데이트하지 않습니다. 그런데도 기울기를 계속 계산하면 메모리와 시간이 낭비됩니다. torch.no_grad()는 평가와 추론에서 불필요한 계산을 줄여 줍니다.

테스트 손실과 정확도 계산하기

분류 모델의 출력은 보통 각 클래스에 대한 점수입니다. 가장 높은 점수를 가진 클래스가 모델의 예측입니다.

pred = model(X)
predicted = pred.argmax(1)

argmax(1)은 각 샘플마다 가장 큰 값의 위치를 찾습니다. 예를 들어 클래스가 10개라면 출력 모양은 보통 [batch_size, 10]입니다. 여기서 차원 1 방향으로 가장 큰 값을 찾으면 예측 클래스 번호가 됩니다.

테스트 루프는 다음처럼 작성할 수 있습니다.

def test_loop(dataloader, model, loss_fn, device):
    model.eval()
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    test_loss = 0
    correct = 0

    with torch.no_grad():
        for X, y in dataloader:
            X = X.to(device)
            y = y.to(device)

            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()

    test_loss /= num_batches
    accuracy = correct / size

    print(f"Test Error: Accuracy: {(100 * accuracy):.1f}%, Avg loss: {test_loss:.4f}")

여기서 (pred.argmax(1) == y)는 예측 라벨과 정답 라벨이 같은지 비교합니다. 결과는 True, False로 나오고, 이를 숫자로 바꾼 뒤 합하면 맞힌 개수를 구할 수 있습니다.

학습 루프와 평가 루프 분리하기

좋은 습관은 학습 함수와 평가 함수를 분리하는 것입니다. 학습은 model.train()으로 시작하고, 평가는 model.eval()로 시작합니다.

for epoch in range(epochs):
    print(f"Epoch {epoch + 1}n-------------------------------")
    train_loop(train_dataloader, model, loss_fn, optimizer, device)
    test_loop(test_dataloader, model, loss_fn, device)

이 구조를 사용하면 매 epoch마다 모델이 얼마나 좋아지는지 확인할 수 있습니다. 학습 손실은 줄어드는데 테스트 정확도가 오르지 않는다면 과적합을 의심할 수 있습니다. 반대로 학습 손실도 잘 줄지 않는다면 모델이 너무 작거나, 학습률이 맞지 않거나, 데이터 전처리에 문제가 있을 수 있습니다.

샘플 하나로 예측해 보기

전체 테스트셋 성능도 중요하지만, 실제로 샘플 하나를 넣었을 때 어떤 클래스로 예측하는지도 확인해야 합니다.

classes = ["T-shirt/top", "Trouser", "Pullover", "Dress", "Coat",
           "Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"]

model.eval()

x, y = test_data[0]
x = x.unsqueeze(0).to(device)

with torch.no_grad():
    pred = model(x)
    predicted_class = pred.argmax(1).item()

print(f"Predicted: {classes[predicted_class]}")
print(f"Actual: {classes[y]}")

test_data[0]에서 꺼낸 x는 보통 이미지 한 장입니다. 하지만 모델은 배치 단위 입력을 기대합니다. 그래서 unsqueeze(0)으로 맨 앞에 배치 차원을 추가합니다. 예를 들어 [1, 28, 28] 모양의 이미지는 [1, 1, 28, 28]처럼 바뀝니다.

성능이 낮을 때 먼저 볼 것

평가 결과가 낮다고 바로 복잡한 모델을 만들 필요는 없습니다. 먼저 기본을 확인합니다.

  1. 데이터와 라벨이 제대로 연결되어 있는가?

  2. 입력 모양과 dtype이 모델에 맞는가?

  3. 학습률이 너무 크거나 너무 작지 않은가?

  4. epoch 수가 너무 적거나 너무 많지 않은가?

  5. 모델 크기가 문제에 비해 지나치게 작지 않은가?

평가 루프는 모델의 현재 상태를 보여 주는 계기판입니다. 학습 루프가 모델을 바꾸는 코드라면, 평가 루프는 그 변화가 실제로 도움이 되는지 확인하는 코드입니다.

책 목록

초보자를 위한 PyTorch 사용법: 텐서부터 이미지 분류 프로젝트까지

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기