seqgan

GAN与SeqGAN的区别

  • 标准GAN 通常应用于静态数据,如图像,它们的生成器一次性生成整个输出(例如,一张图片的所有像素)。
  • SeqGAN 针对的是序列数据生成,如文本或音乐,这些数据具有时序性和顺序依赖,生成器逐步生成序列的各个元素。
  • 标准GAN 中,判别器通常以完整的生成数据作为输入,并提供一个评分,指示输入是真实数据的可能性。
  • SeqGAN 由于顺序生成数据,因此使用蒙特卡洛搜索来估计中间生成状态的奖励,并通过策略梯度方法将这些奖励反馈给生成器,帮助其学习如何生成更高质量的序列。

生成器的损失函数:

这个损失函数的目标是最小化生成器生成的样本被判别器识别为假的概率。换句话说,生成器希望“欺骗”判别器,使其认为生成的样本是真实的,从而使 logD(G(z)) 尽可能大,即最小化其负值。

蒙特卡洛搜索 

在SeqGAN中,蒙特卡洛搜索(Monte Carlo search)用于对中间序列状态进行采样,以便为这些状态估计一个奖励信号。具体来说,这是在策略梯度训练过程中使用的,因为我们需要评估不完整序列的质量,但判别器D只能对完整的序列进行评价。

因此,当生成器G在生成序列的过程中处于一个中间状态,即已经生成了序列的一部分,但序列还没有结束时,我们使用蒙特卡洛搜索来估计这个部分序列的期望奖励。以下是蒙特卡洛搜索在SeqGAN中使用的步骤:

  1. 生成后缀: 对于生成器G在时间步t生成的部分序列,使用蒙特卡洛搜索来随机地生成多个可能的后缀,这些后缀将部分序列补充为完整序列。这个过程通常是通过从生成器G当前的状态出发,按照其策略进行多步采样直到序列结束。

  2. 模拟完成序列: 对于每个部分序列,通过重复上述过程N次(N是预先定义的样本数量),我们可以获得N个完整的序列样本。这些样本被认为是从当前部分序列可能衍生出的完整序列。

  3. 评估序列: 使用判别器D来评估这些完整序列的质量。判别器会为每个完整序列提供一个分数,这个分数代表了序列被认为是真实数据的概率。

  4. 估计奖励: 对于初始部分序列,它的奖励可以通过计算所有模拟出的完整序列的评估分数的平均值来估计。这个平均值作为蒙特卡洛估计的奖励,指导生成器G如何更新其策略以生成更高质量的序列。

  5. 策略更新: 利用这个估计的奖励,结合策略梯度算法(如REINFORCE),更新生成器G的参数,使得生成的序列尽可能地获得高奖励。

通过蒙特卡洛搜索,SeqGAN解决了无法直接从不完整序列中获得足够反馈来训练生成器的问题。这种方法不需要等到序列完全生成之后才能开始训练,而是可以在生成过程中不断地对生成器进行调整和优化。

Policy Gradient 策略梯度

Policy Gradient是用来训练生成器的一种方法。Policy Gradient方法的核心思想是调整生成器的参数,使得生成的序列能够获得更高的预期奖励。

Policy Gradient的基本思想,即通过reward作为反馈,增加得到reward大的动作出现的概率,减小reward小的动作出现的概率,如果我们有了reward,就可以进行梯度训练,更新参数。

在标准的强化学习设置中,一个代理(agent)根据当前的状态采取行动,然后接收来自环境的奖励,并更新其策略以提高长期奖励的总和。SeqGAN将序列的生成过程视作一个决策过程,其中每个时间步骤产生一个新的词或符号可以被认为是一个行动

在SeqGAN的上下文中,Policy Gradient用于解决传统的GAN在处理序列数据时面临的问题。(传统GAN的问题:生成器通过对判别器的反馈进行梯度更新,但是当序列尚未完成时,判别器无法准确评价这个不完整的序列。)

