機械学習

カテゴリ変数の変換(one-hot encodingなど)

機械学習において特徴量としてカテゴリ変数(質的変数)を扱うためには、学習に適した形に変換する必要があります。
このページでは、one-hot encodingをはじめとするカテゴリ変数の変換方法について紹介します。

カテゴリ変数の変換

機械学習においてカテゴリ変数(質的変数)はそのままでは扱うことができません。
そのため、モデルごとに適した形に変換する必要があります。
代表的な手法としてone-hot encodingがありますが、それ以外にも様々な手法があります。
ここでは、それぞれの手法について順に概観します。

各手法のサンプルコードについては、以下のリンク先に記載があります。
ch03-02-categorical.py
関連書籍:門脇 大輔 他「Kaggleで勝つデータ分析の技術」技術評論社 (2019)

one-hot encoding

one-hot encodingでは、質的変数がもつカテゴリ1つずつ(水準)に対応するカラムをそれぞれ作り、そのレコードがそのカテゴリ値に該当する場合は1、該当しない場合は0という値を格納します。
このような変数をダミー変数とよびます。

以下のデータの質的変数Cityに対してone-hot encodingを行った結果を示します。

元データ

No. City
1 Tokyo
2 New York
3 London
4 New York

one-hot encoding後

No. City_Tokyo City_New_York City_London
1 1 0 0
2 0 1 0
3 0 0 1
4 0 1 0

3種類のカテゴリ値(Tokyo, New York, London)からなる質的変数Cityが3つの特徴量に分かれました。
これら3つの特徴量はいずれか1つだけ(元のデータに該当する箇所)のみ1をとり、残りは0になります。
質的変数をダミー変数に変換することで、機械学習に投入できるような形に変換できます。

ダミー変数トラップ

one-hot encodingを行う際には、ダミー変数トラップに気をつける必要があります。
ダミー変数トラップとは、上のデータのCityカラムのように3種類のカテゴリ値がある場合、one-hot encodingで3個の特徴量を作ってしまうと特徴量間に相関が生まれ、多重共線性の原因となってしまいます。
3種類のカテゴリ値がある場合の自由度は2なので、1種類減らして2個のカラムに抑える必要があります。
たとえば、City_TokyoとCity_New_Yorkだけで元の特徴量Cityを表現します。
特徴量CityはTokyo, New York, Londonのいずれかの値を取ることがわかっています。
そのため、Londonに対応する特徴量が無くなっても、他の2種類の特徴量の値が全て0である場合はLondonであるということがわかるため情報量としては変わりません。
$ k $ 個のカテゴリをもつ特徴量の自由度は $ k-1 $ であるため、特徴量の数を $ k-1 $ に抑えることができます。
このように自由度に合わせて余分なカテゴリ値を除いてone-hot encodingを行うことで、多重共線性を予防した変換を行うことができます。

実装

one-hot encodingの実装は以下の2種類の関数が存在します。

1.pandasのget_dummies

import pandas as pd
cat_cols = [cat_col1, cat_col2, cat_col3]
pd.get_dummies(df, columns=cat_cols, drop_first=True)
# drop_first=Trueで先頭のカテゴリ値を削除してダミー変数作成(残す場合はNone)

2.sklearn.preprocessingのOneHotEncoder

import sklearn.preprocessing
# インスタンス生成
enc = sklearn.preprocessing.OneHotEncoder(sparse=False, drop='first')
# drop='first'で先頭のカテゴリ値を削除してダミー変数作成(残す場合はNone)
# 対象カラムの指定
cat_cols = [cat_col1, cat_col2, cat_col3]
# one-hot encoding実行
ohe_dp1st = enc.fit_transform(df[cat_cols])

1つ目のpandasのget_dummiesでは、pandas,dataframeを入力とすると質的変数にone-hot encodingを行った結果をpandas.dataframeとして返します。
一方、OneHotEncoderではpandas,dataframeを入力としてもnumpy arrayで返ってきます。
そのため、データフレームに戻すために後続の処理が必要になります。

# ダミー変数の列名作成
columns = []
for i, col in enumerate(cat_cols):
    columns += [f'{col}_{ctg}' for ctg in enc.categories_[i]]
    # drop='first'の場合は最初の要素を削除
    columns = columns[1:]
# 生成されたダミー変数をデータフレームに戻す
df_dummy = pd.DataFrame(ohe_dp1st, columns=columns)

注意点

実際に機械学習で行う際には学習データには含まれていないカテゴリ値が検証・テストデータには含まれていることがあるため、扱いに注意が必要です。

また、one-hot encodingはカテゴリ値の種類が増えるほど特徴量が大量に生成されます。
そのため、カテゴリ値の種類が極端に多いと、ほとんどの値が0になる情報が少ない特徴量が多すぎて学習に時間がかかったり大きなメモリが必要になります。
そのような場合には、以下のような対策が考えられます。
・別のencoding手法を検討
・カテゴリをグループ分けしてカテゴリの種類を削減
・頻度の少ないカテゴリを「その他」として集約

label encoding

label encodingでは、カテゴリ値(水準)を整数に置き換えます。
順序尺度のように相対的な大小関係が決まっているカテゴリ変数に対して使われます。
たとえば、最高金賞、金賞、銀賞、銅賞といったカテゴリ値があるときに、最高金賞=0、金賞=1、銀賞=2、銅賞=3というように順序尺度の順に整数を割り振ります。

