数値データのクラスタリング - cybernetデータに対する基礎的な k...

Post on 14-Jul-2020

0 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Page 1

数値データのクラスタリング- Maple 上での簡単オブジェクト指向プログラミング紹介 -

実験値などの数値データを、データの分布形態に応じて小分けにすることはデータの傾向や対応する数式モデルを考える上でも有効な手段のひとつです。本資料では、離散数値データに対する基礎的な K 平均(K-Means)法によるクラスタリング手法を紹介します。特に、Maple 16 で導入された option object の記述による、Maple 上でのオブジェクト指向的なプログラミング手法を用いることで、データのクラスタリングのためのプログラムがシンプルかつ拡張性ある形で記述できるようになることも合わせて紹介します。

K 平均法とはK 平均法は、離散データを複数のクラスタ(分類)に分けるための、もっとも単純な非階層型クラスタリング算法として知られています。(参考:ウィキペディア)その手順は、大まかに以下となります:

[K 平均法による非階層型クラスタリング]

入力:データ集合 S = s1, s2,..., sn , 空のクラスタ集合 V = V1,..., Vm出力:クラスタ集合 V

手順1:各データ si をランダムに Vj へ割り振る。

手順2:割り振ったデータを元に、クラスタ Vj の中心 cj を計算する。

手順3:各 si とcj の距離を求め、si をもっとも中心と近いクラスタ Vj ' に移動する。

手順4:上記処理を繰り返し、変更がなかった場合にこの手順を終了する。(または指定回数だけ上記手順を繰り返す)

データ間の距離は、ユークリッド距離以外にも、二乗ユークリッド距離やマンハッタン距離など、任意の距離を用いることが可能です。

プログラムの基本構造本資料では、Maple のオブジェクト指向手法によるクラスタリング算法を考えます。オブジェクト指向の詳細については一般的なプログラミング関連の教本を参照していただくため、ここでは割愛します。また、本資料では、データの所属する「クラスタ」をひとつのオブジェクト(もしくはクラス)として考え、「クラスタ」オブジェクトにメンバ変数およびメンバ関数を実装していきます。また、各クラスタオブジェクトのインスタンス(実体)に対して、トップレベルの関数を用意して、K 平均法によるクラスタリングのプログラムを適用するようにします。

option object 宣言によるクラスタ・オブジェクトの定義Maple 16 以降では、 module 文に option object 宣言を用いることで module をひとつのオブジェクトクラスとして扱うことが出来るようになりました。 module 文とは、複数の proc...end proc 文を含む、プログラムの集合として考えられるものです。 module 文の詳細については、ヘルプを参照してみてください。(参考:Maple 上の多くのパッケージは、 module 文により実装されています。)

ここでは、クラスタ・オブジェクトには以下のメンバ変数とメンバ関数を用意することにします。

Page 2

> >

(3.1.1)(3.1.1)

> > > >

(3.1.2)(3.1.2)

> >

[メンバ変数]・データ点を格納する変数 data・データ点から計算される中心 center

[メンバ関数]・データ点をセット及び取得するための関数・データ点を削除するための関数・データ点からなる中心 center を計算及び取得するための関数・データ点と中心の距離を計算するための関数・任意の2つのクラスタが等価であるかを確認するための関数・クラスタオブジェクトをコピーするための関数

この基本設計に基づいて、まずはオブジェクトの実装からはじめてみます。

クラスタ・オブジェクトの定義

オブジェクト化した module 文の定義は、通常次のような記述で定義されます。

restart;module Cluster() option object;end module;

module Cluster option object; end module

なお、一度 option object 宣言を用いて評価(実行)を行った場合、すでに既存の Maple セッション中にはこのオブジェクトクラスが読み込まれています。したがって、同じ宣言をもう一度記述して評価を行うと、すでに「Cluster」という名前のオブジェクトクラスが存在している旨のエラーが返されます:

module Cluster() option object;end module;

Error, (in Cluster) attempting to assign to `Cluster` which is protected

したがって、再度オブジェクトの定義を更新する場合は、必ずセッションをrestart してから評価する必要があります。

さて、ここからは実際にメンバ変数やメンバ関数を Cluster オブジェクトに実装していきます。まずはメンバ変数についてです。

restart;module Cluster() option object;

local data:=NULL, center;end module;

module Cluster option object; local data, center; end module

上記のように、通常の proc 文の宣言同様に local 宣言を用いてメンバ変数を定義しています。data 及び center はこのオブジェクトクラス内のメンバ変数ですので、オブジェクトの外から直接参照することはできません。なお、data 変数についてはデフォルトの値として NULL 値を設定しています。同様な形の記述( := による変数への割当て)で標準の値を用意することも可能です。

Page 3

(3.2.2)(3.2.2)

