OneHotEncoder

概要

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

なお、DataFrameget_dummis()メソッドでもone-hotエンコーディングができる。

使い方

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'に変換される。

 

 

LabelEncoder

概要

sklearn.preprocessingLabelEncoderは、クラスデータ(カテゴリーデータ)を数値ラベルに変換する。

  • コンストラクターは引数をとらない
  • fit()メソッドに特徴量を要素とする1次元配列(特徴量数)の元データを与える
  • 特徴量のクラス数がn_classのとき、特徴量データが0~n_class−1の整数ラベルに変換される
  • 特徴量が定量的な数値データであっても整数ラベルに変換される

使い方

LabelEncoderを使うには、まずそのインスタンスを生成し、fit()メソッドで数値ラベルを生成する。fit()メソッドを実行すると、元データのクラスの重複を除いたクラスリストがclasses_プロパティーに保存され、transform()メソッドで任意のデータを変換する変換器が準備される。

準備された変換器で、変換したいデータにtransform()メソッドを適用して、変換された数値ラベルを得る。

このラベルデータにinverse_transform()を適用すると、数値ラベルが元のクラスデータに逆変換される。

transform()の引数に元データに存在しないクラスデータが含まれていた場合、エラーとなる。

注意

LabelEncoderは、元データに定量的な数値データを与えた場合でもこれらを数値ラベルに変換する。

transform()の引数に元データに存在しない数値が含まれている場合はエラーとなる。

 

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は異常値の影響に対して比較的頑健である

 

MLP – 多層パーセプトロン

線形モデルの多層化

“Pythonではじめる機械学習”の写経。多層パーセプトロン(Multilayer perceptron : MLP)はフィードフォワード・ニューラルネットワークとも呼ばれる。

まず、線形モデルを以下の式で表す。

(1)    \begin{equation*} b + w_0 x_0 + \cdots + w_n x_n \end{equation*}

n = 3の場合について図示すると、以下のように表せる。左側のノードの特徴量xiに対して、wiによる重み付き和を計算している。

MLPは、この構造に中間層を導入し、中間層に隠れユニット(hidden units)を配置する。特徴量入力はまず隠れユニットに対して重み付き線形和を計算し、その後に隠れユニットの出力の重み付き線形和を出力とする。

特徴量xi (i = 0~n)の隠れユニットhj (j = 0~m)に対する重みをwij、切片をbjとすると、hjへの入力となる重み付き線形和は以下のようになる。

(2)    \begin{equation*} h_j = \sum_{i=0}^n (b_j + w_{ij} x_i) \end{equation*}

また、隠れユニットhjの出力\hat{y}に対する重みをvj、切片をcとすると、出力への重み付き線形和は以下のようになる。

(3)    \begin{equation*} \hat{y} = c + \sum_{j=0}^m v_j h_j = c + \sum_{j=0}^m v_j \sum_{i=0}^n (b_{ij} + w_{0ij} x_i) \end{equation*}

これは結局、xiに対する重み付き線形和となる。たとえば特徴量0~3、隠れユニット0~2の場合は以下のとおり。

(4)    \begin{align*} \hat{y} &= c + v_0 h_0 + v_1 h_1 + v_2 h_2 \\ &= c + v_0 (b_0 + w_{00} x_0 + w_{10} x_1 + w_{20} x_2) \\ &\phantom{=c+}v_1 (b_1 + w_{01} x_0 + w_{11} x_1 + w_{21} x_2) \\ &\phantom{=c+}v_2 (b_2 + w_{02} x_0 + w_{12} x_1 + w_{22} x_2) \\ &= c + v_0 b_0 + v_1 b_1 + v_2 b_2 \\ &\phantom{=}+ (v_0 w_{00} + v_1 w_{01} + v_2 w_{02}) x_0 \\ &\phantom{=}+ (v_0 w_{10} + v_1 w_{11} + v_2 w_{12}) x_1 \\ &\phantom{=}+ (v_0 w_{20} + v_1 w_{21} + v_2 w_{22}) x_2 \end{align*}

非線形活性化関数

単純な線形和をいくら多層化しても、結果は特徴量の線形和にしかならない。そこで、隠れユニットの入力に対して非線形関数を適用して出力とし、複雑・柔軟な動作を可能とする。

このような関数を活性化関数(activation function)あるいは伝達関数(transfer function)と呼び、様々な種類がある。書籍では、このうちReLU (Rectified linear unit)とtanh (hyperbolic tangent)が紹介されている。ReLUは以下の式で表され、負の値が採用しえない(計算過程での)ノイズであるような場合に好都合らしい。tanhは(−∞, +∞)の入力に対して(−1, +1)の出力を返す。

