본문 바로가기

코딩/Deep Learning

[파이썬/Pytorch] 딥러닝 - Softmax Regression(소프트맥스 회귀) 2편

반응형

  이번편은 소프트맥스 회귀에 대한 내용을 직접 코드로 구현해보았다. 코드는 위키독스의 'Pytorch로 시작하는 딥러닝'을 참고하였다.

(이전 포스팅 : 2021.08.04 - [파이썬/Pytorch] 딥러닝 - Softmax Regression 이해를 위한 정리 1편)

 

 

1. import

 

<코드>

import torch
import torch.nn as nn #nn. module 사용
import torch.nn.functional as F #softmax 함수 사용
import torch.optim as optim #옵티마이저 사용

torch.manual_seed(1) #랜덤 시드 고정

 

 

2. 훈련 데이터 샘플

 

 

<코드>

#각 샘플은 4개의 특성을 갖고 있고 총 8개 샘플임
x_train = [[3, 3, 2, 5],
           [2, 1, 3, 2],
           [3, 1, 3, 4],
           [2, 3, 5, 1],
           [1, 7, 5, 5],
           [9, 6, 3, 3],
           [4, 1, 2, 3],
           [1, 7, 3, 7]]

#y_train은 각 샘플에 대한 레이블(0,1,2 총 3개의 클래스 존재)
y_train = [2, 2, 2, 1, 1, 1, 0, 0]

 

 

  위 훈련 데이터를 도식화하면 아래와 같다. 입력값은 총 8개 샘플이며, 샘플 당 각 4개의 특성을 가지고 있다. 실제값의  Class는 총 3개로 각각 0, 1, 2의 값을 가지고 있다. 실제 소프트맥스 회귀 결과를 확인해보면 레이블이 총 3개인(0,1,2)의 확률값이 각각 표시되는데 Pytorch상에서 레이블 숫자 순서대로 표기해준다. 

 

 

 

 

 

3. Model 및 Optimizer

 

<코드>

#모델 설정(각 샘플의 input_dim = 4, output_Dim = 3)
model = nn.Linear(4,3)

# optimizer 설정
optimizer = optim.SGD(model.parameters(), lr=0.1)

 

-. 모델은 입력이 4, 출력이 3차원으로 정의(각 샘플 특성이 4개, 출력 레이블이 3개인 것을 기억하자)

-. 옵티마이저는 model.parameters( )를 가중치 w와 편향 b로 설정하고 학습률을 0.1로 설정

-. model.parameters( )를 리스트로 변환하여 출력해보면 아래와 같다.

 

 

<코드>

print('#1')
print(list(model.parameters()))

 

<결과>

#1
[Parameter containing:
tensor([[ 0.2576, -0.2207, -0.0969,  0.2347],
        [-0.4707,  0.2999, -0.1029,  0.2544],
        [ 0.0695, -0.0612,  0.1387,  0.0247]], requires_grad=True), Parameter containing:
tensor([ 0.1826, -0.1949, -0.0365], requires_grad=True)]

 

  텐서가 2개가 나오는데 각각 가중치 W와 편향 B를 의미한다. 학습을 하기 전이므로 저 값은 랜덤 시드 고정 값에 따라 발생한 값이다. 학습대상이므로 2개 텐서 모두 requires_grad는 True로 설정되어있다.

 

 

 

4. Epoch 설정 및 학습

 

 

<코드>

#학습횟수 Epoch = 1000
for epoch in range(1001):

    # 예측값 계산
    prediction = model(x_train)

    # cost 계산(cross_entorpy 함수는 원핫벡터와 소프트맥스 함수를 포함한다)
    cost = F.cross_entropy(prediction, y_train)

    # cost 함수 미분 및 가중치 w, b 업데이트
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    # 20번마다 로그 출력
    if epoch % 100 == 0:
        print('Epoch {:4d}/{} Cost: {:.6f}'.format(
            epoch, nb_epochs, cost.item()
        ))

 

 

<결과1 : 학습에 따른 cost 변화>

