Pyplot – グラフの標準色

Pyplotのグラフを描くときに標準で使われる色を直接指定する方法。色名に"tab:blue"のように指定する。

waveデータセット – knn

概要

k-最近傍回帰の例として、scikit-learnのwaveデータKNeighborsRegressorを適用してみた結果。

近傍点数とクラス分類の挙動

訓練データとして10個のwaveデータを訓練データとして与え、2つのテストデータの予測するのに、近傍点数を1, 2, 3と変えた場合の様子を見てみる。

近傍点数=1の場合

2つのテストデータの特徴量の値に最も近い特徴量を持つ訓練データが選ばれ、その属性値がそのままテストデータの属性値となっている。

近傍点数=2の場合

テストデータの特徴量に最も近い方から1番目、2番目の特徴量を持つ訓練データが選ばれ、それらの属性値の平均がテストデータの属性値となっている。

近傍点数=3の場合

同様に、テストデータの特徴量に最も近い3つの訓練データの属性の平均がテストデータの属性値となっている。

実行コード

上記の計算のコードは以下の通り。

knnの精度

O’Reillyの”Pythonではじめる機械学習”中、KNeighborsRegressorのwaveデータに対する精度が計算されている。40サンプルのwaveデータを発生させ訓練データとテストデータに分け、テストデータに対するR2スコアが0.83となることが示されている。実際に計算してみると、確かに同じ値となる。

これを見ると比較的高い精度のように見えるが、train_test_split()の引数random_stateを変化させてみると以下のように精度はばらつく。乱数系列が異なると精度が0.3未満の場合もあるが、全体としてみると0.6~0.7あたりとなりそうである。

ためしにmake_wave(n_samples=1000)としてみると、結果は以下の通りとなり、精度は0.67程度(平均は0.677)と一定してくる。

予測カーブ

訓練データが少ない場合

40個のwaveデータに対して、n_neighborsを変化させたときの予測カーブを見てみる。

  • n_neighbors=1の時は、全ての訓練データを通るような線となる
  • n_neighborsが多くなるほど滑らかになる
  • n_neighborsがかなり大きくなると水平に近くなる
  • n_neighborsが訓練データ数と同じになると、予測線は水平になる(任意の特徴量に対して、全ての点の平均を計算しているため)

訓練データが多い場合

今度はwaveデータでn_samples=200と数を多くしてみる。データ数を多くするとその名の通り、上下に波打ちながら増加している様子が見られる。これに対してn_neighborsを変化させたのが以下の図。

n_neighbors=10~20あたりで滑らかに、かつ波打つ状況が曲線で再現されている。

n_samples=300として訓練データに200を振り分け、n_neighborsを変化させたときのスコアは以下の通り。n_neighbors=20あたりで精度が最もよさそうである。

あるデータが得られたとき、その科学的なメカニズムは置いておいて、とりあえずデータから予測値を再現したいときにはそれなりに使えるかもしれない。

 

pyplot – zorder~グラフの描画順

pyplotでグラフを描画する際、点よりも線の方が上になって見栄えが悪い・・・といった場合に、どのグラフから上にするかという指定が必要になる。

グラフ描画の優先性はplot()scatter()などのグラフメソッドの引数にzorderを指定して実現できる。zorderに指定した値がより大きいグラフの方が上のレイヤーになる。指定できる値は正負の実数。

左のグラフは後から実行しているscatterplotの下に表示されている。

右のグラフではzorderを指定しており、scatterの方が値が大きいため上のレイヤーに表示されている。

 

 

forgeデータセット – knn

概要

ここでは、Pythonのscikit-learnパッケージのKNeighborsClassifierクラスにmglearnパッケージのforgeデータを適用してknnの挙動を確認する。

近傍点数を変化させたときのクラス分類の挙動や学習率曲線についてみていく。

近傍点数によるクラス分類の挙動

近傍点数=1の場合

