개발/AI

[Pytorch] 04. 로지스틱 회귀

yun000 2024. 7. 30. 20:42

파이토치 위키독스 공부하기

 

04-01 Logistic Regression

이진분류(Binary Classification)=둘 중 하나를 결정하는 문제.(ex 시험이 합격인지 불합격인지. 메일이 스팸인지 아닌지)

로지스틱 회귀(Logistic Regression)=이진분류를 풀기위한 알고리즘

 

1.이진 분류

시험 성적과 합격 불합격 여부가 기록된 데이터가 있다.

합격 커트라인은 공개되지 않았는데

이 데이터로 특정 점수를 입력했을 때 합,불합 여부를 판정하는 모델을 만들어보자

 

해당 그래프는 직선이 아닌 S자 형태라 Wx+b(직선함수)로 표현할 수 없다. 

따라서 로지스틱 회귀 가설은 H(x)=f(Wx+b)의 가설을 사용한다.

그리고 함수 f는 주로 시그모이드 함수를 사용한다.

 

2. 시그모이드 함수

선형회귀와 마찬가지로 이 가설에서도 W,b를 찾는것이 목표이다.

그러면 이 함수에서는 W,b가 무슨역할을 할까?

 

w=1,b=0인 그래프를 그려보자

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1/(1+np.exp(-x))

x = np.arange(-5.0, 5.0, 0.1)#x값 범위
y = sigmoid(x)#시그모이드 함수

plt.plot(x, y, 'g')#그래프를 그린다
plt.plot([0,0],[1.0,0.0], ':') # 가운데 점선 추가
plt.show()

함수 출력값이 0과 1사이다. 

x가 0이면 y는 0.5이며 x가 아주 작아지면 0에 수렴하고 x가 아주 커지면 1에 수렴한다

 

 

 

w값이 0.5, 1, 2일때의 그래프를 세개 다 그려보자

x = np.arange(-5.0, 5.0, 0.1)
y1 = sigmoid(0.5*x)#w=0.5
y2 = sigmoid(x)#w=1
y3 = sigmoid(2*x)#w=2

plt.plot(x, y1, 'r', linestyle='--') # W=0.5
plt.plot(x, y2, 'g') # W=1
plt.plot(x, y3, 'b', linestyle='--') # W=2
plt.plot([0,0],[1.0,0.0], ':')#가운데 점선 추가
plt.show()

W=0.5 --> 빨간색 선

W=1 --> 초록색 선

W=2 --> 파란색 선

W는 그래프의 경사도를 정하는 것을 알 수 있다.

 

 

 

b값 변화에 따른 그래프 변화를 살펴보자

x = np.arange(-5.0, 5.0, 0.1)
y1 = sigmoid(x+0.5) #b=0.5
y2 = sigmoid(x+1) #b=1
y3 = sigmoid(x+1.5) #b=1.5

plt.plot(x, y1, 'r', linestyle='--') #b=0.5
plt.plot(x, y2, 'g') #b=1
plt.plot(x, y3, 'b', linestyle='--') #b=1.5
plt.plot([0,0],[1.0,0.0], ':') # 가운데 점선 추가
plt.show()

b=0.5 -->빨간선

b=1 -->초록선

b=1.5 --> 파란선

b값에 따라 그래프가 좌 우로 움직인다.

 

-결론

시그모이드 함수는 입력값이 아주 커지면 1에 수렴하고, 입력값이 아주 작아지면 0에 수렴한다.

즉 출력값이 0과 1사이 값을 가진다. 이 특성을 이용하여 분류 작업에 사용 가능하다.

예를들어 임계값이 0.5라면 출력값이 0.5이상이면 True, 이하라면 False로 구분이 가능하다.

 

 

3. 비용 함수 cost function

로지스틱 회귀의 비용함수를 정의해보자

선형회귀에 사용한 평균 제곱 오차식을 적용하고 미분하면 다음과 같은 비볼록 그래프가 나온다

이 그래프에 gradient descent를 사용하면

로컬 미니멈을 오차가 최소가 되는 구간이라고 여길 수도 있다.

글로벌 미니멈(실제 오차가 최소값이 되는 구간)을 찾지 못할 수 있다는 것이다.

 

시그모이드 함수를 생각해보자

실제값이 0인데 예측값이 1에 가까워지면 오차가 커지고

실제값이 1인데 예측값이 0에 가까워져도 오차가 커져야 한다.

이를 반영하는 함수가 로그 함수이다.

초록색 선 = 실제값이 0일 때 그래프

