본문 바로가기
AI

파이토치로 구현한 논리회귀

by 코낄2 2024. 1. 8.

 

x_train = torch.FloatTensor([[0], [1], [3], [5], [8], [11], [15], [20]])
y_train = torch.FloatTensor([[0], [0], [0], [0], [0], [1], [1], [1]])

print(x_train.shape) // torch.Size([8, 1])
print(y_train.shape) // torch.Size([8, 1])

1. 단항 논리회귀(Logistic Regression) 실습

단항 논리회귀(Logistic Regression)는 주로 이진 분류 문제에 사용되는 간단하면서도 효과적인 통계 기반의 머신러닝 알고리즘입니다. 선형 회귀 공식으로부터 나왔기 때문에 이름에 "회귀"가 포함되어 있지만, 사실은 분류 알고리즘이며, 특히 이진 분류에 적합합니다.

위 그림처럼 직선 하나(선형회귀)를 사용하여 분류를 예측한다면 제대로 예측하지 못할 경우가 많습니다. 따라서 Sigmoid 함수(Logistic 함수)를 사용하여 정확도를 높힐 수 있습니다.

2. 시그모이드( Sigmoid ) 함수

Sigmoid 함수는 입력값 에 대해 0과 1 사이의 값을 출력합니다. Sigmoid 함수는 이진 분류 모델에서 출력값을 해당 클래스에 속할 확률로 해석할 수 있습니다. 일반적으로는 0.5를 기준으로 0.5보다 크면 클래스 1로, 그렇지 않으면 클래스 0으로 분류됩니다. 그래프는 S 자 형태를 갖습니다. 가 음의 무한대로 갈수록 0에 가까워지고, 가 양의 무한대로 갈수록 1에 가까워집니다.

import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
torch.manual_seed(2024)
plt.figure(figsize = (8,5))
plt.scatter(x_train, y_train)

 

선형 조합의 결과를 시그모이드 함수에 입력으로 주어, 출력을 0과 1 사이의 값으로 제한합니다.

model = nn.Sequential(
    nn.Linear(1,1),
    nn.Sigmoid()
)

print(model)
Sequential(
  (0): Linear(in_features=1, out_features=1, bias=True)
  (1): Sigmoid()
)

✔️ Sequential 모듈 : 여러 개의 레이어를 순차적으로 연결하여 신경망을 만들 때 사용됩니다. nn.Sequential 내부에 레이어를 순서대로 나열하여 전달하면, 입력 데이터가 처음부터 끝까지 순차적으로 각 레이어를 통과하게 됩니다.

print(list(model.parameters()))

[Parameter containing:
tensor([[0.0634]], requires_grad=True), Parameter containing:
tensor([0.6625], requires_grad=True)]

3. 비용 함수

비용 함수(cost function)는 머신러닝 및 딥러닝 모델에서 모델의 예측값과 실제 값 사이의 차이를 측정하는 함수입니다. 논리 회귀(Logistic Regression)에서는 주로 크로스 엔트로피 손실(Cross-Entropy Loss) 함수를 사용합니다. 크로스 엔트로피 손실은 분류 문제에서 예측값과 실제값 간의 차이를 측정하는 데에 많이 활용됩니다. 논리 회귀는 주로 이진 분류 문제에 사용되고, 이진 분류는 이진 크로스 엔트로피 손실(Binary Cross-Entropy Loss)을 사용합니다. [ nn.BCELoss() ]

y_pred = model(x_train)
y_pred
tensor([[0.6598],
        [0.6739],
        [0.7012],
        [0.7270],
        [0.7631],
        [0.7958],
        [0.8340],
        [0.8734]], grad_fn=<SigmoidBackward0>)
        
loss = nn.BCELoss()(y_pred, y_train)
loss // tensor(0.8364, grad_fn=<BinaryCrossEntropyBackward0>)
optimizer = optim.SGD(model.parameters(), lr = 0.01)

epochs = 1000

for epoch in range(epochs + 1):
    y_pred = model(x_train)
    loss = nn.BCELoss()(y_pred, y_train)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if epoch % 100 == 0:
        print(f'Epoch: {epoch}/{epochs} Loss: {loss: .6f}')

Epoch: 0/1000 Loss:  0.233272
Epoch: 100/1000 Loss:  0.227195
Epoch: 200/1000 Loss:  0.221575
Epoch: 300/1000 Loss:  0.216361
Epoch: 400/1000 Loss:  0.211508
Epoch: 500/1000 Loss:  0.206979
Epoch: 600/1000 Loss:  0.202740
Epoch: 700/1000 Loss:  0.198763
Epoch: 800/1000 Loss:  0.195023
Epoch: 900/1000 Loss:  0.191498
Epoch: 1000/1000 Loss:  0.188169

