StatsFragments

Python, R, Rust, 統計, 機械学習とか

Python XGBoost + pandas 連携の改善

一部 こちらの続き。その後 いくつかプルリクを送りXGBoostpandas を連携させて使えるようになってきたため、その内容を書きたい。

sinhrks.hatenablog.com

できるようになったことは 以下 3 点。

  1. DMatrix でのラベルと型の指定
  2. pd.DataFrame からの DMatrix の作成
  3. xgb.cv の結果を pd.DataFrame として取得

補足 XGBoost では PyPI の更新をスクリプトで不定期にやっているようで、同一バージョンに見えても枝番が振られていたりして見分けにくい。記載は本日時点のこのコミットの情報。

%matplotlib inline
import numpy as np
import xgboost as xgb
from sklearn import datasets

import matplotlib.pyplot as plt
plt.style.use('ggplot')

xgb.__version__
# '0.4'

1. DMatrix でのラベルと型の指定

これは pandas 関係ない。DMatrix に以下 2 つのプロパティが追加され、任意のラベル/型が指定できるようにした (指定しない場合はこれまでと同じ挙動)。

  • feature_names: 変数のラベルを指定。ラベルは英数字のみ。
  • feature_types: 変数の型を指定。指定できるのは、
    • q: 量的変数 (既定)
    • i: ダミー変数
    • int: 整数型
    • float: 浮動小数点型。

まずは feature_names のみを指定。

iris = datasets.load_iris()

features = ['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth']
dm = xgb.DMatrix(iris.data, label=iris.target, feature_names=features)
dm.feature_names
# ['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth']

DMatrix で指定した feature_namesxgb.Booster へも引き継がれ、.get_dump 時 やプロットに利用できる。

params={'objective': 'multi:softprob',
        'eval_metric': 'mlogloss',
        'eta': 0.3,
        'num_class': 3}

np.random.seed(1)
bst = xgb.train(params, dm, num_boost_round=18)
xgb.plot_importance(bst)

f:id:sinhrks:20151003080826p:plain

次に feature_types。これは内部的な学習には全く関係がなく、.get_dumpxgb.plot_tree をする際の ラベルのフォーマットを決めるだけ。例えば以下のようなデータがあったとする。

data = np.array([[1, 0.1, 0], [2, 0.2, 1], [2, 0.2, 0]])
data
# array([[ 1. ,  0.1,  0. ],
#        [ 2. ,  0.2,  1. ],
#        [ 2. ,  0.2,  0. ]])

このとき、1 列目を int, 2 列目を float, 3 列目をダミー変数 i として扱いたければ以下のように指定すると、 plot_tree したときの見た目が若干変わる。全変数 既定 (q: 量的変数) でもとくに不自由ないので、強いこだわりのある方以外は指定する必要はない。

dm = xgb.DMatrix(data, feature_names=['A', 'B', 'C'],
                 feature_types=['int', 'float', 'i'])
dm.feature_types
# ['int', 'float', 'i']

補足 これまで、変数のラベル/型の指定は "featmap.txt" のような設定ファイルから別途指定する必要があった。この指定が Python で直接できるようになった。

2 pd.DataFrame からの DMatrix の作成

pandas.DataFrame から DMatrix が作成できるようにした。このとき、feature_namesfeature_typesDataFrame の定義から適当に設定される。

補足 DMatrixlabel には pd.Series が渡せる (前からできた)。

import pandas as pd

# 表示行数を指定
pd.set_option('display.max_rows', 8)
# 一行に表示する文字数を指定
pd.set_option('display.width', 120)

# pd.DataFrame を作成
train = pd.DataFrame(iris.data, columns=['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth'])
train
#      SepalLength  SepalWidth  PetalLength  PetalWidth
# 0            5.1         3.5          1.4         0.2
# 1            4.9         3.0          1.4         0.2
# 2            4.7         3.2          1.3         0.2
# 3            4.6         3.1          1.5         0.2
# ..           ...         ...          ...         ...
# 146          6.3         2.5          5.0         1.9
# 147          6.5         3.0          5.2         2.0
# 148          6.2         3.4          5.4         2.3
# 149          5.9         3.0          5.1         1.8
# 
# [150 rows x 4 columns]