データセットとしてmglearnで提供されているforgeデータを用いて、近傍点数=1とした場合の、3つのテストデータのクラス判定を以下に示す。各テストデータに対して最も距離(この場合はユークリッド距離)が近い点1つが定まり、その点のクラステストデータのクラスとして決定している。

なお、いろいろなところで見かけるforgeデータセットの散布図は当該データセットの特徴量0(横軸)と特徴量1(縦軸)の最小値と最大値に合わせて表示しており、軸目盛の比率が等しくない。ここでは、距離計算に視覚上の齟齬が生じないように、縦軸と横軸の比率を同じとしている。

後の計算のために、このグラフ描画のコードを以下に示す。

概要は以下の通り。

  • 5行目でforgeデータセットを準備
  • 7行目で近傍点数を1で指定してクラス分類器を構築
  • 8行目で訓練データとしてforgeデータを与える
  • 12行目で3つのテストデータを準備
  • 13行目でテストデータに対する近傍点のインデックスとテストデータまでの距離を獲得
  • 14行目でテストデータのクラスを決定
  • 18-19行目で訓練データの散布図を描画
  • 23行目で、テストデータとそのクラス決定結果、クラス決定に用いられた点群のインデックス、テストデータと各点の距離を並行してループ
    • 24行目でテストデータの座標を出力
    • 25行目でテストデータを描画
    • 26行目のループで、テストデータごとの近傍点に関する処理を実行
      • 27行目でテストデータと近傍点の間に直線を描画
      • 28行目で近傍点とテストデータからの距離を出力

出力結果は以下の通りで、各予測点に対して近傍点が1つ決定されている。

近傍点数=3の場合

先の例で、コードの7行目で近傍点=3で指定してクラス分類器を構築する。

一般にknnでは、テストデータに対して複数の近傍点を指定する場合、各近傍点のクラスのうち最も多いものをテストデータのクラスとする(多数決)。

近傍点数=2の場合

テストデータのクラスを近傍点のクラスの多数決で求めるとすると、近傍点数が偶数の時の処理が問題になる。KNeighborsClassifierの場合、偶数でクラス分類が拮抗する場合は、クラス番号が最も小さいものに割り当てられるらしい。実際、n_neighbors=2としたときの3つのテストデータのうち中央の点(10.0, 3.0)については、赤い点(10.24, 2.45)~class-1~距離0.5952の方が青い点(9.5017, 1.9382)~class-0~距離1.1729よりも距離は近いがクラス番号が0である青い点のクラスで判定されている。

偶数の点で多数決で拮抗した場合には、最も近い点のクラスで決定する、平均距離が近い方のクラスで決定するといった方法が考えられるが、この場合は必ず番号が小さなクラスが選ばれるため、若干結果に偏りがでやすいのでは、と考える。

決定境界

近傍点の数を変えた時の決定境界の変化を確認する。k近傍法はscikit-learnのKNeighborsClassifierクラスを利用する。

近傍点の数を1, 2, 3, …と変化させたときの決定境界の変化は以下の通り。

近傍点数が少ないときは訓練データにフィットするよう決定境界が複雑になるが、近傍点数が多いと決定境界は滑らかになる。特に近傍点数が訓練データの点数に等しいとき、全訓練データの多数決でクラス決定され、全領域で判定結果が同じとなる(この場合は近傍点数26が偶数なので、クラス番号の小さいclass-0で決定されている)。

この図を描画したコードを以下に示す。

  • 7行目、引数で与えたAxesに対して決定境界を描く関数を定義
    • 18行目、決定境界をcontourf()を利用して描いている
  • 21行目、引数で与えたAxesに対してクラスごとに色分けした散布図を描く関数を定義
  • 54行目、2次元配列のAxes1次元配列として扱っている

k-最近傍法 – 回帰

概要

k-最近傍法(k nearest neighbors: knn)による回帰は、テストデータの近傍の訓練データからテストデータの属性値を決定する。その手法は単純で、特段の学習処理はせず、訓練データセットの特徴量と属性値を記憶するのみで、テストデータが与えられたときに近傍点から属性値を決定する。手順は以下の通り。

  1. パッケージをインポートする
  2. 特徴量と属性値のデータセットを記憶する
  3. テストデータが与えられたら、特徴量空間の中で近傍点を選ぶ
  4. 近傍点の属性値からテストデータの属性値を決定する

