SIGNATE BIGINNER's 向けコンペ 肝疾患判定に残り1週間の段階で参加した

6 min読了の目安(約6000字TECH技術記事

SIGNATEのコンペに、締切約1週間前に参加した内容をまとめてみました。

【第2回_Beginner限定コンペ】健診データによる肝疾患判定
[https://signate.jp/competitions/293:embed:cite]

結果は精度0.83くらいで300位以下と微妙でしたが、何かのお役に立てれば幸いです。

コードはこちら

シンプルなlightGBM

精度0.83くらい

とりあえずlightGBM回しつつやってみようがコンセプト。

簡単に外れ値を除去して、Optunaのチューニングを使って、randam_stateの数値の違うものを3つ作り、アンサンブルしました。

lightGBM + Optunaのチューニング

import optuna.integration.lightgbm as lgb

params = {
    'boosting_type': 'gbdt',
    'metric': 'auc',
    'objective': 'binary',
    'verbosity': -1,
    'seed': 42,
    'learning_rate': 0.1,
    'bagging_fraction': 0.75,
    'bagging_freq': 10, 
    'colsample_bytree': 0.75,
}

model_lgb = lgb.train(params, train_set, valid_sets=val_set,
                  verbose_eval=50,
                  num_boost_round=1000,
                  early_stopping_rounds=100
                 )

Gender (性別)で分割して学習

精度0.82くらいで悪化

どうしたものか悩んでたところにSIGNATEのフォーラムを見たら、性別や年齢で分けて学習モデルを構築、というのを見かけたので試しました。

とりあえず性別で分けるだけなら簡単に作成できそうなので、性別から始めました。

# 男性データ
train_M = train[train["Gender"] == 1]
test_M = test[test["Gender"] == 1]

# 女性データ
train_F = train[train["Gender"] == 0]
test_F = test[test["Gender"] == 0]

# 男性データ
X_train_M, X_test_M, y_train_M, y_test_M = train_test_split(train_M[features], 
                                                    train_M["disease"], test_size=0.3, random_state=42)

# 女性データ
X_train_F, X_test_F, y_train_F, y_test_F = train_test_split(train_F[features], 
                                                    train_F["disease"], test_size=0.3, random_state=42)

Age (年齢) で分割して学習

精度0.73~0.83くらいをウロウロ

次に年齢で分けました。

カテゴリデータの性別と違って、何歳ごとにわけるのか?と悩みましたが、ヒストグラムを参考にいくつかのブロックに分けてみました。

train['Age'].hist()
plt.show()

# 0_30歳代
train_0_30 = train[train["Age_cls"] == "0_30"]
test_0_30 = test[test["Age_cls"] == "0_30"]

# 30_45歳代
train_30_45 = train[train["Age_cls"] == "30_45"]
test_30_45 = test[test["Age_cls"] == "30_45"]

# 45_60歳代
train_45_60 = train[train["Age_cls"] == "45_60"]
test_45_60 = test[test["Age_cls"] == "45_60"]

# 60_100歳代
train_60_100 = train[train["Age_cls"] == "60_100"]
test_60_100 = test[test["Age_cls"] == "60_100"]

ただ、年齢だと、区別する年齢を変えるといちいちコードを変更するのが大変そうだな・・・、と思い、良い方法がないか探しました。

Gender (性別) とAge (年齢)で分割して学習

精度0.8を下回ることも多かったですが、色々試して何とか精度0.836の一番いい結果を出す

区分する年齢を簡単に変えられるように、pd.cut()あたりを上手く使って処理することにしました。

split_list = [0, 50, 100]
label_split = []

for i in range(len(split_list)-1):
    label_split.append(str(split_list[i]) + "_" + str(split_list[i+1]))

label_split

['0_50', '50_100']

train["Age_cls"] = pd.cut(train['Age'], split_list, labels=label_split)
test["Age_cls"] = pd.cut(test['Age'], split_list, labels=label_split)

学習モデルもfor文を使って年齢別と性別をうまく一気に処理できるようにしました。

なお、性別(Gender)は、0 女性、1 男性にラベルエンコーディングしています。

# 年代別に学習
# 性別
for i in range(2):

    for j in label_split:
        train_tmp = train[train["Gender"]==i]
        test_tmp = test[test["Gender"]==i]

        train_tmp = train_tmp[train_tmp["Age_cls"]==j]
        test_tmp = test_tmp[test_tmp["Age_cls"]==j]
    
        # ホールドアウト法
        X_train, X_test, y_train, y_test = train_test_split(train_tmp[features],
                                                            train_tmp["disease"], train_size=0.7, test_size=0.3, random_state=42)

        # lightGBM用のデータセット作成
        train_set = lgb.Dataset(X_train, y_train)
        val_set = lgb.Dataset(X_test, y_test, reference=train_set)
    
        # 学習
        model_lgb = lgb.train(params, train_set, valid_sets=val_set,
                      verbose_eval=None,  # 50イテレーション毎に学習結果出力
                      num_boost_round=100,  # 最大イテレーション回数指定
                      early_stopping_rounds=100
                     )
    
        # 評価
        val_prob = model_lgb.predict(X_test, num_iteration=model_lgb.best_iteration)
        val_pred = np.round(val_prob)
    
        # 誤差
        print(f"性別{i}{j}歳代")
        print(f"正解率: {accuracy_score(y_test, val_pred)}")
        print(f"適合率: {precision_score(y_test, val_pred)}")
        print(f"再現率: {recall_score(y_test, val_pred)}")
        print(f"f1_score: {f1_score(y_test, val_pred)}\n")
    
        vali_list.update([(str(i)+" "+j, f1_score(y_test, val_pred))])
    
        # 予測データ作成
        y_prob = model_lgb.predict(test_tmp[features], num_iteration=model_lgb.best_iteration)
        y_pred = np.round(y_prob)
    
        test_tmp["disease"] = y_pred
        test_pred = pd.concat([test_pred, test_tmp]).sort_values('No')

女性のサンプル数がすくなく、validationデータの女性のスコアがなかなか上がらず、年齢・性別のあらゆる組み合わせを試しました。

xfeatを使った特徴量の自動掛け合わせ

精度0.817くらい

同じくフォーラムに、特徴量を機械的に組み合わせる手法も紹介されていたので、過去ブログにまとめてたxfeatを試しました。

[https://megane-man666.hatenablog.com/entry/xfeat/:embed:cite]

import xfeat
from xfeat import SelectNumerical, ArithmeticCombinations, Pipeline

encoder = Pipeline([
    SelectNumerical(exclude_cols=["Gender", "Age"]),
    ArithmeticCombinations(
        drop_origin=True,
        operator="*",
        r=2,
        output_suffix="",
    ),
])

train = encoder.fit_transform(train)
test = encoder.fit_transform(test)

こんな感じで特徴量が掛け合わされます。

相関係数の高い説明変数同士の片方を削る、というテクニックも途中から実践していたのですが、特徴量を掛け合わせると説明変数が増えすぎて、相関係数を目視で確認するのが大変になってしまいました。

train.corr()

うわっ…私の特徴量、多すぎ…?

できれば自動で削除する説明変数をピックアップするコードを組みたかったのですが、期限が近かったので諦めました。

次回以降のコンペでコードを組みたいと思います。

以上になります、最後までお読みいただきありがとうございました。