粗大メモ置き場

個人用,たまーに来訪者を意識する雑記メモ

Kaggle-titanicコンペのデータを用いてRandomForest,SVM,LightGBMを試す

前回のつづきです。

(注)本記事はコンペ目的ではなく,ライブラリの使用感を確かめているところで個人メモの範疇です。

ossyaritoori.hatenablog.com

前処理やデータなど

KaggleのTitanicを使っています。 前処理は前記事を参照してください。

RandomForest

例のごとくscikit learnのライブラリを用いていきます。

scikit-learn.org

前回の木構造を複数学習して多数決をとるような手法です。

Boostingなどのキーワードは調べておいても良いでしょう。

コード

デフォルトでは100本の木でアンサンブルしようとするので10本程度に収まるようにさせました。深さ4を上限としています。

import sklearn.tree as tree
from sklearn.ensemble import RandomForestClassifier

# RandomForest 10個のモデルの重ね合わせ
clf = RandomForestClassifier(max_depth=4,random_state=0,n_estimators=10)

ここから交差検証に入ります。5分割くらいでも良かったかも。

# 3分割交差検証を指定し、インスタンス化
from sklearn.model_selection import KFold
kf = KFold(n_splits=3, shuffle=True, random_state=0)

# スコアとモデルを格納するリスト
score_list = []
models = []

# 各分割ごとに評価
for fold_, (train_index, valid_index) in enumerate(kf.split(X_train, y_train)):    
    print(f'fold{fold_ + 1} start')
    train_x = X_train.iloc[train_index]
    valid_x = X_train.iloc[valid_index]
    train_y = y_train.iloc[train_index]
    valid_y = y_train.iloc[valid_index]
    
    ## 分割データで学習・予測・評価
    model = clf.fit(train_x, train_y)
    
    # データを用いて予測,記録
    predicted = model.predict(valid_x)
    score_list.append(accuracy_score(predicted,valid_y))
    models.append(model)
print(score_list, '平均score', round(np.mean(score_list), 3))

このように交差検証法で多数決をとることでデータの偏りの影響を低減できます。(ランダムフォレストの多数決とは別の多数決,ややこしいですね。)

もちろん図示も可能ですが,複数の木の多数決構造をとっているので可視化をする時はそのうちの一つに絞ります。 model.estimators_にリストで入ってるので適当な番号のを覗いてみましょう。

図は前回を参照にしてください。

重要度は全部の木について計算してくれているのかmodelから直接とることができます。

# bar plot
fig, ax = plt.subplots()
plt.grid()
ax.bar(train_x.columns,model.feature_importances_)
fig.autofmt_xdate() # make space for and rotate the x-axis tick labels
plt.show()

f:id:ossyaritoori:20200421010909p:plain
RandomForestで見た変数重要度。決定木と比べて Fareに重点が置かれています。

何本アンサンブルすればいいの?

木を増やせば汎化性能は上がりそうですが,計算量の問題もあります。 モデルの複雑性が増すと過学習しないかなという懸念もあります。

結構議論されているようですが,いくつか見て以下の意見を参考にしました。

How to determine the number of trees to be generated in Random Forest algorithm?

f:id:ossyaritoori:20200421011153p:plain
要約:原則木が多いほど性能が上がるが,上がり幅が小さくなっていく。個々のケースでは木が少ない場合が良い結果を出す可能性はあるかもね。10,30,100あたりで決めたら?

10本 RF 交差検証:0.808 本番:0.77990
30本 RF 交差検証:0.805 本番:0.76555
100本 RF 交差検証:0.805 本番:0.78468

確かに,若干は上がるようです。この辺は試行錯誤する必要があるので後で説明するgrid searchをすると良いでしょう。

SVMサポートベクターマシン

木構造とは異なりますが,SVMのような分類手法も有名です。

簡単に言うとデータ群を最適に分割する超平面を求める手法です。

www.slideshare.net

  • カーネルトリック:平面で分割とは言ってもデータの変数をかさ増しすることで非線形な分割も可能になります。例えば元の変数がx,yなら,x^2, xy ,y^2も変数に加えた空間で分割すれば元の空間では直線ではなくなります。ポイントは内積計算が楽になるようにこのかさ増しを工夫することです。

sklearnではデフォルトでRBFカーネルというものが使われています。

参考:

[python 機械学習初心者向け] scikit-learnでSVMを簡単に実装する - Qiita

カーネル近似(クラス分類)【Pythonとscikit-learnで機械学習:第2回】

コード

from sklearn.svm import SVC

clf = SVC(kernel='rbf', C=1, gamma=0.1)


# 3分割交差検証を指定し、インスタンス化
from sklearn.model_selection import KFold
kf = KFold(n_splits=3, shuffle=True, random_state=0)

# スコアとモデルを格納するリスト
score_list = []
models = []

# 各分割ごとに評価
for fold_, (train_index, valid_index) in enumerate(kf.split(X_train, y_train)):    
    print(f'fold{fold_ + 1} start')
    train_x = X_train.iloc[train_index]
    valid_x = X_train.iloc[valid_index]
    train_y = y_train.iloc[train_index]
    valid_y = y_train.iloc[valid_index]
    
    ## 分割データで学習・予測・評価
    model = clf.fit(train_x, train_y)
    
    # データを用いて予測,記録
    predicted = model.predict(valid_x)
    score_list.append(accuracy_score(predicted,valid_y))
    models.append(model)
