비용 민감 모델은 학습 목적식이 정확도를 최대화하는 것을 방지하기 위한 방법이다.

→클래스 불균형 발생 원인 두번째 case를 처리하기 위한 방법

 

비용 민감 모델이란?

일반적으로 학습 목적식 같은 경우 위음성 비용과 위양성 비용을 같다고 본다. 즉, 위음성 비용과 위양성 비용을 구분하지 않는다. 하지만 위음성 비용(암환자의 생명)이 위양성 비용(정상인의 돈과 시간)보다 훨씬 크다. 

→비용 민감 모델은 학습 목적식에서 위음성 비용과 위양성 비용을 다르게 설정하는 모델로, 보통 위음성 비용(소수클래스, 우리의 관심 대상이므로 긍정 클래스)을 위양성 비용(다수클래스, 우리의 관심 대상이 아니므로 부정 클래스)보다 크게 설정한다.

위음성 비용을 위양성 비용보다 w배 크게 설정하는데 w는 1보다 커야한다. w가 1이면 일반적인 모델과 같고, w가 1보다 작으면 부정 클래스에 더 초점을 두는 모델이 된다.

즉, 위음성 비용 = w x 위양성 비용(w > 1)로 설정한 모델을 비용 민감 모델이라고 한다.


비용 민감 모델을 구현할 때 확률 모델과 비확률 모델로 구분해서 알아볼 것이다.

 

확률 모델

확률 모델은 학습 목적식 자체를 바꾸는 것이 아니고 학습의 결과를 바꾸는 방식이다. 로지스틱 회귀, 나이브 베이즈 등의 확률 모델들은 cut-off value, c를 조정하는 방식으로 비용 민감 모델을 구현할 수 있다.

x에 대한 y의 확률이 c보다 크거나 같으면 긍정으로 보고, c보다 작으면 부정으로 본다. 그리고 c를 처음 설정할 때는 보통 0.5로 둔다. 예를 들어, y가 1과 -1이 있을 때 x에 대한 y가 1일 확률이 0.5 이상이면 1로 볼테고 0.5 미만이면 -1로 본다.

 

c값을 작게 둘수록 긍정 클래스로 분류하는 경우가 많아질 것이다. 즉, 긍정 클래스에 대한 결정 공간이 넓어진다는 소리이다. 그렇기 때문에 c값을 0.5보다 작게 설정한다.

→ 우리의 관심 대상인 긍정 클래스 비율이 커질 것이다.

위 그래프와 같이 c값이 작을수록 재현율이 커질테고 클수록 정확도가 커질것이고 그 반대의 경우도 마찬가지로 성립한다.

 

그리고 정확한 확률 추정은 불가능하지만 그 개념을 도입할 수 있는 모델(k-최근접 이웃, 신경망, 의사결정나무, 앙상블 모델 등)에도 역시 적용이 가능하다.

 

●관련 문법: predict_proba

확률 모델은 학습 목적식 자체를 바꾸는 것이 아니고 학습의 결과를 바꾸는 방식으로 predict_proba를 응용한다.

predict_proba는 sklearn의 확률 모델이 갖는 메서드(fit 이후)로서, X(새로 들어온 데이터, 평가 데이터 x_test)를 입력으로 받아 각 클래스에 속할 확률을 출력한다.

X(새로 들어온 데이터, 평가 데이터 x_test)에 포함된 모든 행에 대해 부정클래스와 긍정클래스에 속할 확률들을 출력해준다.

(참고: predict는 각 클래스에 속할 확률이 아닌 다이렉트로 예측값을 출력해준다, 예를 들어, 부정클래스 확률이 0.3 긍정 클래스 확률이 0.7이면 긍정클래스 확률이 더 크기 때문에 다이렉트로 긍정클래스(1)로 분류를 해주고 반대로 부정클래스 확률이 0.7이고 긍정 클래스 확률이 0.3이면 부정클래스 확률이 더 크기 때문에 다이렉트로 부정클래스(-1)로 분류를 해준다.)

 

X(새로 들어온 데이터, 평가 데이터 x_test)에 포함된 모든 행에 대해 부정클래스와 긍정클래스에 속할 확률들을 출력해준

뒤 긍정클래스에 속할 확률이 나오는 컬럼인 Pr(Pos l x)의 값이 c보다 크거나 같으면 긍정클래스로 볼 것이고 c보다 작으면 부정클래스로 볼 것이다.

이것을 구현 할때 필요한 개념이 있다. Numpy와 Pandas를 사용할 때 가능하면 배열 단위 연산을 하는 것이다.

