Python/머신러닝(ML)

Python(30)- 파이토치로 논리회귀 구현

두설날 2024. 6. 20. 14:47

*이 글을 읽기전에 작성자 개인의견이 있으니, 다른 블로그와 교차로 읽는것을 권장합니다.*

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

  • 분류를 할 때 사용하며, 선형 회귀 공식으로부터 나왔기 때문에 논리회귀라는 이름이 붙여짐

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

  • 예측값을 0에서 1사이의 값으로 되도록 만듦: yi = 0~1
  • 0에서 1사의 연속된 값을 출력으로 하기 때문에 보통 0.5(임계값)를 기준으로 구분
  • 분류 문제에서는 분류기가 필요한데 시그모이드함수가 그 역할

import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
torch.manual_seed(2024)

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)
print(y_train.shape)

plt.figure(figsize=(8,5))
plt.scatter(x_train, y_train)

nn. Sequential : 입력 데이터를 첫번째 계층에서 시작하여 순서대로 마지막 계층까지 출력 반환

# Linear로 1차방정식계산을 했는데, 여러개의 계산을 묶어서
model = nn.Sequential(
	# nn. Sequential : 입력 데이터를 첫번째 계층에서 시작하여 순서대로 마지막 계층까지 출력 반환
    nn.Linear(1,1),
    nn.Sigmoid()
)

print(model)

print(list(model.parameters())) # W: [0.0634], b: 0.6625


3. 비용함수

  • 논리회귀에서는 nn.BCELoss() 함수를 사용하여 Loss를 계산
  • Binary Cross Entropy
  • 2진 교차 엔트로피 함수

y_pred = model(x_train)
y_pred

loss = nn.BCELoss()(y_pred, y_train)
loss

optimizer = optim.SGD(model.parameters(), lr=0.01)
# SGD=경사하강법, lr=학습률(업데이트) 0.01로 설정
# 경사하강법으로 파라미터를 학습률 0.01로 설정

epochs = 1000
# 모델 학습 수 설정

for epoch in range(epochs + 1):
    y_pred = model(x_train)
    # BCELoss 계산
    loss = nn.BCELoss()(y_pred, y_train)
    # 경사(gradient) 초기화하여 매 학습마다 새로운 경사 계산
    optimizer.zero_grad()
    # 역전파 수행: gradient 가중치(w) 계산
    loss.backward()
    # 가중치(W) 계산 끝나고 호출
    optimizer.step()

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

print(list(model.parameters())) # W: 0.2019, b: -1.4572

x_test = torch.FloatTensor([[9]])
# 값이 9인 1x1 2차원 텐서를 생성
y_pred = model(x_test)
print(y_pred)

# 임계치 설정하기
# 0.5보다 크거나 같으면
# 0.5보다 작으면 0
y_bool = (y_pred >= 0.5).float()
print(y_bool)

비교연산자로 Boolean tensor로 변환 -> True = 1.0 , False = 0.0 인데, 결과값이 tensor([[1.]])이므로 True확인


4. 다항 논리회귀

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)
print(y_train.shape)

# 뭐든지 분류문제는 확률로 나오기 때문에, 결과가 0, ,1,2, 원래 없었던것
model = nn.Sequential(
    nn.Linear(4, 3)
)

print(model)

y_pred = model(x_train)
print(y_pred)

4-1. CrossEntropyLoss

  • 교차 엔트로피 손실 함수는 Pytorch에서 제공하는 손실 함수 중 하나로 다중 클래스 분류 문제에서 사용
  • log yi hat은 소프트맥스 함수부분
  • 소프트맥스 함수와 교차 엔트로 손실 함수를 결합한 상태
  • 소프트맥스 함수를 적용하여 각 클래스에 대한 확률분포를 얻음
  • 각 클래스에 대한 로그 확률을 계산
  • 실제 라벨과 예측 확률의 로그 값 간의 차이를 계산 : 
  • 계산된 사이의 평균을 개산하여 최종 손실값을 얻음

4-2. SoftMax

  • 다중 클래스 분류 문제에서 사용되는 함수로 주어진 입력 벡터의 값 확률 분포로 변환
  • 각 클래스에 속할 확률을 계산할 수 있으며, 각 요소를 0과 1 사이의 값으로 변환하여 0 값들의 합은 항상 1이 되도록 함.
  • exp(a) = e^a
  • yk = e^ak / sum(e^a1 + ... + e^an)

  • 각 입력 값에 대해 지수함수를 적용
  • 지수 함수를 적용한 모든 값
  • 정규화를 통해 각 값은 0과 1사이의 확률 값으로 출력 : yk = 0~1
