K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

One-Hot Encoding

01.4.1

One-Hot Encoding

最終更新 2024-06-01 読了時間 2 分
まとめ
One-Hot Encoding は、「元号」のようなカテゴリの列を、カテゴリの種類の数だけ 0/1 の列に展開する最も基本的な数値化です。「明治・大正・昭和・平成・令和」の 5 種類なら 5 列ができ、各行はちょうど 1 列だけが 1 になります。このページでは scikit-learn の OneHotEncoder(handle_unknown="ignore", sparse_output=True) を ColumnTransformer に入れ、数値列「西暦」と一緒に Ridge へ渡す Pipeline を組み、get_feature_names_out で展開後の列名を取り出します。カテゴリ間に大小を持ち込まないので線形モデルの係数をそのまま解釈できますが、「町名」が 1,000 種類あれば 1,000 列になる次元爆発が弱点で、その場合は Count / Target Encoding に切り替えます。

直感 #

アンケートの「血液型」を A=1、B=2、O=3、AB=4 と番号にして回帰に入れると、モデルは「AB 型は A 型の 4 倍」という存在しない関係を学びます。One-Hot Encoding はこれを避けるために、「A 型か?」「B 型か?」「O 型か?」「AB 型か?」という 4 つの Yes/No の列に分けます。どの 2 つの血液型も同じだけ離れた表現になり、線形モデルの係数は「その血液型であることが目的変数を何単位動かすか」とそのまま読めます。線形モデル・決定木系モデルいずれでも扱いやすい一方で、カテゴリ数が増えると次元爆発が起こりやすく、学習・推論コストに直結します。

いつ採用するか #

  • カテゴリ種類が 2〜10 程度で、解釈性を保ちたいとき
  • 線形モデル(ロジスティック回帰、GLM)に投入して係数を読み解きたいとき
  • 木系アルゴリズムに入れる前に、カテゴリ間の順序関係を入れたくないとき

注意すべきケース #

カテゴリ数が数百を超える場合は、列数がそのまま数百本になるため Count / Target Encoding を検討します。FaaS や Web API のように推論のレスポンス要件が厳しい基盤では、疎行列を密行列にしたときのメモリ増加が応答時間に直結します。多値カテゴリに対して drop='first' を設定すると、多重共線性は避けられて解釈性は保てますが、落とした基準カテゴリの行がすべて 0 になるため、未知カテゴリの行と区別がつかなくなる点に注意が要ります。


実装フロー #

1. サンプルデータの読み込み #

次のコードは総務省の人口データを読み込み、カテゴリ列と数値列を変数名で分けておき、先頭 5 行を表示します。

1
2
3
4
5
6
import pandas as pd

df = pd.read_csv("../data/sample.csv")
categorical_cols = ["元号", "町名"]
numeric_cols = ["西暦", "人口総数"]
df[categorical_cols + numeric_cols].head()

2. ColumnTransformer で数値とカテゴリを同時処理 #

次のコードは、カテゴリ列を One-Hot に展開し数値列はそのまま通す前処理と Ridge 回帰を 1 つの Pipeline にまとめ、「人口総数」を目的変数として学習します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import Ridge
from sklearn.pipeline import Pipeline

ohe = OneHotEncoder(handle_unknown="ignore", sparse_output=True)

preprocess = ColumnTransformer(
    transformers=[
        ("cat", ohe, categorical_cols),
        ("num", "passthrough", numeric_cols),
    ],
    remainder="drop",
)

model = Pipeline(
    steps=[
        ("preprocess", preprocess),
        ("estimator", Ridge())
    ]
)
model.fit(df[categorical_cols + numeric_cols], df["人口総数"])

3. 特徴量名の確認 #

次のコードは、学習済みの Pipeline から One-Hot 展開後の列名を取り出し、先頭 10 本を表示します。

1
2
3
feature_names = model.named_steps["preprocess"]\
    .named_transformers_["cat"].get_feature_names_out(categorical_cols)
feature_names[:10]

よくある落とし穴と対策 #

課題対策
高次元化で学習時間が肥大化High-cardinality な列を別手法(Count Encoding 等)に切り替え、10件未満のカテゴリを Other に集約
ワンホット列が全て 0 になるサンプルが発生handle_unknown="ignore" + モデル入力にバイアス項(定数列)を追加しておく
カテゴリの追加・削除で推論が壊れる学習時に ColumnTransformer を保存し、必ず同じ順序・列構成で推論する。sklearn の Pipeline で一体管理する

データアナリスト向けチェックリスト #

最初に確認したいのはカテゴリの粒度です。町名が 1,000 件ある場合は区や市のような上位区分にまとめないと、get_feature_names_out の結果が 1,000 本になって次元が増えすぎます。二つ目はベースラインの線形モデルで係数を確認したかで、「令和」の係数の符号が期待と異なる場合は、データのリークや前処理の問題を疑います。三つ目は推論環境での疎行列サポートで、一部のモデルサーバーでは sparse_output=True の疎行列が扱えないため、toarray() で密行列化すると 1,000 列 × 行数ぶんのメモリが数倍に膨らむことを事前に見積もります。


参考リンク #