パラメーターは近傍点の数で、1以上訓練データの数まで任意に増やすことができる。

利用方法

手順

scikit-learnのKNeighborsRegressorクラスの利用方法は以下の通り。

  1. sklearn.neighborsからKNeighborsRegressorをインポート
  2. コンストラクターの引数に近傍点数n_neighborsを指定して、KNeighborsRegressorのインスタンスを生成
  3. fit()メソッドに訓練データの特徴量と属性値を与えて学習
  4. predict()メソッドにテストデータの特徴量を指定して、属性値を予測
  5. 必要に応じて、kneighbors()メソッドでテストデータの近傍点情報を取得

パッケージのインポート

k-最近傍回帰のパッケージは以下でインポートする。

コンストラクター

KNeighborsClassifier(n_neighbors=n)
nは近傍点の数でデフォルトは5。この他の引数に、近傍点を発見するアルゴリズムなどが指定できるようだ。

訓練

fit()メソッドに与える訓練データは、特徴量セットと属性値の2つ。

fit(X, y)
Xは訓練データセットの特徴量データで、データ数×特徴量数の2次元配列。yは訓練データセットの属性値データで要素数はデータ数に等しい

予測

テストデータの属性値の予測は、predict()メソッドにテストデータの特徴量を与える。

y = predict(X)
Xはテストデータの特徴量データで、データ数×特徴量数の2次元配列。戻り値yは予測された属性値データで要素数はデータ数に等しい。

近傍点の情報

テストデータに対する近傍点の情報を、kneighbors()メソッドで得ることができる。

neigh_dist, neigh_ind = kneighbors(X)
テストデータの特徴量Xを引数に与え、近傍点に関する情報を得る。neigh_distは各テストデータから各近傍点までの距離、neigh_indは各テストデータに対する各近傍点のインデックス。いずれも2次元の配列で、テストデータ数×近傍点数の2次元配列となっている。

実行例

以下の例では、n_neighbors=2としてKNeighborsRegressorのインスタンスを準備している。

これに対してfit()メソッドで、2つの特徴量とそれに対する属性値を持つ訓練データを5個与えている。特徴量データX_trainは行数がデータ数、列数が特徴量の数となる2次元配列を想定している。また属性値y_trainは訓練データ数と同じ要素数の1次元配列。

特徴量1 特徴量2 属性値
-2 -3 -1
-1 -1 0
0 1 1
1 2 2
3 3 3

これらの訓練データに対して、テストデータの特徴量X_testとして(-0.5, -2)(1, 0)の2つを与えた時の出力を見てみる。

このコードの実行結果は以下の通り。

属性値の予測結果については、2つのテストデータに対して2つの属性値0.5と1.5が返されている。

kneighbors()メソッドの戻り値から、1つ目のテストデータにはインデックスが1と0の2つの点とそれぞれへの距離1.118と1.802が、2つ目のテストデータにはインデックスが2と3の点とそれぞれへの距離1.414と2.0が得られる。

  • 1つ目のテストデータ(-0.5, -2)からの距離
    • X_train[1]=(-1, -1)\sqrt{(-0.5)^2+1^2}\approx 1.118
    • X_train[0]=(-2, -3)\sqrt{(-1.5)^2+(-1)^2}\approx 1.802
  • 2つ目のテストデータ(1, 0)からの距離
    • X_train[2]=(0, 1)\sqrt{(-1)^2+1^2}\approx 1.414
    • X_train[3]=(1, 2)\sqrt{0^2+2^2}=2

y_predは、テストデータごとに2つの近傍点の属性値の平均をとっている。

  • 1つ目のテストデータの属性値
    • y_train[1]=-1y_train[0]=0の平均→-0.5
  • 2つ目のテストデータの属性値
    • y_train[2]=1y_train[3]=2の平均→1.5

