본문 바로가기

캐글 메달리스트가 알려주는 캐글 노하우

캐글 메달리스트가 알려주는 캐글 노하우 2장

 

- 대회유형 : Featured

 

- 제출방식 : Code Copetition

 

- 주최 : Kaggle

 

- 문제유형 : 이진 분류

 

- 데이터 타입 : 정형(Tabular)

 

- 평가지표 : AUC

 

 

 

 

 

1. 평가지표 AUC?

AUC(Area Under the ROC Curve)는 분류 모델의 성능을 평가하는 데 사용되는 지표 중 하나입니다.

 

ROC(Receiver Operating Characteristic) 곡선은 이진 분류 모델의 성능을 시각화하는 데 사용되며,

 

ROC 곡선 아래의 면적을 AUC라고 합니다.

 

AUC는 모델이 정확하게 분류하는 능력을 나타내며, 보통 0에서 1 사이의 값을 가집니다. AUC1에 가까울수록 모델이 완벽하게 분류하는 것을 나타내며, 0.5에 가까울수록 모델의 성능이 랜덤 수준에 가깝습니다.

 

AUC를 해석하는 방법은 다음과 같습니다:

 

AUC1에 가까울수록 모델이 높은 신뢰도로 양성 및 음성 샘플을 분류합니다.

AUC0.5에 가까울수록 모델이 무작위로 예측하는 것과 유사하며, 성능이 좋지 않습니다.

AUC0에 가까울수록 모델이 반대로 분류하는 것입니다.

AUC는 불균형한 클래스 분포를 가진 데이터셋에서도 유용하며, 정확도나 정밀도 등과 함께 모델의 성능을 평가하는 데 널리 사용됩니다.

 

 

 

잠깐? ROC 곡선은?

 

 

 

ROC(Receiver Operating Characteristic) 곡선은 이진 분류 모델의 성능을 시각화하는 데 사용되는 그래픽 표현입니다. ROC 곡선은 민감도(Sensitivity)와 특이도(Specificity) 간의 관계를 나타내며, 이를 통해 분류 모델의 성능을 평가할 수 있습니다.

 

민감도(Sensitivity): 진짜 양성 비율(TPR, True Positive Rate)로서, 실제 양성 샘플 중 모델이 정확하게 양성으로 예측한 비율을 나타냅니다. 공식적으로는 TPR = TP / (TP + FN)으로 계산됩니다. 여기서 TP는 실제 양성 중에서 모델이 정확히 양성으로 예측한 수이고, FN은 실제 양성 중에서 모델이 잘못된 음성으로 예측한 수입니다.

특이도(Specificity): 진짜 음성 비율(TNR, True Negative Rate)로서, 실제 음성 샘플 중 모델이 정확하게 음성으로 예측한 비율을 나타냅니다. 공식적으로는 TNR = TN / (TN + FP)로 계산됩니다. 여기서 TN은 실제 음성 중에서 모델이 정확히 음성으로 예측한 수이고, FP는 실제 음성 중에서 모델이 잘못된 양성으로 예측한 수입니다.

 

TP, FN, FP, TN

 

ROC 곡선

ROC 곡선은 민감도(TPR)x축으로, 1-특이도(FNR)y축으로 하여 그립니다. 따라서 ROC 곡선은 (0,0)에서 (1,1)까지의 곡선 형태를 가집니다. 이 곡선은 모델의 임계값(Threshold)을 변화시키면서 TPRFPR(False Positive Rate) 간의 관계를 보여줍니다. FPR1에서 특이도를 빼서 구할 수 있으며, 공식적으로는 FPR = FP / (FP + TN)으로 계산됩니다.

 

 

2. 솔루션 소개

 

- 사용모델

     - 1단계 모델 : NuSVC, Quadratic Discriminant, SVC, KNeighborsClassifier, LogisticRegression

     - 2단계모델(앙상블) : LightGBM, MLPClassifier

 

- 코드 진행 과정 : EDA -> 스태킹 입력 데이터 제작 -> 모델 파라미터 값 정의 -> 솔루션 구체화 -> 모델 검증

- 새롭게 알게 된 개념 : 스태킹 , GMM

 

 

# 코드분석

 

히스트모델

class hist_model(object):

def __init__(self, bins=50):
self.bins = bins
def fit(self, X):



bin_hight, bin_edge = [], []



# 전치를 하는 이유 : 행이 아닌 열(칼럼 값, 변수 값, 피쳐 값)들을 빼내서 그 피쳐들을 분석해야하기 때문에

for var in X.T:
# get bins hight and interval
bh, bedge = np.histogram(var, bins=self.bins)
bin_hight.append(bh)
bin_edge.append(bedge)



self.bin_hight = np.array(bin_hight)

self.bin_edge = np.array(bin_edge)



def predict(self, X):



scores = []

for obs in X:

obs_score = []

for i, var in enumerate(obs):
# find wich bin obs is in

bin_num = (var > self.bin_edge[i]).argmin()-1
obs_score.append(self.bin_hight[i, bin_num]) # find bin hitght

scores.append(np.mean(obs_score))



return np.array(scores)

 

# 1단계 모델 학습

 

def run_model(clf_list, train, test, random_state, gmm_init_params='kmeans'):
    MODEL_COUNT = len(clf_list)
    
    oof_train = np.zeros((len(train), MODEL_COUNT))
    oof_test = np.zeros((len(test), MODEL_COUNT))    
    train_columns = [c for c in train.columns if c not in ('id', 'target', 'wheezy-copper-turtle-magic')]

    for magic in tqdm(range(512)):
        x_train = train[train['wheezy-copper-turtle-magic'] == magic]
        x_test = test[test['wheezy-copper-turtle-magic'] == magic]
        
