본문 바로가기

개발 공부/딥러닝

[Deep Learning] PyTorch FashionMNIST 다중분류 | CrossEntropyLoss, Softmax와 argmax

앞에서는 손글씨 숫자 데이터를 이용해

5인가 / 5가 아닌가

를 판단하는 Binary Classification을 진행했다.

이번 ex13에서는 FashionMNIST 데이터를 이용해 10개의 의류 클래스를 구분하는 Multi-class Classification으로 확장했다.

DataLoader와 Training Loop의 기본 구조는 그대로 사용하면서,

입력 이미지 크기, 모델 출력 개수, Loss Function, 최종 예측값을 만드는 방법

이 이진분류와 어떻게 달라지는지를 확인했다.

⠀

1. FashionMNIST 데이터 불러오기

이번에는 torchvision의 FashionMNIST Dataset을 사용했다.

데이터 개수는

Train → 60,000
Test  → 10,000

이었다.

FashionMNIST는 10개의 의류 카테고리를 구분하는 데이터이기 때문에 이번 문제는 이진분류가 아니라 다중분류다.

⠀

2. 이미지 Shape도 달라졌다

데이터 하나를 꺼내 확인하면

Shape은

(1, 28, 28)

형태다.

각 차원의 의미는 다음과 같다.

1  → Channel
28 → Height
28 → Width

즉 하나의 이미지는 28×28 크기의 흑백 이미지다.

앞에서 사용했던 load_digits() 데이터는 8×8 크기였기 때문에

8 × 8 = 64

개의 Feature를 사용했다.

하지만 FashionMNIST는

 

28 × 28 = 784

개의 값을 가지게 된다.

 

⠀

3. DataLoader 사용 방법은 그대로다

Dataset이 바뀌더라도 DataLoader의 사용 방법 자체는 동일하다.

Train 데이터는 60,000개이므로

60,000 / 100
= 600 Batch

Test 데이터는

10,000 / 100
= 100 Batch

가 된다.

한 Batch의 Shape은

features → (100, 1, 28, 28)
targets  → (100,)

이었다.

Dataset이 달라져도

Dataset
↓
DataLoader
↓
Batch
↓
Training Loop

라는 구조 자체는 그대로 유지된다.

⠀

4. MultiModel 구조

이번 실습에서는 새로운 모델 클래스를 처음부터 다시 만드는 것이 아니라,
기존에 사용했던 모델 구조를 확장해
MultiModel을 만들었다.

실제 클래스는 다음과 같이 작성했다.

여기서 처음 보면 조금 헷갈릴 수 있는 부분이 있다.

클래스의 기본값은 여전히

feature_size=1*8*8
target_size=1

로 되어 있다.

하지만 이번 FashionMNIST 데이터는 8×8 이미지가 아니라 28×28 이미지이고, 출력 클래스도 1개가 아니라 10개다.

따라서 모델 객체를 만들 때 필요한 값을 직접 전달한다.

즉 클래스의 기본 구조 자체를 바꾼 것이 아니라,

기본값
feature_size = 1 × 8 × 8
target_size = 1

↓

FashionMNIST 모델 생성 시

feature_size = 1 × 28 × 28
target_size = 10

으로 필요한 값을 덮어쓴 것이다.

⠀
📌
내가 헷갈리기 쉬운 부분

__init__() 안에 작성된 값은 어디까지나 Default 값이다.

feature_size=1*8*8
target_size=1

이라고 적혀 있다고 해서 이 모델이 항상 8×8 입력과 1개의 출력만 사용할 수 있는 것은 아니다.

모델을 생성할 때

MultiModel(
    feature_size=1*28*28,
    target_size=10
)

처럼 새로운 값을 전달하면 해당 값으로 모델이 생성된다.

따라서 같은 MultiModel 구조를 유지하면서도 입력 데이터와 분류 문제에 따라 feature_size와 target_size를 바꿔 사용할 수 있다.

⠀

5. FashionMNIST에서는 왜 feature_size=1×28×28일까?

FashionMNIST 한 장의 Shape은

(1, 28, 28)

이다.

모델 내부에서는 가장 먼저

self.flatten = nn.Flatten()

을 사용한다.

따라서

(batch, 1, 28, 28)

↓

nn.Flatten()

↓

(batch, 784)

형태로 바뀐다.

즉 첫 번째 Linear Layer가 받아야 하는 입력 Feature 수는

1 × 28 × 28
= 784

다.

그래서 모델을 생성할 때

feature_size=1*28*28

을 전달했다.

⠀

6. target_size는 왜 10일까?

이진분류에서는 최종적으로 Logit 하나를 출력했다.

(batch, 1)

하지만 FashionMNIST는 총 10개의 클래스를 구분해야 한다.

따라서 하나의 이미지에 대해

Class 0 Logit
Class 1 Logit
Class 2 Logit
...
Class 9 Logit

총 10개의 값을 출력해야 한다.

그래서

target_size=10

으로 설정한다.

마지막 Linear Layer는

nn.Linear(
    in_features=hidden_size,
    out_features=target_size
)

이므로 최종 출력 Shape은

(batch, 10)

이 된다.

⠀

7. 모델의 전체 Shape 변화

이번 모델의 흐름을 Shape 기준으로 보면 다음과 같다.

