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に凡例を表示させる。

 

Axes.twinx – 2つのy軸のグラフ

基本

Axes.twinx()は元のAxesオブジェクトを複製する。ただし新たなAxesには横軸がなく縦軸が逆側にある。2つのグラフを、それぞれに対するy軸とともに重ねて表示したいときに使う。手順は以下の通り。

  1. 基本のAxesインスタンスを生成
  2. 基本のAxesインスタンスでtwinx()メソッドを実行して2軸目のAxesインスタンスを得る
  3. それぞれのAxesオブジェクトに対して描画、設定

凡例

twinx()で得られたAxesと元のAxesは異なるインスタンスなので、それぞれの凡例を表示させると、ばらばらの位置になったり完全に重なったりしてしまう。

これらの凡例を一括して扱うには、handleとlabelを取得して結合する方法を使う。

Breast cancer wisconsinデータセット

概要

breast cancerデータはUCIの機械学習リポジトリ―にあるBreast Cancer Wisconsin (Diagnostic) Data Setのコピーで、乳腺腫瘤の穿刺吸引細胞診(fine needle aspirate (FNA) of a breast mass)のデジタル画像から計算されたデータ。

乳癌に関する細胞テストの様々な数値と、その結果のデータセット。569人の被検者の複数の腫瘤に関する細胞診の結果得られた30個の特徴量と、各被験者の診断結果(悪性/良性:benign/malignant)が格納されている。

ここではPythonのscikit-learnにあるbreast_cancerデータの使い方をまとめる。

データの取得とデータ構造

Pythonで扱う場合、scikit-learndatasetsモジュールにあるload_breast_cancer()でデータを取得できる。データはBunchクラスのオブジェクト。

データの構造は辞書型で、569人の細胞診の結果に関する30個の特徴量をレコードとしたの配列、各腫瘤の診断結果など。

データのキーは以下のようになっている。

データの内容

'data'~特徴量データセット

569人の細胞診結果に対する30個の特徴量のデータを格納した2次元配列。列のインデックス(0~29)が30個の特徴量に対応している。

'target'~診断結果に対応したコード

各被検者の診断結果(悪性:malignant、良性:benign)を格納した0/1のコードの配列。569個の腫瘤に対応した1次元配列(0:悪性が212、1:良性が357)。

'target_names'~診断結果

診断結果、悪性(malignant)/良性(benign)が定義されている。。

診断結果とコードの関係は以下の通り。

malignant 0
benign 1

'feature_names'~特徴名

データの格納順はDESCRの後。細胞診の結果得られた30個の特徴量の名前。

腫瘤に関する以下の10の属性について、それぞれ平均(mean)、標準偏差(error)、最悪値(worst)の3種類、合計30の特性値に対する名前が格納されている。ここでworstは各属性に関する最大値となっている。

  1. radius:半径(中心から外周までの平均)
  2. texture:テクスチャ―のグレースケールの標準偏差
  3. perimeter:外周長
  4. area:面積
  5. smoothness:中心から外周までの部分偏差
  6. compactness:コンパクト性(外周長2÷面積-1.0)
  7. concavity:コンターの凹部強度
  8. concave points:コンターの凹点の数
  9. symmetry:対称性
  10. fractal dimension:フラクタル次元

特徴名とコードの関係は以下の通り。

mean error worst
radius 0 10 20
texture 1 11 21
perimeter 2 12 22
area 3 13 23
smoothness 4 14 24
compactness 5 15 25
concavity 6 16 26
concave points 7 17 27
symmetry 8 18 28
fractal dimension 9 19 29

'filename'~ファイル名

これも格納順はDESCRの後で、CSVファイルの位置が示されている。1行目にはデータ数、特徴量数、特徴量名称が並んでおり、その後に569行のレコードに対する4列の特徴量と1列の診断結果データが格納されている。このファイルにはfeature_namesDESCRに当たるデータは格納されていない。

'DESCR'~データセットの説明

データセットの説明。print(breast_ds_dataset['DESCR'])のようにprint文で整形表示される。

  • レコード数569個(悪性:212、良性:357)
  • 属性は、30の数値属性とクラス
    →predictiveの意味とclassが単数形なのがわからない

