覚え書きブログ

シーケンスGAN

AAAI-17にて、Lantao YuらによるGAN(Generative Adversarial Net)のフレームワークで、強化学習のpolicy gradientを学習し、テキストや音楽などのシーケンスを生成する方法に関する論文が発表される。
SeqGAN: Sequence Generative Adversarial Nets with Policy Gradient
https://arxiv.org/pdf/1609.05473.pdf

ちなみに、GANの元論文はこちら。
https://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf


強化学習の報酬関数に、本物か偽物かを分類する識別モデルを用いている。強化学習モデルと識別モデルを対立的に学習するところがポイントである。

GANを強化学習に応用した研究には、他にも以下の論文がある。
・Adversarial Attackson Neural Network Policies
https://arxiv.org/pdf/1702.02284.pdf
・Robust Adversarial Reinforcement Learning
https://arxiv.org/pdf/1703.02702.pdf


通常のGANを、系列生成問題に適用するためには、以下の2つの課題がある。
1)離散値の系列データを扱うのが困難。生成モデルGのパラメータの更新に識別モデルDの勾配を用いているが、"微小な"勾配により更新された生成モデルGに対応する出力値が離散のため存在しない可能性があるためである。
2)score/lossは系列データ全体に対してのみ考えられていて、部分的な系列データに対しては、現在と将来のscoreのバランスを考慮しなければならないので自明でない

具体的には、 G_\theta(y_1|s_0)が初期状態s_0のもとで行動y_1を選択する条件付きを確率が強化学習モデルであり、D_\phi(Y_{1:T}^n)が、n番目の行動の系列Y_{1:T}=(y_1,...,y_T)の実写である確率を出力する識別モデルである。

この強化学習モデルのパラメータ\thetaは、初期状態s_0から開始して、終端状態までの系列で得られる報酬和の期待値を最大化するように学習する。
f:id:hirotaka_hachiya:20170314225940p:plain

ここで、Q関数Q^{G_\theta}_{D_\phi}(s_0,y_1)は、初期状態s_0で任意の行動y_1をとったあと、強化学習モデルG_\theta(y_t|Y_{1:T-1})に従って終端状態まで遷移した場合に得られるQ値(割引報酬和の期待値)である。

状態s_Tは、過去の行動の系列s_T=Y_{1:T-1}に対応していて、行動y_Tは次の状態s_{T+1}に追加されるので、状態遷移は決定的(確率1)である。そのため、行動の系列と報酬系列は1対1の関係になっている。報酬には行動の系列が本物か否かを判定する識別モデルD_\phi(Y_{1:T})を用いているので、Q値は以下のように識別モデルの出力そのものとしている。
f:id:hirotaka_hachiya:20170316225803p:plain

2)の課題を解決するために、Q値は、roll-out policyG_\betaに従って、将来の識別結果をモンテカルロサンプリング(遷移しながら系列をサンプリング)し、それらの平均により推定している。
f:id:hirotaka_hachiya:20170316230535p:plain

※roll-out policyG_\betaは、G_\thetaと異なる分布なので、上述したモンテカルロサンプル平均にはバイアスが乗るので、本論文では、G_\beta=G_\thetaとしているとのこと。毎回モンテカルロサンプルするので計算コストが大きい。一様分布で一度集めたサンプルを、重点サンプリングで使いまわすなどの工夫もできる。

識別モデルは、式(5)のloss関数を最小化することによりパラメータ\phiを更新する。
f:id:hirotaka_hachiya:20170316234105p:plain

一方、強化学習モデルは式(6)の勾配法を用いてパラメータ\thetaを更新する。
f:id:hirotaka_hachiya:20170316234251p:plain

右辺の微分は、logテクニックでG_\thetaの期待値の形になるようにしている。
f:id:hirotaka_hachiya:20170316234644p:plain

ここまでは、いたってシンプル。報酬関数を識別モデルに置き換えたpolicy gradient法であり、誰でもGANを強化学習に応用するとこうなるという感じである。

強化学習モデルにはLSTM、識別モデルにはCNNを用いている。CNNは、画像ではなく系列データを識別するために用いられている。系列データを画像のように2次元データにするために、各時間ステップごとのk次元の列ベクトル \bf{x}を、系列の長さT分並べている。つまり入力k x Tの行列としている。
イメージとしては、X. Zhang & Y. LeCunのTextUnderstandingfromScratchの図2が参考になる。f:id:hirotaka_hachiya:20170318191421p:plain
https://arxiv.org/pdf/1502.01710.pdf