具体步骤包括:

  1. 预训练: 首先对生成器G和判别器D进行预训练,以便它们能够执行基本的序列生成和判别任务。

  2. 蒙特卡洛搜索: 为了获得中间状态的奖励,SeqGAN使用蒙特卡洛搜索来为当前步骤生成后续的词语,完成序列,然后使用判别器D来评估这个完整序列的质量。

  3. 奖励信号: 生成器G生产出的每个序列都会被判别器D评估,以此来提供奖励信号。这个奖励信号表明了当前生成的序列像真实数据的程度。

  4. 策略梯度更新: 利用这些奖励信号,SeqGAN通过策略梯度方法来更新生成器G的参数。

model.py

输入层

Input 层定义了模型的输入,其形状是 (None,),这意味着它可以接收任意长度的序列。数据类型为 'int32',因为输入通常是词汇的索引。

input = Input(shape=(None,), dtype='int32', name='Input')

嵌入层

Embedding 层是一种特殊的层,其作用是将正整数(索引值)映射到一个固定大小的密集向量。

对于NLP来说,词嵌入是一种表示词汇表中单词的方法,它能够捕获单词之间的语义和句法关系。每个单词被转换为实数值的向量,这些向量构成了嵌入空间,在这个空间中,语义上或句法上相似的单词通常彼此接近。

为什么使用嵌入层?

维度缩减:将大型稀疏向量(比如one-hot编码)转换为更小的密集向量,减少了模型参数,提高了计算效率。

语义表示:嵌入能够捕获单词之间的复杂关系,比如同义词可能在嵌入空间中距离相近。

可训练性:嵌入层的权重可以在训练过程中学习,这允许模型调整嵌入以更好地适应特定的任务。

在Keras框架中,嵌入层可以通过以下方式添加到模型中:

from keras.layers import Embedding

# 假设我们有一个词汇表大小为V,我们想要嵌入大小为E
embedding_layer = Embedding(input_dim=V, output_dim=E)

在模型中使用嵌入层时,它通常是网络的第一个层,直接处理输入的整数序列(其中每个整数代表一个特定的单词),并将它们转换为密集的嵌入向量。这些向量随后可以被用于模型中的后续层,如LSTM层来处理。 

LSTM层

在LSTM(Long Short-Term Memory)网络中,“隐藏层”通常指的是包含LSTM单元的网络层。这些层被称为“隐藏”的原因是它们的状态不像输入层和输出层那样直接可见,它们在模型内部处理信息,并且其状态不直接暴露给外部。

在LSTM中,每个隐藏层都由多个LSTM单元组成,每个单元都包含以下几个重要的组成部分:

  1. 遗忘门(Forget Gate):决定哪些信息应该从细胞状态中丢弃。
  2. 输入门(Input Gate):决定哪些新的信息应该被添加到细胞状态中。
  3. 细胞状态(Cell State):作为一种内部记忆机制,长期保留网络的重要信息。
  4. 输出门(Output Gate):根据细胞状态和门的激活情况,决定最终的输出。
out = LSTM(H, return_sequences=True, name='LSTM')(out)

LSTM层的作用包括:

  1. 处理序列信息:由于文本数据通常是序列化的(例如,一句话或一段文本),LSTM可以处理这些数据,因为它能够对每个时间步长的输入进行操作,并保留有关先前时间步长的信息。LSTM是专为序列数据设计的,它能够学习序列中的长期依赖关系。在文本数据的情况下,这意味着LSTM可以理解词语之间的上下文关系。

  2. 记忆能力:LSTM通过其内部门结构(忘记门、输入门和输出门)可以长时间记忆信息,并根据当前输入和过去的状态来更新这些信息。这允许LSTM在模型中维持一个持续的状态。

  3. 捕捉长期依赖性:传统的RNNs往往难以学习长期依赖性(即,当序列非常长时,模型难以记住序列开始的信息)。LSTM通过其门控机制解决了这个问题,使其能够在必要时保持或丢弃信息。

LSTM层接收嵌入表示的序列数据(可能是文本的一部分),并为序列中的每个时间步长输出一个新的隐藏状态向量。这个隐藏状态向量可以用来预测下一个词,或者在强化学习中作为代理状态,用来决定下一个动作。