> >

> >

(3.2.1)(3.2.1)

> >

> >

データの取得・設定のためのメンバ関数次にデータ変数 data の取得及び設定のためのメンバ関数の実装を考えます。取得のための関数 getData を以下のように記述します。

restart;module Cluster() option object;

local data:=NULL, center;

export getData::static := proc(self::Cluster) if evalb(self:-data = 'self:-data') then return(NULL); else return(self:-data); end if; end proc;

end module;module Cluster option object; local data, center; end module

getData 関数の前に付加している export とは、このメンバ関数をオブジェクトの外からでもアクセス可能にするための宣言です。また、getData の後に付加している ::static とは、文字通りこのメンバ関数を静的に取り扱うための宣言になります。getData 関数への引数は、self という名前の Cluster オブジェクトが渡されます。実際にこのメンバ関数の利用法を以下で見てみます。

c := Object(Cluster);c := Object!!Cluster,282615812OO

getData(c);

上記で割り当てられた変数 c は、Cluster オブジェクトの実体(インスタンス)となります。このインスタンスに対してメンバ関数 getData を適用することでメンバ変数 data の値を取得しようとしていることになります。(この例では data に何も割当てられていないためデフォルト値のまま NULL が返されています)

同様にして、今度は data 変数を設定するためのメンバ関数 setData を実装してみます。

restart;module Cluster() option object;

local data:=NULL, center;

export getData::static := proc(self::Cluster) if evalb(self:-data = 'self:-data') then return(NULL); else return(self:-data); end if; end proc;

export setData::static := proc(self::Cluster, d) if self:-data=NULL then # はじめて data 変数に値をセットする場合 if type(d, listlist) then

Page 4

(3.2.3)(3.2.3)

(3.2.8)(3.2.8)

> >

> >

> >

> >

(3.2.9)(3.2.9)

(3.2.7)(3.2.7)

> > (3.2.4)(3.2.4)

(3.2.5)(3.2.5)

> >

(3.2.10)(3.2.10)

> >

(3.2.6)(3.2.6)

# セットするデータ d が2次元(入れ子)以上のリストの場合 self:-data := d; else self:-data := [d]; end if; else # すでにある data 変数にデータ d を追加する場合 if type(d, listlist) then self:-data := [self:-data[], d[]]; else self:-data := [self:-data[], d]; end if; end if; return(true); end proc;

end module;module Cluster option object; local data, center; end module

上記の setData 関数では、メンバ変数 data の状態に応じて if-then-else 文でデータの設定方法を考えています。また、与えられるデータ d の状態によってもデータの格納方法をケース分けして記述していることに注意してください。いずれの場合も、新しいデータ d は、既存データの最後(後ろ)からセットされます。なお、この例題ではプログラムをシンプルに記述するため、エラーコードなどは記載していません。広く汎用性を考えたプログラムを用意する場合は、 try...catch 文などを用いて例外処理も考える必要がありますが、ここでは割愛します。

ここまでの Cluster オブジェクトの宣言で、データの設定及び取得のためのメンバ関数が用意できました。新しいインスタンスを生成して機能動作を確認してみます。

c := Object(Cluster);c := Object!!Cluster,282616012OO

# 2次元の座標データの設定setData(c, [[1,3],[2,5],[2,3]]);

true# 設定したデータの取得getData(c);

1, 3 , 2, 5 , 2, 3# 一つの座標データを追加setData(c, [5,8]);

true# 追加した後の data 変数の中身を確認getData(c);

1, 3 , 2, 5 , 2, 3 , 5, 8# 複数の座標データを追加setData(c, [9,1],[3,5],[8,4]);

true# 追加した後の data 変数を確認getData(c);

1, 3 , 2, 5 , 2, 3 , 5, 8 , 9, 1

データ消去と中心値のためのメンバ関数前章と同様な手順で、データの消去と中心値を計算及び取得するためのメンバ関数を実装していきます。

Page 5

> >

データの消去は、変数 data 内の番号(位置)を指定してデータを消去できるようにします。また、番号は一つだけでなく複数の番号をリストで与えられるようにします。

restart;module Cluster() option object;

local data:=NULL, center;

export getData::static := proc(self::Cluster)

if evalb(self:-data = 'self:-data') then return(NULL); else return(self:-data); end if;

end proc;

export setData::static := proc(self::Cluster, d)

if self:-data=NULL then # はじめて data 変数に値をセットする場合 if type(d, listlist) then # セットするデータ d が2次元(入れ子)以上のリストの場合 self:-data := d; else self:-data := [d]; end if;

else # すでにある data 変数にデータ d を追加する場合 if type(d, listlist) then self:-data := [self:-data[], d[]]; else self:-data := [self:-data[], d]; end if; end if;