다시말해, 유니버설 함수, 브로드캐스팅, 마스크 연산을 최대한 활용해야 한다.

(예시) 확률 모델의 cut_off_value를 0.3으로 설정하기

긍정클래스에 속할 확률이 나오는 컬럼인 Pr(Pos l x)의 값을 0.3보다 크거나 같은지를 본다. 여기서 긍정클래스에 속할 확률이 나오는 컬럼인 Pr(Pos l x)의 값들은 배열이고 0.3은 하나의 스칼라 숫자로 브로드캐스팅을 통해서 각각의 값들이 비교가 될 것이다. 그리고 0.3 이상인 것은 True를 그렇지 않은 것은 False로 나올 것이다. 

그 후 클래스가 1과 -1이다라고 하면 False를 -1로 True를 1로 바꿔야한다. replace를 사용해서 .replace{"False":-1, "True":1}로 바꿀수 있지만 2*R-1로 단순한 연산을 통해 바꿀 수도 있다.(False는 0, True는 1이기 때문)

(부울을 -1과 1로 바꾸는 테크닉 2*R-1 도 알아두면 도움이 된다.)

 

비확률 모델

비확률 모델은 확률 모델과 다르게 학습 목적식 자체를 바꾸는 것이다.

 

(1) 서포트 벡터 머신

일반 서포트 벡터 머신의 목적식에서 왼쪽에 있는 llwll가 마진을 최대화하는 부분, 오른쪽 C*크사이i가 오차를 최소화 하는 부분이다.

크사이i는 샘플i에 대한 오차이다. 다시 말해, 샘플i에 대한 오차가 위양성비용인지 위음성비용인지 구분이 안되있고 샘플i에 대한 그냥 오차이다. 

비용 민감 서포트 벡터 머신 목적식에서 비용을 두 개로 구분했다. 

yi가 1일 때 발생하는 비용
yi가 -1일 때 발생하는 비용

yi가 1일 때 발생하는 비용이라는 것은 yi가 1인데 예측을 -1로 했다는 소리이다. 즉, 긍정을 부정으로 오분류한 비용이다.

그러면 yi가 -1일 때 발생하는 비용은 부정을 긍정으로 오분류한 비용이다.

우리가 관심이 있는 것은 긍정을 부정으로 오분류한 비용으로 C1을 C2보다 크게 둬야한다.

 

(2)의사결정나무

소수 클래스에 대한 가중치를 부여하는 방식으로 가능하면 소수 클래스로 분류하도록 유도하고 k-최근접 이웃, 앙상블 모델(랜덤포레스트, xgboost, lightgbm)에도 똑같이 정의할 수 있다. 

동그라미가 다수클래스(우리의 관심 대상이 아닌 부정클래스)고 세모가 소수클래스(우리의 관심 대상 긍정클래스)라고 하면

w1을 1로 두고, w2는 긍정이 부정보다 중요한 정도를 나타내도록 설정한다. 보통은 w1을 1로 두고 w2를 클래스 불균형 비율, 클래스 불균형 비율보다 조금 작은 정도로 두는 경우가 많다.

 

●관련 문법: class_weight

의사결정나무, 서포트벡터머신, 랜덤포레스트, xgboost, lightgbm 등에서는 class_weight를 설정함으로써 비용 민감 모델을 구현할 수 있다. class_weight는 사전 형태로 입력한다.▷key: class 이름

▷value: class weight

(예시) SVC(class_weight = {1:10, -1:1}) → 클래스 1에 클래스 -1보다 10배의 가중치를 부여

 


## 코드 실습 ##

●확률 모델의 비용 민감 모델 구현

import os
os.chdir(r"C:\Users\82102\Desktop\데이터 전처리\머신러닝 모델의 성능 향상을 위한 전처리\데이터")

import pandas as pd
df = pd.read_csv("Secom.csv")
df.head()

# 특징과 라벨 분리
X = df.drop('Y', axis=1)
Y = df['Y']
# 학습 데이터와 평가 데이터 분할
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(X, Y)
# 클래스 불균형 확인
y_train.value_counts()

# 클래스 불균형 비율 계산
y_train.value_counts().iloc[0] / y_train.value_counts().iloc[-1]

# KNN을 사용한 클래스 불균형 테스트
from sklearn.neighbors import KNeighborsClassifier as KNN
from sklearn.metrics import *