Epoch    0/1000 Cost: 1.601916
Epoch  100/1000 Cost: 0.342889
Epoch  200/1000 Cost: 0.290131
Epoch  300/1000 Cost: 0.259507
Epoch  400/1000 Cost: 0.237403
Epoch  500/1000 Cost: 0.219854
Epoch  600/1000 Cost: 0.205167
Epoch  700/1000 Cost: 0.192478
Epoch  800/1000 Cost: 0.181289
Epoch  900/1000 Cost: 0.171291
Epoch 1000/1000 Cost: 0.162271

 

  이전 포스팅에서도 설명했던 내용이지만 소프트맥스 회귀 과정상 실제값을 원핫 벡터 방식을 통해 인코딩하고 소프트맥스 함수 계산 과정을 거쳐 각각의 레이블에 대한 확률값을 계산하게 된다. 위 코드에는 해당 과정이 생략되어 있다. 이유는 cost(손실 비용 함수)를 구현한 cross_entropy( ) 함수가 이 과정들을 모두 포함하고 있기 때문이다.

 

  corss_entropy( )를 사용하지 않고 원-핫 벡터 인코딩과 소프트맥스 함수를 통해 계산하는 방식은 참고 링크에 잘 명시되어있어 참고하면 좋을 것 같다. 사용자 입장에서는 cross_entropy( ) 함수 1개로 많은 것을 해결할 수 있어 유용하다.

 

  결과는 보면 알겠지만 에포크가 반복될 수록 cost를 0으로 줄여나가는 방향으로 W와 B를 학습시키는 것을 확인할 수 있다. 가중치 W와 편향 B로 아래와 같이 출력시켜보았다. (epoch를 200마다 가중치 W와 B가 어떻게 변화하는지 출력)

 

 

<결과2 : 학습에 따른 W와 B의 변화>

가중치 W :  Parameter containing:
tensor([[ 0.1440, -0.2355, -0.1538,  0.2182],
        [-0.3652,  0.3329, -0.0504,  0.2137],
        [ 0.0776, -0.0794,  0.1430,  0.0818]], requires_grad=True)
편향 B :  Parameter containing:
tensor([ 0.1652, -0.1869, -0.0270], requires_grad=True)


가중치 W :  Parameter containing:
tensor([[ 0.1968,  0.0290, -0.7259,  0.5713],
        [ 0.1475,  1.1862,  0.3116, -1.3098],
        [-0.4880, -1.1973,  0.3531,  1.2522]], requires_grad=True)
편향 B :  Parameter containing:
tensor([ 0.2071, -0.4364,  0.1806], requires_grad=True)


가중치 W :  Parameter containing:
tensor([[ 0.3610,  0.1810, -0.7812,  0.4384],
        [ 0.3145,  1.5408,  0.2820, -1.6970],
        [-0.8191, -1.7038,  0.4381,  1.7723]], requires_grad=True)
편향 B :  Parameter containing:
tensor([ 0.2352, -0.5509,  0.2670], requires_grad=True)


가중치 W :  Parameter containing:
tensor([[ 0.4884,  0.3316, -0.8095,  0.2951],
        [ 0.4462,  1.7994,  0.2654, -1.9707],
        [-1.0782, -2.1131,  0.4831,  2.1893]], requires_grad=True)
편향 B :  Parameter containing:
tensor([ 0.2444, -0.6319,  0.3388], requires_grad=True)


가중치 W :  Parameter containing:
tensor([[ 0.5998,  0.4730, -0.8373,  0.1615],
        [ 0.5580,  2.0135,  0.2599, -2.1974],
        [-1.3014, -2.4686,  0.5163,  2.5496]], requires_grad=True)
편향 B :  Parameter containing:
tensor([ 0.2451, -0.6975,  0.4037], requires_grad=True)


가중치 W :  Parameter containing:
tensor([[ 0.7005,  0.6048, -0.8661,  0.0389],
        [ 0.6576,  2.1999,  0.2597, -2.3961],
        [-1.5017, -2.7867,  0.5453,  2.8710]], requires_grad=True)