return(true); end proc;

export removeData::static := proc(self::Cluster, di) local id, i, dataTemp;

if type(di, nonnegint) then # di が一つの番号の場合 self:-data := subsop(di=NULL,self:-data);

elif type(di, list) then # di が番号のリストである場合 dataTemp := self:-data; id := remove(has, [i$i=1..nops(dataTemp)], di); self:-data := map(i->op(i,dataTemp), id);

else # di が一つの番号及び番号のリストの形式でない場合にエラーを返す ERROR("removeData: Unknown index type", di); end if;

end proc;

Page 6

> >

> >

(3.3.1)(3.3.1)

(3.3.8)(3.3.8)

(3.3.5)(3.3.5)

> >

(3.3.2)(3.3.2)

(3.3.7)(3.3.7)

(3.3.6)(3.3.6)> >

(3.3.4)(3.3.4)

> >

> >

> >

> >

> >

> > (3.3.3)(3.3.3)

end module:

適当なデータをセットして、消去機能を確認してみます。

c := Object(Cluster);c := Object!!Cluster,282616232OO

setData(c, [[1,3],[2,1],[4,2],[3,5],[7,4]]);true

# 3番目のデータを消去removeData(c, 3);

1, 3 , 2, 1 , 3, 5 , 7, 4getData(c);

1, 3 , 2, 1 , 3, 5 , 7, 4# 2番目と4番目のデータを消去removeData(c, [2,4]);

1, 3 , 3, 5getData(c);

1, 3 , 3, 5# 存在しない番号を指定した場合removeData(c, 3);

Error, (in Cluster:-removeData) improper op or subscript selector

なお、最後の例でのエラーは、一つの番号を指定して消去を行う際の subsop コマンドによって出されているものです。

続けて、中心値を設定及び取得するためのメンバ関数 setCenter, getCenter を用意します。なお、リスト化されたデータの平均は、次の Maple コマンドで計算できます:

foo := [x1,x2,x3,x4];foo := x1, x2, x3, x4

add(fk, fk in foo)/nops(foo);14

x1C 14

x2C 14

x3C 14

x4

restart;module Cluster() option object;

local data:=NULL, center;

export getData::static := proc(self::Cluster)

if evalb(self:-data = 'self:-data') then return(NULL); else return(self:-data); end if;

end proc;

export setData::static := proc(self::Cluster, d)

if self:-data=NULL then

Page 7

> >

# はじめて data 変数に値をセットする場合 if type(d, listlist) then # セットするデータ d が2次元(入れ子)以上のリストの場合 self:-data := d; else self:-data := [d]; end if;

else # すでにある data 変数にデータ d を追加する場合 if type(d, listlist) then self:-data := [self:-data[], d[]]; else self:-data := [self:-data[], d]; end if; end if;

return(true); end proc;

export removeData::static := proc(self::Cluster, di) local id, i, dataTemp;

if type(di, nonnegint) then # di が一つの番号の場合 self:-data := subsop(di=NULL,self:-data);

elif type(di, list) then # di が番号のリストである場合 dataTemp := self:-data; id := remove(has, [i$i=1..nops(dataTemp)], di); self:-data := map(i->op(i,dataTemp), id);

else # di が一つの番号及び番号のリストの形式でない場合にエラーを返す ERROR("removeData: Unknown index type", di); end if;

end proc;

export setCenter::static := proc(self::Cluster) if type(self:-data,list) and nops(self:-data)>0 then self:-center := add(di, di in self:-data)/nops(self:-data); end if; end proc;

export getCenter::static := proc(self::Cluster) return(self:-center); end proc;

end module:

次に、クラスタ内の data と中心との距離を計算するためのメンバ関数distanceToCenter を実装します。このメンバ関数には、データと中心の距離を計算する算法を指定するためのオプション method を付加します。関数のオプション値の記述方法の詳細は、Maple ヘルプの _options の項を参照してください。

restart;module Cluster()

Page 8

option object;

local data:=NULL, center;

export getData::static := proc(self::Cluster)

if evalb(self:-data = 'self:-data') then return(NULL); else return(self:-data); end if;

end proc;

export setData::static := proc(self::Cluster, d)

if self:-data=NULL then # はじめて data 変数に値をセットする場合 if type(d, listlist) then # セットするデータ d が2次元(入れ子)以上のリストの場合 self:-data := d; else self:-data := [d]; end if;

else # すでにある data 変数にデータ d を追加する場合 if type(d, listlist) then self:-data := [self:-data[], d[]]; else self:-data := [self:-data[], d]; end if; end if;

return(true); end proc;

export removeData::static := proc(self::Cluster, di) local id, i, dataTemp;

if type(di, nonnegint) then # di が一つの番号の場合 self:-data := subsop(di=NULL,self:-data);

