【練習問題】毒キノコの分類【データコンペ01】

コンペ概要

キノコが食用か有毒かを分類する

コンペ詳細

目標

Y : 毒キノコか否か(毒キノコ=p, 食用キノコ=e)

データ

  • 学習用データ (train.tsv)
  • 評価用データ (test.tsv)
  • 応募用サンプルファイル (sample_submit.csv)

課題種別:分類
データ種別:多変量
学習データサンプル数:4062
説明変数の数:22
欠損値:あり

評価指標

Accuracy:正解率。判定が正しい割合。

情報公開ポリシー

モデル  : 公開可
分析結果 : 公開可

学習の目的

選択理由

練習問題とは言え自身の力のみでの参加は初めてなので、簡単そうなものを選びました。 (表形式/テーブル数少)

目的・目標

  • 本を参考にしながらデータ分析に慣れること。
  • 学んだことを少しずつ調べながらパラメータなど理解したい。
  • 精度80%を目標。
  • コンペ終了後に振り返りをする。
  • 楽しんで走りきること!!!

分析

ベースライン作成

ベースラインにはキノコ自体の特徴ではなく生えている環境や生え方の2つ("habitat","population")を説明変数として使っていきます。 毒性を持つかどうかを判断するときに見た目だけでは判断できかねるという話を聞いたことがあります。 なので見た目以外の指標であるこの2つを選びました。

#ライブラリインポート
import numpy as np
import pandas as pd
import lightgbm as lgb
from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import accuracy_score
import warnings
warnings.filterwarnings("ignore")

#データのインポート、データの簡単な確認
df_train = pd.read_csv("../input/train.tsv",sep="\t")
df_train.head()
df_train.info()
df_train.isnull().sum()

#省略

df_train["Y"].value_counts()
#Yに大きな偏りはない
#e    2103
#p    1959
#ベースライン検証用のデータを分けておく
X, X_, y, y_ = train_test_split(X, y, test_size=0.2, shuffle=True, stratify=y, random_state=123)
print([i.shape for i in [X, X_, y, y_]])
#[(3249, 22), (813, 22), (3249, 1), (813, 1)]

#train,valデータを作成
X_tr_val = X[["habitat","population"]].reset_index(drop=True)
y_tr_val = y[["Y"]].reset_index(drop=True)
#objectをcategoryに変更
for col in X_tr_val.columns:
    X_tr_val[col] = X_tr_val[col].astype("category")
#パラメータ設定
params = {
    "boosting_type":"gbdt",
    "objective":"binary",
    "metric":"auc",
    "learning_rate":0.1,
    "num_leaves":16,
    "n_estimators":10000,
    "random_state":123,
    "importance_type":"gain",
    "verbose":-1
}
#クロスバリデーション検証
metrics = []
imp = pd.DataFrame()

for nfold in np.arange(n_sp):
    print("-"*20,nfold,"-"*20)
    idx_tr,idx_val = cv[nfold][0], cv[nfold][1]
    X_tr, y_tr = X_tr_val.loc[idx_tr, :], y_tr_val.loc[idx_tr, :]
    X_val, y_val = X_tr_val.loc[idx_val, :], y_tr_val.loc[idx_val, :]
    
    print([i.shape for i in [X_tr, y_tr, X_val, y_val]])
    
    model = lgb.LGBMClassifier(**params)
    model.fit(X_tr, y_tr, eval_set=[(X_tr, y_tr),(X_val, y_val)],
              callbacks=[lgb.early_stopping(stopping_rounds=100,verbose=True),lgb.log_evaluation(100)])
    
    y_tr_pred = model.predict(X_tr)
    y_val_pred = model.predict(X_val)
    metric_tr = accuracy_score(y_tr, y_tr_pred)
    metric_val = accuracy_score(y_val, y_val_pred)
    metrics.append([nfold, metric_tr, metric_val])
    
    _imp = pd.DataFrame({"col":X_tr.columns,"imp":model.feature_importances_,"nfold":nfold})
    imp = pd.concat([imp,_imp], axis=0,ignore_index=True)

metrics = np.array(metrics)
print(metrics)
print(f'tr:{metrics[:,1].mean()}+-{metrics[:,1].std()}')
print(f'val{metrics[:,2].mean()}+-{metrics[:,2].std()}')

#-------------------- 0 --------------------
#[(2599, 2), (2599, 1), (650, 2), (650, 1)]
#Training until validation scores don't improve for 100 #rounds
#[100] training's auc: 0.893991    valid_1's auc: #0.896274
#省略
#tr:0.8017845325125048+-0.0049599711034794495
#val0.8017750385208012+-0.019853217905296917
#ベースライン検証用データにも同様の処理をして確認
X_val2 = X_[["habitat","population"]].reset_index(drop=True)
y_val2 = y_[["Y"]].reset_index(drop=True)

for col in X_val2.columns:
    X_val2[col] = X_val2[col].astype("category")

