sklearn.preprocessing

使い方

機械学習のうち、ニューラルネットワークやSVMなどのモデルは、データの値の大きさやレンジが異なる場合、過学習になったり精度が悪くなることがあり、データを揃えるための前処理が必要になる(SVMの例ニューラルネットワークの例)。

scikit-learnのpreprocessingモジュールには、データの前処理を行う各種のクラスが準備されている。一般的な使い方は以下の通り。

  1. データを訓練データとテストデータに分ける
  2. 各preprocessorのfit()メソッドに訓練データを与えて変換用のパラメータを準備する(変換モデルを構築する)
    • fit()メソッドは、各列が特徴量、各行がデータレコードである2次元配列を想定している
  3. 変換器のtransform()メソッドに訓練データを与えて前処理を施す
  4. 同じ変換器のtransform()メソッドにテストデータを与えて前処理をほどこす

なお、fit()メソッドとtransform()メソッドをそれぞれ分けて行うほか、fit().transform()とメソッドチェーンで実行してもよい。またpreprocessorにはこれらを一体化したfit_transform()というメソッドも準備されている。

実行例

preprocessingのscaler系のクラスの1つ、MinMaxScalerを例にして、その挙動を追ってみる。

まず必要なライブラリーやクラスをインポートし、Breast cancerデータを読み込み、データを訓練データとテストデータに分ける。cancerデータは30の特徴量を列とし、569のレコードを持つが、それを3:1に分け、426セットの訓練データと143セットのテストデータとしている。

次にMinMaxScalerのインスタンスを生成し、fit()メソッドに訓練データX_trainを与えて、変換用のモデルを構築する。

preprocessingでいうモデルの構築とは、基準となるデータを与えて、変換用のパラメータを算出・保持するのに相当する。

今回の例のMinMaxScalerオブジェクトでは、特徴量数を要素数とする1次元配列で、データセット中の各特徴量の最小値(data_min_)、最大値(data_max_)、最大値-最小値のレンジ(data_range_)、レンジの逆数であるscales_がインスタンス内に保持されている。

これらのパラメーターは、30の特徴量について、426個のデータの最小値、最大値・・・などとなっている。たとえば1つ目の特徴量については、最大値-最小値は28.11−6.98=21.13となり、data_range_の1つ目の値と符合している。またscales_の各要素は、data_range_の各要素の逆数となっている。

構築された変換器によりX_trainを変換すると、すべての特徴量について最小値が0、最大値が1となる。

同じ変換器でテストデータも変換すると、変換後の特徴量の最小値・最大値は0、1になっていない。これはテストデータの最大値・最小値が必ずしも訓練データのそれらと一致しないので当然である。また、テストデータの最大値が訓練データの最大値よりも大きい場合は、テストデータの最大値が1を超えることになる。

テストデータで改めてfit()メソッドを実行してテストデータに適用するとレンジが0~1になるが、そうすると訓練データとテストデータで異なる変換を行うことになり、結果が歪んでしまう。

preprocessingの各種モデル

sklearn.preprocessingには多様な変換器が準備されているが、それらを目的ごとのカテゴリーに分けて整理する。

scaler~スケール変換

データの大きさやレンジを変換してそろえる。

MinMaxScaler
各特徴量が0~1の範囲になるよう正規化する(線形変換)。
StandardScaler
各特徴量の標本平均と標本分散を使って標準化する(線形変換)。
RobustScaler
各特徴量の中央値と4分位数を使って標準化する(線形変換)。

normalization~正則化

特徴量ベクトルのノルムをそろえる。レンジをそろえる目的のscalerに比べて、元のデータ分布の相似性はなくなる。

Normalizer
特徴量ベクトルのノルムを1にそろえる。

binalize~2値化

特徴量データを0/1の2値に分ける。

encoder~カテゴリーデータのエンコード

カテゴリーで与えられたデータ(性別、曜日など)をモデルで扱うために数値化する。

LabelEncoder
1次元配列で与えられた特徴量クラスデータを、数値ラベルに変換する。
OrdinalEncoder
2次元配列で与えられた特徴量クラスデータを、数値ラベルに変換する。
OneHotEncoder
2次元配列で与えられた特徴量クラスデータを、特徴量ごとのインジケーター列に変換する。