FashionMNIST Batch
(batch, 1, 28, 28)

↓

nn.Flatten()

(batch, 784)

↓

Linear
784 → 512

↓

ReLU

↓

Linear
512 → 1024

↓

ReLU

↓

Linear
1024 → 512

↓

ReLU

↓

Final Linear
512 → 10

↓

Logit
(batch, 10)

즉 이미지 하나마다 10개의 Logit을 출력하게 된다.

⠀

8. 이진분류와 Loss Function이 달라진다

앞에서 진행한 Binary Classification에서는

nn.BCEWithLogitsLoss()

를 사용했다.

하지만 이번에는 10개의 클래스 중 하나를 고르는 다중분류 문제이기 때문에

를 사용한다.

정리하면

문제Loss

이진분류 BCEWithLogitsLoss
다중분류 CrossEntropyLoss

CrossEntropyLoss()를 사용할 때도 모델 마지막에 Softmax를 붙이지 않고 Logit을 그대로 Loss에 전달한다.

⠀

9. Optimizer는 Adam 사용

이번 실습에서는 Optimizer로 Adam을 사용했다.

그리고 모델은 GPU에서 학습하도록 Device에 올렸다.

⠀

10. 다중분류에서는 round가 아니라 argmax

이진분류에서는

Logit
↓
Sigmoid
↓
round
↓
0 / 1

을 사용했다.

하지만 다중분류에서는 클래스가 10개이기 때문에 round()로 처리할 수 없다.

먼저 Softmax를 이용해 각 클래스의 확률로 바꾼다.

출력 Shape은 그대로

(batch, 10)

이다.

그리고 10개 클래스 중 확률이 가장 큰 위치를 선택한다.

te_round = torch.argmax(
    te_proba,
    dim=1
)

예를 들어

[0.01, 0.02, 0.03, 0.80, ...]

라면 가장 큰 값이 있는 Class Index를 최종 예측값으로 사용한다.

⠀

11. 왜 Softmax의 dim=1일까?

이번 Logit Shape은

(batch, class)

즉

(100, 10)

형태다.

0번 차원은 Batch이고,

1번 차원이 10개의 Class다.

따라서 클래스 방향으로 확률을 계산하기 위해

Softmax(dim=1)

을 사용한다.

마찬가지로 가장 확률이 높은 클래스를 찾을 때도

argmax(dim=1)

을 사용한다.

⠀
📌
내가 헷갈리기 쉬운 부분

dim=1을 무조건 외우는 것보다 현재 Tensor Shape에서

어느 차원이 Class인지

를 먼저 확인하는 것이 중요하다.

⠀

12. 이진분류와 다중분류 비교

구분이진분류다중분류

클래스 수 2 3개 이상
마지막 출력 1 클래스 개수
Loss BCEWithLogitsLoss CrossEntropyLoss
확률 변환 Sigmoid Softmax
최종 Class round argmax
출력 Shape (batch, 1) (batch, class)

즉 전체 Training Loop는 크게 달라지지 않는다.

달라지는 것은 문제 유형에 따라 출력과 Loss를 해석하는 방식이다.

⠀

13. Best Model로 최종 평가

ex13에서는 앞에서 만든 EarlyStopper를 그대로 적용했다.

학습이 끝난 후 마지막 모델을 사용하지 않고 저장해둔 Best Model을 다시 불러왔다.

그 다음 Best Model을 이용해 Test 데이터를 예측했다.

실습 결과에서는 최종 Accuracy가 약 85.51%로 출력됐다.

⠀

14. FashionMNIST도 Confusion Matrix로 평가

다중분류에서도 실제값과 예측값을 모은 뒤 Confusion Matrix를 만들었다.

그리고 Heatmap의 x축과 y축에 실제 FashionMNIST 클래스명을 넣었다.

이렇게 하면 단순히

Class 0
Class 1
Class 2

라고 보는 것이 아니라 실제

T-shirt/top
Trouser
Pullover
...

와 같은 클래스 이름을 기준으로 어떤 옷을 어떤 옷으로 많이 잘못 분류했는지 확인할 수 있다.

⠀

15. 핵심 정리

개념기억할 내용

FashionMNIST 28×28 흑백 의류 이미지
Train Dataset 60,000개
Test Dataset 10,000개
Batch Shape (batch, 1, 28, 28)
Flatten (batch, 784)로 변경
target_size 클래스 수인 10
Logit Shape (batch, 10)
CrossEntropyLoss 다중분류 Loss
Softmax 클래스별 확률로 변환
argmax 가장 확률이 높은 Class 선택
Early Stopping 개선되지 않으면 학습 종료
Best Model 가장 낮은 Test Loss를 기록했을 때 저장한 모델
Confusion Matrix 클래스별 오분류 패턴 확인

이번 실습에서 가장 중요한 흐름은

Binary Classification
↓
Multi-class Classification

으로 문제는 더 복잡해졌지만,

PyTorch의 기본 학습 구조 자체는 거의 바뀌지 않았다는 점이다.

결국 어떤 문제를 풀고 있는지에 따라

Output Size, Loss Function, Probability 변환 방법, 최종 Class 선택 방법

을 맞춰주는 것이 중요하다.

반응형