Synthetic Data Experimentsでは、評価値として、クロスエントロピー -\mathcal{E}_{x\tilde p}q(x)のではなく、チューリングテストにとってより適切な -\mathcal{E}_{x\tilde q}\log p_{human}(x)を用いている。ここで、p(x)は真の分布、q(x)は近似した分布、p_{human}(x)は人間の観測者の分布である。具体的には、評価値は、次のように p_{human}(x)として、Oracleを用いている。
f:id:hirotaka_hachiya:20170319111420p:plain
つまり、 p_{human}(x)=G_{oracle}と設定している。

G_{oracle}として、正規分布に従ってランダムに設定したLSTMを用いている。学習データ\mathcal{S}としては、長さ20の系列データを10,000本用いている。

識別モデルDのCNNは、以下のようになっている。conv, relu, max_poolを、1つのconvセットとし、カーネルサイズ(フィルターサイズ)を、[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20]、フィルタ数を[100, 200, 200, 200, 200, 100, 100, 100, 100, 100, 160, 160]のように変えた12種類のconvセットを用意する。それぞれのconvセットの処理をした後に、concatしている。そして、最後にfull connectedで分類している。

dis_filter_sizes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20]
dis_num_filters = [100, 200, 200, 200, 200, 100, 100, 100, 100, 100, 160, 160]
pooled_outputs = []

for filter_size, num_filter in zip(filter_sizes, num_filters):
    with tf.name_scope("conv-maxpool-%s" % filter_size):

        # Convolution Layer
        filter_shape = [filter_size, embedding_size, 1, num_filter]
        W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name="W")
        b = tf.Variable(tf.constant(0.1, shape=[num_filter]), name="b")
        conv = tf.nn.conv2d(
            self.embedded_chars_expanded,
            W,
            strides=[1, 1, 1, 1],
            padding="VALID",
            name="conv")
        # Apply nonlinearity
        h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu")
        # Maxpooling over the outputs
        pooled = tf.nn.max_pool(
            h,
            ksize=[1, sequence_length - filter_size + 1, 1, 1],
            strides=[1, 1, 1, 1],
            padding='VALID',
            name="pool")

        pooled_outputs.append(pooled)

# Combine all the pooled features
num_filters_total = sum(num_filters)
self.h_pool = tf.concat(3, pooled_outputs)
self.h_pool_flat = tf.reshape(self.h_pool, [-1, num_filters_total])

# Add highway
with tf.name_scope("highway"):
        self.h_highway = highway(self.h_pool_flat, self.h_pool_flat.get_shape()[1], 1, 0)

 # Add dropout
with tf.name_scope("dropout"):
        self.h_drop = tf.nn.dropout(self.h_highway, self.dropout_keep_prob)



# Final (unnormalized) scores and predictions
with tf.name_scope("output"):
        W = tf.Variable(tf.truncated_normal([num_filters_total, num_classes], stddev=0.1), name="W")
        b = tf.Variable(tf.constant(0.1, shape=[num_classes]), name="b")
        l2_loss += tf.nn.l2_loss(W)
        l2_loss += tf.nn.l2_loss(b)
        self.scores = tf.nn.xw_plus_b(self.h_drop, W, b, name="scores")
        self.ypred_for_auc = tf.nn.softmax(self.scores)
        self.predictions = tf.argmax(self.scores, 1, name="predictions")

サンプルコードは、下記のgithubにて公開されている。
https://github.com/LantaoYu/SeqGAN

tensorflowのバージョンは0.12が推奨されている。ちなみに、サンプルコードをTensorflow v1.0.0で実行すると、pack, unpack, split, nn.rnn_cell._linearなどv0.12から変更した部分に関するエラーがでる。pack, unpack, splitは簡単な関数名の置き換えなどで対応が可能である。

hirotaka-hachiya.hatenablog.com

しかし、nn.rnn_cell._linearの解決方法は下記で議論されていて、3/2時点ではまだ明らかになっていない。
http://stackoverflow.com/questions/42437115/tensorflow-replacement-for-tf-nn-rnn-cell-linearinput-size-0-scope

v1.0.0をアンインストールして、v0.12に戻した。。。

> sudo pip uninstall tensorflow-gpu
> sudo pip install --upgrade https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-0.12.0-cp27-none-linux_x86_64.whl