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とは人間が認識制御困難な多数の特徴量=多次元における判別や相関を如何に実行するかというところなのでは、と思われる。

 

Irisデータセット

概要

Irisデータセットはアヤメの種類と特徴量に関するデータセットで、3種類のアヤメの花弁と萼(がく)に関する特徴量について多数のデータを提供する。

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

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

Pythonで扱う場合、scikit-learndatasetsモジュールにあるload_iris()でデータを取得できる。データはBunchクラスのオブジェクトととのことだが、通常の扱い方は辞書と同じようだ。

データの構造は辞書型で、150個体のアヤメに関する特徴量の配列と各個体の種類、種類名などが格納されている。

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

データの内容

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

150個体のアヤメに関する、4つの特徴量をレコードとしたデータセット。各個体の4つの特徴量の配列を要素とした2次元配列。列のインデックス(0, 1, 2, 3)が四つの特徴量に対応している。

'target'~アヤメの種類に対応したコード

3種類のアヤメに対応した0~2のコードの配列。150個体のアヤメに対応した1次元配列。

'target_names'~アヤメの種類名

アヤメの3つの種類の種類名。stosaは「ヒオウギアヤメ」といって少し大人締めの色形だが、versicolorとvirginicaは素人にはその違いがよく分からない。

種類名とコードの関係は以下の通り。

setosa 0
versicolor 1
virginica 2

'feature_names'~特徴名

データの格納順はDESCRの後。アヤメの種類のクラス分けに使う特徴。

sepal(萼)とpetal(花弁)の長さと幅、計4つの特徴の名称が、単位cmを含む文字列で格納されている。

  • ‘sepal length (cm)’ 萼の長さ
  • ‘sepal width (cm)’ 萼の幅
  • ‘petal length (cm)’ 花弁の長さ
  • ‘petal width (cm)’ 花弁の幅

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

sepal length (cm) 0
sepal width (cm) 1
petal length (cm) 2
petal width (cm) 3

'filename'~ファイル名

これも格納順はDESCRの後で、CSVファイルの位置が示されている。1行目にはデータ数、特徴量数、特徴量名称が並んでおり、その後に150行のアヤメの個体に対する4列の特徴量と1列の種類データが格納されている。このファイルにはfeature_namesDESCRに当たるデータは格納されていない。

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

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

  • レコード数は150個(3つのクラスで50個ずつ)
  • 属性は、4つの数値属性とクラス(種類)
    →predictiveの意味とclassが単数形なのがわからない

データの利用

データの取得方法

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

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

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

'data'から、150の個体に関する4つの特徴量が150行4列の2次元配列で得られる。4つの特徴量は’feature_names’の4つの特徴名に対応している。

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

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

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

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

 

 

k平均法

概要

k平均法(k-means clustering)はクラスタリングの手法の1つで、与えられたデータ群の特徴と初期値に基づいて、データを並列(非階層)のクラスターに分類する。

ここではk平均法の簡単な例を実装したKMeansClusteringクラスによって、その挙動を確認する。

テストケース

基本形

2つのクラスターがある程度明確なケースで試してみる。一定の円内にランダムに点を発生させ、そのグループを2つ近づけた例。

以下のように、重なった部分は仕方がないが、かなり元のグループに近い分類となっている。

初期値を変えた場合

代表点の初期値を変えて実行してみる。

上記とはかなり離れた初期値を設定しても、解は同じになる。

収束解も上記と全く同じ値になる。

クラスターが不明確な場合

先の結果だけを見ると、かなり初期値がずれてもクラス分類は安定なように見える。

そこで次に、元々の分布に明確なクラス分けが見えない場合に3つのクラスターに分ける例を考える。

初期値1

初期値2

上記に対して初期値を変更。

データは同じだが、クラスター分けは違ってきている。

極端な例

次に、元の分布でクラスターが見いだせないような極端な場合を考える。

初期値1

代表点の初期値は縦に並んでおり、クラスターも縦方向に分割されている。

初期値2

全く同じデータで代表点の初期値を横に並べた場合、クラスター分けは大きく異なっている。

3クラスター

最後に、元のデータでクラスターがかなり明確な場合を試してみる。

初期値1

初期値が隅の方から始まっていても、3つのクラスターによく分かれている。

初期値2

初期値の場所や並びがかなり異なっていても、クラスター分けは安定している。

まとめ

k平均法は初期値によって解が変動するとされているが、明らかにクラスターが明確な場合には解は安定している。

ただしそのようなケースは、特徴量の数が少なく分布が一目瞭然の場合に相当するので、特徴量が多く一目ではそのクラスターがわかりにくいような場合には、やはり初期値の取り方に大きく影響されるものと考えられる。

 

Python3 – KMeansClustering

概要

k平均法(k-means clustering)はクラスタリングの手法の1つで、与えられたデータ群の特徴と初期値に基づいて、データを並列(非階層)のクラスターに分類する。