elif type(di, list) then # di が番号のリストである場合 dataTemp := self:-data; id := remove(has, [i$i=1..nops(dataTemp)], di); self:-data := map(i->op(i,dataTemp), id);

else # di が一つの番号及び番号のリストの形式でない場合にエラーを返す ERROR("removeData: Unknown index type", di); end if;

end proc;

export setCenter::static := proc(self::Cluster) if type(self:-data,list) and nops(self:-data)>0 then self:-center := add(di, di in self:-data)/nops(self:-data); end if; end proc;

Page 9

> >

(3.3.10)(3.3.10)

(3.3.15)(3.3.15)

> >

> >

(3.3.9)(3.3.9)

(3.3.12)(3.3.12)

(3.3.13)(3.3.13)

> >

> >

> >

> >

(3.3.11)(3.3.11)

> > (3.3.14)(3.3.14)

export getCenter::static := proc(self::Cluster) return(self:-center); end proc;

export distanceToCenter::static := proc(self::Cluster, d::list, {method::string:="Euclidean"}) local t, methodopt;

methodopt := eval(method,_options['method']);

if methodopt = "Euclidean" then evalf(sqrt(add(t^2,t in self:-center - d)));

elif methodopt = "SquareEuclidean" then evalf(add(t^2,t in (self:-center - d)));

elif methodopt = "Manhattan" then evalf(add(abs(t), t in self:-center - d));

elif methodopt = "Chessboard" then max(evalf(seq(abs(t), t in self:-center - d)));

else ERROR("Unknown method is specified.", _options['method']);

end if; end proc;

end module:

上記の例では、代表的なユークリッド距離の他、二乗ユークリッド距離、マンハッタン距離、チェスボード距離の4種類を用意しています。必要に応じてこの距離関数の定義を拡張することが可能です。ここまでの機能を確認するため、適当なデータ点を与えてデータ点と中心の距離を計算してみます。

c := Object(Cluster);c := Object!!Cluster,282616352OO

setData(c, [[1,2],[3,2],[4,3],[5,3],[7,5],[8,3]]);true

# データ点の中心をセットsetCenter(c);

143

, 3

# 点 [3,2] と中心との距離を計算distanceToCenter(c, [3,2]);

1.943650631# 他の手法で計算distanceToCenter(c, [3,2], method="SquareEuclidean");

3.777777778distanceToCenter(c, [3,2], method="Manhattan");

2.666666667distanceToCenter(c, [3,2], method="Chessboard");

1.666666667# 用意されていない手法を指定した場合distanceToCenter(c, [3,2], method="Analytic");

Error, (in Cluster:-distanceToCenter) Unknown method is

Page 10

(3.4.2)(3.4.2)

(3.4.1)(3.4.1)

> >

> >

> >

specified., method = "Analytic"

その他のメンバ関数Cluster オブジェクトの用意するメンバ関数として、最後に2つの Cluster オブジェクトを比較するための関数と、オブジェクトをコピーするための関数 ModuleCopy を用意します。

通常、Maple で2つの式を比較する場合、等号を用いて次のように記述できます:

sin(x) = cos(x);sin x = cos x

これを判定するには、 evalb コマンドや is コマンドなどを用いて式の等価性を得ることができます:

evalb((3.4.1));false

一方、リストや行列、ベクトルなどの「構造」を持ったデータを比較する場合、Maple には組込みコマンドとして EqualEntries が用意されています。EqualEntries コマンドは構造内の個々のデータを比較しその AND 計算を行った結果を返します。

そこで、オブジェクト指向における既存関数の継承を用いて、等号 = を Cluster オブジェクトに対しても有効にするような記述を行います。ここで、Cluster オブジェクトの等価性は、 EqualEntries コマンドの機能によりメンバ関数 data が共に同じデータを持っているかどうかで判定するようにします。

= の継承を含むコード例を以下に記載します。(最後のメンバ関数定義を参照してください)

restart;module Cluster() option object;

local data:=NULL, center;

export getData::static := proc(self::Cluster)

if evalb(self:-data = 'self:-data') then return(NULL); else return(self:-data); end if;

end proc;

export setData::static := proc(self::Cluster, d)

if self:-data=NULL then # はじめて data 変数に値をセットする場合 if type(d, listlist) then # セットするデータ d が2次元(入れ子)以上のリストの場合 self:-data := d; else self:-data := [d]; end if;

Page 11

else # すでにある data 変数にデータ d を追加する場合 if type(d, listlist) then self:-data := [self:-data[], d[]]; else self:-data := [self:-data[], d]; end if; end if;

return(true); end proc;

export removeData::static := proc(self::Cluster, di) local id, i, dataTemp;

