DictVectorizerで辞書のリストを処理する機械学習パイプラインを作る

2021.09.02

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

データアナリティクス事業本部の鈴木です。

今回は、sklearn.feature_extractionモジュールから、辞書のリストをNumPy配列やSciPyのスパース行列に変換するDictVectorizerを紹介します。

DictVectorizerとは

特徴量名と特徴量の値の辞書のリストを、NumPy配列やSciPyのスパース行列に変換し、scikit-learnのestimatorで使用できるようにするtransformerです。

sklearn.feature_extraction.DictVectorizer — scikit-learn 0.24.2 documentation

準備

検証した環境

  • コンテナ:jupyter/datascience-notebook
  • scikit-learn:0.24.2

データの作成

今回は、sklearn.datasetsモジュールのアイリスデータセットを使います。アイリスデータセットは言わずと知れた有名なデータセットで、3種のアヤメの情報が合計150サンプル分入っています。

データを作成していきます。少しまわりくどいかもしれませんが、いったんpandasのDataFrameに変換し、JSON形式のカラムを作成した後、リストに変換します。

import pandas as pd
from sklearn import datasets

# アヤメのデータをsklearnから読み出す。
iris = datasets.load_iris()

# pandasデータフレームに変換する。データをJSONに変換しやすいため。
# カラム名にスペースが入っていたので、扱いやすい名前に変えた。
feature_names = ["sepal_length", "sepal_width", "petal_length", "petal_width"]
df_iris = pd.DataFrame(iris["data"], columns=feature_names)

# 各行が何の種類なのか取り出す。
df_iris['species'] = iris.target_names[iris["target"]]

# 行ごとにJSON文字列に変換し、新しい列に格納する。
df_iris["iris_json"] = df_iris.apply(lambda row: json.loads(row.to_json()), axis=1)

# 作成したカラムをリストに変換する。
iris_json_list = df_iris["iris_json"].values.tolist()
## [{'sepal_length': 5.1,
##  'sepal_width': 3.5,
##  'petal_length': 1.4,
##  'petal_width': 0.2,
##  'species': 'setosa'},...

やってみる

DictVectorizerの動作を確認する

DictVectorizerインスタンスを作成し、fit_transformメソッドで作成したデータを変換してみましょう。

また、get_feature_namesメソッドで変換後の特徴量名も確認してみます。

from sklearn.feature_extraction import DictVectorizer
vec = DictVectorizer()

# 変換し、最初のレコードの結果を確認する。
result = vec.fit_transform(iris_json_list).toarray()
result[0]
## array([1.4, 0.2, 5.1, 3.5, 1. , 0. , 0. ])

# 各特徴の名前を確認する
vec.get_feature_names()
## ['petal_length',
##  'petal_width',
##  'sepal_length',
##  'sepal_width',
##  'species=setosa',
##  'species=versicolor',
##  'species=virginica']

fit_transformで変換すると、カテゴリ変数はOne-hot表現で出力されます。あるJSONに該当するキー・バリューがなかった場合は、検証したバージョンでは、全て0のOne-hot表現になりました。

変換後の特徴量名は、セパレータがデフォルトだと"キー名=カテゴリ名"のようになります。セパレータはseparator引数で指定が可能です。

inverse_transformで、結果を辞書のリストへと変換することも可能です。

# arrayやsparse matrixを辞書に変換する
vec.inverse_transform(result)

## [{'petal_length': 1.4,
##   'petal_width': 0.2,
##   'sepal_length': 5.1,
##   'sepal_width': 3.5,
##   'species=setosa': 1.0},...

DictVectorizerを使ってパイプラインを構築する

辞書のリストを入力として与えられている想定で、以下のようなパイプラインを構築してみます。

  1. 辞書のリストを読み込む。
  2. 量的変数は標準化する。
  3. カテゴリ変数はOne-hot表現のまま触らない。

今回は以下のように実装しました。

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import FunctionTransformer

# ColumnTransformerの作成
preprocessor = ColumnTransformer(
    transformers=[
         ('numeric_transformer', StandardScaler(), [0, 1, 2, 3]),
         ('categorical_transformer', FunctionTransformer(), [4, 5, 6])])

# 全体のパイプラインの作成
pipe = Pipeline([
    ("json_loader", DictVectorizer(sparse=False)),
    ("preprocessor", preprocessor)])

パイプラインをダイアグラムで表示すると以下のようになります。

from sklearn import set_config
set_config(display='diagram')   
pipe

DictVectorizerを使ったパイプライン例

パイプラインを作成する際には、以下のことに気を付けました。

  • 今回はDictVectorizerのsparse引数をFalseに設定しました。SciPyのスパース行列をStandardScalerに渡す際、StandardScalerはwith_mean引数をFalseにする必要があり、標準化の際に平均が引かれなくなってしまうためです。今回のデータでは、One-hot表現のカラムが3つ増えるだけなので、大きな影響はないだろうと思い、このように設定しています。

  • DictVectorizerは結果をNumPy配列かSciPyのスパース行列で返すため、ColumnTransformerを使ってTransformerをカラムごとに適用できるよう、カラムをインデックスで指定しました。カラムのインデックスは、事前に、データにDictVectorizerを試しておき、get_feature_namesメソッドを使って調べるのが良さそうでした。

  • ColumnTransformerを使う際、カテゴリ変数にはFunctionTransformerをfunc引数なしで適用しました。func引数なしの場合、FunctionTransformerは恒等関数として利用できます。

変換結果は以下のようになります。

# 変換結果を出力する。
pipe.fit_transform(iris_json_list)

## array([[-1.32790667, -1.30363303, -0.89529213, ...,  1.        ,
##          0.        ,  0.        ],
## ...

最後に

sklearn.feature_extractionモジュールのDictVectorizerの使い方と、DictVectorizerを使った機械学習パイプラインの例を紹介しました。

DictVectorizerはカテゴリ変数をOne-hot表現に変換するため、カテゴリ変数のカテゴリ数が多い場合は、事前にそのキー・バリューは落としておく必要がありそうでした。

辞書のリストを直接インプットにできるのは、JSONファイルをインプットとするような際にとても便利なので、ぜひ使っていきたいです。