この様子を特徴量平面上に描いたのが以下の図である。各点の数値は、各データの属性値を示している。

各種データに対する適用例

 

waveデータセット

概要

waveデータセットは、”Pythonではじめる機械学習”(O’REILLY)中で用いられる架空のデータセットである。

その内容は、引数n_samplesで指定した個数の点について1つの特徴量とターゲットの値を持ち、回帰を扱うのに適している。

利用方法

mglearnパッケージから、たとえば以下のように利用する。

実行するとdeprecatedの警告が出るが、放置してもよいらしい。

内容

waveデータの特徴は以下の通り。

  • 引数のn_samplesには任意の整数を指定できる
  • 特徴量(x座標の値)は決まっている
    • n_samplesが増えてもx0, x1, …の値は変わらない
    • x0, x1, …は実行のたびに同じパターン
  • ターゲットの値(y座標の値)は変化するが実行ごとに同じ
    • n_samplesが変わると同じx0, x1, …の値に対するy0, y1, …の値は変化する
    • y0, y1, …は実行のたびに同じパターン

このことを、n_samplesの値を変化させたときのX, yの内容で確認してみる。

このコードは何度実行しても同じ値を返す。x座標のパターンが変わっていないこと、y座標のパターンは実行のたびに変化していることがわかる。ただし異なるn_sampleに対して、同じxに対するyの値は大きくは変化していない。

なお、n_samplesが6の時のxの最後の値とその1つ前の値がかなり近く、対応するyの値も近い。n_samplesが1の時と3の時に、先頭のXとyの値が殆ど等しい。

以上のことから、waveデータセットはXについては毎回同じ系列でランダムな値を返し、yはXに対して一定の計算値に毎回同じ系列の乱数で擾乱を加えていると想像される。

最後に、n_samplesを多くしたときの結果を見てみると明らかに線形で上昇しつつ波打っているのがわかる。おそらくy=a \sin b x + cのような式に擾乱を与えていると思われる。

手法の適用

 

Breast cancer データセット – k-近傍法

概要

breast_cancerデータセットにscikit-learnのKNeighborsClassifierクラスでk-最近傍法を適用した結果。

学習率曲線

breast_cancerデータセットにk-最近傍法を適用し、近傍点数を変化させて学習率の変化をチェック。データセットを学習データとテストデータに分けるときのrandom_stateを変え、近傍点数に伴う変化を見てみた。

irisデータセットの場合に比べると、学習データとテストデータの傾向は落ち着いていて、近傍点数=8で制度が0.92~0.95程度。

 

irisデータセット – knn

概要

irisデータセットにscikit-learnのKNeighborsClassifierクラスでk-最近傍法を適用した結果。

学習率曲線

irisデータセットにk-最近傍法を適用し、近傍点数を変化させて学習率の変化をチェック。データセットを学習データとテストデータに分けるときのrandom_stateを変え、近傍点数に伴う変化を見てみた。

レコード数が150と少ないこともあって、random_stateを変えるごとにかなり推移が異なるが、概ね95%の精度が保たれている。

 

Breast cancer wisconsinデータセットの俯瞰

概要

breast_cancerデータは、複数の乳癌患者に関する細胞診の結果と診断結果に関するデータセットで、569人について腫瘤の細胞診に関する30の特徴量と診断結果(悪性/良性)が格納されている。このデータセットについて、irisデータセットと同じ流れで、一般的なグラフによる可視化によって俯瞰してみる。

各特徴量と診断結果

30個の特徴量について悪性と良性に色分けしてヒストグラムを描いてみると、特徴量によって悪性と良性がある程度分かれているものと、重なりが大きいものがあることがわかる。

特徴量の数は多いが、低い次元で見る限りは明確に悪性/良性を分離できる特徴量はあまり多くなさそうである。

2つの特徴量同士の関係

特徴量が30個あるので、scatter_matrixやpairplotで全ての特徴量の関係を見るのはあまり得策ではない。そこで、30個の特徴量の中から、悪性/良性が分かれているものを選んで相互の関係を見てみる。