print(list(model.parameters()))

Parameter containing:
tensor([[0.3627]], requires_grad=True), Parameter containing:
tensor([-3.1457], requires_grad=True)]
x_test = torch.FloatTensor([[12]])
y_pred = model(x_test)
print(y_pred)
tensor([[0.7698]], grad_fn=<SigmoidBackward0>)

# 임계치로 출력값 조정하기

# 0.5보다 크거나 같으면 1
# 0.5보다 작으면 0

y_bool = (y_pred >= 0.5).float()
print(y_bool)
// tensor([[1.]])

3. 다항 논리회귀 실습

x_train = [[1, 2, 1, 1],
           [2, 1, 3, 2],
           [3, 1, 3, 4],
           [4, 1, 5, 5],
           [1, 7, 5, 5],
           [1, 2, 5, 6],
           [1, 6, 6, 6],
           [1, 7, 7, 7]]
y_train = [0, 0, 0, 1, 1, 1, 2, 2]
x_train = torch.FloatTensor(x_train)
y_train = torch.LongTensor(y_train)

print(x_train.shape) // torch.Size([8, 4])
print(y_train.shape) // torch.Size([8])

# nn.Linear 레이어를 포함한 대부분의 레이어는 자동으로 입력 차원을 조정할 수 있음

Sequential(
  (0): Linear(in_features=4, out_features=3, bias=True)
)

print(model)
Sequential(
  (0): Linear(in_features=4, out_features=3, bias=True)
)
y_pred = model(x_train)
print(y_pred)
tensor([[ 1.5008, -0.6192, -0.3806],
        [ 2.1085, -0.4708, -0.6108],
        [ 2.4724, -0.2809, -0.5491],
        [ 3.2771, -0.4412, -0.8273],
        [ 3.0106, -2.4868,  0.2437],
        [ 1.9664, -0.8810,  0.7413],
        [ 2.9449, -2.2593,  0.5079],
        [ 3.2731, -2.6490,  0.6760]], grad_fn=<AddmmBackward0>)
# 다항 논리회귀에서는 BCELoss() 대신 CrossEntropyLoss()를 사용
# 소프트맥스 함수가 포함되어있음
# 클래스 개수만큼 분할해서 확률을 계산해주는 함수
# CrossEntropyLoss()에 들어있기 때문에 Sequential안에 따로 써주지 않은 것
loss = nn.CrossEntropyLoss()(y_pred, y_train)
print(loss) // tensor(2.2683, grad_fn=<NllLossBackward0>)

⭐ 소프트맥스 함수(softmax function)는 입력된 실수 값을 0과 1 사이의 값으로 정규화(normalize)하고, 각 값을 모두 합하면 1이 되도록 만드는 함수입니다. 주로 다중 클래스 분류 문제에서 마지막 레이어의 출력으로 사용되어 각 클래스에 속할 확률을 계산하는 데에 쓰입니다. CrossEntropyLoss()함수는 소프트맥스 함수와 크로스 엔트로피 손실 함수를 결합한 것으로 torch.nn.CrossEntropyLoss를 사용할 때는 소프트맥스 함수를 Sequential안에 따로 적용할 필요가 없습니다. 

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

epochs = 1000

for epoch in range(epochs + 1):
    y_pred = model(x_train)
    loss = nn.CrossEntropyLoss()(y_pred, y_train)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if epoch % 100 == 0:
        print(f'Epoch: {epoch}/{epochs} Loss: {loss: .6f}')

Epoch: 0/1000 Loss:  2.268312
Epoch: 100/1000 Loss:  0.627239
Epoch: 200/1000 Loss:  0.555661
Epoch: 300/1000 Loss:  0.504641
Epoch: 400/1000 Loss:  0.461729
Epoch: 500/1000 Loss:  0.423050
Epoch: 600/1000 Loss:  0.386655
Epoch: 700/1000 Loss:  0.351187
Epoch: 800/1000 Loss:  0.315551
Epoch: 900/1000 Loss:  0.279202
Epoch: 1000/1000 Loss:  0.246820

x_test = torch.FloatTensor([[1,7,8,7]])
y_pred = model(x_test)
print(y_pred)
// tensor([[-8.3104,  2.3911,  7.2251]], grad_fn=<AddmmBackward0>)
# 예측값과 확률 구하기
y_prob = nn.Softmax(1)(y_pred)
y_prob // tensor([[1.7765e-07, 7.8922e-03, 9.9211e-01]], grad_fn=<SoftmaxBackward0>)