if type(di, nonnegint) then # di が一つの番号の場合 self:-data := subsop(di=NULL,self:-data);

elif type(di, list) then # di が番号のリストである場合 dataTemp := self:-data; id := remove(has, [i$i=1..nops(dataTemp)], di); self:-data := map(i->op(i,dataTemp), id);

else # di が一つの番号及び番号のリストの形式でない場合にエラーを返す ERROR("removeData: Unknown index type", di); end if;

end proc;

export setCenter::static := proc(self::Cluster) if type(self:-data,list) and nops(self:-data)>0 then self:-center := add(di, di in self:-data)/nops(self:-data); end if; end proc;

export getCenter::static := proc(self::Cluster) return(self:-center); end proc;

export distanceToCenter::static := proc(self::Cluster, d::list, {method::string:="Euclidean"}) local t, methodopt;

methodopt := eval(method,_options['method']);

if methodopt = "Euclidean" then evalf(sqrt(add(t^2,t in self:-center - d)));

elif methodopt = "SquareEuclidean" then evalf(add(t^2,t in (self:-center - d)));

elif methodopt = "Manhattan" then evalf(add(abs(t), t in self:-center - d));

elif methodopt = "Chessboard" then max(evalf(seq(abs(t), t in self:-center - d)));

else ERROR("Unknown method is specified.", _options

Page 12

> >

> >

(3.4.4)(3.4.4)

(3.4.3)(3.4.3)

> >

> >

> >

(3.4.5)(3.4.5)

['method']);

end if; end proc;

export `=`::static := proc(c1::Cluster, c2::Cluster) EqualEntries(c1:-data, c2:-data); end proc; end module:

この定義により、Clusterオブジェクトの比較を等号を使って行えます。

# 2つの Cluster オブジェクトを用意c1 := Object(Cluster);c2 := Object(Cluster);

c1 := Object!!Cluster,282616392OOc2 := Object!!Cluster,282616492OO

# それぞれに別のデータ変数をセットsetData(c1, [[2,1],[3,4],[5,3]]):setData(c2, [[2,1],[3,4],[5,1]]):# データの等価性を判定evalb(c1 = c2);

false# c2 の最後のデータを書き換えて再度等価性を判定removeData(c2,3):setData(c2, [5,3]):evalb(c1 = c2);

true

最後に、 ModuleCopy 関数の定義です。ModuleCopy 関数は、 module 文によるオブジェクトを定義する場合に Maple により提供される機能のひとつです。ModuleCopy 関数を明示的に実装することにより、そのオブジェクトの複製を用意することが可能になります。

以下のコードの最後に、 ModuleCopy 関数の定義を含んだ Cluster オブジェクトの定義を記載しています。

restart;module Cluster() option object;

local data:=NULL, center;

export getData::static := proc(self::Cluster)

if evalb(self:-data = 'self:-data') then return(NULL); else return(self:-data); end if;

end proc;

export setData::static := proc(self::Cluster, d)

if self:-data=NULL then # はじめて data 変数に値をセットする場合

Page 13

if type(d, listlist) then # セットするデータ d が2次元(入れ子)以上のリストの場合 self:-data := d; else self:-data := [d]; end if;

else # すでにある data 変数にデータ d を追加する場合 if type(d, listlist) then self:-data := [self:-data[], d[]]; else self:-data := [self:-data[], d]; end if; end if;

return(true); end proc;

export removeData::static := proc(self::Cluster, di) local id, i, dataTemp;

if type(di, nonnegint) then # di が一つの番号の場合 self:-data := subsop(di=NULL,self:-data);

elif type(di, list) then # di が番号のリストである場合 dataTemp := self:-data; id := remove(has, [i$i=1..nops(dataTemp)], di); self:-data := map(i->op(i,dataTemp), id);

else # di が一つの番号及び番号のリストの形式でない場合にエラーを返す ERROR("removeData: Unknown index type", di); end if;

end proc;

export setCenter::static := proc(self::Cluster) if type(self:-data,list) and nops(self:-data)>0 then self:-center := add(di, di in self:-data)/nops(self:-data); end if; end proc;

export getCenter::static := proc(self::Cluster) return(self:-center); end proc;

export distanceToCenter::static := proc(self::Cluster, d::list, {method::string:="Euclidean"}) local t, methodopt;

methodopt := eval(method,_options['method']);

if methodopt = "Euclidean" then evalf(sqrt(add(t^2,t in self:-center - d)));

elif methodopt = "SquareEuclidean" then evalf(add(t^2,t in (self:-center - d)));

elif methodopt = "Manhattan" then

Page 14(4.1)(4.1)

> >

evalf(add(abs(t), t in self:-center - d));

elif methodopt = "Chessboard" then max(evalf(seq(abs(t), t in self:-center - d)));

