koji/メガネ男の日誌

日々の学び、活動状況を記録します。仕事のことは少なめ。

タイタニックのデータを使って xfeat で特徴量作成 + lightGBM で学習をしてみた

f:id:kj_man666:20200622221032j:plain

(9/23 修正)制度評価指標をf1値に、lightGBMの学習モデルをLGBMClassifier()に修正

mathmatical22.xyz

特徴量エンジニアリングのライブラリ xfeat が公開されていたのでチュートリアルを参考にkaggleのタイタニックのデータを使って特徴量を作り、lightGBMを回してみました。

使用するkaggleのタイタニックのデータはこちらからダウンロードしてください。

なお、xfeat ライブラリは、git をインストールしたうえで、下記コマンドをターミナルに入力すればインストールできます。

pip install git+https://github.com/pfnet-research/xfeat.git

qiita.com

なお、ブログで使ったコードはこちらになります。

では内容を見てみましょう。

import pandas as pd
import numpy as np
import os

from IPython.display import display

import xfeat

# xfeatのチュートリアルで紹介されているラベルエンコーダー
from xfeat import SelectCategorical, LabelEncoder, Pipeline, ConcatCombination, SelectNumerical, ArithmeticCombinations,  LambdaEncoder

# ホールドアウト法
from sklearn.model_selection import train_test_split

# lightGBM
import lightgbm as lgb

# 二乗平均平方根誤差 (RMSE)
from sklearn.metrics import mean_squared_error

csvを読み込んでfeather形式に変換します。

feather形式は、C++で実装されており、読み込みが非常に高速とのことです。

pandasのデータフレームに対してdf.to_feather(filepath)とすることによってFeather形式でのシリアライズが可能。

amalog.hateblo.jp

path = os.getcwd() + "/"
d_name = ["train", "test"]

# feather形式に変換。
for i in d_name:
    xfeat.utils.compress_df(pd.read_csv(path + i + ".csv")).to_feather(path + i + ".ftr")

# feather形式のデータを読込
dtrain = pd.read_feather("./train.ftr")
dtest = pd.read_feather("./test.ftr")

display(dtrain.head(3))
display(dtrain.tail(3))
display(dtrain.dtypes)
display(dtest.head(3))
display(dtest.tail(3))
display(dtest.dtypes)

f:id:kj_man666:20200622221855p:plain

f:id:kj_man666:20200622221911p:plain

xfeat のラベルエンコーディング

特にカラムを指定しなくても、カテゴリカルデータ(object)なのか、数値データ(int、float等)なのかを勝手に認識してラベルエンコーディング可能なのは便利ですね。

#  SelectCategorical() カテゴリカルデータ(object)のみをラベルエンコーディング
# 除外するカラムをexclude_colsに指定
encoder1 = Pipeline([
    SelectCategorical(exclude_cols=["Name"]),
    LabelEncoder(output_suffix=""),
])

display(encoder1.fit_transform(dtrain).head())
display(encoder1.fit_transform(dtest).head())

f:id:kj_man666:20200622222307p:plain

カテゴリカルデータのみのラベルエンコーディングと、複数のカテゴリカルデータを組み合わせてラベルエンコーディングを合わせて実行

#  SelectCategorical() カテゴリカルデータ(object)のみをラベルエンコーディング
#  ConcatCombination() 複数のカテゴリカルデータを組み合わせてラベルエンコーディング
# r=2 は結合するカラムの数 

encoder2 = Pipeline([
    SelectCategorical(exclude_cols=["Name"]),
    ConcatCombination(drop_origin=True, output_suffix="", r=2),
    LabelEncoder(output_suffix=""),
])

display(encoder2.fit_transform(dtrain).head())
display(encoder2.fit_transform(dtest).head())

4つの項目から2つを選ぶ組み合わせ 4C2 = 6 6通りが出力される

f:id:kj_man666:20200622222827p:plain

数値データのみのラベルエンコーディング

# SelectNumerical() 数値データのみをラベルエンコーディング
# train.csv のみに含まれる目的変数 Survived は数値データなので、SelectNumerical() の対象となる
# train.csv はSurvivedカラムを含み、test.csvは Survivedを含まないので、SelectNumericalのexclude_cols(除外するカラム)でSurvivedを指定したい場合は、
# train.csvとtest.csvそれぞれ分けてラベルエンコーディングする必要がある

encoder3_1 = Pipeline([
    # 除外するカラムをexclude_colsに指定
    SelectNumerical(exclude_cols=["PassengerId", "Survived"]),
    LabelEncoder(output_suffix=""),
])

display(encoder3_1.fit_transform(dtrain).head())

encoder3_2 = Pipeline([
    SelectNumerical(exclude_cols=["PassengerId"]),
    LabelEncoder(output_suffix=""),
])

display(encoder3_2.fit_transform(dtest).head())

f:id:kj_man666:20200622223318p:plain

f:id:kj_man666:20200622223329p:plain

数値データのみのラベルエンコーディングと、複数の数値データを加算して新しい項目を作る処理を合わせて実行