y_val2_pred = model.predict(X_val2)
print("val2",accuracy_score(y_val2, y_val2_pred))
#val2 0.8044280442804428
#似たようなスコアなのでよい感じに学習できている
#推論用のデータにも同様の処理
df_test = pd.read_csv("../input/test.tsv",sep="\t")

X_test = df_test[["habitat","population"]].reset_index(drop=True)
id_test = df_test[["id"]]

for col in X_test.columns:
    X_test[col] = X_test[col].astype("category")

y_test_pred = model.predict(X_test)

df_submit = pd.DataFrame({"id":id_test["id"],"Y":y_test_pred})
display(df_submit)
df_submit.to_csv("../output/submission_baseline.csv",index=None,header=None)
#スコア:0.7998523

2つの説明変数で目標の80%に迫るスコアがでて驚いた。さすがの練習問題。 いくつか特徴量を追加していきたいとおもう。

特徴量生成

#関数化
def train_cv(input_X, input_y, input_id, params, n_sp):
    metrics = []
    imp = pd.DataFrame()

#省略

    imp = imp.groupby("col")["imp"].agg(["mean","std"])
    imp.columns = ["imp","imp_std"]
    imp = imp.reset_index(drop=False)
    
    print('Done')
    
    return model,imp, metrics
df_col = pd.DataFrame()
for i in X.columns:
    if i == "habitat" or i == "population":
        continue
    print(i)

    X_tr_val = X[["habitat","population",i]].reset_index(drop=True)
    y_tr_val = y[["Y"]].reset_index(drop=True)
    for col in X_tr_val.columns:
        X_tr_val[col] = X_tr_val[col].astype("category")

    params = {
        "boosting_type":"gbdt",
        "objective":"binary",
        "metric":"auc",
        "learning_rate":0.1,
        "num_leaves":16,
        "n_estimators":10000,
        "random_state":123,
        "importance_type":"gain",
        "verbose":-1
    }
    n_sp = 5

    model,imp,metrics = train_cv(X_tr_val, y_tr_val, id_train, params, n_sp)
    
    _df_col = pd.DataFrame({i:[metrics[:,1].mean(),metrics[:,2].mean()]}).T
    df_col = pd.concat([df_col,_df_col],axis=0)

df_col.mean(axis=1).sort_values(ascending=False)[:5] 
#ベースラインに特徴をひとつ加えた時のスコアの上位5つ
#odor                 0.994151
#spore-print-color    0.959412
#ring-type            0.958449
#bruises              0.954217
#gill-color           0.940672
dtype: float64
X_tr_val = X[["habitat","population","odor","spore-print-color","ring-type","bruises","gill-color"]].reset_index(drop=True)
y_tr_val = y[["Y"]].reset_index(drop=True)

#省略

n_sp = 5

model,imp,metrics = train_cv(X_tr_val, y_tr_val, id_train, params, n_sp)

#tr:0.9980763311332762+-0.0019918706038352498
#val0.9969226028209078+-0.0019456424059182999
#すごいスコアが出た
#ベースライン検証用のデータで確認
X_val2 = X_[["habitat","population","odor","spore-print-color","ring-type","bruises","gill-color"]].reset_index(drop=True)
y_val2 = y_[["Y"]].reset_index(drop=True)
for col in X_val2.columns:
    X_val2[col] = X_val2[col].astype("category")

y_val2_pred = model.predict(X_val2)
print("val2",accuracy_score(y_val2, y_val2_pred))
#val2 0.995079950799508
#似たようなスコアなので過学習でもなさそう。
#テストデータにも同様の処理
X_test = df_test[["habitat","population","odor","spore-print-color","ring-type","bruises","gill-color"]].reset_index(drop=True)
id_test = df_test[["id"]]

for col in X_test.columns:
    X_test[col] = X_test[col].astype("category")

y_test_pred = model.predict(X_test)

df_submit = pd.DataFrame({"id":id_test["id"],"Y":y_test_pred})
display(df_submit)
df_submit.to_csv("../output/submission_feature_engineerring1.csv",index=None,header=None)
#スコア:0.9985229
#目標達成

振り返り

調べてみるとロジスティック回帰でスコア1.00がでているそうです。練習問題なのでそこまで他の解法だったり特徴選択なり目覚ましいものは見つけられず。

反省点

■ベースラインの特徴選択■
ベースライン作成時、最初の説明変数の選び方がよくわからなかった。 仮説を立てていくつか選んで説明変数を使うのが良いと感じた。今回であればキノコの生え方と環境を選んだ。
■データ分析になれていない■
そもそも手が、考え方が慣れていない。数をこなしていこう。 次はもうちょいむずかしめでもいいカモ。
■振り返りができず■
そもそもほかの人が公開している情報が少ない。 次は情報がありそうなコンペ練習問題を選んでみましょうか。

最後に

コンペの練習問題をまとめてみたが、コードなどを載せずに考え方だけでもいいのでしょうか。
おそらく少し難易度があがるだけでもコード量がすごいことになるので、考え方や進め方、気を付けたことなどをまとめたほうがいいと感じました。
次からそうしようと思います。

とりあえず最初のコンペ練習問題を走り切ったのでよいということで。