前々回、前回、webに載っていた「AIじゃんけん」というCのプログラムを、解析を兼ねてC++で書いてみました。
結局、perceptron関数の腑に落ちる理解には至りませんでしたが、実際に動かしてみて、自分なりに考えてみることにしました。
1.まずはサンプルデータから
このプログラムでランダムに戦ってみます。履歴が5回たまらないと「完全体」にはならないので、最初の5回のデータは考えません。
<最初の5回>
1<->2で、あなたの勝ち
2<->2で、引き分け
2<->2で、引き分け
2<->2で、引き分け
2<->1で、マシンの勝ち
ここで言えるのは、hand_queデータとweightデータが蓄積されるまでは余りプログラムの手が変化しないように思えることです。
2.「完全体」になってからの勝負データとその根拠データ
では、次にまた5回戦を戦ってみましょう。
<次の五回>
3<->1で、あなたの勝ち
3<->2で、マシンの勝ち
1<->2で、あなたの勝ち
2<->1で、マシンの勝ち
3<->1で、あなたの勝ち
こんな感じで、この対戦の背景となるデータは次のようになっています。
<次の五回のデータ>
(2<->1で、マシンの勝ち)
(解説:マシンが勝ったのでweightが修正されなかったと思ってしまいました。)
expectを0で初期化完了後、
expect[0]に次のweight x hand_que 値を加算: 1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
expect[1]に次のweight x hand_que 値を加算: 1, 1, 3, 1, -1, 1, 0, -2, 0, 1, -1, -1, 0, 0, 0, 2
expect[2]に次のweight x hand_que 値を加算: -2, 0, -2, -1, 1, -1, 0, 2, 0, -1, 1, 1, 0, 0, 0, -3
expected[0]の値:-1
expected[1]の値:5
expected[2]の値:-5
3<->1で、あなたの勝ち
予想が外れ、修正された1ブロックのweight: 0, 0, -2, 0, -2, 0, 1, -3, 1, 0, -2, 2, -1, 1, 1, -1
予想が外れ、修正された2ブロックのweight: 1, 1, 1, 0, 2, 0, -1, 3, -1, 0, 2, -2, 1, -1, -1, 2
expectを0で初期化完了後、
expect[0]に次のweight x hand_que 値を加算: 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
expect[1]に次のweight x hand_que 値を加算: 0, 0, -2, 0, -2, 0, -1, -3, -1, 0, -2, -2, 1, 1, -1, 1
expect[2]に次のweight x hand_que 値を加算: -1, -1, 1, 0, 2, 0, 1, 3, 1, 0, 2, 2, -1, -1, 1, -2
expected[0]の値:3
expected[1]の値:-11
expected[2]の値:7
3<->2で、マシンの勝ち
予想が外れ、修正された0ブロックのweight: 0, 0, 0, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1
(解説:「ん?」マシンが勝ったのにweightを修正しているぞ。それも一つだけ?)
expectを0で初期化完了後、
expect[0]に次のweight x hand_que 値を加算: 0, 0, 0, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
expect[1]に次のweight x hand_que 値を加算: 0, 0, -2, 0, 2, 0, -1, -3, -1, 0, -2, -2, 1, 1, -1, 1
expect[2]に次のweight x hand_que 値を加算: -1, -1, 1, 0, -2, 0, 1, 3, 1, 0, 2, 2, -1, -1, 1, -2
expected[0]の値:-9
expected[1]の値:-7
expected[2]の値:3
1<->2で、あなたの勝ち
予想が外れ、修正された0ブロックのweight: -1, -1, 1, 0, -2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
予想が外れ、修正された2ブロックのweight: 2, 2, 0, 1, 3, -1, 0, 2, 0, 1, 1, -1, 2, -2, 0, 3
expectを0で初期化完了後、
expect[0]に次のweight x hand_que 値を加算: -1, 1, -1, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
expect[1]に次のweight x hand_que 値を加算: 0, 0, 2, 0, 2, 0, -1, 3, 1, 0, -2, -2, 1, 1, -1, 1
expect[2]に次のweight x hand_que 値を加算: 2, -2, 0, -1, -3, -1, 0, -2, 0, -1, 1, 1, -2, -2, 0, -3
expected[0]の値:3
expected[1]の値:5
expected[2]の値:-13
2<->1で、マシンの勝ち
予想が外れ、修正された0ブロックのweight: -2, 0, 2, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1
(解説:矢張りマシンが勝ってもweightを修正しています。)
expectを0で初期化完了後、
expect[0]に次のweight x hand_que 値を加算: 2, 0, -2, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1
expect[1]に次のweight x hand_que 値を加算: 0, 0, 2, 0, 2, 0, -1, 3, 1, 0, 2, 2, 1, 1, -1, 1
expect[2]に次のweight x hand_que 値を加算: -2, 2, 0, 1, -3, 1, 0, -2, 0, -1, -1, -1, -2, -2, 0, -3
expected[0]の値:-5
expected[1]の値:13(解説:この程度の数に発展してゆきます。)
expected[2]の値:-13(解説:Ditto)
3<->1で、あなたの勝ち
3.結果を眺めてみて
最初は(思い込みから)「予想手と現実の手が異なる場合、学習する」ものだと考えていましたが、内部モニターで確認すると「(手のIDである1~3が)同じ手でも、予想手(↑の様に絶対値が大きくなるexpected)と現実の手(-1と+1しかないactual)が異なる場合、(weightを修正して)学習する」ことに変わりはないことが判り、結局
(1)最初から想定していたように「予想手」「現実の手」「重さ」を入力し、「予想手」を出力する、という(パーセプトロンになっている)ことは間違いなさそうですが、
(2)何をどのように評価して、どのように処理するのか、は依然不明
という結論は変わりありませんでした。
4.原点に返って考えてみる
「じゃんけん」は典型的なツーサムのゼロサムゲームです。1回の勝負で勝つ確率は3つの手の数それぞれについて相手の手が1/3で、手が3つある為、1/ (3 x 3) x 3 = 1/3で、同じく1/3の確率で発生する引き分けを考慮すると(1/3 + 1/3x1/3 + 1/3x1/3x1/3 ... + (1/3)^n)、勝つ確率は (n=1→∞)Σ(1/3)^nで1/2に収束してゆきます。しかしこれは飽くまで数学的コンテキストの話で、人間的コンテキスト(即ち自分がじゃんけんをする際に何を考えて意思決定しているか)ではそうではなさそうです。では、じゃんけんをする際に何が意思決定(関数)に影響を与える(引数)のでしょうか?
(1)最初は相手の手の予想に関わる情報が無く、根拠のない勘、ゲン担ぎや手の好みになるのでしょうか?
(2)次に明らかに関連する影響要因となるものは「前回(または過去)出した自分の手」「前回(または過去)出した相手の手」「前回(または過去)出した双方の手の結果」になるのでしょう。
(3)更に(引き分けを含み)「相手の次の手を予想する」ことが自分の手の決定に大きな影響を与えることでしょう。
(4)ここまでは良いのですが、これらのデータをどう処理するかで、大昔小中学生辺りで考えたループに嵌ります。
①前回自分の(または相手の)出した手を変えてくるか、否か、
②変えるとすれば、残りの二手のいずれか。
③相手もこちらの出し手を読むので、それに勝つ手を選ぶ筈。
④自分と相手はミラーの様に対峙しているので、同じように意思決定するかもしれない。
⑤なので、その読みをもうひとつ深読みしなければならない。
例えば「グー」であいこになった場合、①を検討し、変えてくると想定すると「チョキかパー」で勝つには「グーかチョキ」。変えないと想定すると勝つには「パー」、相手がそう考えるならば、もぅひと読みしてそれに勝つ「チョキ」。更に相手がそう考えるならば、もぅひと読みしてそれに勝つ「グー」... so on and so forth.
(5)ということで、想定を深くして行くほどに3つの選択肢はぐるぐると廻り、堂々巡りに陥ってしまいます。従って、考えられるのは「相手個体(人間)が過去に同様、類似の状況にあった時にどう行動したか」の履歴記録を求め、そのデータに偏差があればそれをどう修正するか、というデータだけから判定するという考え方を取るしかないでしょう。
そんなことを考えていたら、このような面白い記事を見つけました。今はやりの機械学習させたらどうなるか、というテーマです。(最後のサンプルはPythonで書かれていますね。)
結論(「機械学習でやる意味はない?」)は頷けますし、その場合は相手プレーヤーの個性に応じたデータの蓄積が必要かと思いますが、その場合でも上記4.の問題はあり、結局
「たかがじゃんけんでそこまでやる?」
という恐ろしい結末が待っていました。そんなことで急激にテンションが下がり、意欲が減退し、この「無駄話」は終わりを迎えるのでした。(最初に「高度に戦略的な」などと書いたことを、自己矛盾を、恥じ入っております。)