スケール変換の頑健性

MinMaxScalerは計算過程が簡明だが、飛び離れた異常値がわずかでもあるとそれが全体のレンジを規定し、本来適用したいデータの値が歪んでしまう。StandardScalerやRobustScalerはこのような異常値に対して頑健な変換を行う。これら3つの頑健性についてはこちらで確認している。

 

OneHotEncoder

概要

OneHotEncoderは、あるクラスデータの特徴量をエンコードする。LabelEncoderOrdinalEncoderが特徴量内のクラスに一連の数値を振るのに対して、OneHotEncoderはクラスの数だけ列を確保し、データごとに該当するクラスのみに1を立てる。エンコードされたデータは、該当するクラスのみに反応するインデックス引数となる。

使い方

fit()~インデックス列の生成

以下の例は、2つのクラス特徴量を持つ6個のデータセットをOneHotEncoderで変換。

  • sklearn.prreprocessingからOneHotEncoderをインポート
  • エンコーダーのインスタンスを生成
    • デフォルトではスパース行列になるので、オプションでsparse=Falseを指定
  • fit()メソッドでデータをフィッティングし、変換器を準備
  • この段階でcategories_プロパティーには各特徴量ごとのインデックス構成がセットされる

以下の例では、1つ目の特徴量は3つのクラス、2つ目の特徴量は2つのクラスを持つので、3要素、2要素の配列を要素に持つリストがcategories_にセットされる。

transform()~インデックスデータへの変換

fit()メソッドで準備された変換器によってデータを変換する。変換後のデータは特徴量のクラス数分の列を持つ2次元のndarrayで返される。なおfittransformを一度に行うfit_transform()メソッドも準備されている。

出力の右3列は3つの都市、それに続く2列は性別に対応していて、たとえば1行目のデータの都市はcategories_[0]の3番目'Tokyo'、性別はcategories_[1]の2番目の'Male'であることがあらわされている。

DataFrameによる操作

OneHotEncoderpandas.DataFrameも扱える。ただしtransfrom()fit_transform()メソッドの戻り値はndarrayなので、以下の例ではこれをDataFrameの形にしている。このときcolumns引数にエンコーダーのインスタンスのcategories_プロパティーを使うと個別のクラス名まで打ち込まずに済んで便利。

数値データとクラスデータが混在する場合

DataFrameの準備

以下の例では、2つのクラス特徴量と2つの数値特徴量を持つデータセットをDataFrameとして扱う。

クラスデータのヘッダーの準備

クラスデータを複数のインデックスデータの列にするための準備。

  • 特徴量のうち、クラスデータのものと数値データのもののヘッダーを分けておく
  • クラスデータ用のDataFrameを準備して、元データからクラスデータの列だけを切り出し
  • エンコーダーを生成してfit_trans()を実行
  • 実行後にエンコーダーのcategories_に保持されているクラスリストを取得

このクラスリストが変換後のデータのヘッダーになる。

クラスデータと数値データの合体

以下の処理では、変換されたクラスデータ列と元の数値データ列を合わせて最終的なデータセットとしている

  • クラスリストをヘッダーとして、変換後のクラスデータ(ndarray)をDataFrameとして読み込み
  • 上記DataFrameに元データの数値データを追加

この処理によって元データセットから特徴量の順番が変わるが、学習過程で特徴量の順番は影響しない。

inverse_transform()

上でdf_X_trans = df_X_class_trans.copy()としたので、df_X_class_transは保存されている。このデータをエンコーダーのinverse_transform()に与えると、複数列で表現されていたクラスが元の表現で得られる。

新しいデータの変換

訓練済みモデルにデータを与えて予測する場合、前処理のエンコーディングでは、フィッティング済みのエンコーダーに新しいデータを与えて変換する。

未知のクラスへの対処

フィッティング時になかったクラスに遭遇した場合の動作は、エンコーダーのインスタンス生成時に指定する。

OneHotEncoder(handle_unknown='error'/'ignore')

