0
0

More than 3 years have passed since last update.

Category Encodersでテストデータセットにも学習用データセットと同じ数字をマッピングする

Last updated at Posted at 2021-09-19

前提

  • いくら調べても出てこなかったので書き下すことにしました。
    • もしかしたら当たり前過ぎて誰も書いてないのかもですが、誰かの役に立てればと。
  • 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ではなくfittransformを別々に使う。

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)

参考

0
0
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
0
0