[ML] Logistic Regression

redbeet1007·2023년 7월 17일
3

ML

목록 보기
2/2
post-thumbnail

선형 회귀는 선형적인 관계를 가지는 데이터에 대한 분석을 진행하기에 적합하지만, 데이터를 두 개의 클래스로 나누는 문제에는 매우 부적합하다. 클래스를 나누는 분류 문제는 또다시 다중 클래스 분류와 이진 클래스 분류로 나눌 수 있는데, 로지스틱 회귀는 그 결과값으로 0 또는 1의 값을 가지게 하여 이진 분류 문제에서 매우 자주 활용되는 알고리즘이다. 본 포스트에서는 로지스틱 회귀를 이용하여 NBA선수들의 실적을 토대로 드래프트 참가 여부를 예측하는 알고리즘을 python을 이용하여 구현한다.

  • 일러두기: 이 포스트에 사용된 학습 데이터는 Chat GPT로부터 임의로 생성받은 것이다.

Logistic Regression

로지스틱 회귀(Logistic Regression)는 이진 분류 문제를 풀기 위한 통계적 기법이다. 스팸 메일인지 아닌지, 암 환자인지 아닌지 등의 예/아니오로 답할 수 있는 문제를 로지스틱 회귀로 해결할 수 있다.
로지스틱 회귀는 선형 회귀와 비슷하게 입력 변수들의 가중합을 구하지만, 그 결과를 바로 출력하지 않고 로지스틱 함수라는 비선형 함수를 적용하여 0과 1 사이의 값으로 변환한다. 이렇게 하면 출력 값이 확률로 해석될 수 있다. 즉, 로지스틱 회귀는 입력 변수들이 주어졌을 때 어떤 범주에 속할 확률을 예측하는 모델이다. 로지스틱 함수는 다음과 같은 식으로 정의된다.

f(x)=11+exf(x) = \frac{1}{1+e^{-x}}

이 함수는 xx가 커질수록 1에 가까워지고, xx가 작아질수록 0에 가까워진다. 그리고 xx가 0일 때는 가 0.5가 된다.
로지스틱 회귀에서는 입력 변수들의 가중합을 xx라고 하고, 이를 로지스틱 함수에 넣어서 출력된 값을 yy라고 한다. 즉,

x=w0+w1x1+w2x2++wnxny=f(x)=11+exx = w_0 + w_1 x_1 + w_2 x_2 + \cdots + w_n x_n \\ y = f(x) = \frac{1}{1+e^{-x}}

여기서 ww는 가중치이고, xx는 입력 변수이다.

NLL Loss

로지스틱 회귀로 구현한 모델을 학습시키기 위해서는 적절한 가중치를 찾아야 한다. 이를 위해 cost function을 정의하고, 이 함수를 최소화하는 방법을 찾는다. 로지스틱 회귀에서는 로그 우도 함수(NLL Loss)라는 cost function을 사용한다. 로그 우도 함수는 다음과 같이 정의된다.

L(w)=i=1myilogf(xi)+(1yi)log(1f(xi))L(w) = \sum^m_{i=1}y_i \log f(x_i) + (1-y_i) \log (1-f(x_i ))

여기서 mm은 데이터의 개수이고, yy는 실제 값, f(x)f(x)는 예측 값이다. 이 함수는 실제 값이 1일 때 예측 값이 1에 가까울수록 크고, 실제 값이 0일 때 예측 값이 0에 가까울수록 크다. 반대로 실제 값과 예측 값이 다르면 함수값은 작아진다. 그래서 이 함수를 최대화하는 것이 목표가 된다. 하지만 최적화 문제에서는 보통 최소화하는 것이 편리하기 때문에, 로그 우도 함수에 1-1을 곱하여 부호를 바꾼다. 즉,

J(w)=L(w)=i=1myilogf(xi)+(1yi)log(1f(xi))J(w) = -L(w) = -\sum^m_{i=1}y_i \log f(x_i) + (1-y_i) \log (1-f(x_i ))

이제 이 비용 함수를 최소화하는 가중치를 찾기 위해 경사 하강법을 사용한다. 이때 cost function의 gradient는 다음과 같이 계산된다.

J(w)wj=i=1m(yif(xi))xij\frac{\partial J(w)}{\partial w_j} = -\sum^m_{i=1}(y_i - f(x_i )) x_{ij}