print(score_list, '平均score', round(np.mean(score_list), 3))

結果は[0.7104377104377104, 0.6868686868686869, 0.6666666666666666] 平均score 0.688と少し低めに出てしまいました。

ハイパーパラメータ調整の指針

以下のサイトから引用します。

scikit-learn.org

upura.hatenablog.com

  • γ:「一つの訓練データが与える影響の範囲」を意味します。小さいほど「遠く」、大きいほど「近く」まで影響します。
  • C:分類ミスに対するペナルティと分離平面からの距離のトレードオフです。大きいほど誤りに厳しく,データすれすれに線をひきます。したがって小さいほど大雑把でシンプルな分類になりやすいようです。

実際のシチュエーションでは複数のハイパーパラメータを試して最も良さそうな組み合わせをチョイスする必要があります。 for文でも書けますが,sklearnにはgrid_searchという関数があります。

バージョンによって関数名が変わるのですが,Ver1.0では以下のように書きます。

from sklearn.model_selection import GridSearchCV

# グリッドサーチを用いたオートチューニング
parameters = [{'kernel':['rbf'], 'C':np.logspace(-4, 4, 9), 'gamma':np.logspace(-4, 4, 9)}] # RBF kernel でグリッドサーチ

# 交差検定5回でSVCを用いてやる
clf = GridSearchCV(cv=5,estimator=SVC(), param_grid=parameters, n_jobs = -1)
out = clf.fit(X_train, y_train)

print(out.best_estimator_)

このbest estimatorが最も成績の良かった学習器です。

SVC(C=10000.0, break_ties=False, cache_size=200, class_weight=None, coef0=0.0,
    decision_function_shape='ovr', degree=3, gamma=0.0001, kernel='rbf',
    max_iter=-1, probability=False, random_state=None, shrinking=True,
    tol=0.001, verbose=False)

このときの精度は0.8417508417508418と前回より随分高くなりました。

LightGBM

LightGBMは決定木のアルゴリズムを用いた機械学習フレームワークです。以下のサイトが勉強になります。

www.codexa.net

コード

使い方はsklearnのものと非常によく似ています。

import lightgbm as lgb

# LightGBMの分類器をインスタンス化
gbm = lgb.LGBMClassifier(objective='binary')

# trainとvalidを指定し学習
gbm.fit(train_x, train_y, eval_set = [(valid_x, valid_y)],
        early_stopping_rounds=20,  # 20回連続でlossが下がらなかったら終了
        verbose=10  # 10round毎に、lossを表示
) ;

一応推論をしてみると,score 81.48 %と出ます。

# valid_xについて推論
oof = gbm.predict(valid_x, num_iteration=gbm.best_iteration_)  # oofはout of fold
print('score', round(accuracy_score(valid_y, oof)*100,2), '%')  # 正解率の表示

図示

こちらはデフォルトでそこそこ見やすい図が出ます。

lgb.create_tree_digraph(gbm)

f:id:ossyaritoori:20200428003643p:plain
一部分のみ。

重要度は以下のように簡単にplotできます。

lgb.plot_importance(gbm, figsize=(12, 6))

f:id:ossyaritoori:20200428004244p:plain
LightGBMでの変数重要性:船賃と年齢が圧倒的で,性差は低めに見積もられていることがわかります。

提出に用いた交差検証 LightGBM

こちらも交差検証を用いて複数のモデルの多数決をとります。

# 5分割交差検証を指定し、インスタンス化
from sklearn.model_selection import KFold
kf5 = KFold(n_splits=5, shuffle=True, random_state=0)

# スコアとモデルを格納するリスト
score_list = []
models = []
test_predicts = []

# 各分割ごとに評価
for fold_, (train_index, valid_index) in enumerate(kf5.split(X_train, y_train)):    
    print(f'fold{fold_ + 1} start')
    train_x = X_train.iloc[train_index]
    valid_x = X_train.iloc[valid_index]
    train_y = y_train.iloc[train_index]
    valid_y = y_train.iloc[valid_index]
    
    ## 分割データで学習・予測・評価
    gbm.fit(train_x, train_y, eval_set = [(valid_x, valid_y)],
            early_stopping_rounds=20,  # 20回連続でlossが下がらなかったら終了
            verbose=10  # 10round毎に、lossを表示
    ) ;
    # データを用いて予測,記録
    test_predicts.append(gbm.predict(test, num_iteration=gbm.best_iteration_) )
    predicted = gbm.predict(valid_x, num_iteration=gbm.best_iteration_) 
    score_list.append(accuracy_score(predicted,valid_y))
    
print(score_list, '平均score', round(np.mean(score_list), 3))
[0.8491620111731844, 0.8089887640449438, 0.8314606741573034, 0.8033707865168539, 0.8089887640449438] 平均score 0.82

コンペ結果

気休めですが,LightBGMが僅差でランダムフォレストを上回っていました。

f:id:ossyaritoori:20200428004543p:plain
そこまで各手法で劇的に差が出るわけではありませんね。

他にも手法があるので,概略と動かし方をわかっていれば今後の解析の時に役立ちそうですね。

scikit-learn.org

[https://scikit-learn.org/stable/images/sphx_glr_plot_classifier_comparison_001.png:image=https://scikit-learn.org/stable/images/sphx_glr_plot_classifier_comparison_001.png]