アルゴリズムはシンプルで、以下の手順。

  1. クラスターの数だけクラスターの代表点の初期値を設定する
  2. 代表点の位置が収束するまで以下を繰り返す
    1. データの各点から最も近い代表点を選ぶ
    2. 同じ代表点の点群から重心を算出し、新しい代表点の位置とする

このクラスは、特徴量が2つのデータ群と代表点の初期値を与えて、k平均法でクラスタリングを行うテストクラス。

2つの特徴量x_datay_dataを与えてオブジェクトを生成し、代表点の初期値x_meansy_meansを与えてメソッドを実行して結果を得る。

全コード

KMeansClusteringクラスの全コードは以下の通り。

利用方法

クラスタリングを行うインスタンスの生成

初期データを与え、KMeansClusteringクラスのインスタンスを生成する。

コンストラクターに与える引数は以下の通り。

x_data, y_data
クラスタリングを行うデータの特徴量x、yの配列(1次元のndarray)。

クラスタリングの実行

生成したインスタンスに対して、クラスタリングを行うメソッドを実行して結果を得る。

メソッドに与える引数は以下の通り。

x_means, y_means
k個のクラスターの代表点の初期値(1次元のndarray)。

結果は以下のタプルで与えられる。

x_means, y_means
クラスターの代表点のx、yの配列。収束までの各計算段階の値を記録しており、2次元のndarrayの各行が各計算ステップに相当。
groups
各データが属するクラスター(代表点)番号のndarray

実行例

以下のコードでKMeansClusteringクラスをテスト。内容は以下の通り。

  1. 2つの円状に散らばるランダムな点群を発生させ、1つのデータとしてまとめる
    • random_scatter_dataは指定した中心・半径の円内に指定した数のランダムな点を発生させるモジュールで、別途作成(最後の方にコードを掲載)
  2. クラスターの代表点の数と位置、散布図を描画するパラメーターを設定する
    • プロットする図の数は2行3列で固定し、初期状態を除いた5つの図を表示させる
    • 予め実行させてコンソールで収束回数を確認し、33行目で表示させる計算ステップを指定している
  3. 初期状態の散布図をプロット
  4. クラスタリングを行うKMeansClustringオブジェクトを生成し、結果を得るためのメソッドを実行(53-54行目)
  5. 結果をプロット

このコードの実行結果はコンソールで以下のように表示される。

この結果から1、2、3、5、7回目の計算結果を図示するよう上のコードでセットした表示結果は以下の通り。

グループが比較的明確なので、早い段階で代表点の位置が定まっている。

クラス説明

__init__()~インスタンス生成

クラスタリングを行うデータの特徴量x_datay_datandarrayで与えてインスタンスを生成。

プライベート・メンバーは以下の通り。

_x, _y
クラスタリングを行うデータの2つの特徴量の配列。計算過程で変更されない。
_x_means, _y_means
代表点の計算結果を保存していく配列。分析実行時に初期値が与えられるため、初期値はNone。
_num_data
データの個数。
_num_means
代表点(クラスターの個数)。代表点が分析実行時に与えられるため、初期値は0。
_groups
各データの属するクラスターを保存していく配列。

get_result()~分析の実行

k個の代表点のx、yを引数として渡し、結果を得る。

引数

_x_means, _y_means
k個の代表点の初期値x、yを、それぞれ1次元のndarrayで与える。引数で与えた配列は変更されない。

戻り値

x_means, y_means
代表点の計算結果が保存された配列。各行は計算ステップに相当。
groups
各計算ステップにおける、各点のクラスターが保存された配列。各行は計算ステップに相当。

処理内容

  1. 代表点の初期値をプライベート・メンバーにコピーし、代表点の個数をセット
  2. 全ての代表点の位置が収束するまで、以下を繰り返す
    1. 各データについて、最も近い代表点をセット
    2. 共通の代表点を持つデータから、新しい代表点の位置を計算
    3. 代表点の前回最後の計算値と今回の計算値が収束したならループ終了、でなければ計算結果を追加してループ継続
  3. 計算結果を戻り値として終了

プライベートメソッド

_distance()~2点間の距離

2つの点の距離を与える。ここではユークリッド距離の2乗。

_point_converged()~収束判定

2つの点の座標から、点の位置が収束したかどうかを判定。

本来、各座標値はfloatなので'=='による判定は危険だが、ここでは収束の速さと確実性を信じて簡易に設定。

_all_points_converged()~全ての点の収束判定

配列で与えた2組の点がすべて収束条件を満たしているか判定。

_set_nearest_mean_point()~各点に最も近い代表点

処理内容

  1. 各データについて、それぞれから最も近い距離にある代表点を探し、その番号を1次元の配列groupsに記録
  2. _groups配列に、今回の分類結果を行として追加

_revise_mean_points()~代表点の更新

同じ代表点に属するデータからそれらの重心を計算し、新しい代表点として返す。

random_scatter_data.py

今回整理のためにつくったモジュールで、内容は以下の通り。