label encodingの注意点としては、変換した数値間の差分に意味がないため、学習に用いるのが適切では無い場合が多いことです。
label encodingが有用なのは決定木のように分岐を繰り返すことで予測値を行う場合であり、この場合にはlabel encodingした特徴量を学習に用いることができます。

from sklearn. preprocessing import LabelEncoder
ctg_cols = [cat_col1, cat_col2, cat_col3] # 対象カラム
for col in ctg_cols:
    enc = LabelEncoder()
    enc.fit(df_lc[col])
    # 確認用にあえて上書きせずに別カラムとして生成
    df_lc[col + '_lc'] = enc.transform(df_lc[col])

feature hashing

feature hashingでは、ハッシュ値を用いてカテゴリ変数を元の変数の数よりも特徴量の数が少なくするように変換を行います。
変換後の特徴量の数を事前に決めておき、カテゴリ値ごとにフラグが立つ場所をハッシュ関数により決定します。
元の変数よりも水準(カテゴリ値)の数が少ないため、複数の水準が同一のフラグの組み合わせになることもあります。
one-hot encodingでは元の変数の数と変換後の特徴量の数は同一であるのに対し、feature hashingでは元のカテゴリ値よりも特徴量の数を減らす次元圧縮ができます。

サンプルコードについては、以下のページが参考になります。
ch03-02-categorical.py
関連書籍:門脇 大輔 他「Kaggleで勝つデータ分析の技術」技術評論社 (2019)

frequenct encoding

frequency encodingでは、各水準(カテゴリ値)の出現回数や出現頻度でカテゴリ変数を置き換えます。
数値変数のスケーリングと同様に、学習データで使用した変換を検証/テストデータに対しても行う必要があります。
学習データと検証/テストデータを別々に集計・変換してしまうと両者が対応しなくなってしまうので注意が必要です。

cat_cols = [cat_col1, cat_col2, cat_col3] # 対象カラム
for col in cat_cols:
    # 出現回数集計
    freq = df_fc[col].value_counts()
    # 確認用にあえて上書きせずに別カラムとして生成
    df_fc[col + '_fc'] =df_fc[col].map(freq)
    # カテゴリの出現回数で置換しているため、出現回数同数のカラムは同一値

target encoding

target encodingでは、水準(カテゴリ値)ごとに目的変数の平均値を計算し、その値を特徴量として利用します。
回帰の場合は目的変数の平均値、二値分類(0, 1)の場合は割合が特徴量になります。
多クラス分類ではクラスの数だけ二値分類を行います。

たとえば、顧客ごとに特定のサービスの利用の有無(1, 0)を顧客属性から予測する場合において、3種類のカテゴリ値(北海道、東京、福岡)をもつ「都道府県」カラムをtarget encodingで変換するとします。
この場合、カテゴリ値ごとに予約率の集計(1, 0の平均値)を行います。
カテゴリ値ごとの予約率が「北海道:0.23、東京:0.35、福岡:0.19」である場合、この値を元のカテゴリ値の特徴量として使います。

時系列データではカテゴリ値の出現頻度が時間経過に伴って変化することが多いため、target encodingがうまく機能しないことも多いです。

target encodingの注意点として、目的変数をリークしてしまうリスクがあります。
極端な場合を考えると、その水準(カテゴリ値)に1つしかデータがない場合、target encodingをした特徴量は目的変数そのものです。

そのため実際に使う場合には、学習データを複数に分割し、自分自身以外の目的変数を使用せず他データの目的変数の値から計算した値を特徴量として使います(out-of-fold法)。
クロスバリデーションを行う場合は、リークを避けるためにバリデーションデータの目的変数を使用しない必要があります。
そのため、クロスバリデーションのfoldごとに変換をかけ直す必要があります。

サンプルコードについては、以下のリンク先に記載があります。
ch03-02-categorical.py
関連書籍:門脇 大輔 他「Kaggleで勝つデータ分析の技術」技術評論社 (2019)

embedding

自然言語処理において、日本語に含まれる単語ごとにカテゴリを作成してしまうとその数は膨大になってしまいます。
そこで、自然言語の単語などをベクトルに変換する方法をembeddingといいます。
自然言語処理のembeddingではすでに学習済みのword embeddingが公開されており、代表的なものにWord2VecやfastTextなどがあります。
学習済みembeddingを使うことで単語を数値ベクトルとして扱うことができ、単語ごとの意味の近さを調べたり単語同士で演算を行うことができます(例:王様 – 男 + 女 = 女王)。

参考文献

門脇 大輔 他「Kaggleで勝つデータ分析の技術」技術評論社 (2019)
Alice Zheng and Amanda Casari 「機械学習のための特徴量エンジニアリングーーその原理とPythonによる実践」オライリー・ジャパン(2019)
データサイエンス質的変数を説明変数として使う方法を解説(one-hot エンコーディングとダミー変数トラップ)【機械学習入門13】 米国データサイエンティストのブログ 2024/2/4閲覧
Word2vecとは?前後の言葉から単語の意味を表す手法 株式会社アイスマイリー 2024/2/5閲覧

-機械学習
-,