LoginSignup
19
21

More than 3 years have passed since last update.

データ分析コンペで使う思考停止初手LightGBM

Last updated at Posted at 2020-08-24

はじめに

最近KaggleやSignateなどのデータ分析コンペにはまっており、いくつかのコンペに少しづつ参加しながら日々勉強中。
毎回はじめにデータに向き合う前に、コンペの難易度やデータの傾向を知るために行っているLightGBMのテンプレートがあるので、それを公開します。

もっとこうした方がいいとかあったら教えてください!

環境

全体像

データの読み込み

データの読み込みと必要なライブラリのインポートを行う。学習データをよく確認しずに始めると、意外にデータ量が膨大だった、、、みたいなことがあるのでデータ数量くらいは確認しておく。

from datetime import datetime
import numpy as np
import matplotlib.pyplot as plt
import os
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder

# データの読み込み
train_df = pd.read_csv("./train.csv")
test_df = pd.read_csv("./test.csv")

print(train_df.shape, test_df.shape)
(891, 12) (418, 11)

特徴量加工

はじめにデータを眺める

train_df
PassengerId Survived    Pclass  Name    Sex Age SibSp   Parch   Ticket  Fare    Cabin   Embarked
0   1   0   3   Braund, Mr. Owen Harris male    22.0    1   0   A/5 21171   7.2500  NaN S
1   2   1   1   Cumings, Mrs. John Bradley (Florence Briggs Th...   female  38.0    1   0   PC 17599    71.2833 C85 C
2   3   1   3   Heikkinen, Miss. Laina  female  26.0    0   0   STON/O2. 3101282    7.9250  NaN S
3   4   1   1   Futrelle, Mrs. Jacques Heath (Lily May Peel)    female  35.0    1   0   113803  53.1000 C123    S
4   5   0   3   Allen, Mr. William Henry    male    35.0    0   0   373450  8.0500  NaN S
5   6   0   3   Moran, Mr. James    male    NaN 0   0   330877  8.4583  NaN Q
6   7   0   1   McCarthy, Mr. Timothy J male    54.0    0   0   17463   51.8625 E46 S
7   8   0   3   Palsson, Master. Gosta Leonard  male    2.0 3   1   349909  21.0750 NaN S
8   9   1   3   Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)   female  27.0    0   2   347742  11.1333 NaN S
9   10  1   2   Nasser, Mrs. Nicholas (Adele Achem) female  14.0    1   0   237736  30.0708 NaN C
10  11  1   3   Sandstrom, Miss. Marguerite Rut female  4.0 1   1   PP 9549 16.7000 G6  S
11  12  1   1   Bonnell, Miss. Elizabeth    female  58.0    0   0   113783  26.5500 C103    S
12  13  0   3   Saundercock, Mr. William Henry  male    20.0    0   0   A/5. 2151   8.0500  NaN S
13  14  0   3   Andersson, Mr. Anders Johan male    39.0    1   5   347082  31.2750 NaN S
14  15  0   3   Vestrom, Miss. Hulda Amanda Adolfina    female  14.0    0   

どんなコンペでもデータを見るのは大事。
今回の目的変数がSuvivedPassengerIdNameはユニークな特徴量なので使わないなど、最低限のデータの確認を行う。

説明変数と目的変数に分割

説明変数と目的変数に分割を行う。

train_x, train_y = train_df.drop("Survived", axis=1), train_df["Survived"]

特徴量加工

特徴量加工も行うが最低限。以下の3つ観点でしか行わない。

  • null埋め
  • 質的変数->量的変数(ラベルエンコーディング)
  • 不要カラムの削除(PassengerIdName)
def label_encording(data_col):
    '''
    ラベルエンコーディング
    data_col     : 対象のデータフレームの1つの列
    '''
    le = LabelEncoder()
    le = le.fit(data_col)
    #ラベルを整数に変換
    data_col = le.transform(data_col)

    return data_col

def preprocess(df):
    '''
    前処理を行う
    df : padnas.Dataframe
      対象のデータフレーム
    '''
    df = df.drop("PassengerId", axis=1)
    df = df.drop("Name", axis=1)

    # 質的変数を数値に変換
    for column_name in df:
        if df[column_name][0].dtypes == object: # 欠損値に関してはNULLを代入する
            df[column_name] = df[column_name].fillna("NULL")
            df[column_name] = label_encording(df[column_name])   
        elif df[column_name][0].dtypes == ( "int64"  or  "float64") : # 欠損値に関しては-999を代入する
            df[column_name] = df[column_name].fillna(-999)   

    return df

ラベルエンコーディングを行う際に、学習データとテストデータでラベルの対応関係が崩れるとよくないので、学習データとテストデータを同時に特徴量加工をかける。

all_x = pd.concat([train_x, test_df])
preprocessed_all_x = preprocess(all_x)

# 前処理を行なったデータを,学習データとテストデータに再分割
preprocessed_train_x, preprocessed_test_x = preprocessed_all_x[:train_x.shape[0]], preprocessed_all_x[train_x.shape[0]:]
print(preprocessed_train_x.head(5))

モデル作成