ここでは、双方の分布の山ができるだけ離れており、重なっている部分が少ないものとして、平均凹度、最大半径、最大周囲長、最大凹点数を選んだ。

最大半径と最大周囲長はかなり相関が高く、双方を組み合わせてもあまり効果はなさそうだ。もともと半径と周囲長は円形なら比例関係にあるので当然の結果だろう。

 

3つの特徴量の関係

最後に、平均凹度、最大半径、最大凹点数の3つの特徴量の関係を3次元化してみた。結果の図を回転させて、できるだけ境界面に沿うような角度から見たのが以下の図である。個々の特徴量だけで見るよりはかなり分離の精度は高くなっている。

上記の3d可視化とその前のpairplotのコードは下記の通り。

 

pyplot – legend~凡例

概要

pyplotの各グラフに凡例を入れるには、legend()メソッドを使う。基本の使い方は以下の通り。

  1. plotやscatterなどでグラフを描く時の引数にlabel=”…”でラベルを定義する。ここで設定した文字列が凡例に使われる。
  2. グラフフィールドのオブジェクトのlegend()メソッドを実行する。

凡例の位置

標準的な位置指定

凡例の位置はloc引数に対して定義された文字列で指定する。

位置しての文字列は’[縦位置] [横位置]’で指定。

縦位置はupper, center, lowerの何れか、横位置はleft, center, rightの何れかで、縦位置と横位置の間には半角スペースを入れる(たとえば'upper right')。ただし縦横中心の場合は'center'

デフォルトは'best'で最も適切な位置が自動で設定される。

bboxによる位置指定~凡例の外側への設置

Axes.legend()の位置指定で引数としてbbox_to_anchorを指定することで、グラフの描画領域の相対位置を細かく指定することもできる。

bbox_to_anchor=(x, y)

x, yはグラフ描画行きの左下を(0, 0)、右上を(1, 1)としたときの相対位置。ここで指定した位置とlocで指定した凡例の基準点を一致させる。

以下のコードは、全てbbox_to_anchor=(1, 1)として凡例を描画域の右上に合わせている。その上でlocで指定した凡例の位置がこの点と一致させられる。

たとえばloc="lower right"とすると凡例の左下の位置が描画域の右上と同じになるように配置される。また、標準では凡例の枠の周りに少しパディングが行われるが、borderaxespad引数で数値を指定することでその間隔を調整できる。

ただし、この例では凡例が画面の右側ではみ出て切れてしまっている。このようなときは、tightlayout()をFigureに対して実行することで描画領域に全体を収めることができる。

凡例の並べ方

凡例はデフォルトでは縦に並べられるが、ncolに整数を指定して凡例の列数を指定できる。

デザイン等

このほか、デザイン関連で以下のような引数がある

title=[文字列]
凡例内にタイトルを設定。
fancybox=False/True
Trueを指定すると凡例の枠の角が丸くなる。
shadow=False/True
Trueを指定すると凡例に影がつけられる。

凡例の文字サイズ

凡例本体の文字サイズは、legend()の引数fontsizeで指定する。

凡例のタイトルの文字サイズは、凡例オブジェクトからget_title()でタイトルオブジェクトを取得し、set_fontsize()で設定する。

この2行の手続きは、以下のようにチェインによって1行で書くこともできる。

引数handleslabels

legend(handles, labels)という指定方法。公式ドキュメントではこちらが先に示されている。

Axes.get_legend_handles_labels()の戻り値として、グラフ要素のhandleとそれに対するlabelのリストが得られる。

この方法は、後述のように複数のグラフの凡例をまとめて扱うときに利用する。

複数グラフの場合の凡例

複数のAxesのグラフの凡例を1つにまとめて表示したい場合は、それぞれのAxesでhandleとlabelを取得しておき、それらを結合してlegend()の引数とする。

上の方法だと特定のAxesに凡例が表示されるが、これをまとめたいときには、figureに凡例を表示させる。