前提
- いくら調べても出てこなかったので書き下すことにしました。
- もしかしたら当たり前過ぎて誰も書いてないのかもですが、誰かの役に立てればと。
- lightgbmを使うので、OrdinalEncoderを想定して書きます。
- 他のエンコーダは試してないですが、同じ方法で解決できるのではないかと思います。
解決したいこと
「モデルの学習をするランタイム」と「実際にモデルから予測するランタイム」が別れているときに、category_encodersが別々のアルゴリズムでカテゴリ特徴量を変換してしまう現象を解決したい。
OrdinalEncoderのfit_transform
は与えられたデータセットから各カテゴリに1から順番に数字を採番する仕組みのようなので、学習に使うデータセットと予測に使うデータセットでカテゴリ数が一致していて出現する順番も等しくないと、別の数字が振られてしまう。
実際には予測に使うデータセットの方がカテゴリが少ないことが多いため、以下のようなやり方だと学習用と予測用のデータセットで異なる値に変換されるはず。
learn.py
# a~iのカテゴリを持つ特徴量を変換する
df1 = pd.DataFrame({'label': ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'],
'value': ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']})
list_cols = ['value']
ce_oe = ce.OrdinalEncoder(cols=list_cols,handle_unknown='impute')
df = ce_oe.fit_transform(df1)
print(df)
"""
a~iに1から順に数字が振られる
A B
0 a 1
1 b 2
2 c 3
3 d 4
4 e 5
5 f 6
6 g 7
7 h 8
8 i 9
"""
pred.py
# aとiを変換する
df2 = pd.DataFrame({'label': ['a', 'i'],
'value': ['a', 'i']})
list_cols = ['value']
ce_oe = ce.OrdinalEncoder(cols=list_cols,handle_unknown='impute')
df = ce_oe.fit_transform(df2)
print(df)
"""
a->1, i->9を期待するが、実際には上から順番に採番されてしまう
A B
0 a 1
1 i 2
"""
解決方法
fit_transform
ではなくfit
とtransform
を別々に使う。
fit
はデータセットを受け取り、エンコーダを返す関数。
ここで返されたエンコーダはカテゴリと数字のマッピングを学習済なので、このインスタンスを予測用データセットにも使い回せば学習用データセットと同じ数字を割り振ることができる。
learn.py
# df1とdf2は同上のため定義を省略
fitted = ce.OrdinalEncoder(cols=list_cols,handle_unknown='impute').fit(df1)
# fit済エンコーダを使い回すのがポイント
df1_trans = fitted.transform(df1)
df2_trans = fitted.transform(df2)
print(df1_trans)
print("==========")
print(df2_trans)
"""
A B
0 a 1
1 b 2
2 c 3
3 d 4
4 e 5
5 f 6
6 g 7
7 h 8
8 i 9
==========
A B
0 c 3
1 i 9
"""
あとはインスタンスを使い回せない別のランタイムでどうするかですが、お察しの通りpickle
を使います。
learn.py
ce_oe = ce.OrdinalEncoder(cols=list_cols,handle_unknown='impute').fit(df1)
with open('model.pickle', mode='wb') as fp:
pickle.dump(model, fp)
pickle.dump(ce_oe, fp)
pred.py
with open('model.pickle', mode='rb') as fp:
model = pickle.load(fp)
ce_oe = pickle.load(fp)
df = ce_oe.transform(dataset)