주황색 선 = 실제값이 1일 때 그래프

이 두개의 로그 함수를 식으로 표현하자

y실제값이 1일때, y실제값이 0일때 함수는 각각 다음과 같다

이 두 식을 하나로 톻합해보자

두 식의 오차의 평균을 구해서 비용함수를 정의했다.

실제값과 예측값 차이가 커질수록 cost가 커진다.

 

4. 파이토치로 로지스틱 회귀 구현하기

다중 로지스틱 회귀를 구현해보자

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

torch.manual_seed(1)

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

W = torch.zeros((2, 1), requires_grad=True) # 크기는 2 x 1
b = torch.zeros(1, requires_grad=True)


optimizer = optim.SGD([W, b], lr=1)

nb_epochs = 1000
for epoch in range(nb_epochs + 1):
    
    #가설 설정
    hypothesis = 1 / (1 + torch.exp(-(x_train.matmul(W) + b)))
    
    #cost function
    losses = -(y_train * torch.log(hypothesis) + (1 - y_train) * torch.log(1 - hypothesis))
    cost = losses.mean()
    
    #cost 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()
    
    #print log
    if epoch%100==0:
        print('Epoch {:4d}/{} Cost: {:.6f}'.format( epoch, nb_epochs, cost.item()))

 

+)

hypothesis = torch.sigmoid(x_train.matmul(W) + b)

로 구할 수 있다. 함수를 사용하여 구한것으로 차이는 없다

 

cost function은 F.binary_cross_entrophy(hypothesis,y_train)

으로도 구할 수 있다. 함수를 사용하여 구했다.

 

04-02 nn.Module로 로지스틱 회귀 구현

nn.Linear()결과(선형회귀모델)를 nn.Sigmoid() 거치게 하면 로지스틱 회귀 가설식이다.

파이토치의 nn.Linear, nn.Sigmoid로 로지스틱 회귀 구현해보자

 

nn.Sequential()은 nn.Module층을 쌓을수 있게 한다

간단히 말하자면 서로 다른 함수를 연결해주는 것이다.

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

torch.manual_seed(1)

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

model = nn.Sequential(
   nn.Linear(2, 1), # input_dim = 2, output_dim = 1
   nn.Sigmoid() # 출력은 시그모이드 함수를 거친다
)
model(x_train)

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

nb_epochs = 1000
for epoch in range(nb_epochs + 1):

    # H(x) 계산
    hypothesis = model(x_train)

    # cost 계산
    cost = F.binary_cross_entropy(hypothesis, y_train)

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

 
    if epoch % 10 == 0:
        prediction = hypothesis >= torch.FloatTensor([0.5]) # 예측값이 0.5를 넘으면 True로 간주
        correct_prediction = prediction.float() == y_train # 실제값과 일치하는 경우만 True로 간주
        accuracy = correct_prediction.sum().item() / len(correct_prediction) # 정확도를 계산
        #print accuracy
        print('Epoch {:4d}/{} Cost: {:.6f} Accuracy {:2.2f}%'.format(
            epoch, nb_epochs, cost.item(), accuracy * 100, ))

 

04-03 클래스로 파이토치 모델 구현하기

로지스틱 회귀를 클래스로 구현해보자

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

torch.manual_seed(1)

#data
x_data = [[1, 2], [2, 3], [3, 1], [4, 3], [5, 3], [6, 2]]
y_data = [[0], [0], [0], [1], [1], [1]]
x_train = torch.FloatTensor(x_data)
y_train = torch.FloatTensor(y_data)

#model을 class로 구현
class BinaryClassifier(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(2, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        return self.sigmoid(self.linear(x))
model = BinaryClassifier()

# optimizer
optimizer = optim.SGD(model.parameters(), lr=1)

nb_epochs = 1000
for epoch in range(nb_epochs + 1):

    # H(x) 계산
    hypothesis = model(x_train)

    # cost 계산
    cost = F.binary_cross_entropy(hypothesis, y_train)

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    if epoch % 10 == 0:
        prediction = hypothesis >= torch.FloatTensor([0.5]) # 예측값이 0.5를 넘으면 True로 간주
        correct_prediction = prediction.float() == y_train # 실제값과 일치하는 경우만 True로 간주
        accuracy = correct_prediction.sum().item() / len(correct_prediction) # 정확도를 계산
        print('Epoch {:4d}/{} Cost: {:.6f} Accuracy {:2.2f}%'.format( # 각 에포크마다 정확도를 출력
            epoch, nb_epochs, cost.item(), accuracy * 100,
        ))