KNN_model = KNN(n_neighbors = 11).fit(x_train, y_train)
pred_y = KNN_model.predict(x_test)
print("정밀도:", recall_score(y_test, pred_y))
print("정확도:", accuracy_score(y_test, pred_y))
# 재현율이 0%로 불균형이 심각한 수준이라 판단

# 비용 민감 모델 적용전 logistic regression 모델 테스트
from sklearn.linear_model import LogisticRegression as LR

# max_iter는 Gradient Descent 방식을 반복해서 몇번 수행할 것인가 인데, 이게 일단 수렴(Convergence)하게 되면 횟수를 늘려도 성능이 거의 달라지지 않음
# max_iter는 경사하강법을 통해 weight값을 최적화 즉, 가장 최적화된 모델을 만들기 위해서 경사하강법을 몇 번 수행 할 것인지에 대한 설정
model = LR(max_iter = 100000).fit(x_train, y_train)
pred_y = model.predict(x_test)
print("정밀도:", recall_score(y_test, pred_y))
print("정확도:", accuracy_score(y_test, pred_y))

# cut off value 조정
probs = model.predict_proba(x_test)
probs = pd.DataFrame(probs, columns = model.classes_)

cut_off_value = 0.3

pred_y = 2 * (probs.iloc[:, -1] >= cut_off_value) -1
print("재현율:", recall_score(y_test, pred_y))
print("정확도:", accuracy_score(y_test, pred_y))

# cut off value를 조정하는 함수 작성
def cost_sensitive_model(model, cut_off_value, x_test, y_test):
    probs = model.predict_proba(x_test)
    probs = pd.DataFrame(probs, columns = model.classes_)
    pred_y = 2 * (probs.iloc[:, -1] >= cut_off_value) -1
    recall = recall_score(y_test, pred_y)
    accuracy = accuracy_score(y_test, pred_y)
    return recall, accuracy
# cut off value에 따른 recall과 accuracy 변화 확인
from matplotlib import pyplot as plt
import numpy as np

cut_off_value_list = np.linspace(0, 1, 101)
recall_list = []
accuracy_list = []

for c in cut_off_value_list:
    recall, accuracy = cost_sensitive_model(model, c, x_test, y_test)
    recall_list.append(recall)
    accuracy_list.append(accuracy)

%matplotlib inline
plt.plot(cut_off_value_list, recall_list, label = 'recall')
plt.plot(cut_off_value_list, accuracy_list, label = 'accuracy')
plt.legend()
# 그래프를 통해 대략적인 정확도와 재현율의 변화를 보고 cut off value를 찍어보면서 조정해본다

 

 

●비확률 모델의 비용 민감 모델 구현

import os
os.chdir(r"C:\Users\82102\Desktop\데이터 전처리\머신러닝 모델의 성능 향상을 위한 전처리\데이터")

import pandas as pd
df = pd.read_csv("page-blocks0.csv")
df.head()

# 특징과 라벨 분리
X = df.drop('Class', axis=1)
Y = df['Class']
# 학습 데이터와 평가 데이터 분리
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(X, Y)
# 클래스 불균형 확인
y_train.value_counts()

# 라벨이 문자이니 변환
y_train.replace({"negative":-1, "positive":1}, inplace=True)
y_test.replace({"negative":-1, "positive":1}, inplace=True)
# 클래스 불균형 비율 계산
y_train.value_counts().iloc[0] / y_train.value_counts().iloc[-1]

# KNN을 사용한 클래스 불균형 테스트
from sklearn.neighbors import KNeighborsClassifier as KNN
from sklearn.metrics import *

KNN_model = KNN(n_neighbors = 11).fit(x_train, y_train)
pred_y = KNN_model.predict(x_test)
print("재현율:", recall_score(y_test, pred_y))
print("정확도:", accuracy_score(y_test, pred_y))
# 재현율을 보니 불균형이 심각한 수준은 아니라고 판단

# 비용 민감 모델 적용전 support vector machine 모델 테스트(클래스 웨이트 조정 전)
from sklearn.svm import SVC

model = SVC().fit(x_train, y_train)
pred_y = model.predict(x_test)
print("재현율:", recall_score(y_test, pred_y))
print("정확도:", accuracy_score(y_test, pred_y))

# 클래스 웨이트 조정
model = SVC(class_weight = {1:8, -1:1}).fit(x_train, y_train)
pred_y = model.predict(x_test)
print("재현율:", recall_score(y_test, pred_y))
print("정확도:", accuracy_score(y_test, pred_y))

 

cut off value랑 class weight 값도 튜닝하면 좋을 것 같긴한데...

+ Recent posts