# DMatrix を作成
dm = xgb.DMatrix(train, label=pd.Series(iris.target))

dm.feature_names
# ['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth']

dm.feature_types
# ['q', 'q', 'q', 'q']

DMatrix に変換できるのは、列が int64, float64, boolDataFrame のみ。他の型を自動でダミー変数に変換したりはしない。

df = pd.DataFrame([[1, 0.1, False], [2, 0.2, True], [2, 0.2, False]],
                  columns=['A', 'B', 'C'])
df
#    A    B      C
# 0  1  0.1  False
# 1  2  0.2   True
# 2  2  0.2  False

df.dtypes
# A      int64
# B    float64
# C       bool
# dtype: object

dm = xgb.DMatrix(df)

dm.feature_names
# ['A', 'B', 'C']

dm.feature_types
# ['int', 'q', 'i']

# object 型を含むためエラー
df = pd.DataFrame([[1, 0.1, 'x'], [2, 0.2, 'y'], [2, 0.2, 'z']],
                  columns=['A', 'B', 'C'])

df
#    A    B  C
# 0  1  0.1  x
# 1  2  0.2  y
# 2  2  0.2  z

df.dtypes
# A      int64
# B    float64
# C     object
# dtype: object

xgb.DMatrix(df)
# ValueError: DataFrame.dtypes must be int, float or bool

3. xgb.cv の結果を pd.DataFrame として取得

クロスバリデーションを行う xgb.cv はこれまで実行結果を文字列で返していたため、その結果をパースもしくは目視で確認する必要があった。この返り値を pd.DataFrame もしくは np.ndarray とし、プログラムで処理がしやすいようにした。

train = pd.DataFrame(iris.data, columns=['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth'])
dm = xgb.DMatrix(train, label=pd.Series(iris.target))

cv = xgb.cv(params, dm, num_boost_round=50, nfold=10)
cv
#     test-mlogloss-mean  test-mlogloss-std  train-mlogloss-mean  train-mlogloss-std
# 0             0.753459           0.027033             0.737631            0.003818
# 1             0.552303           0.048738             0.526929            0.005102
# 2             0.423481           0.066469             0.390115            0.005873
# 3             0.339942           0.082163             0.295637            0.006148
# ..                 ...                ...                  ...                 ...
# 46            0.208001           0.259161             0.018070            0.001759
# 47            0.208355           0.261166             0.017898            0.001724
# 48            0.208468           0.261520             0.017755            0.001703
# 49            0.208566           0.260967             0.017617            0.001686
# 
# [50 rows x 4 columns]

cv.sort(columns='test-mlogloss-mean')
#     test-mlogloss-mean  test-mlogloss-std  train-mlogloss-mean  train-mlogloss-std
# 11            0.175506           0.177823             0.058496            0.004715
# 13            0.175514           0.194641             0.045222            0.004117
# 14            0.176150           0.203265             0.040493            0.004050
# 12            0.176277           0.186902             0.051074            0.004423
# ..                 ...                ...                  ...                 ...
# 3             0.339942           0.082163             0.295637            0.006148
# 2             0.423481           0.066469             0.390115            0.005873
# 1             0.552303           0.048738             0.526929            0.005102
# 0             0.753459           0.027033             0.737631            0.003818
# 
# [50 rows x 4 columns]

pandas がインストールされていない、もしくは as_pandas=False を指定した場合、返り値は np.ndarray となる。

cv = xgb.cv(params, dm, num_boost_round=50, nfold=10, as_pandas=False)
cv
# array([[ 0.7534586 ,  0.02703308,  0.7376308 ,  0.00381775],
#        ...
#        [ 0.2085664 ,  0.26096721,  0.0176169 ,  0.00168606]])

np.argmin(cv, axis=0)
# array([11,  0, 49, 49])

まとめ

XGBoost と pandas をより便利に使うため、以下 3 点の修正を行った。

  1. DMatrix でのラベルと型の指定
  2. pd.DataFrame からの DMatrix の作成
  3. xgb.cv の結果を pd.DataFrame として取得

これ使って Kaggle で大勝利したい...と思ったのですが、他の日本勢に使っていただいたほうが勝率高いはずなのでシェアします。