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を使って各データで確率が最大となるクラスを探している。

 

Axes.set_xlabel/ylabel~軸のラベル

概要

Axesに描いたグラフの軸にラベルを付けるには、set_xlabel()set_ylabel()を使う。

位置等の調整

ラベルの位置はlocパラメーターで調整する。x軸とy軸では設定値が異なる(matplotlibのバージョンが3.1.1ではエラーになるので、3.3.1にアップグレード)。

set_xlabel("labelname", loc='left'/'center'/'right')
set_ylabel("labelname", loc='bottom'/'center'/'top')

以下の例では、set_ylabel()のパラメーターにTextのプロパティーとしてrotationを指定している(そのままだと横にしたラベルがエリア外に切れてしまうので、8行目でsubplots_adjustを指定している)。

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”のクラス番号として表示している。

 

scikit-learn – make_circles

概要

sklearn.datasets.make_circles()はクラス分類のためのデータを生成する。2つのクラスのデータが同心円状に分布し、各クラスの半径の差異、データのばらつきを指定できる。

得られるデータの形式

2つの配列X, yが返され、配列Xは列が特徴量、行がレコードの2次元配列。ターゲットyはレコード数分のクラス属性値の整数で0か1の値をとる。

パラメーターの指定

n_samples
総データ数で、奇数の場合は内側のデータが1つ多くなる。2要素のタプルで指定した場合、1つ目が外側、2つ目が内側のデータ数となる。デフォルトは100
shuffle
データをシャッフルするかどうかを指定。Falseにすると、前半がクラス0、後半がクラス1となるように並ぶ。デフォルトはTrue。
noise
ガウス分布のノイズを標準偏差で指定。デフォルトはNoneでノイズなし。
random_state
データを生成する乱数系列を指定。デフォルトはNone
factor
外側に対する内側のデータのスケールファクター。デフォルトは0.8。

利用例

以下はスケールファクターを0.5、ノイズを0.15としてデフォルトの100個のデータを生成した例。

以下はノイズの程度を変化させた例。

以下はスケールファクターを変化させた例。

 

pyplot – subplotの位置調整

subplot間の間隔調整

Figure内のSubplotの位置や相互の間隔を調整するには、subplots_adjust()メソッドを用いる。pyplot.subplots_adjust()でもよいが、figureのメソッドとしてもよい。

pyplot.subplots_adjust(left, bottom, right, top, wspace, hspace)
left, bottom, right, topsubplots全体の左端、下端、右端、上端の位置。wspace, hspaceはそれぞれ各subplot間の幅方向と高さ方向の間隔。

引数の意味はドキュメントで以下のように説明されている。

left~topはキャンバスの左上を(0, 0)、右下を(1, 1)としたときの比率。

たとえばデフォルトで4つのsubpotsを描く。

これに対してsubplots全体の左端をキャンバス内の左から0.3の位置に、下端を下から0.5の位置に設定すると以下のようになる。

また、wspace、hspaceを1とすると以下のようになる。

 

Python – How to do what I want to do

ndarray関係

1次元配列の2次元化

1次元配列を単に2次元化

1次元配列を2次元一要素の行ベクトルにする方法。

  • np.array([a])と2次元で構築する
  • reshape(1, -1)で2次元1行の配列として変形

1次元配列の列ベクトル化

1次元配列を2次元の列ベクトルにする方法。hstack()などで列ベクトルを横に結合していくときに必要。

  • reshape(-1, 1)でn行1列の配列として変形

2つの1次元配列の結合

縦に積み重ねる

素直に実行するならvstack()を使うのがおすすめ。

  • vstack()は1次元配列のままで積み重ねられる
  • append()は1次元配列を2次元化する必要がある

列ベクトルとして横につなげていく

この場合はhstack()が意外にややこしく、c_が手軽。ただし列ベクトルを意識するならhstack()もアリ。

  • 1次元配列をreshape()で列ベクトル化し、hstack()を使う(1次元配列のままだと横1列に伸びるだけ)
  • c_を使う(1次元配列でも列ベクトル化されて結合される)

空のベクトルへの追加

1次元配列の縦方向への追加

縦方向に追加するならvstack()が全般によさそう。

empty((0, n), dtype=type)で空の配列を準備し、これにvstack()で1次元配列をそのまま追加していく。

1次元配列を列ベクトルとして横方向に追加