Dense全连接层

Dense(V, activation='softmax', name='DenseSoftmax') 定义了一个全连接层:

  • V 表示该层的输出大小(也是输出的维度,通常对应于分类任务中的类别数量)。
  • Dense(V, activation='softmax') 创建了一个全连接层,它的输出大小是 V,其中 V 是词汇表的大小。这意味着这个层会为每个可能的词汇生成一个概率softmax 激活函数保证了这些概率的和为 1,使得输出可以被解释为概率分布
out = TimeDistributed(
        Dense(V, activation='softmax', name='DenseSoftmax'),
        name='TimeDenseSoftmax')(out)

时间步

在序列模型和RNN中,一个“时间步”通常指的是序列中的一个元素(例如,一个单词或一个字符)在处理过程中的一个位置。让我们以句子生成为例来进一步解释时间步的概念:

  1. 假设我们想生成一个简单的句子,模型开始时可能会接收一个特殊的开始符号 <BOS>(Begin Of Sentence)来标识句子的开始。
  2. 在时间步1,模型以 <BOS> 作为输入,并预测句子中的第一个单词。
  3. 在时间步2,模型将时间步1中预测的单词作为输入,并预测句子中的第二个单词。
  4. 这个过程会持续进行,直到模型预测出一个特殊的结束符号 <EOS>(End Of Sentence)或达到某个预定的最大长度限制,这时候句子生成过程结束。

在每个时间步,RNN都会根据当前的输入(当前时间步的单词)和前一个时间步的隐藏状态来更新其隐藏状态,并预测下一个时间步的输出。因此,“下一个时间步”实际上是指在时间序列中下一个位置的处理过程,以及与这个位置相关的预测任务。

隐藏状态(Hidden State)

隐藏状态是LSTM单元的直接输出,它在每个时间步都会被计算出来,并且可以被用于进一步的处理或作为下一个时间步的输入。在序列生成任务中,如文本生成,隐藏状态常用于预测下一个字符或单词。

细胞状态(Cell State)

细胞状态是LSTM中用于长期记忆的组件。它以类似于“传送带”的方式运作,信息可以在其中以较少的变化进行长距离传输。

细胞状态的主要目的是保持长期的状态信息。通过门控机制(忘记门、输入门、输出门),LSTM可以选择性地添加或移除信息,从而在整个序列处理过程中保持和修改这些长期依赖。

学习率 

学习率(Learning Rate)是机器学习和深度学习中的一个超参数,它决定了在训练过程中我们更新模型权重的幅度。具体来说,对于模型参数 θ(权重),梯度下降的更新规则可以表示为:

如果学习率设置得太高,模型在训练过程中可能会“跳过”最佳解,导致训练不稳定或甚至发散。如果学习率设置得太低,训练过程将非常缓慢。 

现代优化算法如Adam等已经内置了自适应调整学习率的机制,可以在一定程度上减轻手动调整学习率的负担。如下图,定义了一个Adam优化器,最后调用优化器的 minimize() 方法来更新模型的权重,以最小化损失函数。

log_prob = tf.log(tf.reduce_mean(prob * action, axis=-1)) # 模型预测的动作概率分布
loss = - log_prob * reward # 损失
optimizer = tf.train.AdamOptimizer(learning_rate=self.lr) 
minimize = optimizer.minimize(loss)

梯度下降

梯度下降是一种优化算法,用于最小化一个函数。在机器学习中,该函数通常是损失函数,它衡量模型预测值与实际值之间的差异。梯度下降的目标是找到能够最小化损失函数的参数值。 

奖励 reward 

一个正的奖励(reward > 0)意味着所采取的动作对于达成任务目标是有利的。

为什么要在loss那里加负号:

loss = - log_prob * reward

在策略梯度方法中,损失函数的设计是为了最大化总奖励。然而,由于大多数深度学习框架(如TensorFlow或PyTorch)设计为执行梯度下降来最小化损失函数,而不是最大化。为了适应这种优化算法,我们将奖励最大化问题转换成损失最小化问题。