データの利用

データの取得方法

breast_cancerデータセットから各データを取り出すのに、以下の2つの方法がある。

  • 辞書のキーを使って呼び出す(例:breast_cancer_dataset['DESCR']
  • キーの文字列をプロパティーに指定する(例:breast_cancer_dataset.DESCR

全レコードの特徴量データの取得

'data'から、569のレコードに関する30の特徴量が569行30列の2次元配列で得られる。30の特徴量は'feature_names'の30の特徴名に対応している。

特定の特徴量のデータのみ取得

特定の特徴量に関する全レコードのデータを取り出すときにはX[:, n]の形で指定する。

特定のクラスのデータのみ抽出

特定のクラス(この場合は診断結果)のレコードのみを抽出する方法。ndarrayの条件による要素抽出を使う。

 

 

k-最近傍法 – クラス分類

概要

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

  1. 特徴量とクラス分類の訓練データセットを記憶する
  2. テストデータが与えられたら、特徴量空間の中で近傍点を選ぶ
  3. 近傍点のクラスからテストデータのクラスを決定する

パラメーターは近傍点の数で、1以上の自然数を設定できる。

利用方法

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

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

コンストラクターには、通常n_neighborsで近傍点を指定する。デフォルトはn_neighbors=5

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

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

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

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

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

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

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

実行例

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

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

特徴量1 特徴量2 クラス値
-2 -1 0
-1 -2 0
-0.5 -0.5 0
0.5 0.5 1
1 2 1
2 1 1

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

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

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

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

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

y_predは、テストデータごとに2つの近傍点のクラス値から多数決でクラス値を決定している。

  • 1つ目のテストデータの属性値
    • y_train[1]=0y_train[2]=0y_train[0]=0の多数決→0
  • 2つ目のテストデータの属性値
    • y_train[3]=1y_train[5]=1y_train[2]=0の多数決→1

この様子を特徴量平面上に描いたのが以下の図である。各点の色は、各データのクラスを示していて、下方の点は3つの近傍点のクラスが全て0なのでテストデータのクラスも0、右方の点は近傍点のうち2つがクラス1で1つがクラス0なのでテストデータのクラスは多数決で1となっている様子がわかる。

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

 

ndarray – 2次元配列の1次元化

概要

ndarrayの2次元配列を1次元化して扱いたいとき(たとえば2次元のAxes配列を一括で扱いたいとき)の方法に、reshape()メソッド/関数、flatten()メソッド、ravel()メソッド/関数を使う方法がある。

以下、次の配列を使う。

ndarrayのメソッド

ndarrayのメソッドのreshape()flatten()ravel()を使う。戻り値もndarray。

reshape()メソッド

reshape()メソッドで1次元化する場合、reshape(-1)とする。

1次元の列ベクトルが必要な場合はreshape(-1, 1)

reshape(1, -1)とすると1行になるが、次元が2次元であるため、1つの1次元要素配列を要素に持つ2次元配列となってしまう。

flatten()メソッド

flatten()メソッドは、reshape(-1)と同じ効果を持つ。

ravel()メソッド

ravel()メソッドも、reshape(-1)と同じ効果を持つ。

Numpyの関数

Numpy.reshape()関数、Numpy.ravel()関数は、引数にndarray以外のarray-likeオブジェクトをとることができる。ただし戻り値はndarray。

Numpy.flatten()は定義されていない。

numpy.reshape()関数

numpy.reshape()関数は、第2引数で次元・次数を与える。

2次元にしたい場合は、第2引数をタプルにする。

numpy.ravel()関数

numpy.ravel()関数の機能はravel()メソッドと同じ。

戻り値のビューとコピーの違い

reshape()ravel()は可能な限りビューを返す(結果の変更がオリジナルに影響を与える)。

flatten()はコピーを返す(結果の変更はオリジナルに影響しない)。

 

forgeデータセット

概要

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

その内容は、2クラスに分類された26個のデータで、2つの特徴量を想定した2次元配列データと各データのクラス分類を示したターゲットデータが得られる。

利用方法

mglearnパッケージから、たとえば以下の方法で利用する。

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

内容

特徴量データ

2つの特徴量を持った配列が26個、2次元配列の形で得られる。

ターゲットデータ

26個のデータに対する2つのクラス(0, 1)が定められた1次元配列で、クラス0、1がそれぞれ13個ずつとなっている。

データ総括

特徴量とクラス分類の組を、データ番号とともに整理すると以下の通り。

 

訓練データとテストデータの分割~train_test_split()

概要

scikit-learnのtrain_test_split()関数を使うと、与えたデータをいろいろな方法で訓練データとテストデータに切り分けてくれる。

8行目で、train_test_split()に配列を与えた結果、それが2つの配列に分割されていることがわかる。

11行目では、その結果を訓練用、テスト用の配列として取得している。

デフォルトでtrain_test_split()は、テスト用データのサイズが与えた配列のサイズの0.25となるように配列を分割する(1つ目のサイズ:2つ目のサイズ=3:1)。x_testのサイズが12×0.25=3、x_trainのサイズが9となっていることが確認できる。

乱数系列の固定

データの分割あたって、要素の選択はtrain_test_split()の実行ごとにランダムに行われるが、random_stateパラメーターを指定することで固定できる。

データのサイズ

テストデータサイズの指定

テストデータのサイズはtest_sizeパラメーターで指定することができる。

以下の例では、テストデータの比率をデフォルトの0.25→0.3に変更しており、テストデータのサイズが4となっている(test_size=0.26としてもx_testのサイズが4になり、テストデータのサイズは切り上げで計算されている)。

比率によってデータサイズを指定する場合は0<test_size<1の実数で指定(0や1.0で指定するとエラー)

訓練データのサイズを比率ではなく実際のサイズ(要素数)で指定することもできる。その場合、test_sizeを1以上の整数で指定。

以下の例ではテストデータのサイズを4として指定している。

訓練データサイズの指定

train_sizeパラメーターで訓練データのサイズを指定することもできる。

以下の例ではtrain_size=0.8とし、訓練データサイズが9となっている(訓練データサイズの計算は切り下げで行われている)。

訓練データサイズも要素数での指定が可能。

データ選択の内部手続

ここで、random_state=0としてtest_sizetrain_sizeを変化させたとき、テストデータの要素が現れる順番は変わらないということに気づいた。

test_size/train_sizeのどちらで指定しても、また比率/要素数の何れで指定しても、常にテストデータの要素は7, 12, 5,…の順番で現れている。

これに対して訓練データの方は、テストデータの要素数が変わると変化するが、テストデータの結果が同じなら訓練データのパターンも同じ。

すなわちtrain_test_split()のサイズ指定は、どのように指定しても一旦テストデータの要素数に変換し、共通の手順でテストデータを選んでいっていると考えられる。

複数データの同時分割

train_test_split()は複数データを同時に分割することもできる。

以下の例では、二つの配列を引数として与えている。その結果は、与えた配列ごとに訓練データ、テストデータの順でタプルとして返される。

これが一般的な使い方で、複数の特徴量に関する個体のデータセットと各個体のクラスに関するデータを、同時に訓練データとテストデータに分割するときに用いられる。

元のデータは

これを訓練データとテストデータに分割した結果は

stratifyによる層化(相似化)

train_test_split()による要素の選択はランダムに行われる。この場合、クラス分類のパターンが、元データ、訓練データ、テストデータで異なってくる。

以下の例では、元のデータの0と1の比率が1:2だが、訓練データでは1:4、テストデータでは2:1になっている。ケースによっては特定のクラスが極端に少ない/存在しないということも起こり得る。

そこで、stratifyパラメーターで配列を指定すると、その配列でのパターンと同じになるように訓練データとテストデータを分割してくれる。

以下の例では、先の配列を元の配列の0/1のパターンと相似になるように分割している。

次の例は、9個体の特徴量データxと各個体のクラス区分データyを、クラスの分布に沿って訓練データとテストデータに分割するイメージ。

シャッフルの有無

デフォルトでtrain_test_split()は、データの分割にあたって要素の選択をランダムに行うが、shuffle=Falseを指定すると要素の順番を保持する。

 

irisデータの俯瞰

概要

irisデータは3つのアヤメの種類(setosa, versicolor, varginica)の150個体について、萼(sepal)と花弁(petal)の長さと幅の組み合わせ4つの特徴量のデータを提供する。これらについて一般的なグラフによる可視化によって俯瞰してみる。

特徴量の分布

クラス分けしない場合

まずアヤメの150個体における4つの特徴量について、3つの種類を区別せずにその分布を見てみる。

この結果を見る限り特に際立った特徴は見いだせない。敢えて言うなら、萼の長さは若干ばらつきが大きく、萼の幅は割合”きれいな”分布。花弁については、長さ・幅とも値の小さいところで独立した分布が見られる。

このデータが異なる種類のものが混在したものだと知っていれば、花弁の独立した分布は特定の種類のものかもしれないと推測できるくらい。

クラス分けした場合

次に4つの特徴量について、3つの種類ごとに分けて表示してみる。

こうすると少し特徴が見えてくる。

花弁の独立した分布はsetosa(ヒオウギアヤメ)のものであることがわかり、額の長さの分布がばらついているのは、複数の種類の特徴量が少しずつずれて重なっているからだということもわかる。

この分布だけだと、花弁の長さ2.5cm、花弁の幅が0.7~0.8cmあたりから小さいと、アヤメの種類はsetosaと特定できそうだが、versicolorとvirginicaは重なっていて、花弁の幅が1.75cmあたりで分けると少し誤判定はあるが概ね分けられそうである。

2つの特徴量同士の関係

比較例

例として、萼の長さと萼の幅、萼の長さと花弁の幅、それぞれの間の関係をプロットしてみる。

萼の長さと花弁の長さの関係を見ると、setosaは明らかに独立したグループだが、versicolorとverginicaは混ざり合っていて分離できそうにない。先ほどのヒストグラムでは、萼の長さ、萼の幅それぞれだけではversicolorとvirginicaは区分できなかった。2次元でプロットするとそれらがうまく区分する可能性もあるが、この場合はうまくいかないようである。

一方、萼の長さと花弁の長さの関係を比べると、versicolorとversinicaも何とか区分できそうである。よくみると、この3つの区分は萼の長さと関係なく、花弁の幅のみで概ね区分できそうである。これも先ほどの花弁の幅のヒストグラムの結果と符合する。

scatter_matrixによる確認

上記のような特徴量の組み合わせは、特徴量がn個の場合にはnC2通りとなる。irisデータの場合、特徴量は4つだから6個の特徴量ペアがあり得る。pandasscatter_matrixを利用すると、このような特徴量のペアについて網羅的に確認できる。

ただしscatter_matrixでは、対角要素のヒストグラムを特徴量ごとに分けることはできないようだ。

pairplotによる確認

seabornpairplotを使うと、対角要素に各特徴量ごとの頻度分布/密度分布を表示することができる。pairplotの場合、ターゲットの品種を文字列で与えるとそれに従った色分けをしてくれて、対角要素の密度分布も品種ごとに分けてくれる。

ペアプロットの結果から、3つの種類は複数の散布図で比較的きれいにグループとなっていることがわかる。

3つの特徴量の関係

最後に、4つの特徴量のうち3つを取り出して3次元の散布図で表示してみる。2次元の散布図ではversicolorとvirginicaで若干の重なりがあるが、3次元化するときれいに分かれるかもしれない。

3次元空間で見ても若干の重なりはあるが、2つの特徴量だけの時に比べて、よりグループ分離の精度が高まることは期待できそうだ。

考えてみれば、アヤメの品種区分のように特徴量が少ない場合のクラス分類問題は、1次元の頻度分布、2次元・3次元の頻度分布のように次元を増やして確認ができれば、区分は比較的容易なように思われる。一方で人の間隔では3次元を認識するのがやっとなので、特徴量の数が増えた時には太刀打ちできない。

畢竟、機械学習・AIとは人間が認識制御困難な多数の特徴量=多次元における判別や相関を如何に実行するかというところなのでは、と思われる。