empty((n, 0), dtype=type)で空の配列を準備し、1次元配列をreshape()で列ベクトルに変形してhstack()で追加していく。

または、c_を使うとreshape()を使わなくてもそのまま列ベクトルとして追加してくれる。

多次元配列の1次元化

2次元以上の配列を1次元としたいときは、reshape(-1)を使う。

たとえばpyplotのsubplotで2次元の配列として得られたAxesオブジェクト(への参照)に対して全て同じ処理を施したいときに、以下のようにする。

条件による抽出

条件に合う要素を取り出す

条件式による要素の取り出しを参照。

条件に合う要素のインデックスを取り出す

1次元配列の条件に合う行を2次元配列から切り出す

特徴量データの配列のうち、特定のクラスに属するデータだけを取り出したいときなど。

インデックス配列の置き換え

例えばclass_name = np.array(["Class-0", "Class-1", "Class-2"])と定義されているとき、配列np.array([0 1, 2, 0])の各要素をインデックスとしてclass_nameの要素で置き換えたい(numpy.ndarray(['Class-0', 'Class-1', 'Class-2', 'Class-0'])を得たい)。

インデックス配列の置き換えを参照。

統計値の計算

min, max, argmin, argmax

1次元配列のmin()メソッド/max()メソッドを使うと、要素の中の最小値/最大値が得られる。また、argmin()メソッド/argmax()メソッドを使うと、最小の要素/最大の要素のインデックスが得られる。

2次元配列の場合は、a.reshape(-1).min()a.reshape(-1).argmin()などと同じ結果となる。

メソッドの引数でaxis=0を指定すると、各列ごとの最小値/最大値を要素とする1次元配列を得る。

axis=1を指定すると、各行ごとの最小値/最大値を要素とする1次元配列を得る。この場合、この配列を列ベクトルとして考えると対比がわかりやすい。

sum,mean

要素の和や平均を計算するsum()メソッド/mean()メソッドもmin()max()と同じように機能する。

1次元配列の場合。

2次元配列の場合は全要素で計算したスカラーを返す。

axis=0で列ごとに計算した結果を1次元配列で返す。

axis=1で行ごとに計算した結果を1次元配列で返す。この配列が列ベクトルだと解釈すると分かりやすい。

順列や組み合わせを得たい

概要

順列や組み合わせの結果としての要素のコレクションを得たいときは、itertoolsパッケージを使い、結果のイテレーターをforループなどで利用。

要素数(選び出す個数)指定のパラメーター名や省略時の挙動がそれぞれで異なっているので注意。

直積

itertools.product()で直積のイテレーターを得る。

順列

itertools.permutations()で順列のイテレーターを得る。

組み合わせ

itertools.combinations()で組み合わせのイテレーターを得る。

重複組み合わせ

combinations_with_replacement()で重複ありの組み合わせのイテレーターを得る。

 

インデックス配列の置き換え

表題だけではよくわからないが、以下のような場合に使う。

たとえばクラス分類のためのターゲットのデータセットが以下のように与えられているとする。

このとき、クラス0~2に対応する以下のような名前で表現したターゲット配列を得ることができるというもの。

順を追って考えてみるのに、まずnames配列から一つの要素を取り出す。

配列の要素をリストとすると、そのリストの要素をインデックスとみなして、インデックスに対応する元の配列の要素を並べた配列を返す。結果はリストではなくndarray。

配列の要素を配列としても同じように動作する。

これより、クラス分類のターゲット配列などが与えられたときに、これを番号ではなくクラス名などの配列に変換することができる。

なお、インデックス配列が2次元の場合は結果の配列も2次元となる。

 

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に直接描画している。

 

ndarray – ブロードキャスト

1次元の場合

以下の配列を元の配列とする。

数値は1次元配列に拡張されて、要素ごとに演算される。

要素が1つの配列(リスト)は同じサイズの配列に拡張されて、要素ごとに演算される。

2次元の場合

以下の配列を元の配列とする。

数値は2次元配列に拡張されて、要素ごとに計算される。

要素が一つの配列は2次元に拡張されて、要素ごとに計算される。

列数と同じ要素数の1次元配列(リスト)は、同じ列数の2次元配列に拡張されて計算される。

行数と同じ要素数の列ベクトルは、同じ行数の2次元配列に拡張されて計算される。