デフォルトは'error'で、未知のクラスに遭遇するとエラーを投げる。'ignore'を指定すると未知のクラスの場合はその特徴量のすべてのクラスラベルが0になる。

以下の例では、2行目のデータにフィッティングでは含まれていなかった”Nagoya”があるため、変換後のデータの2行目の1~3列が0となっている。

この変換データをinverse_transform()で逆変換すると、未知のクラスであったところは'None'に変換される。

 

 

OrdinalEncoder

概要

sklearn.preprocessingOrdinalEncoderは、2次元のデータ(行数×列数=データ数×特徴量数)を須知ラベルデータに変換する。

  • コンストラクターでencoderのインスタンスを生成
  • fit()メソッドに2次元の元データを与える(元データは2次元のリスト、ndarray、DataFrameは可)
  • 元データの特徴量ごと(列ごと)にデータが数値ラベル化される
  • 特徴量のカテゴリー数がn_classのとき、特徴量データが0~n_class−1の整数ラベルに変換される
  • 1次元のデータを変換する場合も2次元に変形する必要がある
  • 変換は全ての列が対象となり、定量的な数値データが含まれていてもそれらが数値ラベルに変換される

使い方

fit~ラベルの設定

以下の例では、3つの特徴量を持つ6つのデータを例題としている。特徴量は3つともクラスデータで、fit()メソッドで変換器の準備をする。

  • エンコーダーにおけるfit()は、特徴量ごとにクラスデータのラベルを設定し、変換器を準備する
  • フィッティングの後、categories_プロパティーにリストがセットされる
  • categories_ndarrayを要素とするリストで、各配列には特徴量ごとの重複を除いたクラス名が格納される
  • 各特徴量のクラスはcategories_各要素の配列の先頭から数値ラベル0, 1, 2, …に対応している。

transform~ラベルへの変換

この変換器のtransform()メソッドで元データを変換すると、元データと同じ次元・次数の2次元配列が得られ、各クラスデータが数値データに変換された結果が格納されている。

なお、OrdinalEncoderにもfit_transform()メソッドが準備されている。

1次元のデータを変換する場合でも、1×1の2次元とする必要があり、結果も2次元の配列で返される。

inverse_transform()で数値ラベルをクラスデータに逆変換可能。

categories_パラメーターについて

なおコンストラクターのcategories_パラメーターを指定できるが、これはあらかじめ特徴量のクラスデータがわかっている場合に、これらを全特徴量について指定する。この際、元データに含まれないクラスを含めてもよい。

数値データとクラスデータが混在する場合

クラスデータと数値データが混在する場合にOrdinalEncoderで変換すると、すべてのデータがクラスデータとみなされ、数値データもラベルに変換されてしまう。

以下の例では、最後の列の実数データも、1, 1.5, …, 5に対して0, 1, …, 5のラベルに変換されている。

このような場合は、クラスデータのみ取り出して変換させる。OrdinalEncoderpandas.DataFrameを扱うことができるので、列操作のために元データをDataFrameとする。

今回の例では、最初の3列がクラスデータなので、一時的なDataFrameにそれらを切出してOrdinalEncoderを適用する。transform()の結果はndarrayで戻るので、それを元のDataFrameの列に入れ替えている。

最後の列はそのままで、その前の3列がラベルデータに変換されている。

 

Normalizer

概要

sklearn.preprocessorsモジュールのNormalizerは、特徴量ベクトルのノルムが1になるようにする。具体的には、データごとに特徴量Fiを以下の式によってFi*に変換する。

(1)    \begin{equation*} {F_i}^* = \frac{\sum F_i}{\left( \sum {|F_i|}^p \right) ^\frac{1}{p}} \end{equation*}

ノルムのタイプはコンストラクターの引数で指定する。デフォルトは'l2'で、その他に'l1''max'を指定可能。

Normalizer(norm='l2')

挙動

それぞれ異なる正規分布に従う2つの特徴量について、Normalizerを適用したときの挙動を以下に示す。

scalerのような相似性の変換ではないので左下の変換後のヒストグラムは変換前の形状と異なっている。