여기서 xijx_{ij}ii번째 데이터의 jj번째 입력 변수이다. 이제 이 식을 이용하여 각 가중치에 대해 기울기를 구하고, 경사 하강법을 적용하여 가중치를 업데이트한다. 이 과정을 여러 번 반복하면 비용 함수의 값이 줄어들고, 최적의 가중치에 수렴하게 된다.
이렇게 로지스틱 회귀 모델을 학습시킨 후에는 새로운 입력 변수들에 대해 예측 값을 구할 수 있다. 예측 값이 0.5보다 크면 1로, 작으면 0으로 분류한다. 이렇게 로지스틱 회귀를 이용하여 이진 분류 문제를 풀 수 있다.

코드 전문

from math import exp
from math import log

filename = 'NBA_Rookie_draft.csv'
data = []

with open(filename, 'r') as file:
    lines = file.readlines()
    for line in lines[1:]:
        values = line.strip().split(',')
        values = [float(value) for value in values]
        data.append(values)

W = [[0.5], [0.5], [0.5]]
B = [[0.5]]

learning_rate = 0.001

def logistic_forward(X, Y, W, B):
    pred = []
    loss = 0

    for i in range(len(X)):
        XWB = B[0][0]
        for j in range(len(X[i])-1):
            XWB += X[i][j] * W[j][0]

        pred_i = sigmoid(XWB)
        pred.append(round(pred_i))
        loss -= Y[i][0] * log(pred_i) + (1 - Y[i][0]) * log(1 - pred_i)

    return pred, loss

def sigmoid(x):
    if x >= 0:
        return 1 / (1 + exp(-x))
    else:
        return exp(x) / (1 + exp(x))

def loss_gradient(X, Y, W, B):
    dloss_dW = [[0.0] for _ in range(len(W))]
    dloss_dB = [[0.0]]

    for i in range(len(X)):
        XWB = B[0][0]
        for j in range(len(X[i])-1):
            XWB += X[i][j] * W[j][0]

        pred_i = sigmoid(XWB)
        for j in range(len(W)):
            dloss_dW[j][0] += X[i][j] * (pred_i - Y[i][0])
        dloss_dB[0][0] += pred_i - Y[i][0]

    return dloss_dW, dloss_dB

pred, loss = logistic_forward(data, [row[-1:] for row in data], W, B)
epochs = 30000

for i in range(epochs + 1):
    dL_dW, dL_dB = loss_gradient(data, [row[-1:] for row in data], W, B)
    for j in range(len(W)):
        W[j][0] += -1 * learning_rate * dL_dW[j][0]
    B[0][0] += -1 * learning_rate * dL_dB[0][0]
    if i%50 == 0:
        print(f"epoch : {i} / {epochs}")

pred, loss = logistic_forward(data, [row[-1:] for row in data], W, B)

print("input\t\t\toutput\t\treal index")
for i in range(len(data)):
    if i%45 == 0:
        print()
        print("input\t\t\toutput\t\treal index")
    input_data = data[i][:3]
    prediction = pred[i]
    target = data[i][-1]
    print(f"{input_data}\t{prediction}\t\t{target}")
print("Loss:", loss)
print("Weight:", W)
print("Bias:", B)

correct_count = sum([1 for i in range(len(data)) if pred[i] == data[i][-1]])
accuracy = correct_count / len(data)
print("Accuracy:", accuracy)

결과

모델의 학습이 완료되면, 학습된 모델을 사용하여 입력값 X\mathbf{X}과 예측값 y^\hat y, 실제 값 yy을 출력하고, 손실 LL, 가중치 ,W\mathbf{W} 편향 B\mathbf{B}, 그리고 정확도를 계산하여 보여주도록 하였다. 그 결과는 다음과 같았다.
result of logistic regression
본 포스트에서는 학습에 사용한 데이터를 그대로 테스트에 사용하였지만, 이를 고려하여도 약 95%의 매우 높은 예측 정확도를 보이는 것을 확인할 수 있었다.

profile
Every single post in the following series is a summary note from the lectures in Kaist: System Programming (CS230)

2개의 댓글

comment-user-thumbnail
2023년 7월 17일

잘봤습니다. 좋은 글 감사합니다.

답글 달기
comment-user-thumbnail
2023년 7월 17일

저도 개발자인데 같이 교류 많이 해봐요 ㅎㅎ! 서로 화이팅합시다!

답글 달기