캐글 competition을 도입하면서 피드백을 받기 위해 열린 대회이다.
AUC
이진 분류 문제로 열의 의미가 익명화되어 있는 캐글에서 생성한 가상 데이터이다.
>>> print(f'train: {train.shape}, test: {test.shape}')
train: (262144, 258), test: (131072, 257)
print(f"정답 비율:\n {train['target'].value_counts()}")
정답 비율이 비슷하여 딱히 문제 없어보인다.
train['target'].cumsum().plot()
선형으로 나오는 것을 보니 정답에 따라 잘 섞여있는 것 같다.
>>> 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()
object는 id string이였고 int는 하나는 타겟, 하나는 특성이였다.
train_columns = [c for c in train.columns if c not in ('id', 'target')]
train[train_columns].nunique().sort_values()
각 열의 중복되지 않는 값을 세어보니 아까 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')
여기 코드에는 없지만 더 살펴보면 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)
대부분 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()
일단 25개만 표시했는데 여기서는 정답값에 따라 분포가 바뀌는 특성은 없다.
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)
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
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]))
데이터 정체가 모두 익명화되어 있어서 데이터 분포를 유심히 지켜봐야 했다.
특이하게도 이 데이터셋에서는 특정 열 값에 따라서 모델을 따로 만들어야 했다.