データの空間的な分布は、デフォルトのL2ノルムの指定によって全データが半径1の円周上に位置するよう変換される。

変換後のデータを拡大してみると以下の通りで、原点を中心とした半径1の円周上に各点が並んでいる。

他の2つ、L1ノルムと最大値ノルムを指定して実行した結果が下記の通りで、それぞれのノルムに応じた線上に各点が並んでいる。

コードは以下の通りで、データに対してfit()メソッドでスケールパラメーターを決定し、transform()メソッドで変換を行うところを、これらを連続して実行するfit_transform()メソッドを使っている。

特徴

Normalizerは特徴量ベクトルの方向だけが重要な場合に用いる。たとえば空間内の特定の方向範囲にあるクラスターの分離などかと思うが、抽象的なものになると想像がつかない。実際、サイト上で見ても、Normalizerの意義とデータの性質に基づいて適用しているケースは、検索上位には出てこない。

なおNormalizerによる変換は不可逆であり、scalerのようなinverse_transform()を持たない。

 

preprocessor – 異常値に対する頑健性

機械学習モデルにデータを適用するための前処理としていくつかのアルゴリズムによっては、異常値の影響を受けやすいことがある。

たとえば下図の左のような分布のデータがあるとする(平均が1、分散が1の正規分布に従う500個のランダムデータ)。そしてこのデータに値20の異常値が10個発生したとすると、全体の分布は右のようになる。

このデータに対して、MinMaxScalerStandardScalerRobustScalerで変換した結果を以下に示す。ただしStandardScalerRobustScalerについては、異常値は表示させず元の正規分布に係る範囲のみを表示している。

まず左側のMinMaxScalerについては、異常値を含めてレンジが0~1となるので、本体の正規分布のデータが0付近の小さな値に集中する。このため、本来学習の精度に効いてくるべき本体部分のデータの分離が十分でない可能性が出てくる。

真ん中のStandardScalerと右側のRobustScalerについては、本体部分の形は元の正規分布の形と大きく変わらず、頑健であることがわかる。

ここで異常値の個数を10個から20個に増やして、同じく3種類の変換を施してみる。

左側のMinMaxScalerについては、異常値の個数とは関係なくその値のみでレンジが決まり、元の分布が0付近に押し込められている状況は同じ。

真ん中のStandardScalerについては、10個の時に比べて少し分布の形が変わっていて、レンジが狭くなっている。

右側のRobustScalerについては、元の分布の形は大きくは変わっていない。

以上のことから、少なくとも3つの変換器について以下のような特徴があることがわかる。

  • MinMaxScalerは異常値によって本来分析したいデータのレンジが狭くなる可能性がある
  • StandardScalerは異常値の影響を受けにくいが、その大きさや頻度によって若干本体部分の分布が影響を受ける
  • RobustScalerは異常値の個数が極端に多くなければ、本来のデータの特性を頑健に保持する

なお、上記の作図のコードは以下の通り。

 

RobustScaler

概要

sklearn.preprocessingモジュールのRobustScalerは、各特徴量の中央値(medi)と第1-4分位数(q1i)、第3-4分位数(q3i)を用いて特徴量を標準化する。

(1)    \begin{equation*} {F_i}^* = \frac{F_i - med_i}{q_{3i} - q_{1i}} \end{equation*}

挙動

それぞれ異なる正規分布に従う2つの特徴量について、RobustScalerを適用したときの挙動を以下に示す。異なる大きさとレンジの特徴量が、変換後には原点を中心としてほぼ同じような広がりになっているのがわかる。

コードは以下の通りで、データに対してfit()メソッドでスケールパラメーターを決定し、transform()メソッドで変換を行うところを、これらを連続して実行するfit_transform()メソッドを使っている。

簡単なデータでRobustScalerの計算過程を確認しておく。以下の例では5個のデータにRobustScalerを適用している。これは1つの特徴量を持つ5個のデータを模していることになる。

インスタンス内に保持されたパラメーターのうち、center_は特徴量の標本平均、scale_が第3-4分位数-第1-4分位数となっていて、これらで各特徴量が標準化されているのが確認できる。

特徴

