안녕하세요. 어려운 수학들을 보니 머리가 아파져오고 있습니다.
코드도 길~고요 ㅎㅎ
오늘은 로지스틱 회귀에 대해 알아보겠습니다.
로지스틱회귀(Logistic Regression) 또는 로짓 회귀(Logit Regression)는 샘플이 특정 클래스에 속할 확률을 추정하는 데 널리 사용됩니다. 예를 들면, 이 이메일이 스팸 확률일 확률은 얼마인가? 추정 확률이 50%가 넘는다면 모델은 그 샘플이 해당 클래스에 속한다고 예측합니다. (즉, 레이블이 '1' 인 양성 클래스). 아니면 클래스에 속하지 않는다고 예측합니다. (레이블이 '0'인 음성 클래스). 이를 이진 분류기라고 합니다.
그렇다면, 실제 어떻게 작동하는 걸까요? 선형 회귀 모델과 같이 로지스틱 회귀 모델은 입력 특성의 가중치 합을 계산합니다.
(그리고 편향을 더합니다.) 대신, 선형회귀처럼 바로 결과를 출력하지 않고, 결괏값의 로지스틱(logistic)을 출력합니다.
[로지스틱 회귀 모델의 확률 추정(벡터 표현식)]
로지스틱 또는 로짓이라고 부르며, 시그마로 표시합니다. 이는 0과 1 사이의 값을 출력하는 시그모이드 함수(sigmoid function) 입니다. 즉, S자 형태이며 아래와 같습니다.
[로지스틱 함수]
로지스틱 회귀 모델이 샘플 x가 양성 클래스에 속할 확률 를 추정하면, 이에 대한 예측을 쉽게 구할 수 있습니다.
[로지스틱 회귀 모델 예측]
이면 이고, 이면 이므로
로지스틱 회귀 모델은 가 양수일 때 1(양성 클래스)라고 예측하고, 음수일 때 0 (음성 클래스)라고 예측합니다.
이제 로지스틱 회귀 모델이 어떻게 확률을 추정하고 예측하는지 알았다면, 어떻게 훈련시킬 지 알아야 합니다.
훈련의 목적은 양성 샘플(y=1)에 대해서는 높은 확률을 추정하고, 음성 샘플 (y=0)에 대해서는 낮은 확률을 추정하는 모델의 파라미터 벡터(theta)를 찾는 것입니다. 이러한 아이디어가 하나의 훈련 샘플 x에 대해 나타낸 비용 함수에 드러나 있습니다.
이 비용 함수는 t가 0에 가까워지면, -log(t)가 매우 커지므로 타당하다 할 수 있습니다.
그러므로 모델이 양성 샘플을 0에 가까운 확률로 추정하면 비용이 크게 증가합니다. 또한, 음성 샘플을 1에 가까운 확률로 추정해도 비용이 증가합니다. 반면에, t가 1에 가까우면 -log(t)는 0에 가까워집니다. 따라서 기대한 대로 음성 샘플의 확률을 0에 가깝게 추정하거나 양성 샘플의 확률을 1에 가깝게 추정한다면 비용은 0에 가까워질 것입니다.
전체 훈련 세트에 대한 비용 함수는 모든 훈련 샘플의 비용을 평균한 것입니다.
이를 로그 손실(log loss)이라 부르며, 아래의 식처럼 하나의 식으로 쓸 수 있습니다.
[ 로지스틱 회귀의 비용 함수 (로그 손실, log loss) ]
하지만 안타깝게도 이 비용 함수의 최솟값을 계산하는 알려진 해가 없습니다. (정규방정식 같은)
하지만만 이 비용 함수는 볼록 함수이므로 경사 하강법(또는 어떤 다른 최적화 알고리즘)이 전역 최솟값을 찾는 것을 보장합니다.
(학습률이 너무 크지 않고 기다릴 시간이 있다면)
이 비용 함수의 j번째 모델 파라미터 에 대해 편미분을 하면 다음과 같습니다.
[로지스틱 비용 함수의 편도 함수]
이 식은 각 샘플에 대해 예측 오차를 계산하고, j번째 특성값을 곱해서 모든 훈련 샘플에 대해 평균을 냅니다.
모든 편도함수를 포함한 그래디언트 벡를 만들면 배치 경사 하강법 알고리즘을 사용할 수 있습니다.
이제 로지스틱 회귀를 훈련시키는 방법을 알게 되었습니다. 아래는 데이터를 통해 코드로 설명합니다.
로지스틱 회귀를 설명하기 위해, iris 데이터셋을 사용합니다.
이 데이터셋은 세 개의 품종 Iris-Setosa, Iris-Versicolor, Iris-Virginica에 속하는 붓꽃 150개의 꽃잎(petal)과 꽃받침(sepal)의 너비와 길이를 담고 있습니다.
이제, 꽃잎의 너비를 기반으로 Iris-Versicolor종을 감지하는 분류기를 만듭니다.
from sklearn import datasets
iris = datasets.load_iris()
list(iris.keys())
['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename']
X = iris['data'][:,3:] #꽃잎의 너비
y = (iris['target']==2).astype(np.int) # Iris Virginica면 1 아니면 0
#훈련
from sklearn.linear_model import LogisticRegression
log_reg = LogisticRegression(solver='liblinear', random_state=42)
log_reg.fit(X,y)
#향후 사이킷런 0.22버전에서 LogisticRegression 클래스의 solver 매개변수 기본값이 liblinear에서
#lbfgs로 변경될 예정입니다. 따라서 0.20버전에서 solver 매개변수를 지정하지 않으면 경고 출력
#이를 피하기 위해 매개변수를 설정하였습니다.
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=100, multi_class='warn', n_jobs=None, penalty='l2', random_state=None, solver='warn', tol=0.0001, verbose=0, warm_start=False)
#사이킷런의 LogisticRegression은 클래스 레이블을 반환하는 predict()메서드와,
#클래스에 속할 확률을 반환하는 predict_proba() 메서드를 가지고 있습니다.
#predict 메서드는 확률 추정식에서 0보다 클 때를 양성 클래스로 판단하여 결과를 반환하고,
#predict_proba메서드는 시그모이드 함수를 적용하여 계산한 확률을 반환합니다.
#reshape(-1,1)에서 -1의 의미는 변경된 배열의 -1위치의 차원은 원래 배열의 길이와 남은 차원으로부터 추정된다라는 뜻입니다.
#즉 가변적이며 고정적이지 않다는 뜻이 됩니다.
#따라서 (-1, 1)이면 1열로 표현이 되고, (-1, 3)이면 3열로 표현이 됩니다. 직접 해보시는 것을 추천..합니다.
#반대로 (1, -1)이면, 1행으로 표현이 되겠죠. (2, -1)이면 2행으로 표현이 됩니다. (3, -1)은 3행으로 표현이 되죠.
#우리가 위에서 X(꽃잎의 너비)와 y(레이블)을 적용하여 훈련시켰습니다.
#아래는 X_new는 비슷하게 만든 것입니다. ( petal width의 최솟값 0.1 최댓값 2.5 평균 1.20 표준편차 0.76 )
X_new = np.linspace(0,3,100).reshape(-1,1)
y_proba = log_reg.predict_proba(X_new)
#새롭게 만든 X_new에 훈련된 모델을 적합시켜 확률이 계산됩니다.
#따라서 0.5가 넘는 부분의 첫 확률을 결정 경계로 삼습니다.
decision_boundary = X_new[y_proba[:, 1] >= 0.5][0]
plt.figure(figsize=(8,3))
plt.plot(X[y==0], y[y==0], 'bs') #음성 클래스
plt.plot(X[y==1], y[y==1], 'g^') #양성 클래스
plt.plot([decision_boundary, decision_boundary], [-1,2], 'k:', linewidth=2)
plt.plot(X_new, y_proba[:, 1], 'g-', linewidth=2, label='Iris-Virginica')
plt.plot(X_new, y_proba[:, 0], 'b--', linewidth=2, label='Not Iris-Virginica')
plt.text(decision_boundary+0.02, 0.15, 'Decision Boundary', fontsize=14, color='k', ha='center')
plt.arrow(decision_boundary, 0.08, -0.3, 0, head_width=0.05, head_length=0.1, fc='b', ec='b')
plt.arrow(decision_boundary, 0.92, 0.3, 0, head_width=0.05, head_length=0.1, fc='g', ec='g')
plt.xlabel('petal width(cm)', fontsize=14)
plt.ylabel('probability ', fontsize=14, rotation=0)
plt.legend(loc='center left', fontsize=14)
plt.axis([0,3,-0.02, 1.02])
plt.show()
decision_boundary
결정 경계는 1.63으로 나왔고,
아래에서 예측을해보면 1.63이상인 것은 1 아니면 0으로 분류했습니다!
log_reg.predict([[1.7], [1.5]])
그래프를 보면, 꽃잎 너비가 2cm 이상인 꽃은 Iris-Verginica라고 강하게 확신하고, 1cm 아래면 Iris-Verginica가 아니라고 강하게 확신합니다. 하지만 이 극단 사이에는 분류가 확실하지 않고, 양 쪽의 확률이 50%가 되는 1.6cm 근방에서 결정 경계가 만들어집니다.
따라서 꽃잎 너비가 1.6cm보다 크면 분류기는 Iris-Verginica라고 분류하고, 그보다 작으면 아니라고 예측할 것입니다. (아주 확실하지 않아도)
아래는 데이터셋을 이번에는 꽃잎 너비와 꽃잎 길이 두 개의 특성으로 보여줍니다.
( 이 경계는 선형임을 주목 )
from sklearn.linear_model import LogisticRegression
X = iris['data'][: , (2, 3)] #petal length, petal width
y = (iris['target'] == 2).astype(np.int)
log_reg = LogisticRegression(solver = 'liblinear', C=10**10, random_state=42)
log_reg.fit(X,y)
x0, x1 = np.meshgrid(
np.linspace(2.9, 7, 500).reshape(-1,1),
np.linspace(0.8, 2.7, 200).reshape(-1,1),
)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_proba = log_reg.predict_proba(X_new)
plt.figure(figsize=(10,4))
plt.plot(X[y==0, 0], X[y==0, 1], 'bs')
plt.plot(X[y==1, 0], X[y==1, 1], 'g^')
zz = y_proba[:, 1].reshape(x0.shape)
contour = plt.contour(x0, x1, zz, cmap=plt.cm.brg)
left_right = np.array([2.9, 7])
boundary = -(log_reg.coef_[0][0] * left_right + log_reg.intercept_[0] / log_reg.coef_[0][1])
plt.clabel(contour, inline=1, fontsize=12)
plt.plot(left_right, boundary, 'k--', linewidth=3)
plt.text(3.5, 1.5, 'Not Iris_Virginica', fontsize=14, color='b', ha='center')
plt.text(6.5, 2.3, 'Iris_Virginica', fontsize=14, color='g', ha='center')
plt.xlabel('Petal Length', fontsize=14)
plt.ylabel('Petal Width ', fontsize=14, rotation=0)
plt.axis([2.9, 7, 0.8, 2.7])
plt.show()
다른 선형 모델처럼 로지스틱 회귀 모델도 페널티를 사용하여 규제할 수 있습니다.
사이킷런은 를 기본으로 합니다.
** 사이킷런의 LogisticRegression 모델의 규제 강도를 조절하는 하이퍼파라미터는 다른 선형 모델처럼 alpha가 아니고,
그 역수에 해당하는 C입니다. 따라서 C가 높을 수록 모델의 규제는 줄어듭니다.
블로그
출처
이 글의 상당 부분은 [핸즈온 머신러닝, 한빛미디어/오렐리앙 제롱/박해선] 서적을 참고하였습니다.
나머지는 부수적인 함수나 메서드에 대해 부족한 설명을 적어두었습니다.
학습용으로 포스팅 하는 것이기 때문에 복제보다는 머신러닝에 관심이 있다면 구매해보시길 추천합니다.
도움이 되셨다면 로그인 없이 가능한
아래 하트♥공감 버튼을 꾹 눌러주세요!
'## 오래된 게시글 (미관리) ## > Python (Linux)' 카테고리의 다른 글
34. Python - 4장 연습문제 (0) | 2019.01.30 |
---|---|
33. Python - 소프트맥스 회귀(다항 로지스틱 회귀) (0) | 2019.01.29 |
31. Python - 규제(릿지 회귀, 라쏘 회귀, 엘라스틱넷, 조기종료) (0) | 2019.01.27 |
30. Python - 다항 회귀 (0) | 2019.01.25 |
29. Python - 미니배치 경사 하강법 (0) | 2019.01.25 |