美文网首页
SeqGAN: Sequence Generative Adve

SeqGAN: Sequence Generative Adve

作者: yingtaomj | 来源:发表于2018-03-07 17:50 被阅读416次

    github链接:https://github.com/LantaoYu/SeqGAN
    论文及appendix里有很好的代码说明。

    sequence_gan.py是主文件

    main函数里,首先定义了generator和discriminator结构体。

    generator里:

    是RNN生成长度为20的句子的过程(即:一行有20个数字)。LSTM结构是自己实现的,即g_recurrent_unit函数。由于输出的是hidden_state,再用g_output_unit函数转换为output_token_prob。
    placeholder里,self.x是real sentence,self.rewards是RL里的Rewards。
    self.h0里包括了LSTM的hidden_state和Ct。
    gen_xgen_o是generator生成的sentence(word id)和每个word的prob。
    接下来是一个循环,循环生成20个word。start_token和start_hidden_state是初始化好的。具体生成方法见函数_g_recurrence。依次把结果写入gen_xgen_o
    到以上部分截止,都和待输入的placeholder没什么关系,就是简单的RNN网络。


    接下来是有监督学习部分,即用MLE的思想来训练网络。和GAN没关系。用和上文循环相似的结构来生成prediction,即每个word的prob,而不是具体的word id。通过和转化成one-hot形式的placeholder里的x作对比来计算self.pretrain_loss
    再接下来是无监督学习部分,即GAN的generator部分。根据论文里的公式

    self.g_loss = -tf.reduce_sum(
        tf.reduce_sum(
            tf.one_hot(tf.to_int32(tf.reshape(self.x, [-1])), self.num_emb, 1.0, 0.0) * tf.log(
                tf.clip_by_value(tf.reshape(self.g_predictions, [-1, self.num_emb]), 1e-20, 1.0)
            ), 1) * tf.reshape(self.rewards, [-1])  # rewards是RL才有的
    )
    

    把输入的64*20个单词全变成一维,乘以对应的reward,再加和得到expected rewards。取负即为GAN的generator部分的loss。

    target_lstm里:

    结构大致和generator相同,但是它的作用是生成真实的句子,所以不存在训练过程。
    设置tf.set_random_seed(66),这就是real sentence的特征。

    discriminator里:

    待输入的placeholder是x(sentence), y([0,1] or [1,0]), keep_prob.
    其内容是标准的CNN用于text classification的代码。

    # 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, #(batch_size, sequence_length , embedding_size, 1)
        W,
        strides=[1, 1, 1, 1],
        padding="VALID",
        name="conv")
    # Apply nonlinearity
    h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu")#(batch_size, sequence_length,filter_size, num_filter )
    # 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")#(batch_size, filter_size, 1, num_filter)
    

    最后把输出转化成[-1, num_filters_total],再用linear函数输出最后的prob。


    回到主函数里。
    使用target_lstm生成real sentence,写入real_data.txt文件。

    pre-train generator:
    outputs = sess.run([self.pretrain_updates, self.pretrain_loss], 
                feed_dict={self.x: x}) # 不需要generator生成sentence
    
    pre-training discriminator:
    feed = {
        discriminator.input_x: x_batch,
        discriminator.input_y: y_batch,
        discriminator.dropout_keep_prob: dis_dropout_keep_prob
    }
    _ = sess.run(discriminator.train_op, feed)
    
    到了最关键的部分,roll-out policy,即计算reward的部分。
    先定义reward结构体

    参数设置都和generator一样,placeholder里的x也是real sentence,但是新增了placeholder given_num,也就是上文公式中的t,意指generator句子的长度为given_num
    当i<given_num的时候,不用生成sentence,直接读取:

    h_t = self.g_recurrent_unit(x_t, h_tm1)  # hidden_memory_tuple
    x_tp1 = ta_emb_x.read(i)
    gen_x = gen_x.write(i, ta_x.read(i))
    return i + 1, x_tp1, h_t, given_num, gen_x
    

    反之,还是要按基本法,依次选出单词:

    # 这里的input_x是generator生成的fake sentence
    h_t = self.g_recurrent_unit(x_t, h_tm1)  # hidden_memory_tuple
    o_t = self.g_output_unit(h_t)  # batch x vocab , logits not prob
    log_prob = tf.log(tf.nn.softmax(o_t))
    next_token = tf.cast(tf.reshape(tf.multinomial(log_prob, 1), [self.batch_size]), tf.int32)
    x_tp1 = tf.nn.embedding_lookup(self.g_embeddings, next_token)  # batch x emb_dim
    gen_x = gen_x.write(i, next_token)  # indices, batch_size
    return i + 1, x_tp1, h_t, given_num, gen_x
    

    get_reward函数中,从1到20尝试given_num,计算expected reward。

    for given_num in range(1, 20):  # 最后只到19
        feed = {self.x: input_x, self.given_num: given_num}# 这里需要输入input_x才能生成gen_x,因为gen_x的前一部分是固定好的
        samples = sess.run(self.gen_x, feed)
        feed = {discriminator.input_x: samples, discriminator.dropout_keep_prob: 1.0}
        ypred_for_auc = sess.run(discriminator.ypred_for_auc, feed)
        ypred = np.array([item[1] for item in ypred_for_auc])# 得到每个句子的分值
        if i == 0:
            rewards.append(ypred)
        else:
            rewards[given_num - 1] += ypred  # 把所有得分加在一起
    
    # the last token reward
    feed = {discriminator.input_x: input_x, discriminator.dropout_keep_prob: 1.0}#最后一个word就不用自己run生成了,直接读取input_x
    ypred_for_auc = sess.run(discriminator.ypred_for_auc, feed)
    ypred = np.array([item[1] for item in ypred_for_auc])
    if i == 0:
        rewards.append(ypred)
    else:
        rewards[19] += ypred
    

    这样,就得到了对于任意长度的句子的rewards。

    就是对于已经生成的不同长度的sentence,扩展到完整的句子,再用discriminator来打分。

    再根据形如

     self.Wi = self.update_rate * self.Wi + (1 - self.update_rate) * tf.identity(self.lstm.Wi)
    

    的公式更新roll-out里的参数。
    discriminator还是根据相同的公式来训练。

    相关文章

      网友评论

          本文标题:SeqGAN: Sequence Generative Adve

          本文链接:https://www.haomeiwen.com/subject/qlajfftx.html