RobustScalerは異常値に対して頑健であり、StandardScalerより頑健性が高い。

 

StandardScaler

概要

sklearn.preprocessingモジュールのStandardScalerは、各特徴量の標本平均と標本分散を用いて特徴量を標準化する。

具体的には、特徴量Fiの標本平均(mi)と標本分散(vi)から以下の式により各特徴量FiFi*に変換する。

(1)    \begin{equation*} {F_i}^* = \frac{F_i -m_i}{\sqrt{v_i}} \end{equation*}

挙動

それぞれ異なる正規分布に従う2つの特徴量について、StandardScalerを適用したときの挙動を以下に示す。異なる大きさとレンジの特徴量が、変換後には原点を中心としてほぼ同じような広がりになっているのがわかる。

コードは以下の通りで、データに対してfit()メソッドでスケールパラメーターを決定し、transform()メソッドで変換を行うところを、これらを連続して実行するfit_transform()メソッドを使っている。

簡単なデータでStandardScalerの計算過程を確認しておく。以下の例では5個のデータにStandardScalerを適用している。これは1つの特徴量を持つ5個のデータを模していることになる。

インスタンス内に保持されたパラメーターのうち、mean_は特徴量の標本平均、var_は標本分散(不偏分散ではない)となっている。scale_はvar_の平方根。

各データの特徴量は次式で標準化されているのが計算で確認できる。

(2)    \begin{equation*} {F_i}^* = \frac{F_i - \rm{mean\_}}{\rm{scale\_}} = \frac{F_i - \rm{mean\_}}{\sqrt{\rm{var\_}}} \end{equation*}

特徴

StandardScalerは異常値の影響に対して比較的頑健である

 

MinMaxScaler

概要

sklearn.preprocessingモジュールのMinMaxScalerは、各特徴量が0~1の範囲に納まるように変換する。具体的には、特徴量Fiの最小値(mini)と最大値(maxi)から以下の式により各特徴量FiFi*に変換する。

(1)    \begin{equation*} {F_i}^* = \frac{F_i - min_i}{max_i - min_i} \end{equation*}

挙動

それぞれ異なる正規分布に従う2つの特徴量について、MinMaxScalerを適用したときの挙動を以下に示す。異なる大きさとレンジの特徴量が、変換後にはいずれも0~1の間に納まっているのが確認できる。

コードは以下の通りで、データに対してfit()メソッドでスケールパラメーターを決定し、transform()メソッドで変換を行うところを、これらを連続して実行するfit_transform()メソッドを使っている。

特徴

MinMaxScalerは簡明な方法だが、極端に値が離れた異常値が発生すると本来のデータがその影響を受ける場合がある。

scikit-learn – predict_proba

概要

decision_function()は各データが推測したクラスに属する確信度(confidence)を表すが、超平面のパラメータに依存し、そのレンジや値の大きさと確信度の関係が明確ではない。

これに対してpredict_probaは、それぞれのターゲットが予測されたクラスに属する確率を0~1の実数で表す。2クラス分類では、結果の配列の形状は(n_sumples, 2)となる。

predict_proba()の挙動

以下はmake_circles()で生成した2クラスのデータをGradient Boostingによって分類したときの確信度。各データに対応した2要素の配列の1つ目がクラス0(blue)、2つ目がクラス1(orange)に属する確率を表し、2つの和は1となる。なお16行目でsuppress=Trueとすることで、ndarrayの表示を常に固定小数点としている。

decision_function()との比較

先のコードに以下を続けて、predict_proba()による確率、予測されたクラス、decsion_function()の値と、各データの正解クラスを並べて表示する。予測されたクラスの方の確率が大きいこと、その予測結果とdecision_function()の符号が一致していることが確認できる。

このデータをクラス0(blue)に対する確率(prob0)でソートし、decision_function()との関係を見てみると、以下のことがわかる。

  • blueクラスの確率が高いとdecision_functionの確信度はマイナスで絶対値が大きくなり、orangeクラスの確率が高いと確信度はプラスで絶対値が大きくなる
  • blueクラスの確率とorangeクラスの確率が同程度の時、確信度の絶対値が同程度になり、符号が逆になる
  • 確率に対して確信度は線形ではない