else ERROR("Unknown method is specified.", _options['method']);

end if; end proc;

export `=`::static := proc(c1::Cluster, c2::Cluster) EqualEntries(c1:-data, c2:-data); end proc;

export ModuleCopy::static := proc(self::Cluster, proto::Cluster, d) if (_npassed=2) then self:-data := proto:-data; else setData(self, d); end if; setCenter(self); end proc;

end module:

ModuleCopy 関数の最初の引数 self は新しいオブジェクト、proto が元のオブジェクトになります。この処理では、オブジェクトをコピーする際に既存の data 変数のみが渡される場合(_npassed=2 の場合)と新しいデータを付加する場合についての記述を用意しています。また、いずれの場合もコピーされたオブジェクトのインスタンスに対して中心値を計算する setCenter 関数を呼び出していることに注意してください。

以上で一通りの Cluster オブジェクトに対するメンバ変数及びメンバ関数の定義が完了しました。次節では個々の Cluster オブジェクトのインスタンスに対する K 平均法によるクラスタリング、さらにはデータ点のプロット機能などのトップレベルコマンドを用意していきます。

トップレベル関数群の作成前節までに定義した Cluster オブジェクトの定義を下記に記載しています。この節を実行する前にコードを実行してください。(コメント行を削除したものにしています)

restart;

さて、この節では Cluster オブジェクトを用いて実際にクラスタリングの処理を行うための関数群を用意します。K 平均法の手順1から、まずは与えられたクラスタの個数 m からランダムなクラスタ番号を生成するための関数、及び初期データをランダムに個々のクラスタへ割り振るための関数を用意します。

・ランダムなクラスタ番号の生成randClusterID := (m::nonnegint) -> RandomTools:-MersenneTwister:-GenerateInteger(range=1..m);

randClusterID := m::nonnegint/RandomTools:-MersenneTwister:-

Page 15

> >

> >

(4.3)(4.3)

(4.4)(4.4)

> >

(4.2)(4.2)> >

> >

GenerateInteger range = 1 ..m

上記のコードでは、1 から m までの範囲のランダムな整数は、 RandomTools パッケージのメルセンヌツイスターを用いた乱数を使って生成するようにしています。

次に、与えられたデータ集合の各要素をランダムなクラスタへ割り振る処理です。

・初期データのランダムなクラスタ割り振りrandomClustering := proc(data::list, {NumberOfClusters::nonnegint:=4}) local i, dlen, initClusters, noc, rci;

dlen := nops(data); noc := NumberOfClusters;

# 初期クラスタを生成 initClusters := [seq(Object(Cluster), i=1..noc)];

for i to dlen do rci := randClusterID(noc); setData(initClusters[rci], data[i]); end do;

seq(setCenter(initClusters[i]), i=1..noc); return(initClusters);end proc:

上記のプログラムは、引数 data で与えられた数値データの各要素を NumberOfClusters オプションで指定された個数の初期クラスタへランダムに割り振るための処理を記述しています。この関数の戻り値は、データが割り振られた初期クラスタのリストです。次のコードで動作を確認してみます。

# r1からr2までの範囲の乱数を n 個生成する関数を用意r := (r1,r2,n) -> RandomTools:-Generate(list(float(range=r1..r2),n));

r := r1, r2, n /RandomTools:-Generate list float range = r1 ..r2 , n# [-1,1] の範囲の乱数で2次元座標値を用意data := zip((x,y)->[x,y], r(-1,1,10), r(-1,1,10));

data := K0.351727964, 0.797189083 , 0.418130698,K0.196140740 ,K0.742863086, 0.190905574 , K0.438850937, 0.798645409 , 0.454595796,K0.964976871 , K0.775435781,K0.996681049 , 0.050666015, 0.323056991 ,

0.431521350, 0.030029039 , K0.323567403,K0.963517098 , 0.581067165,0.528147374# 初期クラスタへの割り振りic := randomClustering(data);

ic := Object!!Cluster,282601308OO, Object!!Cluster,282601488OO,Object!!Cluster,282601528OO, Object!!Cluster,282601568OO

実際に割り振られたデータ及び中心値はそれぞれ以下のようになっています:

for i to 4 do i, getData(ic[i]), getCenter(ic[i]);end do;

1, K0.351727964, 0.797189083 , K0.742863086, 0.190905574 , 0.454595796,K0.964976871 , K0.2133317513, 0.007705928667

Page 16

> >

> >

(4.5)(4.5)

2, 0.418130698,K0.196140740 , K0.775435781,K0.996681049 , 0.050666015,0.323056991 , K0.1022130227,K0.2899215993

3, 0.431521350, 0.030029039 , K0.323567403,K0.963517098 , 0.05397697350,K0.4667440295