편향 B :  Parameter containing:
tensor([ 0.2416, -0.7538,  0.4635], requires_grad=True)

 

 

 

5. 학습 된 모델에 테스트 데이터 입력해보기

 

 

  위에 훈련데이터였던 샘플 중 [2,1,3,2]를 입력하여 실제 학습된 모델에 따라 예측값이 어떻게 되는지 확인해보았다. [2,1,3,2]는 위 훈련 데이터에 따르면 결과값이 2로 분류되어야 한다. 코드는 아래와 같이 구현해보았다.

 

 

<코드>

test_data = torch.FloatTensor([[2,1,3,2]])
predic_result = model(test_data)
print("예측값")
print(predic_result)

 

<결과>

예측값
tensor([[-0.2732, -1.2517,  2.0511]], grad_fn=<AddmmBackward>)

 

 

 결과는 음수와 양수가 섞인 값이 나온다. 위 레이블이었던 class 순서대로 0,1,2에 대한 예측값이다. 소프트맥스 함수 결과에 따르면 각각의 레이블 확률값의 합계가 1이 되어야한다. 근데 결과가 합이 1이 아닐뿐더라 요소 중 -값도 나온다. 이유는 아래 cross_entrophy( ) 함수를 다시보자.

 

 # cost 계산(cross_entorpy 함수는 원핫벡터와 소프트맥스 함수를 포함한다)
    cost = F.cross_entropy(prediction, y_train)

 

  cost를 계산할 때는 cross_entropy( ) 함수를 사용하므로써 소프트맥스 함수가 적용되어 학습이 진행된다. 학습은 소프맥스 함수가 적용되어 정상적으로 진행되지만 실제 위에 테스트한 데이터는 model( ) 을 통해 입력하였다. 즉, model에는 소프트맥스 함수가 적용되지 않는 것이다. 따라서, 결과를 제대로 보려면 결과 data인 predic_result를 F.softmax( ) 함수에 입력시켜줘야 한다. 아래 코드를 보며 이해해보자.

 

 

<코드>

hy = F.softmax(predic_result, dim=1)
print("예측값(소프트맥스 함수 통과)")
print(hy)

 

<결과>

예측값(소프트맥스 함수 통과)
tensor([[0.0862, 0.0324, 0.8813]], grad_fn=<SoftmaxBackward>)

 

 

  모델 예측값을 소프트맥스 함수에 입력한 결과는 각 레이블의 확률값이 출력된다. 위 확률값에 따르면 2가 정답일 확률이 88%이다. 즉, 학습 된 모델이 [2,1,3,2]를 2로 예측했다. (0.0862 + 0.0324 + 0.08813 = 0.9999)

 

  여기서 좀더 궁금한 부분이 생겨 정리하였다. 확률값의 합은 모두 1이어야 하는데 실제 계산해보니 0.9999가 나온다. 이유는 F.softmax( ) 함수에 따른 것이다. 위 입력값에는 음수가 있다. softmax 함수는 음수 입력이 들어오면 사실상 거의 0과 가깝게 바꿔버린다고 한다.(위 예측값과 소프트맥스 함수 결과값을 대조해서 보자.)

 

  한가지 더 정리하자면, 소프트맥스 함수에 입력시키지 않아도 결과는 예측할 수 있다. 위 2개의 결과를 아래와 같이 비교해서 표시해보았다.

 

 

  0일 확률 1일 확률 2일 확률
예측값(model(test_data)) -0.2732 -1.2517 2.0511
예측값(F.softmax( )) 0.0862 0.0324 0.8813
확률 순위 2 3 1

 

 

  표를 보면 2일 확률이 그냥 예측값이나 소프트맥스 함수를 통한 예측값이 동일한 것을 확인할 수 있다.

 


참고링크 : 위키독스 - 'Pytorch로 시작하는 딥러닝 입문' : Softmax 회귀 편

https://wikidocs.net/60575

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

 

728x90