クラス0(blue)に対する確率とdecision_function()の確信度の関係を図示すると以下のようになり、確率に対して確信度が必ずしも線形になっていないことがわかる。

コードはmatplotlib.pyplotをインポートした上で、以下を追加。

決定境界

以下は、predict_proba()で計算された確率を可視化したもので、decision_function()の場合に比べて、直感的にも分かりやすい分布となっている。

コンターに表す値として、30行目でpredict_proba()の結果の0列目、すなわちClass0の確率を取り出している。

3クラス以上の場合

3クラスのirisデータセットにGradientBoostingClassifierを適用し、predict_proba()の出力を見てみる。

このコードの出力結果は以下の通り。3つのクラスに対する確率が得られ、合計は1になる。こちらはdecision_function()が2クラスの時だけ配列が1次元となるのと違って、どのような場合でも行数×列数=データ数×クラス数の配列になる。

なお17行目で、argmaxを使って各データで確率が最大となるクラスを探している。

 

scikit-learn – decision_function

概要

decision_function()は、超平面によってクラス分類をするモデルにおける、各予測データの確信度を表す。

2クラス分類の場合は(n_samples, )の1次元配列、マルチクラスの場合は(n_samples, n_classes)の2次元配列になる。2クラス分類の場合、符号の正負がそれぞれのクラスに対応する。

decision_function()を持つモデルは、LogisticRegressionSVCGladientBoostClassifierなどで、RandomForestはこのメソッドを持っていない。

decision_function()の挙動

decision_function()の挙動をGradientBoostingClassifierで確認する。

まずmake_circles()で2クラスのデータを生成し、外側のクラス0をblue、内側のクラス1をorangeとして再定義する。

次に、データを訓練データとテストデータに分割し、訓練データによって学習する。データ分割にあたって、Xyに加えて文字列に置き換えたy_namedを分割している。学習の際にはXy_namedの訓練データとテストデータのみを用いるのでyについては特に含める必要ないが、ここではtrain_test_split()が3つ以上のデータでも分割可能なことを示している。

学習後の分類器のclasses_プロパティーを参照すると、クラスがどのように表現されるかを確認できる。上のfit()メソッドでy_train_namedを与えたのでクラスの表現が文字列になっているが、代わりにy_trainを用いると[0, 1]のように元のyに対応したクラス表現が返される。

次に、学習済みモデルにテストデータを与えて、decision_function()の結果とpredict()の結果を並べてみる。decision_function()fit()で与えたテストデータ数の1次元配列を返し、各要素の負の値に対してクラス0のblueが、正の値に対してはクラス1のorangeがpredict()で予測されていることがわかる。

decision_function()の各要素の符号に応じてpredict()と同じ結果を得たいなら、次のように処理していくとよい。

最後に、上記のデータと正解であるy_test_namedのデータを先ほどのデータフレームに追加して全体を確認する。predit()メソッドの結果とdecision_function()の符号による判定結果は等しく、y_testと異なるデータがあることがわかる。

decision_function()の意味

decusuib_function()のレベルは超平面上の高さになるが、これはデータ、モデルパラメーターにより変化し、このスケールの解釈は難しい。それはpredict_proba()で得られる予測確率とdecision_function()で計算される確信度の非線形性からも予想される。

circlesデータに対するGradientBoostingClassifierの決定境界とdecision_function()の値の分布を表示したのが以下の図。コンターが交錯していてわかりにくく、直感的にはpredict_proba()の方がわかりやすい

3クラス以上の場合

3クラスのirisデータセットにGradientBoostingClassifierを適用して、decision_function()の出力を見てみる。

このコードの出力結果は以下の通り。2クラスの場合は1次元配列だったが、3クラスになると行数×列数がデータ数×クラス数の配列になる。predict_proba()は2クラスでも2列の配列になるので、decision_function()の2クラスの場合だけ特に1次元配列になると言える。

なお、19行目で各データごとに最大の値をとる列をargmaxで探して、そのサフィックスを”decision”のクラス番号として表示している。