4, K0.438850937, 0.798645409 , 0.581067165, 0.528147374 , 0.07110811400,0.6633963915

今度は K 平均法のメインルーチンの実装です。ここでは、K 平均法の1回の処理を実施する形でコードを記述します。(最終的なコードでは、ユーザは反復回数を指定して K 平均法の反復を行うことにします)

・K 平均法メインルーチンの定義KMeans := proc(cl::list(Cluster), {method::string:="Euclidean"}) local clist, cln, cli, dn, di, dist, nearCls, dataSet, remId;

clist := map(p->Object(p), cl); cln := nops(clist);

for cli to cln do dataSet := getData(clist[cli]); dn := nops(dataSet); remId := NULL;

for di to dn do dist := seq([ci,distanceToCenter(clist[ci], dataSet[di], _options['method'])], ci = 1..cln); nearCls := sort([dist], (a,b)->is(a[2]<b[2]))[1,1]; if cli <> nearCls then remId := remId, di; setData(clist[nearCls], dataSet[di]); end if; end do; removeData(clist[cli], [remId]); end do;

seq(setCenter(clist[ci]), ci = 1..cln);

return(clist);end proc:

上記コードの手順を行毎に説明するため、 showstat コマンドで行番号と共にコードを出力します。

showstat(KMeans);

KMeans := proc(cl::list(Cluster), {method::string := "Euclidean"})local clist, cln, cli, dn, di, dist, nearCls, dataSet, remId; 1 clist := map(p -> Object(p),cl); 2 cln := nops(clist); 3 for cli to cln do 4 dataSet := getData(clist[cli]); 5 dn := nops(dataSet); 6 remId := NULL;

Page 17

> >

7 for di to dn do 8 dist := seq([ci, distanceToCenter(clist[ci],dataSet[di],_options['method'])],ci = 1 .. cln); 9 nearCls := sort([dist],(a, b) -> is(a[2] < b[2]))[1,1]; 10 if cli <> nearCls then 11 remId := remId, di; 12 setData(clist[nearCls],dataSet[di]) end if end do; 13 removeData(clist[cli],[remId]) end do; 14 seq(setCenter(clist[ci]),ci = 1 .. cln); 15 return clistend proc

KMeans 関数の個々の手順は以下の通りです。

(1) 行番号 1 : 元の cl 引数内の Cluster オブジェクトのリストから、オブジェクトのコピーを作成。(cl 内のオブジェクトをそのまま用いて計算を行うとオブジェクトに対する上書き更新となってしまうため)(2) 行番号 4 : 個々の Cluster オブジェクトの data メンバ変数を取得(3) 行番号 6 : remId 変数は、データを別のクラスタへ変更する際、元のクラスタ内のデータの位置を保存するための番号。最終的に、保存された各データ要素の番号は行 13 の処理で元のクラスタから削除。(4) 行番号 8 : あるクラスタに対して、クラスタ内の data メンバ変数とすべてのクラスタの中心値との距離を計算。(5) 行番号 9 : 距離のリストを昇順で並び替えし、一番最初の要素(すなわち最も距離が近い中心値のクラスタ番号)を取得。(6) 行番号 10, 12: 現在のクラスタ番号 cli と最も近いクラスタ番号 nearCls が異なる、つまり現在のデータよりも近い中心値を持つ別のクラスタがある場合は setData メンバ関数によりそのデータを別クラスタへ移動。(7) 行番号 13 : 移動したデータを元のクラスタから削除(8) 行番号 14 : すべてのクラスタに対して中心値を更新。

KMeans 関数は一回だけ K 平均法の反復を実施するための関数なので、指定された回数だけ K 平均法を実施するための関数 FindClusters を用意します。FindClusters 関数はもっともトップレベルの関数(ユーザが利用するための関数)となります。

・最上位の関数 FindClusters を定義 入力:データ点のリスト、各オプション値 出力:クラスタ化したデータのリスト