(5)    \begin{equation*} h(x) = \max (0, x) = \left\{ \begin{align} x \quad (x \ge 0) \\ 0 \quad(x < 0) \end{aling} \right. \end{equation*}

ニューラルネットワークのチューニング

two moonsデータでの確認

two moonsデータセットに対してMLPを適用する。隠れユニットの数はデフォルトの100としている。

隠れユニット数と決定境界

隠れユニット数を10とした場合の結果は数の通り。先のユニット数100の場合に比べて、決定境界が折れ線になっている。

隠れユニット数の指定はhidden_layer_sizes=[10]のように指定する。複数の隠れ層を表現するためにリストとなっていて、1層の場合でも1要素のリストとする。また、収束計算回数の最大値がデフォルトのmax_iter=200では収束しきれないという警告が出るため、この値を1000に引き上げている。

結果は書籍のものと少し異なっていて、上方の▲の点より上に鋭く境界が突き抜けている。いくつかパラメーターを変えてみたが、書籍のような境界の形状は再現できなかった。

隠れユニットの数を[1]~[4]と変化させたときの決定境界の様子は以下の通りで、ユニット数が増えるにしたがって決定境界を構成する線分の数が増えている。

隠れ層の数

隠れユニット数が10程度でも、隠れ層の数を増やすと決定境界は滑らかになる。

隠れ層が2層の場合に、各層のユニット数を変化させたときの決定境界の変化を見てみる。1層目のユニット数が大まかな形に影響し、2層目のユニットは決定境界の滑らかさに影響していると言えそうだ。

活性化関数tanh

デフォルトでは非線形活性化関数にReLUが用いられるが、これをtanhとすることで下図のように決定境界が滑らかになる。デフォルトのまま(右)だと書籍のような形にならないが、最大計算回数max_iter=115と制限すると大体似たような形になる。

ここでも2つの隠れ層のユニット数を変化させてみると、第1層が大まかな形、第2層が細部の表現に影響していると言えそうだ。

正則化

MLPClassifierはL2正則化が可能で、パラメーターalphaに大きな値を設定すると正則化を強くできる。デフォルトはalpha=0.0001で正則化が効いていない状態。

以下に、2層のユニット数[10, 10]と[100, 100]に対してalphaをデフォルトの0.0001から1.0まで変化させたときの様子を示す。ただしmax_iter=500として未収束の警告が出ないようにしている。alphaを大きくするにしたがって正則化が強くなり、決定境界がシンプルなものになっていく様子が見られる。

ランダムな重みづけの影響

ニューラルネットワークでは、学習開始前に各重み係数がランダムに割り当てられるため、その初期値がモデルに影響を与える。以下は同じパラメーター設定に対してrandom_stateのみを変化させたもので、決定境界の形が異なっている。

データの前処理等

MLPのBreast cancerデータセットへの適用例で、データの標準化や重み係数の分布の確認等を行っている。

今後の課題

  • 総数・ユニット数と計算量の関係
  • パラメーター調整のパターン
  • scikit-learn以外のライブラリー(keras, lasagna, tensor-flow)
  • GPUのサポート
  • 収束計算のアルゴリズム(lbfgs, adam, sgd)

 

Breast cancerデータセット – MLP

精度不足

書籍”Pythonではじめる機械学習”の”2.3.8.2 ニューラルネットワークのチューニング”で、scikit-learnのMLPBreast Cancerデータセットに適用した例が示されている。

デフォルトのパラメーターのままで実行した例は以下の通りだが、訓練スコアとテストスコアは、書籍ではそれぞれ0.92と0.90となっていて、下の結果とは異なる。

データの標準化

これに対して書籍では、特徴量データを標準化(standardize)する例を示している。同じコードで計算したのが以下の結果で、この場合は書籍と同じ値となっている。

ここで未収束の警告が出て、これも書籍と同じ。

書籍に倣ってmax_iter=1000とすると正常終了するが、今度は書籍の結果(0.995/0.965)と異なる結果となってしまう。

random_stateが違う?

よく見ると、最初のコードではMPLClassifierのパラメーターでrandom_state=42とそれ以前と同じ値を使っているが、その後の2つの計算ではrandom_state=0と異なる値を使っている。MLPの解説で重みの初期値に影響するrandom_stateの値によってモデルが異なることを注意しているにもかかわらず、このパラメーターを変更している理由がよくわからない(値を42に揃えてみたところ、ドラスティックな変化はなかったが)。

重み係数の分布

最後に、書籍に掲載されているimshowを使った重み係数の分布を再現してみる。imshowは画像ファイルを表示するほかに、配列を与えてその内容に応じたイメージを表示できる。colorbarは扱いがややこしそうで、Axesに対して適当なメソッドが見当たらなかったので、ここではpyplotに直接描画している。

 

Python/pyplot – 決定境界の描き方

決定境界の描き方として以前ループを使った泥臭い方法を考えたが、meshgridを使って数行で書けることを知ったのでまとめ。

結論としては以下の19~25行目の8行で、以下の手順で決定境界を書いている。

  1. 2つの特徴量の全領域をカバーする値をnumpy.linspace()で生成
  2. numpy.meshgrid()で2次元のグリッドに変換
  3. 各特徴量のメッシュグリッドを1次元に変形し、縦2列の配列化
  4. prediction()メソッドでその配列の各座標に対応する予測値を計算(結果は1次元配列)
  5. 結果の配列をmeshgridと同じ形状の2次元配列に変形
  6. contour/contourf()で決定境界を描画

具体的な変数の変形状況を要素数4の少ない例で示すと以下の通り。

まず、2つの特徴量の範囲の数列を生成する。

それらの数列を、meshgridで2次元配列に変形する。

予測モデルに与える変数は各特徴量を列とする2次元配列とする必要があるので、まず上の2次元配列をそれぞれ1次元に変形。この変形では、2次元配列の各行を連ねていった1行の配列を列ベクトルにした形になる。

次に2つの列ベクトルを横方向に並べて、総計算データ数×特徴量数(2)の2次元配列とする。

この配列の各座標に対する予測値を、predict()メソッドで予測。この結果は、1次元化されたf0やf1と同じく、2次元のmeshgridの各行を横に連ねたものになっている。

この結果を、meshgrid化されたf0(またはf1)と同じ形に変形。これで予測結果がf0×f1平面の各座標に対応した予測値の2次元配列となっている。

この結果を使い、contour()/contourf()で決定境界あるいは決定領域を描画。

ここでlevelsの指定は以下のようにしている。

まずcontour()の場合、ドキュメンテーションには“If an int n, use n data intervals; i.e. draw n+1 contour lines. The level heights are automatically chosen.”と書かれているので、levels=0と指定すると0+1本の線が描かれると考えたが以下のような警告が出て線の位置がずれた。

そこでlevels=[0.5]と2つのクラス値0と1の間をとると適切に表示される。

なおcontourf()のときは、levels=1として2つの領域が描かれる。

 

 

Breast Cancerデータセット – SVM

過学習?

書籍”Pythonではじめる機械学習”の”2.3.7.4 SVMパラメータの調整”の最後の方で、scikit-learnのSVMをBreast Cancerデータセットに適用した例が示されている(カーネル法によるSVMについてはこちらにまとめている)。

ここで、原典ではSVC()の引数を指定せずデフォルトのままとしているが、そのまま実行すると以下のような結果になった。

scikit-learnのドキュメンテーションによると、

Kernel coefficient for ‘rbf’, ‘poly’ and ‘sigmoid’.

  • if gamma='scale' (default) is passed then it uses 1 / (n_features * X.var()) as value of gamma,
  • if ‘auto’, uses 1 / n_features.

Changed in version 0.22: The default value of gamma changed from ‘auto’ to ‘scale’.

とされていて、gammaのデフォルト設定が変わったようである。新しい仕様ではデフォルトでデータのスケーリングが行われるため、どちらかといえば適合不足の状態になる。先のコードでは明示的にgamma=autoを設定し、書籍と同じ結果を得ている。

特徴量データのサイズの違い

Breast Cancerデータの30の特徴量について、各々の分布状況を箱髭図で描いてみた。縦軸の対数スケールに対してでも、各特徴量がかなりばらついており、1万倍~100万倍ほどの違いがあることがわかる。

データの前処理

データのスケールを揃えるために使われるMiniMaxScalorでは、各特徴量の訓練データを最小値と最大値でスケーリングし、0~1に納まるようにする。具体的には、特徴量ごとに最小値を引いて、最大値-最小値のレンジで除する。

この結果、訓練データの各特徴量の最小値はすべて0となり、最大値はすべて1となる。

テストデータに対してもスケーリングを行うが、ここで使う最小値とレンジは訓練データのものとし、訓練データとテストデータでスケーリングに歪がでないようにする。その結果、スケーリング後のテストデータには、最小値が0より小さい値や最大値が1より大きい値が出ている。

スケーリングされた訓練データとテストデータについてスコアを計算すると以下のようになり、先ほどの過学習の状態から適合不足の状態となった。尚この結果は、新しいSVCクラスにおいてデフォルトのgamma='auto'を指定したときの傾向と似ていて、若干の適合不足となっている。

パラメーター調整

上記の適合不足の結果に対して、パラメーターを変化させてみる。デフォルトのC=1からC=1000としてみると、訓練スコア、テストスコアとも改善された。テストスコアはランダムフォレスト決定木の勾配ブースティングの結果と同じになっている。

さらにいくつかのCとgammaで試してみると、特にスコアがいいのは以下のケースだった。なおgamma=1, 10の場合、C=100, 1000, 10000に対して訓練スコアが1.000、テストスコアが0.95程度で全て過学習となった。

C gamma 訓練スコア テストスコア
1000 auto 0.988 0.972
1000 0.01 0.986 0.979
100 0.1 0.986 0.972