ちょいワルQ-Networkの話


春の陽気が待ち遠しい今日この頃,皆様いかがお過ごしでしょうか.
はじめまして,休日Deep Learning勢の329と申します.

はじめに

DQNの生い立ち + Deep Q-NetworkをChainerで書いた
http://qiita.com/Ugo-Nama/items/08c6a5f6a571335972d5

東方ProjectをDeepLearningで攻略した…かった。
http://qiita.com/imenurok/items/c6aa868107091cfa509c

これらの記事に影響を受け,
「DQNってもっと面白いことできるんじゃないの」
「DQN,強化学習,Deep Learningに興味を持ってもらうためのライトな記事を書こう」
などと考え,この記事を書きました.

対象読者

  • DQNって聞いたことあるけど.詳しくは知らない(詳しく知るためのモチベが欲しい)
  • DQNの気分を味わい,知った気になりたい

以上のような方に読んで頂き,将来的に面白コンテンツが1つでも増えれば良いと思っています.
DQNの,よりDeeeepな解説は強い技術者・研究者の皆様にお願いしたいと思います.

攻略対象

この記事では,ニューラルネットワークを使って「後出しジャンケン」を攻略します.
相手が出した手を見た後に自分のジャンケンの手を決定し,勝利するDQNなAIを生み出します.

プログラムと解説

github

上記のプログラムは, 状態を入力すると,各行動の良さを出力することを目指して学習します.
後出しジャンケンで言うなら,「相手の手を見た時に,グー,チョキ,パーがそれぞれ何点くらいの価値がありそうかを計算する」ことに相当します.
勝利で1点,負けたら-1点,あいこでは0点が得られることにすると,相手の手を見た後の自分の手の価値は以下のようになります.

グーの価値 チョキの価値 パーの価値
相手がグー 0 -1 +1
相手がチョキ +1 0 -1
相手がパー -1 +1 0

# 後出しジャンケンゲームに関してはこの表があれば既に必勝ですね?

相手の手を入力した際に,対応する列を出力するモデルを学習により獲得します.
以下がニューラルネットワークに関する記述です.

class DqnModel(Chain):
    def __init__(self,num_of_state,num_of_action):
        self.num_of_state = num_of_state
        self.num_of_action = num_of_action
        super(DqnModel, self).__init__(
                state=F.EmbedID(num_of_state, 32),  
                l2=L.Linear(32, 64),
                l3=L.Linear(64, 64),
                l4=L.Linear(64, 32),
                q_action=L.Linear(32, self.num_of_action, initialW=np.zeros((self.num_of_action, 32), dtype=np.float32))
        )

    def __call__(self, state, target, train=True):
        h = self.state(state)
        h = self.l2(h)
        h = self.l3(h)
        h = self.l4(h)
        h = self.q_action(h)
        if train:
            loss = F.mean_squared_error(h, target)
            return loss
        else:
            return h

後出しジャンケンゲームでは状態が3つ,行動が3つ存在するので以下のように初期化します.

model = DqnModel(num_of_state=3,num_of_action=3)

ネットワークの出力が,教師信号に近付くように最適化問題を解きます.
以下の記述で,最適化手法を選択しています.いろいろあります.

optimizer = optimizers.Adam()
optimizer.setup(model)

以上で準備は完了です.あとはデータを用意して,

optimizer.zero_grads()
loss = model(state, target)
loss.backward()
optimizer.update()

とすると最適化問題を解いて,「後出しジャンケンゲーム」の関数近似による攻略を試みます.
githubからcloneたプログラムを実行すると,以下のような結果が得られると思います.

[[ 0.02005649 -0.00832902 -0.01909292]]
相手    :  チョキ
あとだし :  グー

やりました.
後出しジャンケンゲームを「ちょい悪 Q Network」で攻略することができました.

残された課題

私が書いたプログラムでも,「後出しジャンケン」のような小規模なゲームなら攻略することができます.
しかし,以下のような課題からこのプログラムを通常のゲームに用いることは難しそうです.

1.「状態に対する行動の良さ」の測定方法が不明である
2. 状態の番号付けが困難(TVゲームの画面状態って,いったいどのくらいあるの?)

上記の問題は.今回私がお見せしたプログラムが「なんちゃってDQN」だったことに由来しています.
つまり「本物のDQN」は,これらの問題を何らかの方法で解決しているということです.

どうです,気になってきましたか?

おわりに

読んで下さった方がもれなく「私気になります」となったなら私は小躍りでもしそうになるわけですが,
きっとそのようにはならないのだろうと予測しています.
ぜひとも不明な点や間違っている点などなどなどなど,コメントとして投稿して頂ければと思います.

Chainerはいいぞ