FindClusters := proc(d::list, {iteration::nonnegint:=5, #反復回数 clusters::nonnegint:=4, #クラスタ個数 method::string:="Euclidean"} #距離計算手法 ) local clist, ite, ret, obj;

clist := randomClustering(d, NumberOfClusters=clusters); for ite from 1 to iteration do clist := KMeans(clist, _options['method']); end do;

ret := map(obj->getData(obj), clist); return(ret);

Page 18

> > (5.2)(5.2)

> >

> > (5.1)(5.1)

> >

end proc:

以上で K 平均法によるデータのクラスタリングための関数が用意できました。このままでもすぐにデータのクラスタリングを実施できますが、クラスタリングの結果を容易に確認できるようにするため、クラスタ化したデータのプロットのための関数を用意します。

・クラスタ化したデータリストのプロットClusterPlot := proc(d::listlist, {palettename::symbol:="Niagara", symbol:=solidcircle, symbolsize:=12})

local nd, ndi, gr, plt, opts;

nd := nops(d); plt := ColorTools:-GetPalette(palettename); opts := _options['symbol', 'symbolsize'];

if nops(d[1,1])=2 then # 2D plot gr := seq(plots[pointplot](d[ndi],opts,_rest,color=plt[ndi]), ndi=1..nd); elif nops(d[1,1])=3 then # 3D plot gr := seq(plots[pointplot3d](d[ndi],opts,_rest,color=plt[ndi]), ndi=1..nd); else _ERROR("Unable to plot data"); end if;

return(plots[display](gr));end proc:

この関数では、クラスタ毎の色分けを使用するために Maple 16 で導入された ColorTools パッケージを用いています。また、座標データが3次元より大きい場合のエラー処理も付加しています。

数値データの K 平均法によるクラスタリング前節までに定義した関数群をすべてまとめたコード領域を以下に用意しておきます。この節のコマンドを実行する前に本コードを実行しておいてください。

restart;

さっそく数値データを用意して K 平均法によるクラスタリングを適用してみます。最初の例では、ある区間内で一様にランダムな座標点からなるデータでクラスタリングを行なってみます。

N := 300;N := 300

rn := (r1,r2,n) -> RandomTools:-Generate(list(float(range=r1..r2),n));

rn := r1, r2, n /RandomTools:-Generate list float range= r1 ..r2 , n# ランダムなデータ集合を用意data1 := zip((x,y)->[x,y],rn(-10,10,N),rn(-10,10,N)):

Page 19

> >

(5.3)(5.3)

> >

> >

> >

# 用意したデータ点をプロットClusterPlot([data1], symbolsize=16);

K8 K6 K4 K2 0 2 4 6 8

K8

K6

K4

K2

2

4

6

8

10

このデータ集合に FindClusters 関数を適用し、クラスタリングしたデータ集合を得ます。

cls := FindClusters(data1):nops(cls);

4

FindClusters 関数にはクラスタの個数を指定するオプション値 clusters が標準では 4 としてセットされています。そのため、標準では 4 個のクラスタが生成されます。クラスタ化した点集合をプロットしてみます。

ClusterPlot(cls, symbolsize=16);

Page 20

> > > >

K8 K6 K4 K2 0 2 4 6 8

K8

K6

K4

K2

2

4

6

8

10

反復回数を増やし、結果が異なるか確認してみます。

cls2 := FindClusters(data1, iteration=40):ClusterPlot(cls2, symbolsize=16);

Page 21

(5.4)(5.4)> >

> >

> >

(5.5)(5.5)

K8 K6 K4 K2 0 2 4 6 8

K8

K6

K4

K2

2

4

6

8

10

今回のデータ点は区間 [-10, 10] x [-10, 10] に一様に広がる乱数からなるため、クラスタ数を4個として K 平均法でクラスタリングを行うと各象限毎に分類されるのがわかります。ただし、K 平均法は初期クラスタへの分類によって結果が異なるので、必ずしもすべての試行で同等の結果となるとは限らないことに注意して下さい。

同様の手順で、3次元のデータに対する K 平均法のクラスタリングを試してみます。まず3次元の乱数データを用意します。

N := 500;N := 500

dat2 := convert(Array(1..N,1..3,(i,j)->RandomTools:-Generate(float(range=1..10))),listlist):

このデータに対して FindClusters 関数を適用してみます。なお、クラスタ数は3個、反復回数を30として、クラスタ作成の計算時間を測定しておきます。

st := time():cls3 := FindClusters(dat2, clusters=3, iteration=30):time() - st;

6.614

クラスタ化したデータをプロットしてみます。

Page 22

> > ClusterPlot(cls3, axes=boxed, symbolsize=14);

今回作成した関数群、特に Cluster オブジェクトについては次元を考慮しない形で記述しています。従って、任意の次元に対するクラスタリングがそのまま適用できる形になっているため、極めて効率的かつ柔軟なコーディングを行えることがわかります。

また、今回紹介したオブジェクト指向的な記述方法を用いることで、Maple 内部のメモリ管理も効率的に適用されます。結果として、今回のクラスタリングを手続き的に記述した場合(具体的にはオブジェクトデータを record 型として記述した場合)と比較しても、一連の処理速度は高速に行われます。

オブジェクト指向によるプログラミングスタイルは、現在では Objective-C や Java, Python 等現代的なプログラム言語で一般的となっていますが、option object 宣言の利用で Maple 上でも現代的なプログラミングスタイルの導入が容易になります。

2012 Copyright © Maplesoft / Cybernet Systems Co., Ltd., All rights reserved.

top related