Logistic Regression
개요
이 글은 Andrew Ng. 교수님 강의 중 "Logistic Regression" 부분을 제 나름의 이해방식으로 정리해 기록하는 글입니다.
Logistic Regression
로지스틱 회귀는 선형 회귀의 결과를 시그모이드 함수(Sigmoid function) 에 적용하여 확률 값을 출력하는 모델입니다.
이 함수는 입력값이 뭐든 항상 0과 1 사이의 값을 출력하므로, 이진 분류(Classification) 문제에서 확률 값으로 해석할 수 있습니다.
![]() |
Source : Andrew Ng. Cousera |
따라서 분류문제에서 시그모이드함수의 결과값이 0.5보다 크거나 같으면 1(참), 아니면 0(거짓)으로 생각할 수 있습니다.
여러분이 공무원 시험준비를 한다고 가정해 볼까요. 🤔
공부시간(x0), 시도횟수(x1) 등의 따른 합격여부 데이터(Y)가 있다면, 대략 3시간
이상, 3회 이상 공부하니 합격, 아니면 불합격하더라 라는 데이터가 있습니다.
![]() |
Source : Andrew Ng. Cousera |
붉은색 X는 합격, 파란색 O는 불합격이라면,
어렵지 않게 경계선, 결정경계를 발견할 수 있습니다.
(3회 이상 응시하고 3시간 이상 공부하면 합격)
![]() |
Source : Andrew Ng. Cousera |
그런데 이 선은 선형회귀에서와 마찬가지로 인간이 그려보는것이 아닙니다.
데이터만 주어지면 모델을 학습시켜 x0시간 공부하고 x1회 응시한 친구는 합격인지
불합격인지를 분류하여 예측하고 싶은 것이 목적입니다.
로지스틱회귀는 이러한 목적에 부합하며, 이는 데이터를 가장 잘 분류하는 결정경계(Decision boundary)를 학습하는 과정이라고 볼 수 있습니다.
Model
로지스틱 회귀의 모델은 아래와 같이 정의된다.
다양한 입력특징에 대응하기 위해 벡터, 행렬로 처리.
시그모이드함수는 아래와 같다.
로지스틱 회귀는 선형 회귀의 결과를 시그모이드함수에 적용하므로
Logistic Loss Function
로지스틱 회귀는 선형회귀처럼 MSE(평균 제곱 오차)를 사용하면 Non Convex 함수가 되어 학습이 어려움.
(미분해서 가장 낮은 지점을 찾아야 하는데 Global 이 아닌
Local Minimum에 도달)
![]() |
Source : Andrew Ng. Cousera |
그래서 로그 손실 함수(Log Loss) 를 사용.
이런걸 어떻게 아냐면 First Mover인 Andrew Ng. 교수님 같은 선구자 덕분이다. 🙂↕️
로지스틱 손실 함수는 다음과 같이 정의된다.
만약
이라면
이므로 위 그림의 좌측 로그함수.
- 1에 가까우면
- 0에 가까우면
- 손실이 1일때 줄어들고 0이면 무한대로 커지니 손실함수로 적합.
만약
이라면
이므로 위 그림의 우측 로그함수.
- 1에 가까우면
- 0에 가까우면
- 손실이 0일때 줄어들고 1이면 무한대로 커지니 손실함수로 적합.
Cost Function
로지스틱회귀의 비용함수
Simplified Loss Function
위에서 언급한 두가지 Loss 함수를 하나로 합치면 아래와 같다.
위 손실함수 앞부분
위 손실함수 뒷부분
따라서 위의 둘을 합친 Simplified Loss Function 형태가 가능하다.
Simplified Cost Function
이제 비용함수(J)는 위의 단순화된 손실함수
위 비용 함수는 최대 가능도, 또는 최대 우도 추청 (Maximum
Likelihood Estimation, MLE) 방법에서 유도된다고 한다. 즉,
모델이 실제 데이터를 가장 잘 설명하는 파라미터
통계수학을 깊이 공부한 적은 없어 사실 이부분은 잘 모르겠다.😓
이제 정규화 (Regularization) 를 제외하고 대부분의 수식 정리가 완료되었다.
아래는 위 비용함수 유도과정을 직접 필기한 그림이다.
이제 이론적인 내용은 마치고 파이썬 코드로 구현할 차례이다.
1. Dataset
아래와 같은 데이터를 가상으로 생성. (chatGPT 활용)
![]() |
[나이, 흡연기간, 흡연량에 따른 폐암여부 자료] |
-
Input Features : 흡연자 나이(x0), 흡연기간(x1), 흡연량(x2)
Output : 폐암여부(y)
총 200명분이므로 200(행) X 4(열)로 구성 : lung_cancer_data.csv
예를 들면,
1번 사람은 78세, 20년흡연, 하루 1.68갑 흡연이지만 정상 😧
2번 사람은 68세, 50년흡연, 하루 2.96갑 흡연이라서 폐암 😰
먼저, csv 파일을 불러들이자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import numpy as np import matplotlib.pyplot as plt import seaborn as sns import pandas as pd import copy # load the dataset path = 'lung_cancer_data.csv' df = pd.read_csv(path) np_array = df.to_numpy() print (np_array.shape) fig, axes = plt.subplots( 1 , 2 , figsize = ( 10 , 4 )) fig.suptitle( 'Scatter plot between specific features' , fontsize = 16 ) sns.scatterplot(x = 'Age' , y = 'SmokingDuration' , hue = 'LungCancer' , data = df, ax = axes[ 0 ], alpha = 0.7 ) sns.scatterplot(x = 'SmokingDuration' , y = 'SmokingAmount' , hue = 'LungCancer' , data = df, ax = axes[ 1 ], alpha = 0.7 ) plt.show() |
어떤 데이터인지 시각적으로 확인해보자.
![]() |
[좌 : 나이와 흡연기간, 우: 흡연기간과 흡연량] |
시각적으로 나이, 흡연기간, 흡연량의 상관관계를 알 수 있다.
나이가 많거나, 흡연기간이 길거나, 흡연량이 많을 수록 폐암진단 확률이 높다.
2. Cost Function
위에서 설명한 수식을 토대로 작성된 Sigmoid, Cost Function.
입력 특징들(xi) 과 가중치의 내적을 시그모이드 함수로 전달.
이후 앞서 설명한 Simplified Cost Function 으로 비용 계산.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def sigmoid(z): g = 1 / ( 1 + np.exp( - z)) return g def compute_cost_logistic(X, y, w, b): m = X.shape[ 0 ] cost = 0 for i in range (m): z_i = np.dot(X[i], w) + b f_wb_i = sigmoid(z_i) cost + = - y[i] * np.log(f_wb_i) - ( 1 - y[i]) * np.log( 1 - f_wb_i) cost / = m return cost |
3. Gradient Descent
위에서 설명한 수식을 토대로 작성된 경사하강법 구현.
미분하며 w, b를 update하는 방식은 선형회귀와 동일하다.
다만 로지스틱회귀의 손실함수는 평균제곱오차가 아닌 Log 손실함수.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | def compute_gradient_logistic(X, y, w, b): m,n = X.shape dj_dw = np.zeros((n, )) dj_db = 0 for i in range (m): z_i = np.dot(X[i], w) + b f_wb_i = sigmoid(z_i) err_i = f_wb_i - y[i] for j in range (n): dj_dw[j] + = err_i * X[i][j] dj_db + = err_i dj_dw / = m dj_db / = m return dj_dw, dj_db def gradient_descent(X, y, w_in, b_in, alpha, num_iters): J_history = [] w = copy.deepcopy(w_in) b = b_in for i in range (num_iters): dj_dw, dj_db = compute_gradient_logistic(X, y, w, b) w = w - alpha * dj_dw b = b - alpha * dj_db cost = compute_cost_logistic(X, y, w, b) J_history.append(cost) if i % 100 = = 0 : print (f 'Iteration {i}: w = {w}, b = {b:.3f}, cost = {J_history[-1]:.3f}' ) return w, b, J_history |
4. Machine Learning
이제 초기값 설정 후 학습시작.
가중치 초기값은 Input Feature의 수(3개) 만큼 0으로 초기화.
입력값(X) np_array[ : , :-1] 에서 쉼표 앞 : 은 모든 행(0~199)을,
:-1은 마지막 열을 제외한 (0~2) 까지를 의미.
따라서 X는 200x3 2D 행렬 이다.
y로 사용되는 np_array[ : , -1] 에서 쉼표 앞 : 은 모든 행(0~199),
-1은 마지막 값(열 3)인 폐암여부를 의미.
따라서 y는 200개의 1D 벡터이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # init parameters w = np.zeros_like(np_array[ 0 , : - 1 ]) b = 0 alpha = 0.001 num_iters = 1000 # machine learning w_final, b_final, J_history = gradient_descent(np_array[:, : - 1 ], np_array[:, - 1 ], w, b, alpha, num_iters) print (f 'Final w: {w_final}, Final b: {b_final}' ) # cost function convergence plot plt.plot(J_history) plt.xlabel( "Iteration" ) plt.ylabel( "Cost" ) plt.title( "Cost Function Convergence" ) plt.show() |
경사하강법을 진행하며 w, b가 업데이트되고 비용이 감소하는 것을 확인.
아래 그림의 마지막 Final 가중치(wi) 세가지 w1(나이), w2(흡연기간), w3(흡연량) 중 가장 큰 값인 w2, 즉 흡연기간이 폐암 여부에 미치는 영향이 큰것을 확인. (약 0.075)
(테스트를 위해 가상으로 만들어진 데이터 임을 유의)
단, 음수 가중치인 나이(w1)는 필요없는 값이 아니라 출력확률을 낮추는 중요한 역할을 함.
J_history 리스트에 저장해둔 Cost값이 작아지며 학습이 잘 이루어짐을 확인.
![]() |
5. Prediction
최종적으로 얻어진 w_final, b_final을 이용해 예측.
학습에 사용한 Training dataset과 예측용 Validation dataset을 구분하는 것이 좋지만,
예시에서 편의상 Training dataset 일부를 예측에 사용.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # prediction def predict(w, b, x): z = np.dot(x, w) + b g = sigmoid(z) if g > = 0.5 : return 1 else : return 0 accuracy = 0 sample_cnt = 30 for x in np_array[:sample_cnt, :]: x_test = x[: - 1 ] y_test = int (x[ - 1 ]) y_pred = predict(w_final, b_final, x_test) accuracy + = (y_pred = = y_test) print (f 'Input {x_test} :\tPred:{y_pred} True:{y_test}' ) print (f 'Accuracy: {accuracy/sample_cnt:.2%}' ) |
Pred는 학습 후 예측값이고 True는 실제 폐암 확진데이터.
둘이 일치하면 모델이 정확하다는 것.
30개를 비교해 보니 일부 불일치해서 약 83%의 정확도.
Full Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | import numpy as np import matplotlib.pyplot as plt import seaborn as sns import pandas as pd import copy # load the dataset path = 'lung_cancer_data.csv' df = pd.read_csv(path) np_array = df.to_numpy() print (np_array.shape) fig, axes = plt.subplots( 1 , 2 , figsize = ( 10 , 4 )) fig.suptitle( 'Scatter plot between specific features' , fontsize = 16 ) sns.scatterplot(x = 'Age' , y = 'SmokingDuration' , hue = 'LungCancer' , data = df, ax = axes[ 0 ], alpha = 0.7 ) sns.scatterplot(x = 'SmokingDuration' , y = 'SmokingAmount' , hue = 'LungCancer' , data = df, ax = axes[ 1 ], alpha = 0.7 ) plt.show() def sigmoid(z): g = 1 / ( 1 + np.exp( - z)) return g def compute_cost_logistic(X, y, w, b): m = X.shape[ 0 ] cost = 0 for i in range (m): z_i = np.dot(X[i], w) + b f_wb_i = sigmoid(z_i) cost + = - y[i] * np.log(f_wb_i) - ( 1 - y[i]) * np.log( 1 - f_wb_i) cost / = m return cost def compute_gradient_logistic(X, y, w, b): m,n = X.shape dj_dw = np.zeros((n, )) dj_db = 0 for i in range (m): z_i = np.dot(X[i], w) + b f_wb_i = sigmoid(z_i) err_i = f_wb_i - y[i] for j in range (n): dj_dw[j] + = err_i * X[i][j] dj_db + = err_i dj_dw / = m dj_db / = m return dj_dw, dj_db def gradient_descent(X, y, w_in, b_in, alpha, num_iters): J_history = [] w = copy.deepcopy(w_in) b = b_in for i in range (num_iters): dj_dw, dj_db = compute_gradient_logistic(X, y, w, b) w = w - alpha * dj_dw b = b - alpha * dj_db cost = compute_cost_logistic(X, y, w, b) J_history.append(cost) if i % 100 = = 0 : print (f 'Iteration {i}: w = {w}, b = {b:.3f}, cost = {J_history[-1]:.3f}' ) return w, b, J_history # init parameters w = np.zeros_like(np_array[ 0 , : - 1 ]) b = 0 alpha = 0.001 num_iters = 1000 # machine learning w_final, b_final, J_history = gradient_descent(np_array[:, : - 1 ], np_array[:, - 1 ], w, b, alpha, num_iters) print (f 'Final w: {w_final}, Final b: {b_final}' ) # cost function convergence plot plt.plot(J_history) plt.xlabel( "Iteration" ) plt.ylabel( "Cost" ) plt.title( "Cost Function Convergence" ) plt.show() # prediction def predict(w, b, x): z = np.dot(x, w) + b g = sigmoid(z) if g > = 0.5 : return 1 else : return 0 accuracy = 0 sample_cnt = 30 for x in np_array[:sample_cnt, :]: x_test = x[: - 1 ] y_test = int (x[ - 1 ]) y_pred = predict(w_final, b_final, x_test) accuracy + = (y_pred = = y_test) print (f 'Input {x_test} :\tPred:{y_pred} True:{y_test}' ) print (f 'Accuracy: {accuracy/sample_cnt:.2%}' ) |
느낀점
scikit-learn, tensorflow, pytorch 등 라이브러리 사용보다 순수 파이썬으로 구현해 보는 것이 이해, 공부에 도움이 된다는 Andrew Ng. 교수님 말씀에 전적으로 동의합니다.
이상으로 모든 설명을 마칩니다.
Reference
https://www.coursera.org/specializations/machine-learning-introduction
댓글
댓글 쓰기