やること
ベイジアンネットワークは何の役に立つのか?丸一日考えてもいまいちピンと来なかったので、とりあえず使ってみることにしました。
ここではベイジアンネットワークでポケモン909匹の名前を学習し、新しいポケモン名を生成してみます。
使用したもの
ポケモンデータ
vcoptでポケモン「いろは歌」できるかな(世界初)でもお世話になったポケモンのcsvを使わせていただきます。
前処理
ノーコードでサクサクとデータサイエンスができる「Exploratory」は前処理も楽です。
ベイジアンネットワーク
こちらの記事を参考にしました。
Python環境
WinPython3.6をおすすめしています。
Google Colaboratoryが利用可能です。
Exploratoryでポケモン名を1文字ずつ分割
基本的にはポケモンたちの名前を1文字ずつ分割したいのですが、「メガ」や「:A(アローラのすがた)」といったフォルムを表す文字列は保護します。
まずは文頭の「メガ」と末尾の「:A」「化身」「霊獣」「覚悟」「特大」「完全」「10%」「50%」、その他()で囲われている「(めらめら)」等を列として抽出します。♂ や Y といった1文字のフォルムについては気にせず分割して構いません。
最終的にはこのような形になりました。空白は「*」で埋めました。
前処理後の統計情報です。
head列は「メガ」を担当しています。8文字目に到達しているポケモンは4匹しかいないようでした。
こちらに前処理済みのcsvを置いておきますのでご利用ください。
ベイジアンネットワークの構造
headとc1は自己生成し、その後は直前の2文字を条件とした確率で連鎖的に生成するようなネットワークとします。
コード
pgmpyをインストールしておきます。
pip install pgmpy
前処理済みのデータをpandasのデータフレームとして読み込みます。
import pandas as pd
from pgmpy.models import BayesianModel
from pgmpy.sampling import BayesianModelSampling
#データファイル
file = 'pokemon_status_process.csv'
#データ読み込み
data = pd.read_csv(file)
print(data)
head c1 c2 c3 c4 c5 c6 c7 c8
0 * フ シ ギ ダ ネ * * *
1 * フ シ ギ ソ ウ * * *
2 * フ シ ギ バ ナ * * *
3 メガ フ シ ギ バ ナ * * *
4 * ヒ ト カ ゲ * * * *
.. ... .. .. .. .. .. .. .. ..
904 * カ ミ ツ ル ギ * * *
905 * ア ク ジ キ ン グ * *
906 * ネ ク ロ ズ マ * * *
907 * マ ギ ア ナ * * * *
908 * マ ー シ ャ ド ー * *
[909 rows x 9 columns]
ネットワークを定義して、データを用いて学習します。学習とはCPD(確率表)を求めることです。
#ネットワークを定義
model = BayesianModel([('head','c2'),
('c1','c2'),
('c1','c3'),
('c2','c3'),
('c2','c4'),
('c3','c4'),
('c3','c5'),
('c4','c5'),
('c4','c6'),
('c5','c6'),
('c5','c7'),
('c6','c7'),
('c6','c8'),
('c7','c8')])
#データとネットワークからCPDを求める
model.fit(data)
cpds = model.get_cpds()
#headのCPDだけ確認
print(model.get_cpds('head'))
+----------+-----------+
| head(*) | 0.944994 |
+----------+-----------+
| head(メガ) | 0.0550055 |
+----------+-----------+
head要素のCPDのみ確認してみると、メガが発生する確率は5.5%のようです。c1以降のCPDも出力できますが文字が大量に出ます。
最後に、学習したベイジアンネットワークから100回サンプリングし、元のデータにはなかった新規のポケモン名だけを残して出力します。
#サンプリング
sampler = BayesianModelSampling(model)
new_data = sampler.forward_sample(size=100)
#列の並び替え
new_data = new_data[['head', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8']]
#全サンプルの出力
#print(new_data)
#データセットにある行を削除
new_data = new_data.merge(data, indicator=True, how='outer').query('_merge=="left_only"').drop('_merge', 1)
#新規分のみを出力
print(new_data)
head c1 c2 c3 c4 c5 c6 c7 c8
0 * ガ ン ト ラ ー * * *
2 * ナ ゲ キ ッ ス * * *
8 * ヤ ル キ ア * * * *
10 * モ ロ バ ン コ * * *
11 * コ ジ ョ ッ チ * * *
12 * レ ジ ロ ッ プ * * *
13 * ロ ゼ リ ア ス * * *
15 * イ ル ミ ー * * * *
16 * ユ レ イ シ ア * * *
19 * コ ラ ッ ク ス * * *
21 * ア チ ャ ブ ル * * *
24 * タ マ ゲ ロ ゲ * * *
25 * バ ン バ ル ゴ * * *
26 * ウ ォ ー グ * * * *
29 * オ ニ ゴ ー ト * * *
32 * ウ ル ガ ー * * * *
33 * コ バ ル ド * * * *
37 * ミ ノ ム ー * * * *
38 メガ ブ コ パ ト * * * *
39 メガ ゴ オ ョ タ サ O レ ガ
40 * ビ ー ク ラ ゲ * * *
41 * コ ク ー ダ * * * *
42 * ジ ー ラ ン ド * * *
43 * キ リ キ リ * * * *
44 * ギ ル ガ モ ス * * *
47 * カ リ キ ザ ン * * *
50 * エ ア ー ム * * * *
53 * フ ァ イ ア ス * * *
54 * フ シ ギ ダ マ * * *
56 * ジ ュ ゴ ン 2 * * *
57 * ミ ル タ ン * * * *
58 * ピ ジ ョ ッ チ * * *
59 * ダ ブ ラ イ * * * *
62 * ド サ イ ハ ナ * * *
63 * ミ ミ ロ ッ ト * * *
66 * ド ロ バ ッ ト * * *
68 * ツ ボ ツ ボ ミ * * *
69 * リ グ レ ー ヌ * * *
71 * メ タ グ ロ ス 化身 * *
76 * ハ ー デ ィ ア * * *
78 * ラ ム パ ル ド 剣 * *
82 メガ メ タ モ ン * * * *
83 * ネ ク ロ ー * * * *
85 * タ ネ ボ ー ス * * *
86 * バ ケ ッ チ * * * *
89 * パ ー ル ル ガ * * *
91 * ト リ ミ ア ン :A * *
94 * プ ル リ ア * * * *
99 * パ ル シ ア ン * * *
2 * ナ ゲ キ ッ ス * * * → ステキ。
54 * フ シ ギ ダ マ * * * → いそう。後述
68 * ツ ボ ツ ボ ミ * * * → 姿が想像できますね。
もう一度実行してみましょう。
head c1 c2 c3 c4 c5 c6 c7 c8
3 * エ ー フ ィ ア * * *
4 * フ ロ ー ニ ャ * * *
5 * ヤ ド ラ * * * * *
7 メガ キ パ ィ (コア) ツ コ ー ム
12 メガ ム ガ ギ (戒め) タ 夜 ド ム
13 * ヨ ル ノ ズ ク モ * *
16 * カ ラ ナ ク シ ー * *
20 * フ リ ー ジ ェ ス * *
23 * ス ト ラ イ カ * * *
24 * ス ト ラ イ カ * * *
27 * ヨ ル ノ ー ン * * *
28 * ホ ル ビ ー ル * * *
32 * ヨ ノ ワ ー ズ * * *
33 * ネ マ シ ュ プ * * *
38 * マ ギ ア * * * * *
42 * ズ ガ イ ア ス * * *
46 * ド ク ロ ッ ク * * *
53 * ル リ リ ダ マ * * *
60 * ア チ ャ ブ ル * * *
62 * ノ コ ッ チ ャ 特大 * *
69 メガ マ ク ノ シ タ * * *
72 * パ ラ ス ル * * * *
73 * ゴ ー ス タ ー :A * *
76 メガ レ ッ ク ラ ゲ * * *
77 * モ ル フ ー ン * * *
78 * モ ル フ ー ン * * *
79 メガ ポ ツ ル ミ ギ 小 * *
81 メガ ロ ウ ロ ゲ ミ (ふらふら) * *
82 * プ リ ン ク * * * *
84 * メ ェ ー ク * * * *
85 * マ イ ナ ン ス * * *
86 * コ フ ー ラ * * * *
88 メガ チ ル ッ グ ル * * *
91 * ガ バ イ ト ス * * *
92 メガ ヒ ッ ヨ ッ ウ グ ♂ *
95 メガ ゲ ン シ グ ラ ー ド ン
12 メガ ム ガ ギ (戒め) タ 夜 ド ム → どうした?
53 * ル リ リ ダ マ * * * → みず・でんき・かわいい
95 メガ ゲ ン シ グ ラ ー ド ン → ついに誕生してしまった・・・
アローラのすがたも何匹かいますね。メガ進化ポケモンはバグることが多いようです。おそらくc2の推定時、すでに生成されたメガ+c1に続く前例がなく適当に生成したのでしょう。その後、c1+c2→c3も前例がないので適当に…という悪い連鎖だと思います。最初が肝心なのでhead→c1もエッジを繋げておけばよかったですね。
考察
さて、先ほどのフシギダマについて考えます。
フシギダネとビリリダマの融合だな!と思った方がいるかと思いますが、ネットワーク構造を考慮するとその可能性は低いです。文字は直前の2文字から推定されるので、ビリリダマからはギダ→マが起きません。
そう考えると、フシギダネとクヌギダマでしょう。
まとめ
まだベイジアンネットワークの真価を感じるところまで行けていませんが遊ぶことはできました。ネット上を探していてもベイジアンネットワークの良い例題や最小問題が見つからなかったので、それなら作ってやろうと、いま考えているところです。
後編はこちらです。