最近,別ページ
順序集合や束などに関する基本的な概念の説明で,ガロア接続を詳しく説明しました.直接のリンクは
ガロア接続 (Galois Connection)です.そのページを書いたとき,ガロア接続の応用として 形式的概念分析 のサンプルを作るために,Ruby でごく簡単なプログラムを作成しましたので, こちらにそのプログラムとそれを使ったいくつかの概念分析のサンプルを載せておきます.
例えば,このページのオープニングの絵にあるように,
植物を食べる | 肉を食べる | 哺乳する | 飛ぶ | 卵を産む | 変温動物 | |
---|---|---|---|---|---|---|
ネズミ | 〇 | 〇 | 〇 | |||
スズメ | 〇 | 〇 | 〇 | 〇 | ||
トカゲ | 〇 | 〇 | 〇 | |||
トラ | 〇 | 〇 |
で与えられるような関係が成立していたとしましょう.ガロア接続を使った形式的概念分析は この表から,
という概念の束(lattice)を作り出します.左は個体の集合間に包含関係で線を引いたもので, 右は属性の集合に逆の包含関係で線を引いたものです.この左右の束は対応しています.例えば,左側の 上から2番目の一番左には {トラ, ネズミ} があり,右側の対応する部分には {肉を食べる, 哺乳する} があります.これは,この表で,
ことを示しています.
この図で次の2つのことに注意してください.
一番目の項目について言えば,この例では個体は4つ,属性は6つありますから,個体の方の
部分集合の個数は 24=16個,属性の方の部分集合の個数は 26=64個
あるはずですが,左右とも 8 個ずつしか出てきていません.これは左右の対応において,
お互いに相手側に含まれるものと与えられた表の関係になっているもの
ここでもう一つ,{トラ} という部分集合が左の束に無いことに注意してください.「トラ」が 持っている属性は,{肉を食べる, 哺乳する} ですが,これを満たす左側の集合は {トラ, ネズミ} です.残念ながら,この,個体の集合も,属性の集合も最大化するという概念の表現体系では 「トラ」単独の集合は表すことができません.
二番目の項目について言えば,個体の数,属性の数が異なっていても,上のように表の関係を 満たすすべての要素を選ぶということで,左右がまったく同じ形になります.このとき, 図の線の意味は包含関係ですが,逆向きの包含関係になっていることに注意してください. 一般に,個体の集合が増えれば,それら共通の属性の個数は減り,また,属性の個数が 増えれば,それらを満足する個体の個数は減るのです.つまり,ガロア接続の概念束の 対応は上下の関係が逆になります.
これがガロア接続に基づく形式的概念分析のおよその内容ですが,詳しい話はこのサイトの 別ページ
ガロア接続 (Galois Connection)を読んでください.
ここでは,上のように
このプログラムは Ruby で作っています.このページの後ろの方の プログラムリスト にプログラムを掲載していますので,コピーしてファイルとして保存してください.ここでは "fca.rb" というファイルに保存したとします.
c:\ruby\exper>irb# Ruby の対話的インタープリタ irb を起動します irb(main):001:0> load "fca.rb"# ファイル "fca.rb" をロードします => true irb(main):002:0> fca($sample)# $sample のデータから概念束を作ります # 以下がその結果です ----- Concept Lattice (Objects -> properties) ----- {スズメ, トカゲ, トラ, ネズミ} {肉を食べる} {トラ, ネズミ} {肉を食べる, 哺乳する} {スズメ, ネズミ} {植物を食べる, 肉を食べる} {スズメ, トカゲ} {肉を食べる, 卵を産む} {ネズミ} {植物を食べる, 肉を食べる, 哺乳する} {トカゲ} {肉を食べる, 変温動物, 卵を産む} {スズメ} {植物を食べる, 肉を食べる, 飛ぶ, 卵を産む} {} {植物を食べる, 肉を食べる, 飛ぶ, 変温動物, 卵を産む, 哺乳する}# ここまでが概念束の出力です => [["スズメ", "トカゲ", "トラ", "ネズミ"], ["トラ", "ネズミ"], ["スズメ", "ネズミ"], ["スズメ", "トカゲ"], ["ネズミ"], ["トカゲ"], ["スズメ"], []] irb(main):003:0># 以上で終わりです
本プログラムの動かし方は上の通りです.掲載したプログラムリストの中には サンプルの表が $sample という大域変数に入っています.そのデータを
fca($sample)のように,関数 fca() に入れて呼び出すだけです.
$sample の内容は次のような連想リストです.
$sample = { "ネズミ" => ["哺乳する", "肉を食べる", "植物を食べる"], "スズメ" => ["卵を産む", "飛ぶ", "肉を食べる", "植物を食べる"], "トカゲ" => ["卵を産む", "肉を食べる", "変温動物"], "トラ" => ["哺乳する", "肉を食べる"], }
例えば,「ネズミ」が持つ属性を => の後ろにリストにして列挙してあるだけです. すべての個体のリストおよびすべての属性のリストは,この連想リストからプログラムが 集めます.
次に結果の見方を説明します.結果は次の部分です.
----- Concept Lattice (Objects -> properties) ----- {スズメ, トカゲ, トラ, ネズミ} {肉を食べる} {トラ, ネズミ} {肉を食べる, 哺乳する} {スズメ, ネズミ} {植物を食べる, 肉を食べる} {スズメ, トカゲ} {肉を食べる, 卵を産む} {ネズミ} {植物を食べる, 肉を食べる, 哺乳する} {トカゲ} {肉を食べる, 変温動物, 卵を産む} {スズメ} {植物を食べる, 肉を食べる, 飛ぶ, 卵を産む} {} {植物を食べる, 肉を食べる, 飛ぶ, 変温動物, 卵を産む, 哺乳する}
これは,次のように2行ずつ書いてあります.
個体の部分集合対応する属性の集合
例えば,
{トラ, ネズミ}というところがあると思いますが,これは個体の集合 {トラ, ネズミ} は 属性の集合 {肉を食べる, 哺乳する} に対応するということを表しています. 表示の順番は,集合の要素数の多い方から少ない方に表示しています. 属性の集合はこの逆ですね.属性の要素数の少ない方から多い方です.{肉を食べる, 哺乳する}
ここで作ったプログラムの機能はここまでです.あとは一生懸命結果をパワーポイントなどで 図示してみてください.個体の束と属性の束は同型ですから,例えば,左右に分けるのではなく, 次の図のように,1つの束にして,そのノードの上下に個体の集合と対応する属性の集合を描くのでも 良いかなと思います.
このツールは非常に単機能ですので,サンプルデータを変えてみるくらいしか 楽しみ方はありません.ということで,いくつかのサンプルでの実行結果を 載せておきます.あと,結果を図示するのは面倒ですので,プログラムの出力を ちょっとだけ読み易くするくらいでお許しください.あと,表も Web に貼るのに 自動生成したので, 属性の順番が機械任せで変なところもありますが,あまり気にしないようにしてください.
水星から木星までの惑星を適当な属性で形式的概念分析しました.使ったデータは次の通りです.
小さい | 太陽に近い | 比重大 | 円軌道 | 大きい | |
---|---|---|---|---|---|
水星 | 〇 | 〇 | 〇 | ||
金星 | 〇 | 〇 | 〇 | ||
地球 | 〇 | 〇 | 〇 | ||
火星 | 〇 | 〇 | 〇 | 〇 | |
木星 | 〇 | 〇 |
結果は次のようになりました.
全体集合{火星, 金星, 水星, 地球, 木星} {} 1つの属性に対応する惑星群{火星, 金星, 地球, 木星} {円軌道} 複数の惑星群{火星, 金星, 水星, 地球} {太陽に近い, 比重大} {火星, 金星, 地球} {円軌道, 太陽に近い, 比重大} {火星, 水星} {小さい, 太陽に近い, 比重大} 1つの惑星からなる群{木星} {円軌道, 大きい} {火星} {円軌道, 小さい, 太陽に近い, 比重大} ボトム(空集合){} {円軌道, 小さい, 太陽に近い, 大きい, 比重大}
「水星」は楕円軌道で我々から見れば独特の惑星であり,上の分析で単独の集合として 現れないのは不満がありますが,属性として「円軌道」だけ採用し,「楕円軌道」を採用しないと このようなことになります.次の次の例で惑星の形式的概念分析をもう少し大々的にやっていて, そこでは「楕円軌道」も属性として加えたので,{水星} が抽出されています.
一応,Ruby のデータを書いておきます.
$sample2b = { "水星" => ["太陽に近い", "小さい", "比重大"], "金星" => ["太陽に近い", "円軌道", "比重大"], "地球" => ["太陽に近い", "円軌道", "比重大"], "火星" => ["太陽に近い", "円軌道", "小さい", "比重大"], "木星" => ["円軌道", "大きい"], }
1から10までの数について,思いつく,「2の倍数」,「素数」などの属性で 概念分析してみます.使ったデータは次の通りです.
奇数 | 偶数 | 素数 | 3 の倍数 | べき数 | |
---|---|---|---|---|---|
1 | 〇 | ||||
2 | 〇 | 〇 | |||
3 | 〇 | 〇 | 〇 | ||
4 | 〇 | 〇 | |||
5 | 〇 | 〇 | |||
6 | 〇 | 〇 | |||
7 | 〇 | 〇 | |||
8 | 〇 | 〇 | |||
9 | 〇 | 〇 | 〇 | ||
10 | 〇 |
形式的概念分析の結果は次の通りです.
個体全体{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} {} 1つの属性で抽出された数の集合{2, 4, 6, 8, 10} {偶数} {1, 3, 5, 7, 9} {奇数} {2, 3, 5, 7} {素数} {4, 8, 9} {べき数} {3, 6, 9} {3の倍数} 2つの属性で抽出され,かつ,複数の個体の集合{3, 5, 7} {奇数, 素数} {4, 8} {べき数, 偶数} {3, 9} {3の倍数, 奇数} 1つの個体の集合{9} {3の倍数, べき数, 奇数} {6} {3の倍数, 偶数} {3} {3の倍数, 奇数, 素数} {2} {偶数, 素数} ボトム(空集合){} {3の倍数, べき数, 奇数, 偶数, 素数}
これも Ruby のデータを書いておきます.
$sample4 = { 1 =>["奇数"], 2 =>["偶数", "素数"], 3 =>["3の倍数", "奇数", "素数"], 4 =>["偶数", "べき数"], 5 =>["奇数", "素数"], 6 =>["偶数", "3の倍数"], 7 =>["奇数", "素数"], 8 =>["偶数", "べき数"], 9 =>["奇数", "3の倍数", "べき数"], 10 =>["偶数"], }
最初の例は水星から木星までの惑星の形式的概念分析でした.ここでは もう少し規模を大きくしてやってみたいと思います.
使うデータは次の通りです.
小さい | 太陽に近い | 楕円軌道 | 比重大 | 円軌道 | 中くらい | 少し遠い | 大きい | 比重小 | 輪がある | 遠い | |
---|---|---|---|---|---|---|---|---|---|---|---|
水星 | 〇 | 〇 | 〇 | 〇 | |||||||
金星 | 〇 | 〇 | 〇 | 〇 | |||||||
地球 | 〇 | 〇 | 〇 | 〇 | |||||||
火星 | 〇 | 〇 | 〇 | 〇 | |||||||
木星 | 〇 | 〇 | 〇 | 〇 | |||||||
土星 | 〇 | 〇 | 〇 | 〇 | 〇 | ||||||
天王星 | 〇 | 〇 | 〇 | 〇 | |||||||
海王星 | 〇 | 〇 | 〇 | 〇 | |||||||
冥王星 | 〇 | 〇 | 〇 | 〇 |
概念分析の結果は次の通りです.
全部の惑星の集合{火星, 海王星, 金星, 水星, 地球, 天王星, 土星, 冥王星, 木星} {} 1つの属性で規定される複数の惑星の集合(1){火星, 海王星, 金星, 地球, 天王星, 土星, 木星} {円軌道} {火星, 金星, 水星, 地球, 冥王星} {比重大} 2つ以上の属性で規定される複数の惑星の集合(1){海王星, 天王星, 土星, 木星} {円軌道, 比重小} {海王星, 金星, 地球, 天王星} {円軌道, 中くらい} {火星, 金星, 水星, 地球} {太陽に近い, 比重大} 1つの属性で規定される複数の惑星の集合(2){海王星, 天王星, 冥王星} {遠い} 2つ以上の属性で規定される複数の惑星の集合(2){火星, 水星, 冥王星} {小さい, 比重大} {火星, 金星, 地球} {円軌道, 太陽に近い, 比重大} {土星, 木星} {円軌道, 少し遠い, 大きい, 比重小} {水星, 冥王星} {小さい, 楕円軌道, 比重大} {金星, 地球} {円軌道, 太陽に近い, 中くらい, 比重大} {海王星, 天王星} {円軌道, 遠い, 中くらい, 比重小} {火星, 水星} {小さい, 太陽に近い, 比重大} 一つの惑星からなる集合{冥王星} {遠い, 小さい, 楕円軌道, 比重大} {土星} {円軌道, 少し遠い, 大きい, 比重小, 輪がある} {水星} {小さい, 太陽に近い, 楕円軌道, 比重大} {火星} {円軌道, 小さい, 太陽に近い, 比重大} ボトム(空集合){} {円軌道, 遠い, 小さい, 少し遠い, 太陽に近い, 楕円軌道, 大きい, 中くらい, 比重小, 比重大, 輪がある}
二つ前の例では,属性として,「楕円軌道」を入れていなかったので,{水星} は 抽出されていませんでしたが,ここでは「楕円軌道」を入れたので,抽出されました.
Ruby のデータは次の通りです.
$sample2 = { "水星" => ["太陽に近い", "楕円軌道", "小さい", "比重大"], "金星" => ["太陽に近い", "円軌道", "中くらい", "比重大"], "地球" => ["太陽に近い", "円軌道", "中くらい", "比重大"], "火星" => ["太陽に近い", "円軌道", "小さい", "比重大"], "木星" => ["少し遠い", "円軌道", "大きい", "比重小"], "土星" => ["少し遠い", "円軌道", "大きい", "比重小", "輪がある"], "天王星" => ["遠い", "円軌道", "中くらい", "比重小"], "海王星" => ["遠い", "円軌道", "中くらい", "比重小"], "冥王星" => ["遠い", "楕円軌道", "小さい", "比重大"], }
ここで作成したプログラムは形式的概念分析の原理を味合うのに本当に最小限のものでした. 多分,いくつかの例を自分で作ってみると次のような所が気になるのではないでしょうか?
Objects を個体の集合,Props を属性の集合とします.個体 x∈Objects に対して,x が 持つ属性の集合は f(x) で求められ,属性 y∈Prop に対して,その属性を持つ個体の集合は g(y) で求められるとします.
Objects : 個体の集合Props : 属性の集合
f : Objects → P(Props)
f(x) は個体 x∈Objects が満たす属性の集合
g : Props → P(Objects)
g(y) は属性 y∈Props を持つ個体の集合
細かなことは省きますが,slist を属性 y∈Prop に対する g(y) すべてと Objects を 合わせた集合
slist := {g(y) | y∈Prop} ∪ {Objects}とするとき,slist から取って来た有限個の集合の交わりを求めていって, もう,これ以上新しい集合ができなくなったときのすべての集合が個体の束に 現れる個体の部分集合になります.疑似的なコードで書くと次のようになります. すなわち, slist の任意の2つの集合の交わりを作って次々に slist2 に入れていき, 新たに新しい集合が出来なくなったときに slist2 を結果とする訳です.slist は各属性 y∈Props に対して,その属性を満たす個体の集合 g(y) と 全個体の集合 Objects を合わせたもの
repeat { slist2 = {} for ∀x, ∀y∈slist do { slist2 = slist2 ∪ {x∩y} } } while (slist != slist2)
下に掲載したプログラムでは,連想リスト rel で与えられた個体と属性の関係から 逆の連想リスト rev を求めているので,slist は
[rel.keys.sort] | rel.valuesで求めることができます.この式で | は集合の ∪ を表す Ruby の演算子です. sort しているのは集合としての等しさを判定しやすくするために,要素の順序を 標準化しているのです.
ここで使っている簡易形式的概念分析のプログラムを次に掲載します. 各自でコピペして使ってください.こちらでは
irb 0.9.6(09/06/30)
です.大分古いですね.
# -*- encoding: sjis # Simple Formal Concept Analysis # Ver. 0.90 2021.03.29$sample = { "ネズミ" => ["哺乳する", "肉を食べる", "植物を食べる"], "スズメ" => ["卵を産む", "飛ぶ", "肉を食べる", "植物を食べる"], "トカゲ" => ["卵を産む", "肉を食べる", "変温動物"], "トラ" => ["哺乳する", "肉を食べる"], } deffca (rel) # make the reverse relation of rel rev = {} rel.keys.each {|k| rel[k] = rel[k].sort # standardize the order of elements in set rel[k].each {|p| rev[p] = (rev[p] == nil)?[k]:([k] | rev[p]) } } rev.keys.each {|k| rev[k] = rev[k].sort # standardize the order of elements in set } # make the closure of the rel objects = fca_closure([rel.keys.sort] | rev.values) # output the concept lattice printf("----- Concept Lattice (Objects -> properties) -----\n") objects.each {|u| fca_print_set("{", u, "}\n") v = rev.keys u.each {|y| v = v & rel[y] } fca_print_set(" {", v.sort, "}\n") } end deffca_closure (slist) slist2 = slist.map {|x| x.sort} begin slist = slist2 slist.each {|x| slist.each {|y| z = (x & y).sort if !slist2.index(z) then slist2 = [z] + slist2 end } } end until slist2 - slist == [] slist.sort {|x, y| fca_compare(y, x)} end deffca_compare (x, y) if (x.length == y.length) then x.length.times {|i| if x[i] < y[i] then return -1 elsif y[i] < x[i] then return 1 end } return 0; else return x.length - y.length end end deffca_print_set (str1, x, str2) printf("%s", str1) n = x.length n.times {|i| printf("%s", x[i]) if (i != n - 1) then printf(", ") end } printf("%s", str2) end
ガロア接続に関する基礎や応用を勉強するのに有用な文献をあげておきます.
計算機(主にソフトウェア)の話題 へ
圏論を勉強しよう へ
束論を勉強しよう へ
半群論を勉強しよう へ
ホームページトップ(Top of My Homepage)