#         print('Magic / train_shape / test_shape: ', magic, x_train.shape, x_test.shape)
        
        train_idx_origin = x_train.index
        test_idx_origin = x_test.index
        
        train_std = x_train[train_columns].std()
        useful_cols = train_std[train_std > 2].index
        
        y_train = x_train.target
        x_train = x_train.reset_index(drop=True)
        y_train = x_train.target
        x_train = x_train[useful_cols]
        x_test = x_test.reset_index(drop=True)[useful_cols]

        all_data = pd.concat([x_train, x_test])
        
        
        #Kernal PCA 차원을 줄이는 것이 아닌 cosine kernel에 맞게 변형
        all_data = KernelPCA(n_components=len(useful_cols), kernel='cosine', random_state=random_state).fit_transform(all_data)
        
        #GMM
        gmm = GMM(n_components=5, random_state=random_state, max_iter=1000, init_params=gmm_init_params).fit(all_data)
        gmm_pred = gmm.predict_proba(all_data)
        gmm_score = gmm.score_samples(all_data).reshape(-1,1)
        gmm_label = gmm.predict(all_data)
        
        #hist feature 빈도수가 정답 값과 연관 있을 때 좋은 성능
        hist = hist_model()
        hist.fit(all_data)
        hist_pred = hist.predict(all_data).reshape(-1, 1)
        
        #일반적인 방법은 아니지만 gmm을 여러번 추가했을 때 성능이 올라갔다고 한다.
        all_data = np.hstack([all_data, gmm_pred, gmm_pred, gmm_pred, gmm_pred, gmm_pred])
        # Add Some Features
        all_data = np.hstack([all_data, hist_pred, gmm_score, gmm_score, gmm_score])
        
        # STANDARD SCALER
        all_data = StandardScaler().fit_transform(all_data)

        # new train/test
        x_train = all_data[:x_train.shape[0]]
        x_test = all_data[x_train.shape[0]:]
        
        fold = StratifiedKFold(n_splits=5, random_state=random_state, shuffle=True)
        #일반적으로는 target 값 기준으로 나누지만 gmm label값을 기준으로 나눴더니 성능이 향상되었다고 함.
        for trn_idx, val_idx in fold.split(x_train, gmm_label[:x_train.shape[0]]):
            for model_idx, clf in enumerate(clf_list):
                clf.fit(x_train[trn_idx], y_train[trn_idx])
                oof_train[train_idx_origin[val_idx], model_idx] = clf.predict_proba(x_train[val_idx])[:, -1]
                
        if x_test.shape[0] == 0:
            continue
            
        oof_test[test_idx_origin, model_idx] += clf.predict_proba(x_test)[:, 1] / fold.n_splits

    for i, clf in enumerate(clf_list):
        print(clf, ':', end='')
        print(roc_auc_score(train['target'], oof_train[:, i]))
        print()

    oof_train_df = pd.DataFrame(oof_train)
    oof_test_df = pd.DataFrame(oof_test)
        
    return oof_train_df, oof_test_df

 

2단계 모델 앙상블

SEED_NUMBER = 4
NFOLD = 5

y_train = train['target'].reset_index(drop=True)
oof_lgbm_meta_train = np.zeros((len(train), SEED_NUMBER))
oof_lgbm_meta_test = np.zeros((len(test), SEED_NUMBER))
oof_mlp_meta_train = np.zeros((len(train), SEED_NUMBER))
oof_mlp_meta_test = np.zeros((len(test), SEED_NUMBER))

for seed in range(SEED_NUMBER):
    print("SEED Ensemble: ", seed)
    mlp16_params['random_state'] = seed
    lgbm_meta_param['seed'] = seed
    
    folds = StratifiedKFold(n_splits=NFOLD, shuffle=True, random_state=seed)
    for trn_idx, val_idx in folds.split(train_second, y_train):
        mlp_meta_model = neural_network.MLPClassifier(**mlp16_params)
        mlp_meta_model.fit(train_second[trn_idx], y_train[trn_idx])
        
        oof_mlp_meta_train[val_idx, seed] = mlp_meta_model.predict_proba(train_second[val_idx])[:, 1]
        oof_mlp_meta_test[:, seed] += mlp_meta_model.predict_proba(test_second)[:, 1] / NFOLD
        print("MLP META SCORE: ", roc_auc_score(y_train[val_idx], oof_mlp_meta_train[val_idx, seed]))

        train_dataset_lgbm = lgbm.Dataset(train_second[trn_idx], label=y_train[trn_idx], silent=True)
        val_dataset_lgbm = lgbm.Dataset(train_second[val_idx], label=y_train[val_idx], silent=True)

        lgbm_meta_model = lgbm.train(lgbm_meta_param, train_set=train_dataset_lgbm, valid_sets=[train_dataset_lgbm, val_dataset_lgbm], 
                                     verbose_eval=False, early_stopping_rounds=100)
        
        oof_lgbm_meta_train[val_idx, seed] = lgbm_meta_model.predict(train_second[val_idx])
        oof_lgbm_meta_test[:, seed] += lgbm_meta_model.predict(test_second)/NFOLD
        
        print("LGBM META SCORE: ", roc_auc_score(y_train[val_idx], oof_lgbm_meta_train[val_idx, seed]))