CH 05. 분류
나이브 베이즈
- 예측변수가 주어졌을 때, 결과 Y=i를 관찰할 확률
- 나이브 베이즈는 통계의 방법으로 간주되지 않는다.
- 나이브 베이즈는 상대적으로 통계 지식이 거의 필요 없는 데이터 중심의 경험적 방법이다.
- 나이브 베이즈는 예측변수와 결과변수 모두 범주형(요인)이어야 한다. 각 출력 카테고리 안에서, 어떤 예측변수의 카테고리가 가장 가능성이 높은가? 답하고자 하는 질문이다. 그리고 이 정보는 주어진 예측변수 값에 대해, 결과 카테고리의 확률을 추정하는 것으로 바뀐다.
수치형 예측변수
- 베이즈 분류기는 예측변수들이 범주형인 경우에 적합하다.
- 수치형 변수에 나이브 베이즈 방법을 적용하기 위해서는, 두 가지 접근법 중 하나를 따라야 한다.
- 수치형 예측변수를 비닝하여 범주형으로 변환한 뒤, 알고리즘을 적용한다.
- 조건부확률을 추정하기 위해 정규분포 같은 확률모형을 사용한다.
판별분석
공분산행렬
공분산
: 하나의 변수가 다른 변수와 함께 변화하는 정도(유사한 크기와 방향)를 측정하는 지표
피셔의 선형판별
: 그룹 안의 편차와 다른 그룹 간의 편차를 구분
- 내부 제곱합(그룹 안의 변동을 측정)에 대한 사이 제곱합(두 그룹 사이의 편차를 측정)의 비율을 최대화하는 것을 목표로 한다.
- 사이 제곱합의 각 값은 두 그룹 평균 사이의 거리 제곱을 말하며, 내부 제곱합은 공분산행렬에 의해 가중치가 적용된, 각 그룹 내의 평균을 주변으로 퍼져 있는 정도를 나타낸다. 직관적으로, 사이 제곱합을 최대화하고 내부 제곱합을 최소화하는 것이 두 그룹 사이를 명확하게 나누는 방법이다.
- 판별분석은 예측변수나 결과변수가 범주형이든 연속형이든 상관없이 잘 동작한다.
- 공분산행렬을 사용하여 한 클래스와 다른 클래스에 속한 데이터들을 구분하는 선형판별함수를 계산할 수 있다. 이 함수를 통해 각 레코드가 어떤 클래스에 속할 가중치 혹은 (각 클래스탕 점수)를 구한다.
예제
loan3000 = pd.read_csv(LOAN3000_CSV)
loan3000.outcome = loan3000.outcome.astype('category')
predictors = ['borrower_score', 'payment_inc_ratio']
outcome = 'outcome'
X = loan3000[predictors]
y = loan3000[outcome]
loan_lda = LinearDiscriminantAnalysis()
loan_lda.fit(X, y)
print(pd.DataFrame(loan_lda.scalings_, index=X.columns))
# 0
# borrower_score 7.175839
# payment_inc_ratio -0.099676
- Python에서는 sklearn.discriminant_analysis의 LinearDiscriminantAnalysis를 사용해 선형판별자 가중치를 구할 수 있다.
- LDA를 돌리기 전에 미리 예측변수들을 정규화했다면, 판별자 가중치는 변수의 중요도를 의미하게 된다. 따라서 특정 선택을 위해 계산상으로 효과적인 방법이다.
# default(연체) 혹은 paid off(상환)에 대한 확률 반환
pred = pd.DataFrame(loan_lda.predict_proba(loan3000[predictors]),
columns=loan_lda.classes_)
print(pred.head())
# default paid off
# 0 0.553544 0.446456
# 1 0.558953 0.441047
# 2 0.272696 0.727304
# 3 0.506254 0.493746
# 4 0.609952 0.390048
# Use scalings and center of means to determine decision boundary
center = np.mean(loan_lda.means_, axis=0)
slope = - loan_lda.scalings_[0] / loan_lda.scalings_[1]
intercept = center[1] - center[0] * slope
# payment_inc_ratio for borrower_score of 0 and 20
x_0 = (0 - intercept) / slope
x_20 = (20 - intercept) / slope
lda_df = pd.concat([loan3000, pred['default']], axis=1)
lda_df.head()
fig, ax = plt.subplots(figsize=(4, 4))
g = sns.scatterplot(x='borrower_score', y='payment_inc_ratio',
hue='default', data=lda_df,
palette=sns.diverging_palette(240, 10, n=9, as_cmap=True),
ax=ax, legend=False)
ax.set_ylim(0, 20)
ax.set_xlim(0.15, 0.8)
ax.plot((x_0, x_20), (0, 20), linewidth=3)
ax.plot(*loan_lda.means_.transpose())
plt.tight_layout()
plt.show()
- 대각선 왼쪽의 데이터 포인트는 연체로 예측된다(확률이 0.5보다 크다).
- 실선으로부터 양방향으로 멀리 떨어진 예측 결과일수록 신뢰도가 높다(즉 확률이 0.5로부터 멀어진다).
로지스틱 회귀
로짓(logit)
: (0~1이 아니라) ±∞의 범위에서 어떤 클래스에 속할 확률을 결정하는 함수
오즈
: 실패에 대한 성공의 비율
로그 오즈
: 변환 모델(선형)의 응답변수. 이 값을 통해 확률을 구한다.
로지스틱 반응 함수와 로짓
- 로지스틱 회귀의 핵심 구성 요소는 로지스틱 반응 함수와 로짓이다.
- 예측변수에 로지스틱 반응 혹은 역 로짓 함수라는 것을 적용해서 p를 모델링하면, 이 변환을 통해 p가 항상 0에서 1 사이에 오도록 할 수 있다.
- 분모의 지수 부분을 구하려면 확률 대신 오즈비를 이용한다. 오즈비는 성공과 실패의 비율을 말한다. 예를 들어 어떤 말이 이길 확률이 0.5라면, 이기지 못할 확률은 0.5이고, 오즈비는 1.0이다.
- 로그 오즈 함수, 또는 로짓 함수는 0과 1 사이의 확률 p를 -∞에서 +∞까지의 값으로 매핑해준다. 이제 컷오프(절사) 기준을 이용해 그 값보다 큰 확률값이 나오면 1로 분류하는 식의 과정을 통해 클래스 라벨을 구할 수 있다.
로지스틱 회귀와 일반화선형모형(GLM; Generalized Linear Model)
- 로지스틱 회귀는 계산 속도가 빠르고 새로운 데이터에 대해서도 간단한 산술연산으로 빠르게 결과를 구할 수 있다는 장점 때문에 많이 사용된다.
- 로지스틱 회귀방정식에서 응답변수는 1의 이진 출력에 대한 로그 오즈 값이다. 하지만 우리가 실제 관찰한 데이터는 로그 오즈 값이 아닌 이진 출력값이므로 이 방정식을 피팅하기 위해서는 특별한 확률 기법이 필요하다.
- 로지스틱 회귀는 선형회귀를 확장한 일반화선형모형(GLM)의 특별한 사례이다.
계수와 오즈비 해석하기
- 예를 들어 소득 대비 상환의 비율이 5에서 6만큼 증가했다고 하면, exp(0.08244) ≈ 1.09만큼 연체할 오즈비가 증가한다. 즉 소득 대비 상환 비율이 1 단위 증가할 때마다 연체할 오즈비가 약 1.09배 증가한다는 의미이다.
- 현재 연체 중인 최악의 차용인에 대한 가장 우수한 차용인의 오즈비는 exp(-4.61264) ≈ 0.01로 훨씬 더 적다. 즉 가장 신용이 불량한 차용인의 연체 위험도는 신용이 가장 좋은 차용자에 비해 100배 정도이다.
선형회귀와 로지스틱 회귀: 유사점과 차이점
로지스틱 회귀는 아래 두 가지 점에서 근본적인 차이가 있다.
- 모델을 피팅하는 방식(최소제곱을 사용할 수 없다)
- 모델에서 잔차의 특징과 분석
모델 피팅
- 선형회귀에서는 모델 피팅을 위해 최소제곱을 사용한다.
- 로지스틱 회귀분석에서는 닫힌 형태의 해가 없으므로 최대우도추정(MLE)을 사용하여 모델을 피팅해야 한다.
최대우도추정
: 우리가 보고 있는 데이터를 생성했을 때 가능성이 가장 큰 모델을 찾는 프로세스를 말한다.
- 알고리즘은 현재 파라미터에 기반하여 점수를 얻는 단계(피셔의 점수화)와 적합성을 향상시키는 방향으로 파라미터를 업데이트하는 단계를 계속적으로 반복하는 준뉴턴 최적화 메커니즘으로 동작한다.
모델 평가하기
- p 값을 해석할 때, 통계적인 유의성을 측정하는 지표로 보기보다는 변수의 중요성을 나타내는 상대적인 지표로 봐야 한다.
- 이진 응답변수가 있는 로지스틱 회귀모형은 RMSE나 R 제곱이 있을 수 없다. 대신 분류 문제에서 가장 일반적으로 사용되는 측정 지표들을 사용할 수 있다.
import statsmodels.formula.api as smf
formula = ('outcome ~ bs(payment_inc_ratio, df=8) + purpose_ + ' +
'home_ + emp_len_ + bs(borrower_score, df=3)')
model = smf.glm(formula=formula, data=loan_data, family=sm.families.Binomial())
results = model.fit()
print(results.summary())
잔차분석
- 로지스틱 회귀에서에서 편잔차는 회귀에서보다 덜 중요하긴 하지만, 비선형성을 검증하고 영향력이 큰 레코드들을 확인하는 데 여전히 유용하다.
분류 모델 평가하기
혼동행렬
: 분류 결과를 나타내는 가장 대표적인 행렬로, 응답 유형별로 정확한 예측과 잘못된 예측의 수를 한 번에 보여주는 표
# Confusion matrix
pred = logit_reg.predict(X)
pred_y = logit_reg.predict(X) == 'default'
true_y = y == 'default'
true_pos = true_y & pred_y
true_neg = ~true_y & ~pred_y
false_pos = ~true_y & pred_y
false_neg = true_y & ~pred_y
conf_mat = pd.DataFrame([[np.sum(true_pos), np.sum(false_neg)], [np.sum(false_pos), np.sum(true_neg)]],
index=['Y = default', 'Y = paid off'],
columns=['Yhat = default', 'Yhat = paid off'])
print(conf_mat)
# Yhat = default Yhat = paid off
# Y = default 14336 8335
# Y = paid off 8148 14523
print(confusion_matrix(y, logit_reg.predict(X)))
# [[14336 8335]
# [ 8148 14523]]
classificationSummary(y, logit_reg.predict(X),
class_names=logit_reg.classes_)
# Confusion Matrix (Accuracy 0.6365)
#
# Prediction
# Actual default paid off
# default 14336 8335
# paid off 8148 14523
정밀도, 재현율, 특이도
정밀도: 예측된 양성 결과의 정확도
∑ 참 양성 / (∑ 참 양성 + ∑ 거짓 양성)
재현율(=민감도): 양성 결과를 예측하는 모델의 능력
∑ 참 양성 / (∑ 참 양성 + 거짓 음성)
특이도: 음성 결과를 정확히 예측하는 능력
∑ 참 음성 / (∑ 참 음성 + ∑ 거짓 양성)
conf_mat = confusion_matrix(y, logit_reg.predict(X))
print('Precision', conf_mat[0, 0] / sum(conf_mat[:, 0]))
print('Recall', conf_mat[0, 0] / sum(conf_mat[0, :]))
print('Specificity', conf_mat[1, 1] / sum(conf_mat[1, :]))
ROC(Receiver Operating Characteristic) 곡선
- 재현율과 특이도 사이에는 트레이드오프 관계가 있다. 이러한 트레이드오프 관계를 표현하기 위한 지표가 바로 ROC 곡선이다.
- ROC 곡선은 x축의 특이도에 대한 y축의 재현율(민감도)을 표시한다.
- ROC 곡선은 레코드를 분류할 때 사용하는 컷오프 값을 바꿀 때 재현율과 특이도 사이의 트레이드오프 관계를 잘 보여준다.
fpr, tpr, thresholds = roc_curve(y, logit_reg.predict_proba(X)[:, 0],
pos_label='default')
roc_df = pd.DataFrame({'recall': tpr, 'specificity': 1 - fpr})
ax = roc_df.plot(x='specificity', y='recall', figsize=(4, 4), legend=False)
ax.set_ylim(0, 1)
ax.set_xlim(1, 0)
ax.plot((1, 0), (0, 1))
ax.set_xlabel('specificity')
ax.set_ylabel('recall')
plt.tight_layout()
plt.show()
- 주황색 선은 랜덤으로 예측했을 때의 결과를 의미한다. 극단적으로 효과적인 분류기는 ROC 곡선이 왼쪽 상단에 가까운 형태를 보일 것이다. 이 모델에서 적어도 50% 정도의 특이도를 워한다면 재현율은 약 75%가 될 것이다.
- ROC 곡선과 함께, 정밀도-재현율(PR) 곡선을 사용하기도한다. ROC 곡선과 마찬가지 방법으로 PR 곡선도 구할 수 있다. 확률이 낮은 경우에서 높은 경우로 데이터를 정렬한 후에, 차례대로 정밀도와 재현율을 계산한다. PR 곡선은 클래스 간 데이터 불균형이 심할 때 특히 유용하다.
AUC(Area Underneath the Curve)
- ROC 곡선은 분류기 성능을 나타내는 어떤 하나의 값을 주지는 않는다. 하지만 ROC 곡선을 이용해 곡선 아래 면적이라는 지표를 구할 수 있다.
- AUC는 간단히 말해 ROC 곡선의 아래쪽 면적을 의미한다. AUC 값이 높을수록 더 좋은 분류기라고 할 수 있다.
- 최악의 분류기는 ROC 곡선이 가운데를 지나가는 직선인 경우, 즉 AUC가 0.5인 경우이다.
print(np.sum(roc_df.recall[:-1] * np.diff(1 - roc_df.specificity)))
print(roc_auc_score([1 if yi == 'default' else 0 for yi in y], logit_reg.predict_proba(X)[:, 0]))
# 0.691710795288669
# 0.6917108731135808
리프트
- 예를 들면, 상위 10%의 레코드를 1로 분류하는 알고리즘이, 무작위로 선택하는 경우 0.1%의 정확도를 얻은 반면, 상위 10%에서 0.3%의 결과를 얻었다면, 이 알고리즘은 상위 10%에서 3의 리프트(다른 표현으로 이득(gain))를 갖는다고 할 수 있다. 이 값은 매 십분위수마다 혹은 데이터 범위에서연속적인 값을 따라 얻을 수 있다.
- 리프트 차트를 계산하려면 y축에 재현율을 그리고 x축에 총 레코드 수를 나타내는 누적이득 차트를 작성해야 한다.
- 리프트 곡선은 임의 선택을 의미하는 대각선에 대한 누적이득의 비율을 말한다.
- 리프트 곡선은 레코드를 1로 분류하기 위한 확률 컷오프 값에 따른 결과의 변화를 한 눈에 볼 수 있게 해준다. 적합한 컷오프 값을 결정하기 위한 중간 단계로 활용할 수 있다.
불균형 데이터 다루기
과소표본추출(다운샘플링)
- 다수의 클래스에 속한 데이터들 중에 중복된 레코드가 많을 것이라는 사실에서 출발한다. 작지만 더 균형 잡힌 데이터는 모델 성능에 좋은 영향을 주고, 데이터를 준비하는 과정이나 모델을 검증하는과정이 좀 더 수월해진다.
과잉표본추출과 상향/하향 가중치
- 과소표본 방식의 약점은 데이터의 일부가 버려지기 때문에 모든 정보를 활용하지 못한다는 점이다.
- 이럴 경우, 다수 클래스를 과소표본추출하는 대신, 복원추출 방식(부트스트래핑)으로 회귀 클래스의 데이터를 과잉표본추출(업샘플링)해야 한다.
- 데이터에 가중치를 적용하는 방식으로 이와 비슷한 효과를 얻을 수 있다. 많은 분류 알고리즘에서 상향/하향 가중치를데이터에 적용하기 위해 weight라는 인수를 지원한다.
데이터 생성
- 부트스트랩을 통한 업샘플링 방식의 변형으로 기존에 존재하는 데이터를 살짝 바꿔 새로운 레코드를 만드는 데이터 생성 방법이 있다. 비슷하지만 기존의 데이터와 다른 데이터를 생성해 좀 더 로버스트한 분류 규칙을 배울 수 있는 기회를 주고자 하는 것이다.
- 합성 소수 과잉표본 기법(SMOTE; Synthetic Minority Oversampling TEchnique)은 발표와 동시에 주목을 받았다. SMOTE 알고리즘은 업샘플링된 레코드와 비슷한 레코드를찾고, 원래 레코드와 이웃 레코드의 랜덤 가중평균으로 새로운 합성 레코드를 만든다. 여기에 대해 각각의 예측 변수에 대해 개별적으로 가중치를 생성한다. 새로 합성된 업샘플 레코드의 개수는 데이터의 균형을 맞추기 위해 필요한 업샘플링 비율에 따라 달라진다.
X_resampled, y_resampled = SMOTE().fit_resample(X, y)
print('percentage of loans in default (SMOTE resampled): ',
100 * np.mean(y_resampled == 'default'))
full_model = LogisticRegression(penalty="l2", C=1e42, solver='liblinear')
full_model.fit(X_resampled, y_resampled)
print('percentage of loans predicted to default (SMOTE): ',
100 * np.mean(full_model.predict(X) == 'default'))
X_resampled, y_resampled = ADASYN().fit_resample(X, y)
print('percentage of loans in default (ADASYN resampled): ',
100 * np.mean(y_resampled == 'default'))
full_model = LogisticRegression(penalty="l2", C=1e42, solver='liblinear')
full_model.fit(X_resampled, y_resampled)
print('percentage of loans predicted to default (ADASYN): ',
print( 100 * np.mean(full_model.predict(X) == 'default')))
# percentage of loans in default (SMOTE resampled): 50.0
# percentage of loans predicted to default (SMOTE): 29.55653529132323
# percentage of loans in default (ADASYN resampled): 48.56040383751355
# 27.095435338828374
# percentage of loans predicted to default (ADASYN): None
- 파이썬 패키지 imbalanced-learn에는 사이킷런과 호환되는 API를 사용하여 다양한 메서드가 구현되어있다. 과잉잉표본추출과 과소표본추출을위한 다양한 메서드와 부스팅과 배깅 분류기와 같은 기술을 위한 지원을 제공한다.