# SelectNumerical() 数値データのみをラベルエンコーディング
# ArithmeticCombinations() 複数の数値データを加算して新しい項目を作る
# encode3_x と同様にtrain.csv と test.csv で分ける

encoder4_1 = Pipeline([
    SelectNumerical(exclude_cols=["PassengerId", "Survived"]),
    ArithmeticCombinations(
        drop_origin=True,
        operator="+",
        r=2,
        output_suffix="",
    ),
])

display(encoder4_1.fit_transform(dtrain).head())

encoder4_2 = Pipeline([
    SelectNumerical(exclude_cols=["PassengerId"]),
    ArithmeticCombinations(
        drop_origin=True,
        operator="+",
        r=2,
        output_suffix="",
    ),
])

display(encoder4_2.fit_transform(dtest).head())

f:id:kj_man666:20200622224119p:plain

f:id:kj_man666:20200622224134p:plain

本ブログでは省略しますが、lambdaを使った処理を組み合わせたり、処理をpickleデータに保存する方法もチュートリアルでは紹介されています。

では、学習データとして使用するデータをチョイスします。

# SelectCategorical
dtrain_en1 = encoder1.fit_transform(dtrain)
dtest_en1 = encoder1.fit_transform(dtest)

display(dtrain_en1.head())
display(dtest_en1.head())

# SelectCategorical + ConcatCombination
dtrain_en2 = encoder2.fit_transform(dtrain)
dtest_en2 = encoder2.fit_transform(dtest)

display(dtrain_en2.head())
display(dtest_en2.head())

# SelectNumerical
dtrain_en3_1 = encoder3_1.fit_transform(dtrain)
dtest_en3_2 = encoder3_2.fit_transform(dtest)

display(dtrain_en3_1.head())
display(dtest_en3_2.head())

# SelectNumerical + ArithmeticCombinations
dtrain_en4_1 = encoder4_1.fit_transform(dtrain)
dtest_en4_2 = encoder4_2.fit_transform(dtest)

display(dtrain_en4_1.head())
display(dtest_en4_2.head())

f:id:kj_man666:20200622225016p:plain

f:id:kj_man666:20200622225029p:plain

選択したデータをtrainデータ、testデータにそれぞれマージします。

# データ結合
train = pd.concat([dtrain_en1, dtrain_en2], axis=1)
train = pd.concat([train, dtrain_en3_1], axis=1)
train = pd.concat([train, dtrain_en4_1], axis=1)
train = pd.concat([train, dtrain["Survived"]], axis=1)

test = pd.concat([dtest_en1, dtest_en2], axis=1)
test = pd.concat([test, dtest_en3_2], axis=1)
test = pd.concat([test, dtest_en4_2], axis=1)

display(train.head())
display(train.shape)

display(test.head())
display(test.shape)

f:id:kj_man666:20200622225202p:plain

学習のための特徴量、およびパラメーターを設定します。

# 特徴量とするカラムのリスト
features = [
    "Sex",
    "Ticket",
    "Cabin",
    "Embarked",
    "SexTicket",
    "SexCabin",
    "SexEmbarked",
    "TicketCabin",
    "TicketEmbarked",
    "CabinEmbarked",
    "PclassSibSp",
    "PclassParch",
    "PclassFare",
    "AgeSibSp",
    "AgeParch",
    "AgeFare",
    "SibSpParch",
    "SibSpFare",
    "ParchFare"
]

lightGBMで学習を行っていきます。

# ホールドアウト法
X_train, X_test, y_train, y_test = train_test_split(train[features], train["Survived"], random_state=42)

# 学習モデルの作成
model_lgb = lgb.LGBMClassifier() # モデルのインスタンスの作成
model_lgb.fit(X_train, y_train)

バリデーションデータについて、予測値と実績値を比較して誤差率を算定してみます。

# validationデータで誤差率を算定
val_pred = model_lgb.predict(X_test)

val_score = f1_score(y_test, val_pred)
print(f'f1 score は {val_score}')

出力:f1 score は 0.7314285714285713

気を取り直してテストデータで予測値を作成します。

pred = model_lgb.predict(test[features])

# gender_submission の Survived カラムを予測値に入れ替える
gs["Survived"] = pred

display(gs.head())
display(gs.tail())

f:id:kj_man666:20200622230005p:plain

Survived の値が小数点になってしまっているので、0、1に置き換えます

# 0.6以上の場合は 1、0.6未満の場合は 0 に置換
gs["Survived"].mask(gs["Survived"] >= 0.6, 1, inplace=True)
gs["Survived"].mask(gs["Survived"] < 0.6, 0, inplace=True)

display(gs.head())
display(gs.tail())

f:id:kj_man666:20200923131548p:plain

# csvへの出力
gs.to_csv(path + "submission.csv", index=False)

バリデーションデータでの結果は芳しくなかったですが、とりあえずxfeat のラベルエンコーダーを使ってlightGBMの学習ができました。

今回はタイタニックのデータを使った分類ですが、回帰でやるとどうなるか、そのうち試してみようと思います。

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

参考サイト

amalog.hateblo.jp

qiita.com