この記事は公開されてから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を使ってパイプラインを構築する
辞書のリストを入力として与えられている想定で、以下のようなパイプラインを構築してみます。
- 辞書のリストを読み込む。
- 量的変数は標準化する。
- カテゴリ変数は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の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ファイルをインプットとするような際にとても便利なので、ぜひ使っていきたいです。