print(f'0일 확률: {y_prob[0][0]:.2f}')
print(f'1일 확률: {y_prob[0][1]:.2f}')
print(f'2일 확률: {y_prob[0][2]:.2f}')

0일 확률: 0.00
1일 확률: 0.01
2일 확률: 0.99
torch.argmax(y_prob, axis=1) # max값 추출
// tensor([2])

4. 경사 하강법의 종류

4-1. 배치 경사 하강법

배치 경사 하강법은 가장 기본적인 경사 하강법으로, 전체 데이터셋을 한 번에 고려하여 손실 함수를 계산하고 모든 파라미터를 업데이트합니다. 한 번의 Epoch에 전체 데이터셋을 사용하며, Batch의 개수와 Iteration은 1이고, Batch Size는 전체 데이터의 갯수입니다. 이 방식은 파라미터 업데이트 시 모든 데이터를 고려하므로 정확한 결과를 얻을 수 있지만, 많은 시간과 메모리를 필요로 하는 단점이 있습니다.


4-2. 확률적 경사 하강법

확률적 경사 하강법(Stochastic Gradient Descent, SGD)은 배치 경사 하강법의 시간과 메모리 소모 단점을 개선한 기법입니다. Batch Size를 1로 설정하여 파라미터를 업데이트하며, 각 데이터 포인트마다 파라미터를 조금씩 업데이트하는 방식입니다. 이로 인해 빠르게 학습이 진행되지만, 파라미터 업데이트의 불안정성으로 인해 정확도가 낮아질 수 있습니다.


4-3. 미니 배치 경사 하강법

미니 배치 경사 하강법(Mini-Batch Gradient Descent)은 Batch Size를 설정한 크기로 사용하는 방법입니다. 일반적으로 Batch Size는 2의 n제곱에 해당하는 값으로 설정되며, 배치 경사 하강법과 확률적 경사 하강법의 장점을 조합한 형태입니다. 이 방식은 모델 학습 속도가 빠르고, 확률적 경사 하강법보다 안정적인 특징을 갖추어 딥러닝 분야에서 가장 많이 활용됩니다. 보통 Batch Size는 16, 32, 64, 128 등의 값을 사용하는 것이 일반적입니다.

5. 경사 하강법의 여러가지 알고리즘

5-1. SGD (확률적 경사 하강법)

확률적 경사 하강법(SGD)은 매개변수 값을 조정할 때 전체 데이터를 사용하는 대신, 랜덤으로 선택한 하나의 데이터에 대해서만 손실 함수를 계산하여 파라미터를 업데이트하는 방법입니다. 이는 계산 효율성을 향상시키며, 특히 대규모 데이터셋에서 효과적으로 사용됩니다.

 

5-2. 모멘텀 (Momentum)

모멘텀은 물리학의 관성 법칙을 응용한 경사 하강법 알고리즘입니다. 경사 하강법에 관성을 더하여, 현재의 기울기뿐만 아니라 이전 시점의 접선의 기울기값을 일정한 비율만큼 반영합니다. 이를 통해 수렴 속도를 향상시키고 지역 최솟값에서 빠르게 벗어날 수 있습니다.

 

5-3. 아다그라드 (Adagrad)

아다그라드는 모든 매개변수에 대해 동일한 학습률을 적용하는 것이 비효율적이라는 아이디어에서 출발한 알고리즘입니다. 초기에는 크게 학습하다가 나중에는 조금씩 작게 학습시키는 특징을 가지고 있어, 각 매개변수에 맞춤형 학습률을 적용하여 효과적으로 학습을 진행합니다.

 

5-4. 아담 (Adam)

아담은 모멘텀과 아다그라드를 결합한 알고리즘으로, 모멘텀의 관성 효과와 아다그라드의 매개변수별 학습률 조절을 동시에 적용합니다. 이를 통해 안정적이고 효율적인 학습이 가능하며, 다양한 모델에 적용되어 성능을 높이는 데 사용됩니다.

 

5-5. AdamW

AdamW는 Adam 옵티마이저의 변형으로, Adam의 일부 약점 중 하나인 가중치 감소(Weight Decay)와 성능 향상을 위해 고안되었습니다. 가중치 감소 문제를 개선하면서 Adam의 장점을 그대로 활용할 수 있도록 설계되어 있습니다.

6. 와인 품종 예측해보기

  • sklearn.datasets.load_wine: 이탈리아의 같은 지역에서 재배된 세가지 다른 품종으로 만든 와인을 화학적으로 분석한 결과에 대한 데이터셋
