- はじめに
- やりたい事
- 解決方法
- 1. user feature の次元を item feature に合わせる
- 2. dummy となる key を付与して pandas の merge を使う
- 3. iloc を使う
- まとめ
はじめに
LGBM を本番に持って行く際にパフォーマンスのボトルネックになってしまった pandas の使い方とその改善についてのメモです
やりたい事
LGBM を api 化する際に、全ての特徴量を api の parameter として貰うのではなく、key となる id だけをもらって api 側で持っている data から LGBM の入力を作って推論を行いたいとします
例えば user id と item id をもらって、LGBM への推論を行いたい下記のような場合を考えてみます
サンプルとなるデータは以下のようなコードで作れます
item_features = pd.DataFrame({'A': ['A1', 'A2', 'A3'],
'B': ['B1', 'B2', 'B3'],
'C': ['C1', 'C2', 'C3']})
user_features = pd.DataFrame({'D': ['D2'],
'E': ['E2']})
解決方法
1. user feature の次元を item feature に合わせる
以下の図のように user feature の次元を item feature に合わせる事で目的の DataFrame を作る事が可能です
この方法は以下のようなコードで実現できます
user_features = pd.concat([user_features] * len(item_features), ignore_index=True)
df = pd.concat([item_features, user_features], axis=1)
結果の DataFrame は欲しい形になりますが、パフォーマンスが非常に悪く、item の件数に応じて使い物にならないくらいパフォーマンスが悪化してしまう可能性があります
実際に10万件のデータで動作させてみると 7 sec 程度かかりました
item_features = pd.DataFrame([ [f"i-{i}-1", f"i-{i}-2", f"i-{i}-3"] for i in range(100000)])
user_features = pd.DataFrame([['u-1', 'u-2']])
user_features = pd.concat([user_features] * len(item_features), ignore_index=True)
df = pd.concat([item_features, user_features], axis=1)
2. dummy となる key を付与して pandas の merge を使う
図にすると以下のようになります
コードでは以下のようになります
item_features.assign(key=1).merge(user_features.assign(key=1), on='key').drop('key',axis=1)
一見すると 1 よりも面倒な事をやっているように見えますが、パフォーマンスはこちらの方が圧倒的に良く、特に item feature の数が増えてくると顕著になります
実際に動作確認すると、1 と同じ条件の処理が 0.06 sec 程度で終わりました
item_features = pd.DataFrame([ [f"i-{i}-1", f"i-{i}-2", f"i-{i}-3"] for i in range(100000)])
user_features = pd.DataFrame([['u-1', 'u-2']])
item_features.assign(key=1).merge(user_features.assign(key=1), on='key').drop('key',axis=1)
3. iloc を使う
この記事を書いた後で更に早い方法を弊社エンジニアに教えてもらったので追記です
概念としては 1 の方法をpandas の iloc
を使うとさらに高速になりました
user_features = user_features.iloc[np.repeat(0, len(item_features))].reset_index(drop=True)
df = pd.concat([item_features, user_features], axis=1)
動作確認では、1 と同じ条件で処理が 0.01 sec 程度になりました
item_features = pd.DataFrame([ [f"i-{i}-1", f"i-{i}-2", f"i-{i}-3"] for i in range(100000)])
user_features = pd.DataFrame([['u-1', 'u-2']])
user_features = user_features.iloc[np.repeat(0, len(item_features))].reset_index(drop=True)
df = pd.concat([item_features, user_features], axis=1)
まとめ
pandas で LGBT の入力を作る場合などで覚えておきたいテクニックを紹介しました
結果は 3 → 2 → 1 の手法の順にパフォーマンスが良く、特に 1 は非常に遅いためプロダクションでの利用などは注意が必要です
Name | 結果 |
---|---|
~ 7 sec | |
~ 0.06 sec | |
~ 0.01 sec |