Kaggle: Instant Gratification

유승우·2023년 9월 26일
0

노션 링크

소개

대회 목적

캐글 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개의 댓글