from sklearn.datasets import load_wine

# 13개의 성분을 분석하여 어떤 와인인지 구별하는 딥러닝 모델을 구축
# 단, 데이터를 섞은 후 train 데이터를 80%, test데이터를 20%로 하여 테스트
# 데이터의 0번 인덱스가 어떤 와인인지 출력하고 정확도도 출력
# (Adam을 사용, optim.Adam(model.parameters(),lr=0.01))
x_data, y_data = load_wine(return_X_y=True, as_frame=True)

y_data.head()
0    0
1    0
2    0
3    0
4    0
Name: target, dtype: int64

x_data.head()

x_data=torch.FloatTensor(x_data.values)
y_data = torch.LongTensor(y_data.values)

print(x_data.shape) // torch.Size([178, 13])
print(y_data.shape) // torch.Size([178])
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x_data, y_data, test_size= 0.2, random_state=2024)
print(x_train.shape, y_train.shape) // torch.Size([142, 13]) torch.Size([142])
print(x_test.shape, y_test.shape) // torch.Size([36, 13]) torch.Size([36])
model = nn.Sequential(
    nn.Linear(13, 3)
)
optimizer = optim.Adam(model.parameters(), lr=0.01)
epochs = 1000
for epoch in range(epochs + 1):
    y_pred = model(x_train)
    loss = nn.CrossEntropyLoss()(y_pred, y_train)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    if epoch % 100 == 0:
        y_prob = nn.Softmax(1)(y_pred)
        y_pred_index = torch.argmax(y_prob, axis=1)
        y_train_index = y_train
        accuracy = (y_train_index == y_pred_index).float().sum() / len(y_train) * 100
        print(f'Epoch {epoch:4d}/{epochs} Loss:{loss:.6f} Accuracy:{accuracy:.2f}%')

Epoch    0/1000 Loss:82.074516 Accuracy:25.35%
Epoch  100/1000 Loss:0.254785 Accuracy:90.85%
Epoch  200/1000 Loss:0.182033 Accuracy:93.66%
Epoch  300/1000 Loss:0.145413 Accuracy:95.77%
Epoch  400/1000 Loss:0.125811 Accuracy:95.77%
Epoch  500/1000 Loss:0.112391 Accuracy:97.18%
Epoch  600/1000 Loss:0.102293 Accuracy:97.18%
Epoch  700/1000 Loss:0.094259 Accuracy:98.59%
Epoch  800/1000 Loss:0.087596 Accuracy:98.59%
Epoch  900/1000 Loss:0.081888 Accuracy:98.59%
Epoch 1000/1000 Loss:0.076877 Accuracy:98.59%

y_pred = model(x_test)
y_pred[:5]
tensor([[ 73.8634,  71.9232,  79.8983],
        [128.6981, 121.4927, 120.1327],
        [ 54.9282,  59.4413,  60.2215],
        [133.2639, 127.8738, 128.2596],
        [ 91.3202,  89.3380,  85.9683]], grad_fn=<SliceBackward0>)
        
y_prob = nn.Softmax(1)(y_pred)
y_prob[:5]
tensor([[2.3872e-03, 3.4300e-04, 9.9727e-01],
        [9.9907e-01, 7.4187e-04, 1.9040e-04],
        [3.4340e-03, 3.1319e-01, 6.8337e-01],
        [9.8885e-01, 4.5108e-03, 6.6347e-03],
        [8.7526e-01, 1.2059e-01, 4.1482e-03]], grad_fn=<SliceBackward0>)

print(f'0번 품종일 확률: {y_prob[0][0]: .2f}') // 0번 품종일 확률:  0.00
print(f'1번 품종일 확률: {y_prob[0][1]: .2f}') // 1번 품종일 확률:  0.00
print(f'2번 품종일 확률: {y_prob[0][2]: .2f}') // 2번 품종일 확률:  1.00

y_pred_index = torch.argmax(y_prob, axis=1) # 예측결과
accuracy = (y_test == y_pred_index).float().sum() / len(y_test)*100
print(f'테스트 정확도는 {accuracy: .2f}% 입니다!')
// 테스트 정확도는  97.22% 입니다!

'AI' 카테고리의 다른 글

딥러닝  (0) 2024.01.09
데이터 로더  (0) 2024.01.09
기온에 따른 지면 온도 예측  (0) 2024.01.08
파이토치로 구현한 선형회귀  (1) 2024.01.08
파이토치  (0) 2024.01.07