loss = nn.CrossEntropyLoss()(y_pred, y_train)
print(loss)

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}')

x_test = torch.FloatTensor([[1,7,8,7]])
y_pred = model(x_test)
print(y_pred) # 실제 나온 값을 가지고 실제 소프트맥스 함수에 넣어줘야 함

# 예측값과 확률 구하기
y_prob = nn.Softmax(1)(y_pred)
y_prob

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

torch.argmax(y_prob, axis=1)


5. 경사 하강법의 종류

5-1. 배치 경사 하강법

  • 가장 기본적인 경사 하강법(Vanila Gradient Descent)
  • 데이터셋 전체를 고려하여 손실함수를 계산
  • 한번에 Epoch에 모든 파라미터 업데이트를 다 한번만 수행
  • batch의 개수와 Iteration은 1이고, Batch Size는 전체 데이터 개수
  • 파라미터 업데이트할 때 한 번의 전체 데이터셋을 고려하기 때문에 모델 학습 시 많은 시간과 메모리가 필요하다는 단점이 있음

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

  • 확률적 경사 하강법(Stochastic Gradient Descent)은 배치 경사 하강법이 모델학습 시 많은 시간과 메모리가 필요하다는 단점을 보완하기 위해 제안된 기법
  • Batch Size를 1로 설정하여 파라미터를 업데이트 하기 때문에 배치 경사 하강법보다 훨씬 빠르고 적은 메모리로 학습을 진행
    파라미터 값의 업데이트 폭이 불안정하기 때문에 정확도가 낮은 경우가 생길 수 있음

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

  • 미니 배치 경사 하강법(Mini Batch Gradient Descent)은 Batch Size를 설정한 size로 사용
  • 배치 경사 하강법보다 모델 속도가 빠르고, 확률적 경사 하강법보다 안정적인 장점이 있음
  • 딥러닝 분야에서 가장 많이 활용되는 경사 하강법
  • 일반적으로 Batch Size를 16, 32, 64, 128과 같이 2의 n제곱에 해당하는 값으로 사용하는게 관례적

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

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

  • 매개변수 값을 조정 시 전체 데이터가 아니라 랜덤으로 선택한 하나의 데이터에 대해서만 계산하는 방법

6-2. 모멘텀(Momentum)

  • 관성이라는 물리학의 법칙을 응용한 방법
  • 경사 하강법에 관성을 더해줌
  • 접선의 기울기에 한 시점 이전의 접선의 기울기값을 일정한 비율만큼 반영

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

  • 모든 매개변수에 동일한 학습률(learning rate)을 적용하는 것은 비효율적이라는 생각에서 만들어진 학습 방법
  • 처음에는 크게 학습하다가 조금씩 작게 학습시킴

6-4.아담(Adam)

  • 모멘텀 + 아다그라드
  • 적응형 학습률(Adaptive Learning Rate):
    • 매개변수의 업데이트 방향과 크기를 자동으로 조절합니다.
    • 이를 통해 효율적인 학습이 가능합니다.
  • 모멘텀 기반 업데이트:
    • 이전 gradient 정보를 활용하여 매개변수를 업데이트합니다.
    • 이를 통해 local minima에 빠지는 것을 방지할 수 있습니다.
  • 안정적인 수렴 성능:
    • 다른 optimizer에 비해 안정적으로 수렴하는 경향이 있습니다.
    • 다양한 문제 도메인에서 좋은 성능을 보입니다.

6-5. AdamW

  • Adam optimizer의 변형
  • Adam의 일부 약점(가중치 감소)과 성능 향상을 위해 고안

7. 와인 품종 예측해보기

  • sklearn.datasets.load_wine: 이탈리아의 같은 지역에서 재배된 세가지 다른 품종으로 만든 와인을 화학적으로 분석한 결과에 대한 데이터셋
  • 13개의 성분을 분석하여 어떤 와인인지 구별하는 모델을 구축
  • 데이터를 섞은 후 trian 데이터를 80%, test 데이터를 20%로 하여 사용
  • Adam을 사용
  • 테스트 데이터의 0번 인덱스가 어떤 와인인지 출력, 정확도를 출력
from sklearn.datasets import load_wine

x_data, y_data = load_wine(return_X_y=True, as_frame=True)
x_data

y_data

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

print(x_data.shape)
print(y_data.shape)

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)
print(x_test.shape, y_test.shape)

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}%')

y_pred = model(x_test)
y_pred[:5]

y_prob = nn.Softmax(1)(y_pred)
y_prob[:5]

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

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}% 입니다!')