Kaggle: Instant Gratification

유승우·2023년 9월 26일

노션 링크

소개

대회 목적

캐글 competition을 도입하면서 피드백을 받기 위해 열린 대회이다.

평가 지표

AUC

데이터 소개

이진 분류 문제로 열의 의미가 익명화되어 있는 캐글에서 생성한 가상 데이터이다.

Untitled

풀이

EDA

데이터 행/열 확인

>>> print(f'train: {train.shape}, test: {test.shape}')
train: (262144, 258), test: (131072, 257)

정답 비율 확인

print(f"정답 비율:\n {train['target'].value_counts()}")

Untitled

정답 비율이 비슷하여 딱히 문제 없어보인다.

정답 누출 확인

train['target'].cumsum().plot()

Untitled

선형으로 나오는 것을 보니 정답에 따라 잘 섞여있는 것 같다.

NULL값 확인

>>> print(f"전체 데이터 중 NULL 개수: {train.isnull().sum().sum()}")
전체 데이터 중 NULL 개수: 0

없다.

데이터 유형 살펴보기

>>> train.dtypes.value_counts()   #train.info()
float64    255
int64        2
object       1

특이한 int64와 object를 살펴보자.

train.select_dtypes(include=['object', 'int64']).head()

Untitled

object는 id string이였고 int는 하나는 타겟, 하나는 특성이였다.

각 특성 살펴보기

train_columns = [c for c in train.columns if c not in ('id', 'target')]
train[train_columns].nunique().sort_values()

Untitled

각 열의 중복되지 않는 값을 세어보니 아까 int형이였던 특성만 특이하게도 값이 적다.

wheezy-copper-turtle-magic 을 자세히 살펴보자.

#개수를 세고 index에 이름을 붙여주고 index를 열로 넣으면서 다른 열에도 이름을 붙인다. (Series의 rest_index는 df의 그것과 좀 다른 듯)
train['wheezy-copper-turtle-magic'].value_counts().rename_axis(index='wheezy-copper-turtle-magic').reset_index(name='counts')

Untitled

여기 코드에는 없지만 더 살펴보면 512를 기준으로 정규분포를 이룬다.

다음으로 float형 데이터도 살펴보자.

train_columns = [c for c in train.columns if c not in ('id', 'target', 'wheezy-copper-turtle-magic')]

describe_train = train[train_columns].describe().drop("count", axis=0).T
cmap = sns.diverging_palette(5, 250, as_cmap=True)
describe_train.T.style.background_gradient(cmap, axis=1).set_precision(2)

Untitled

대부분 0부근에서 평균을 갖고 1.6~1.8정도의 표준편차를 갖는다.

그리고 정답값에 따라서 분포가 크게 다른 데이터가 있는지 살펴보자.

train_columns = [c for c in train.columns if c not in ('id', 'target')]
target_0_df = train.loc[train['target'] == 0]
target_1_df = train.loc[train['target'] == 1]

plt.figure(figsize=(48,36))
f, axes = plt.subplots(15,15,figsize=(16,12))
for i, name in enumerate(train_columns):
    ax = axes[i//15, i%15]
    sns.histplot(target_0_df[name], ax=ax)
    sns.histplot(target_1_df[name], ax=ax)    
    ax.title.set_text(name)
    ax.set_xlabel("")
    ax.legend(["0", "1"])
    
plt.tight_layout()
plt.show()

download.png

일단 25개만 표시했는데 여기서는 정답값에 따라 분포가 바뀌는 특성은 없다.

전략

code

hist model

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단계 모델 학습

wheezy-copper-turtle-magic의 값에 따라 모델을 다르게 만들 것이다.

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]))

마치며

배운 점 / 특이사항

데이터 정체가 모두 익명화되어 있어서 데이터 분포를 유심히 지켜봐야 했다.

특이하게도 이 데이터셋에서는 특정 열 값에 따라서 모델을 따로 만들어야 했다.

배운 점

  1. EDA의 과정
  2. 앙상블하는 법
  3. 상상도 못했을 특이한 점은 다른 사람들이 공유한 노트북에서 알 수 있다. 대회 중 다른 사람들의 노트북을 적극 활용하도록 하자.
profile
ㅎㅇㄹ

0개의 댓글