LightGBMを学習するクラスを作成する。細かいパラメータの説明は以下の公式サイトを参照。
* Parameters — LightGBM 3.0.0 documentation

objectivemetricsは学習データやコンペに応じて変更する。

# LightGBM
import lightgbm as lgb

class lightGBM:
    def __init__(self, params=None):
        self.model = None
        if params is not None:
            self.params = params
        else:
            self.params = {'objective':'binary',
                            'seed': 0,
                            'verbose':10, 
                            'boosting_type': 'gbdt',
                            'metrics':'auc',
                            'reg_alpha': 0.0,
                            'reg_lambda': 0.0,
                            'learning_rate':0.01, 
                            'drop_rate':0.5
                         }
        self.num_round = 20000
        self.early_stopping_rounds = self.num_round/100


    def fit(self, tr_x, tr_y, va_x, va_y):
        self.target_columms = tr_x.columns
        print(self.target_columms)
        # データセットを変換
        lgb_train = lgb.Dataset(tr_x, tr_y)
        lgb_eval = lgb.Dataset(va_x, va_y)
        self.model = lgb.train(self.params, 
                            lgb_train, 
                            num_boost_round=self.num_round,
                            early_stopping_rounds=self.early_stopping_rounds,
                            valid_names=['train', 'valid'],
                            valid_sets=[lgb_train, lgb_eval],
                            verbose_eval=self.num_round/100
                            )
        return self.model


    def predict(self, x):
        data = lgb.Dataset(x)
        pred = self.model.predict(x, num_iteration=self.model.best_iteration)
        return pred


    def get_feature_importance(self, target_columms=None):
        '''
        特徴量の出力
        '''
        if target_columms is not None:
            self.target_columms = target_columms
        feature_imp = pd.DataFrame(sorted(zip(self.model.feature_importance(), self.target_columms)), columns=['Value','Feature'])
        return feature_imp

学習器の定義

def model_learning(model, x, y):
    '''
    モデルの学習を行う。
    '''
    tr_x, va_x, tr_y, va_y = train_test_split(x, train_y, test_size=0.2, random_state=0)    
    return model.fit(tr_x, tr_y, va_x, va_y)

モデルをクラスで定義しておき学習器に渡す構成にすることで、異なるモデルを利用する時にソースコードの変更を最小限にすることができる。

例えばXGBoostを使いたい時、以下のように書き換えることですぐに学習するモデルを差し替えることができる。


class XGBoost:
    def __init__(self, params=None):
        # 初期化処理~~~

    def fit(self, tr_x, tr_y, va_x, va_y):
        # 学習の処理~~~

    def predict(self, x):
        # 評価の処理~~~

xgboost_model = XGBoost()
model_learning(xgboost_model, preprocessed_train_x, train_y)

学習

lightgbm_model = lightGBM()
model_learning(lightgbm_model, preprocessed_train_x, train_y)
Index(['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin',
       'Embarked'],
      dtype='object')
Training until validation scores don't improve for 200.0 rounds
Early stopping, best iteration is:
[172]   train's auc: 0.945026   valid's auc: 0.915613

学習が完了!すぐに終わった。

特徴量の重要度の評価

LightGBMでは学習した特徴量の内、どれをよく使ったかを確認することができる。これにより次のステップでおこなうEDAのヒントを得ることができる。
なんとなくAgeTicketFareが上位にあることから、年齢とか座席の位置が重要そうだなあ、AgeSurvivedの相関見るか、などなんとなく次やることが見えてくる。

lightgbm_model.get_feature_importance()
  Value Feature
0   32  Parch
1   58  SibSp
2   158 Embarked
3   165 Cabin
4   172 Sex
5   206 Pclass
6   1218    Fare
7   1261    Ticket
8   1398    Age

評価&提出ファイル作成

モデルの評価。出力結果は確率であるが、今回は0,1のどちらでなければならないので、それに合わせて整形する。

# テスト用のモデルの評価
proba_ = lightgbm_model.predict(preprocessed_test_x)
proba = list(map(lambda x: 0 if x < 0.5 else 1, proba_))

予測値を提出データに合わせて整形する。何気にここが一番詰まりやすい....

# テストデータの作成
submit_df = pd.DataFrame({"Survived": proba})
submit_df.index.name = "PassengerId"
submit_df.index = submit_df.index + len(train_df) + 1

ファイル名はsubmit_{%Y-%m-%d-%H%M%S}形式で保存する。
そうすることで不意の上書きを防ぐことができるし、ファイル名を毎回考えなくていいので地味に便利。

# 保存
save_folder = "results"
if not os.path.exists(save_folder):
    os.makedirs(save_folder)

submit_df.to_csv("{}/submit_{}.csv".format(save_folder, datetime.now().strftime("%Y-%m-%d-%H%M%S")),index=True)

終わりに

この結果を提出したところPublicScoreが0.770336610位/20114人だった。(2020/08/25時点)
とりあえず回してみてコンペの難易度や感覚を掴む、という目的では悪くないテンプレートかと思う。

EDAが甘いなあと毎回思うので今後はEDAをもっとしっかりやっていきたところ。

19
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
21