最小化损失函数意味着我们要改变模型参数,从而使得 loss 减小。在策略梯度的背景下,这意味着我们需要改变我们的策略参数,从而使得 log_prob 变得不那么负(即概率 prob 增加),因为这将导致 -log_prob 变小,进而导致整个损失函数 loss 的值减小。

def GeneratorPretraining

这个模型结构允许它学习序列数据(如文本)的复杂模式,并在每个时间步骤上生成词汇表大小的概率分布。通过预训练,生成器模型可以学习如何根据上下文生成概率上合理的下一个单词。

def sampling_word

返回值是一个 NumPy 数组 action。这个数组包含了对应于输入概率分布 prob 的每个元素(即每个批次中的项)随机采样出的索引。

每个整数代表从相应的概率分布中采样出的词(或行为)的索引。举例来说,如果你的词汇表是 ['apple', 'banana', 'cherry'],且索引分别为 0, 1, 2,那么如果某个批次项采样出了索引 1,对应的词就是 'banana'。如果 prob 数组的第一行是 [0.1, 0.7, 0.2],表示在第一个批次项中,'apple' 被采样的概率是 0.1,'banana' 是 0.7,'cherry' 是 0.2。

使用这个函数能保持文本的多样性并避免生成过于确定性的结果。

def generate_samples

在深度学习模型中,尤其是处理自然语言任务时,通常使用词汇表(vocabulary)来将单词转换成数字索引。这是因为模型不能直接处理原始文本,而需要数值型输入。

在这个 Generator 类中,sampling_sentence 方法生成一系列数字索引,每个索引代表词汇表中的一个单词。这些索引被组织成句子的形式,即一个序列的索引值。然后,generate_samples 方法接收这些索引值,并执行以下步骤:

  1. 遍历每批生成的句子的索引值。
  2. 对于每个句子,将其中的每个索引转换成对应的单词。这一步使用了 g_data.id2word 映射,即一个字典,它把每个词的索引值映射到实际的单词。
  3. 在转换索引的过程中,会忽略某些特殊的索引,比如0(通常用于padding,即填充较短的句子以达到固定长度)以及2(可能是一个特殊标记,如句子结束标记)。
  4. 最后,将每个句子转换成由单词组成的字符串,并将这些字符串写入到输出文件中,每个句子占一行。

这样,generate_samples 方法最终生成了可读的文本数据,它可以用于评估模型的生成性能,或者作为其他任务的输入数据。

utils.py

GeneratorPretrainingGenerator

类似于seqgan原模型里的Gen_Data_loader 类

Gen_Data_loader 是用于生成器的数据加载器。生成器的目标是生成尽可能接近真实数据的新数据。在序列生成的任务中(如文本生成),生成器通常需要大量的序列样本来学习如何产生合理的序列。这些样本需要被加载到内存中,通常是以批次的形式,以便生成器可以通过学习这些样本来改进它的生成策略。

Gen_Data_loader 的主要职责通常包括:

  • 读取真实的序列数据。
  • 将文本序列转换为模型可以处理的数值形式(通常是词汇索引)。
  • 组织数据成批次,以便在训练过程中输入到生成器模型中。

DiscriminatorGenerator

类似于seqgan原模型里的Dis_dataloader 类

Dis_dataloader 是用于判别器的数据加载器。判别器的目标是区分生成器生成的数据和真实数据。为了训练一个有效的判别器,它需要有真实数据的样本和生成器生成的数据样本。

Dis_dataloader 的主要职责通常包括:

  • 读取真实的数据样本和生成器生成的样本。
  • 将这两种样本标记为“真”或“假”(例如,真实数据标记为1,生成数据标记为0)。
  • 将这些带标签的数据组织成批次,以便在训练过程中输入到判别器模型中。

区别:utils.py里的两个类继承自 Keras 的 Sequence 类,可以直接与 Keras 的 fit 函数等集成,支持多线程加载和 Keras 训练循环的其他功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值