This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1
数値データのクラスタリング- Maple 上での簡単オブジェクト指向プログラミング紹介 -
実験値などの数値データを、データの分布形態に応じて小分けにすることはデータの傾向や対応する数式モデルを考える上でも有効な手段のひとつです。本資料では、離散数値データに対する基礎的な K 平均(K-Means)法によるクラスタリング手法を紹介します。特に、Maple 16 で導入された option object の記述による、Maple 上でのオブジェクト指向的なプログラミング手法を用いることで、データのクラスタリングのためのプログラムがシンプルかつ拡張性ある形で記述できるようになることも合わせて紹介します。
K 平均法とはK 平均法は、離散データを複数のクラスタ(分類)に分けるための、もっとも単純な非階層型クラスタリング算法として知られています。(参考:ウィキペディア)その手順は、大まかに以下となります:
[K 平均法による非階層型クラスタリング]
入力:データ集合 S = s1, s2,..., sn , 空のクラスタ集合 V = V1,..., Vm出力:クラスタ集合 V
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 文などを用いて例外処理も考える必要がありますが、ここでは割愛します。
データの消去は、変数 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
続けて、中心値を設定及び取得するためのメンバ関数 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']);
これを判定するには、 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:
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 平均法によるクラスタリング、さらにはデータ点のプロット機能などのトップレベルコマンドを用意していきます。
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;
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
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 平均法によるクラスタリングを適用してみます。最初の例では、ある区間内で一様にランダムな座標点からなるデータでクラスタリングを行なってみます。
今回のデータ点は区間 [-10, 10] x [-10, 10] に一様に広がる乱数からなるため、クラスタ数を4個として K 平均法でクラスタリングを行うと各象限毎に分類されるのがわかります。ただし、K 平均法は初期クラスタへの分類によって結果が異なるので、必ずしもすべての試行で同等の結果となるとは限らないことに注意して下さい。
同様の手順で、3次元のデータに対する K 平均法のクラスタリングを試してみます。まず3次元の乱数データを用意します。