语言模型教程

栏目: 数据库 · 发布时间: 6年前

内容简介:本文介绍传统的N-Gram语言模型和RNN语言模型。我们来看语言模型的定义。首先我们需要定义一个词典$\mathcal{V}$,比如对于英语,它的词典可能是:通常$\mathcal{V}$都会比较大,几千甚至几万,但不管怎样通常都是有限的一个集合。接下来我们定义句子,一个句子就是词的序列:$x_1x_2…x_n$,其中$n \le 1$,对于$i \in {1…(n-1)}$,$x_i \in \mathcal{V}$。同时我们假设$x_n$总是等于一个特殊符号STOP。我们后面会解释为什么句子的最后一个词

本文介绍传统的N-Gram语言模型和RNN语言模型。

目录

简介

我们来看语言模型的定义。首先我们需要定义一个词典$\mathcal{V}$,比如对于英语,它的词典可能是:

通常$\mathcal{V}$都会比较大,几千甚至几万,但不管怎样通常都是有限的一个集合。接下来我们定义句子,一个句子就是词的序列:$x_1x_2…x_n$,其中$n \le 1$,对于$i \in {1…(n-1)}$,$x_i \in \mathcal{V}$。同时我们假设$x_n$总是等于一个特殊符号STOP。我们后面会解释为什么句子的最后一个词总是STOP的原因。下面是一些句子的示例:

  • the dog barks STOP
  • the STOP
  • STOP

我们用符号$\mathcal{V}^{+}$来表示由词典$\mathcal{V}$产生的所有句子,这是一个无限的集合,虽然词典的元素是有限的,但是句子的长度是可以无限的。下面是语言模型的定义。语言模型由一个有限集合$\mathcal{V}$和一个函数$p(x_1, x_2, …, x_n)$,它满足如下条件:

  • 对于任意的$<x_1, x_2, …, x_n> \in \mathcal{V}^{+}, p(x_1, x_2, …, x_n) \le 0$
  • $\sum_{<x_1, x_2, …, x_n> \in \mathcal{V}^{+}}p(x_1, x_2, …, x_n)=1$

因此$p(x_1, x_2, …, x_n)$是$\mathcal{V}$上的所有句子的概率分布。

一种最简单(但很差)的语言模型可以这样定义:$p(x_1, x_2, …, x_n)=\frac{c(x_1, x_2, …, x_n)}{N}$,其中$c(x_1, x_2, …, x_n)$是训练数据中句子$x_1x_2…x_n$出现的次数,而N是训练数据里句子总数。这个模型很不好,因为如果某个句子没有在训练数据里出现过(即使训练数据里有和它很像但不完全一样的句子),那么它的概率就是0。比如前面的示例,$p(the\;dog \; barks\; STOP)=\frac{1}{3}$,但是$p(a\; dog\; barks\; STOP)$的概率就是零,这显然是有问题的。

马尔科夫模型

现在我们的问题是怎么根据训练数据集学习出函数p。我们这里介绍使用马尔科夫模型来表示和学习概率分布p。

定长序列的马尔科夫模型

我们首先考虑固定长度的句子的语言模型。假设句子的长度是n,序列为$X_1, X_2, …, X_n$,其中$X_i \in \mathcal{V}$。对于这个序列的每一种取值$x_1…x_n$,我们需要计算$P(X_1=x_1, X_2=x_2, …, X_n=x_n)$,所有可能的取值组合数为$|\mathcal{V}|^{n}$。如果我们想列举所有的可能,这是很难做到的,我们需要更加紧凑的表示方法。在一阶马尔科夫模型里,我们假设每个词的概率只依赖与它之前的词,而与其它词无关:

上式的第一步使用链式法则,而第二步使用了一阶马尔科夫假设——第i个词只依赖前一个词。我们也可以做二阶的假设——i时刻的词依赖与i-1和i-2时刻:

这里为了把$X_1$和$X_2$也写成条件概率的形式,假设$X_{-1}$和$X_{0}$等于特殊单词*,它表示句子的开始。

变长序列的马尔科夫模型

前面的例子假设句子的长度为固定的n,如果使用最大似然估计,我们如果把所有长度为n的句子的概率加起来是1。这样长度不为n的句子就没有概率了。那怎么建模所有长度的句子呢?这里有一个简单的方法——使用一个特殊的STOP符号。

我们用“生成”一个句子来看这个STOP的作用,以一阶的模型为例。我们首先生成第一个词$p(X_1=x_1 \vert X_0=*)$,这里$X_1 \in \mathcal{V} \cup {STOP}$。因此长度为1的句子的概率为$1-p(STOP \vert *)$,然后我们再根据第一个词生成第二个词$p(X_2=x_ \vert X_1=x_1)$,…,这样所有长度的句子都有一定的概率。

在这里,我们假设一个句子总是以STOP结尾,因此对于二阶的马尔科夫假设,我们有:

上式中$X_n$一定是STOP。

Trigram语言模型

Trigram语言模型包括一个词典集合$\mathcal{V}$和参数$q(w \vert u,v)$,其中

$q(w \vert u, v)$可以认为是看到bigram(u,v)之后出现w的概率。Trigram可以计算一个句子$x_1,x_2,…,x_n(=STOP)$的概率:

其中$x_{-1}=x_0=*$。比如句子”the dog barks STOP”的概率可以这样计算:

为了满足概率的定义,我们要求对于任意的u,v,w,满足$q(w|u,v) \le 0$,并且对于任意的u,v,满足:

接下来需要解决的关键问题是怎么通过训练数据估计出$q(w|u,v)$,假设$\vert \mathcal{V} \vert =10,000$,那么$q(w|u,v)$的可能取值是$\vert \mathcal{V} \vert ^3=10^{12}$,虽然实际在训练数据中很多没出现,但是这个值也是非常大的。

我们可以用最简单的最大似然估计方法来估计trigram的参数。我们首先定义c(u,v,w)为trigram (u, v, w)在训练集上出现的次数。类的c(u,v)为bigram (u,v)在训练集上出现的次数。那么$q(w \vert u,v)$的最大似然估计为:$\frac{c(u,v,w)}{c(u,v)}$。比如我们要估计$q(barks \vert the, dog)$,那么它的计算公式为:

它的意义很简单:我们要估计barks出现在(the,dog)后的概率,我们数一数(the,dog)出现了多少次,然后再数数(the, dog, barks)出现了多少次,两个数一除就计算出来了。但是最大似然估计有一个问题,它的分子很可能为零。一个trigram在训练数里不出现不代表它的概率就为零。此外分母也可能为零,那么这个概率就不是合法的定义(well-defined)。

语言模型的平滑

有很多平滑的方法,我们这里介绍discount的方法。为了简单我们用bigram为例子来说明其原理。它的想法很简单,除了训练集中出现的bigram,还有很多合理的bigram并没有在训练集合里出现,因此如果我们使用最大似然估计,我们会“高估”$p(w|v)=\frac{c(v,w)}{c(v)}$。discount的思路是给c(v,w)打个折扣,把多出来的概率分给没有出现过的bigram。如果c(v,w)>0(说明(v,w)在训练集上出现过),那么我们计算

,其中$\beta$是一个0-1之间的数。我们用这个打折后的

来计算$p(w|v)$:

原来我们有$\sum_wp(w|v)=\sum_w \frac{c(v,w)}{c(v)}=\frac{c(v)}{(c(v))}=1$,现在打折后$\sum_wp(w|v)<1$,多余的概率为:

这些概率可以分配给没有在训练数据中出现的(v,w’),那怎么分配呢?最简单的是平均分配,但是不同的p(w’|v)$是不同的。一个简单的想法就是如果w’出现的概率大,那么$p(w’|v)$也可能大。因此对于那些c(v,w’)=0的w’来说,我们可以根据

来分配$\alpha(v)$。为了形式化的用数学公式来描述,我们首先定义如下两个集合:

根据c(v,w)是否出现过把w划分为了两个集合,对于这两个集合中的w,使用不同的方法来计算$p(w|v)$。

对于trigram,我们可以使用类似的discount策略。对trigram c(u,v,w)进行打折,留一些概率给没有出现的(u,v,w’)。这些概率的时候可以用前面我们估计的$q_D(w’|v)$来分配。打折后$c^{*}(u,v,w)=c(u,v,w)-\beta$。和前面类似,我们根据(u,v,w)是否出现分成如下两个集合:

打折后剩余的概率

语言模型的评价标准

语言模型通常使用perplexity(PPL)来评价,PPL的计算公式如下:

其中m是句子的个数,M是语料库中单词的个数$M=\sum_{i=1}^{m}n_i$。$p(x_i)$是语言模型输出的这个句子的概率。有时候我们的模型输出是自然对数为底的概率,则我们可以这样计算:

这两者是相等的,有兴趣的读者可以自己证明一下。

RNN语言模型

简介

n-gram语言模型问题之一是不能利用长距离的信息,在实际的场景中,由于模型参数过多,5-gram基本就是极限了。另外一个问题就是它的泛化能力差,因为它完全基于词的共现。比如训练数据中有”我 在 北京”,但是没有”我 在 上海”,那么$p(上海|在)$的概率就会比$p(北京|在)$小很多。但是我们人能知道”上海”和”北京”有很多相似的地方,作为一个地名,都可以出现在”在”的后面。

因此我们可以用word embedding的技术来解决第二个问题,而第一个问题可以使用RNN(LSTM/GRU)模型来解决。如所示,给定一个句子”what is the problem”,我们可以构造训练数据——输入”what is the”和输出序列”is the problem”。然后使用RNN根据之前的词来预测当前词。RNN/LSTM/GRU我们都介绍过了,因此下面通过代码来了解RNN语言模型的实现

语言模型教程 图:RNN语言模型

代码

完整代码在 这里 目录下。

reader.py

我们首先看语言模型的训练数据是什么格式以及怎么读取的。原始的数据可以在http://www.fit.vutbr.cz/\%7Eimikolov/rnnlm/simple-examples.tgz下载。下载解压后数据在data目录下,这个目录包含如下文件:

~/data/simple-examples/data$ ls -1
ptb.char.test.txt
ptb.char.train.txt
ptb.char.valid.txt
ptb.test.txt
ptb.train.txt
ptb.valid.txt
README

我们用到的训练数据,验证数据和测试数据分别是ptb.train.txt、ptb.valid.txt和ptb.test.txt。另外3个文件是训练char的语言模型用的,我们暂时用不到。这些文件的格式都是一行一个句子的文本文件。比如ptb.train.txt的前3行:

1  aer banknote berlitz calloway centrust cluett fromstein gitano guterman hydro-quebec ipo kia memotec mlx nahb punts rake regatta rubens sim snack-food ssangyong swapo wachter
2  pierre <unk> N years old will join the board as a nonexecutive director nov. N
3  mr. <unk> is chairman of <unk> n.v. the dutch publishing group

这里有一个特殊的词 ,用来表示未登录词,通常的方法是把训练数据中频率低于某个值的都替换成 ,而在只在测试数据中出现没有在训练数据中出现的词也都替换成

我们首先来看ptb_raw_data函数,它的输入是data目录,输出是3个list,每个list里都是整数,表示词的id,其中换行会被替换成特殊的词

def ptb_raw_data(data_path=None):
	train_path = os.path.join(data_path, "ptb.train.txt")
	valid_path = os.path.join(data_path, "ptb.valid.txt")
	test_path = os.path.join(data_path, "ptb.test.txt")
	
	word_to_id = _build_vocab(train_path)
	train_data = _file_to_word_ids(train_path, word_to_id)
	valid_data = _file_to_word_ids(valid_path, word_to_id)
	test_data = _file_to_word_ids(test_path, word_to_id)
	vocabulary = len(word_to_id)
	return train_data, valid_data, test_data, vocabulary

接下来的ptb_producer函数会用上一个函数返回的list生成一个iterator。这个iterator每次返回一个[batch_size, num_steps]的数据。

def ptb_producer(raw_data, batch_size, num_steps, name=None):
"""PTB data上的iterator

参数:
	raw_data: 来自ptb_raw_data函数
	batch_size: batch size.
	num_steps: 训练时的句子长度
	name: 名字

返回:
	返回两个tensor,每个的shape是[batch_size, num_steps]。比如句子是 it is a good day。那么第一个tensor是"it is a good";第二个是"is a good day"。
"""
with tf.name_scope(name, "PTBProducer", [raw_data, batch_size, num_steps]):
	raw_data = tf.convert_to_tensor(raw_data, name="raw_data", dtype=tf.int32)
	
	data_len = tf.size(raw_data)
	batch_len = data_len // batch_size
	data = tf.reshape(raw_data[0 : batch_size * batch_len],
		[batch_size, batch_len])
	
	epoch_size = (batch_len - 1) // num_steps
	assertion = tf.assert_positive(
	epoch_size,
	message="epoch_size == 0, decrease batch_size or num_steps")
	with tf.control_dependencies([assertion]):
		epoch_size = tf.identity(epoch_size, name="epoch_size")
	
	i = tf.train.range_input_producer(epoch_size, shuffle=False).dequeue()
	x = tf.strided_slice(data, [0, i * num_steps],
	[batch_size, (i + 1) * num_steps])
	x.set_shape([batch_size, num_steps])
	y = tf.strided_slice(data, [0, i * num_steps + 1],
	[batch_size, (i + 1) * num_steps + 1])
	y.set_shape([batch_size, num_steps])
	return x, y
it is a good day <eos> I am <unk> of that <eos> .....       it is funny <eos>

假设文本总共有203个词(跟多少个句子没有关系),batch_size=4。那么data_len=50,也就是把203个词切分成4段,每段50个(连续的)词。data就是4*50=200个词,然后reshape成[batch_size(4), data_len(50)]:

data = tf.reshape(raw_data[0 : batch_size * batch_len],
	[batch_size, batch_len])

如果LSTM的长度是num_steps(10),那么50个单词可以产生(50-1)//10=4个batch的数据。为什么要减一呢?因为我们的预测数据需要把输入数据后移一个时刻。比如句子是”It is a good day”,那么输入是”It is a good”,输出是”is a good day”。

epoch_size = (batch_len - 1) // num_steps

理解了这个,后面的代码就很容易了。我们产生一个大小是epoch_size(4)的range_input_producer,然后用strided_slice得到对于的x和y。

i = tf.train.range_input_producer(epoch_size, shuffle=False).dequeue()
x = tf.strided_slice(data, [0, i * num_steps],
	[batch_size, (i + 1) * num_steps])
x.set_shape([batch_size, num_steps])
y = tf.strided_slice(data, [0, i * num_steps + 1],
	[batch_size, (i + 1) * num_steps + 1])
y.set_shape([batch_size, num_steps])
    return x, y

在产生第i个batch的时候,我们给strided_slice的参数是[0, i * num_steps], [batch_size, (i + 1) * num_steps],它截取的起点是第0行和第(i * num_steps)列,终点(不包含)是第batch_size行和第(i+1) * num_steps列。而y的列下标是x加1。

从代码我们可以看出,训练语言模型是并不是以“句子”为基本单位,而是把固定长度的num_steps个词作为一次训练。句子之间会有 表示结束,因此RNN应该学到当遇到 的时候就要开始一个新的句子,这个时候它(可能)会清空RNN的状态。在遍历完一次数据之前,RNN的状态会保留下来作为下一次的输入,因此除了第一次之外,RNN的初始状态都不是零。

定义模型

模型的代码主要在PTBModel这个类的构造函数中定义,下面是部分重要代码:

with tf.device("/cpu:0"):
	    embedding = tf.get_variable(
		    "embedding", [vocab_size, size], dtype=data_type())
	    inputs = tf.nn.embedding_lookup(embedding, input_.input_data)
    
    if is_training and config.keep_prob < 1:
	    inputs = tf.nn.dropout(inputs, config.keep_prob)
    
    output, state = self._build_rnn_graph(inputs, config, is_training)
    
    softmax_w = tf.get_variable(
	    "softmax_w", [size, vocab_size], dtype=data_type())
    softmax_b = tf.get_variable("softmax_b", [vocab_size], dtype=data_type())
    logits = tf.nn.xw_plus_b(output, softmax_w, softmax_b)
    # 从[batch*time, vocab_size]reshpae成[self.batch_size, self.num_steps, vocab_size]
    logits = tf.reshape(logits, [self.batch_size, self.num_steps, vocab_size])
    
    # 计算loss
    loss = tf.contrib.seq2seq.sequence_loss(
	    logits,
	    input_.targets,
	    tf.ones([self.batch_size, self.num_steps], dtype=data_type()),
	    average_across_timesteps=False,
	    average_across_batch=True)
    
    self._cost = tf.reduce_sum(loss)
    self._final_state = state

代码首先定义embedding矩阵[vocab_size, size],然后对输入使用tf.nn.embedding_lookup函数进行embedding。输入是[batch_size, num_steps],每个元素是一个整数,代表词对于的id;输出是[batch_size, num_steps, size]。

如果是训练阶段并且keep_prob<1的话,就会对inputs再加一层dropout。然后使用_build_rnn_graph函数构造RNN,这个函数返回两个值,一个是output,一个是state。这两个值分别表示RNN的输出和最后一个时刻的隐状态,其中output的shape是[batch_size*num_steps, hidden_size(200)]。而state是一个tuple,分别表示c和h,它们的shape都是[num_layers, batch_size, hidden_size]。

接着我们定义softmax_w和softmax_b,把输出从hidden_size变成vocab_size,这就得到了logits,它的shape是[batch_size*num_steps, vocab_size],然后我们再把logits reshape成[batch_size, num_steps, vocab_size]。最后我们通过tf.contrib.seq2seq.sequence_loss来计算loss,这个函数的参数为:

  • logits shape是[batch_size, sequence_length, num_decoder_symbols],dtype是float
  • targets 真实的label序列,shape是[batch_size, sequence_length],dtype要求是int
  • weights 计算loss时label的权重,如果要实现padding,那么可以把padding的地方设置为零。shape是[batch_size, sequence_length],dtype是float
  • average_across_timesteps 如果是True,那么返回的loss会对timestep这个维度进行求平均值
  • average_across_batch 如果是True,返回的loss会对batch这个维度求平均值
  • softmax_loss_function 如果None,使用默认的softmax函数,调用的时候也可以自己提供以softmax函数
  • name

前两个参数很容易理解,第3个参数一般用来实现mask的功能。对于很多序列问题,一个batch的数据每个训练样本的num_steps是不一样的,我们通常会用一个最大的长度来padding,但是在计算loss的时候,这些padding的地方不应该算到loss里,这个时候我们可以使用weights参数,把padding的地方设置成零,而没有padding的地方设置成1。average_across_timesteps设置是否对loss进行时间维度的平均,而average_across_batch设置是否对batch维度进行平均。

我们这里调用时weights就是一个shape和targets一样的全为1的tensor,因为我们这里是没有padding的。这里我们调研时average_across_timesteps是False,而average_across_batch是True,因此它之后对batch求平均。为什么不对时间求平均呢?因为我们需要把所有时间维度的都加起来然后再求平均,所有这个求平均放在后面的run_epoch函数里:

for step in range(model.input.epoch_size):
	  # 省略了feed_dict的构建
	  vals = session.run(fetches, feed_dict)
	  cost = vals["cost"] 
	  
	  costs += cost
	  iters += model.input.num_steps
	  ppl = np.exp(costs / iters)

对训练数据的一次遍历需要epoch_size次,每次访问num_steps个词,因此对于时间维度的平均放到最后ppl的计算上。接下来我们介绍最关键的函数_build_rnn_graph:

def _build_rnn_graph(self, inputs, config, is_training):
	  if config.rnn_mode == CUDNN:
		  return self._build_rnn_graph_cudnn(inputs, config, is_training)
	  else:
		  return self._build_rnn_graph_lstm(inputs, config, is_training)

它会根据配置config的rnn_mode是否是CUDNN来决定使用CudnnLSTM还是普通的LSTM(也可以通过配置选择BasicLSTMCell或者LSTMBlockCell)。如果有GPU可以在运行的时候加上”–rnn_mode=cudnn”。我们先看_build_rnn_graph_lstm函数:

def _build_rnn_graph_lstm(self, inputs, config, is_training):

	  def make_cell():
		  cell = self._get_lstm_cell(config, is_training)
		  if is_training and config.keep_prob < 1:
			  cell = tf.contrib.rnn.DropoutWrapper(
					  cell, output_keep_prob=config.keep_prob)
		  return cell
	  
	  cell = tf.contrib.rnn.MultiRNNCell(
		  [make_cell() for _ in range(config.num_layers)], state_is_tuple=True)
	  
	  self._initial_state = cell.zero_state(config.batch_size, data_type())
	  state = self._initial_state
	  
	  # 手动unroll来实现tf.nn.static_rnn()。
	  # 这里手动实现unroll的目的是为了展示怎么手动实现。
	  # 实际使用是应该用tf.nn.static_rnn()或者tf.nn.static_state_saving_rnn().
	  #
	  # 实际我们应该使用如下代码: 
	  #
	  # inputs = tf.unstack(inputs, num=self.num_steps, axis=1)
	  # outputs, state = tf.nn.static_rnn(cell, inputs,
	  #                                   initial_state=self._initial_state)
	  outputs = []
	  with tf.variable_scope("RNN"):
		  for time_step in range(self.num_steps):
			  if time_step > 0: tf.get_variable_scope().reuse_variables()
			  (cell_output, state) = cell(inputs[:, time_step, :], state)
			  outputs.append(cell_output)
			  output = tf.reshape(tf.concat(outputs, 1), [-1, config.hidden_size])
	  return output, state

这个函数首先定义一个函数make_cell,它会调用self._get_lstm_cell来根据不同的配置生成不同的RNNCell。然后根据是否有dropout来使用DropoutWrapper对RNNCell进行wrapping。这个在Tensorflow的章节都有详细介绍,忘记了的话可以参考相关内容。

然后用MultiRNNCell来生成多层的RNN。接着用cell.zero_state来生成初始化状态。使用RNNCell.zero_state而不是自己构造初始状态的好处是我们不需要关心RNNCell到底是LSTM还是GRU,到底是单向的还是双向的,到底有几层。这些参数RNNCell都知道,它自己会帮助我们计算,我们只需要告诉它batch大小(因为它不知道batch大小)和dtype就行。然后我们就可以使用static_rnn来构造静态unroll的RNN。但是这里的代码为了教学目的使用了手动展开的方式,我们简单的了解一下即可,实际的工作中我们应该使用注释掉的代码。

接下来的_get_lstm_cell函数根据配置来构造BasicLSTMCell或者LSTMBlockCell:

def _get_lstm_cell(self, config, is_training):
	  if config.rnn_mode == BASIC:
		  return tf.contrib.rnn.BasicLSTMCell(
			  config.hidden_size, forget_bias=0.0, state_is_tuple=True,
			  reuse=not is_training)
	  if config.rnn_mode == BLOCK:
		  return tf.contrib.rnn.LSTMBlockCell(
			  config.hidden_size, forget_bias=0.0)
	  raise ValueError("rnn_mode %s not supported" % config.rnn_mode)

通常LSTMBlockCell会比BasicLSTMCell更快,而且它们两者的用法完全一样,因此建议使用LSTMBlockCell。接下来我们看看_build_rnn_graph_cudnn,如果有GPU,默认会调用这个函数。

def _build_rnn_graph_cudnn(self, inputs, config, is_training): 
	  inputs = tf.transpose(inputs, [1, 0, 2])
	  self._cell = tf.contrib.cudnn_rnn.CudnnLSTM(
		  num_layers=config.num_layers,
		  num_units=config.hidden_size,
		  input_size=config.hidden_size,
		  dropout=1 - config.keep_prob if is_training else 0)
	  params_size_t = self._cell.params_size()
	  self._rnn_params = tf.get_variable(
		  "lstm_params",
		  initializer=tf.random_uniform(
		  [params_size_t], -config.init_scale, config.init_scale),
		  validate_shape=False)
	  c = tf.zeros([config.num_layers, self.batch_size, config.hidden_size],
		  tf.float32)
	  h = tf.zeros([config.num_layers, self.batch_size, config.hidden_size],
		  tf.float32)
	  self._initial_state = (tf.contrib.rnn.LSTMStateTuple(h=h, c=c),)
	  outputs, h, c = self._cell(inputs, h, c, self._rnn_params, is_training)
	  outputs = tf.transpose(outputs, [1, 0, 2])
	  outputs = tf.reshape(outputs, [-1, config.hidden_size])
	  return outputs, (tf.contrib.rnn.LSTMStateTuple(h=h, c=c),)

这个函数会使用CudnnLSTM,注意这个函数构造的是一个完整的LSTM层而不是一个RNNCell,因此它不需要使用static_rnn或者dynamic_rnn来unroll,这个函数都帮我们做好了。因为cudnnLSTM需要的输入是时间主序的,因此我们首先把输入从[batch_size, num_steps, hidden_size] transpose成[num_steps, batch_size, hidden_size]。

接下来使用函数CudnnLSTM,我们需要传入num_layers、num_units、input_size和dropout,这几个参数都很容易理解。这里我们的输入(input_size)和隐单元(num_units)是一样的大小,但是这不是必须的。上面的CudnnLSTM函数只是定义了LSTM的配置,但是并没有真正的在Graph里构造相应operation,真正的构造在调用CudnnLSTM的__call__方法时,也就是outputs, h, c = self._cell(inputs, h, c, self._rnn_params, is_training),调用这个方法需要传入inputs,初始的hidden和cell state和is_training,以及LSTM的参数变量。

注意我们需要自己创建LSTM需要的变量,那么它需要的变量的shape是多大呢?我们可以通过self._cell.params_size()得到。然后构造这样一个shape的变量。调用过_cell之后,我们它的输出也是[num_steps, batch_size, hidden_size],我们需要再次transpose回去变成[batch_size, num_steps, hidden_size]。然后再reshape成[batch_size*num_steps, hidden_size]便于后面的loss的计算(同样也保持和_build_rnn_graph_lstm函数的一致)。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

An Introduction to Genetic Algorithms

An Introduction to Genetic Algorithms

Melanie Mitchell / MIT Press / 1998-2-6 / USD 45.00

Genetic algorithms have been used in science and engineering as adaptive algorithms for solving practical problems and as computational models of natural evolutionary systems. This brief, accessible i......一起来看看 《An Introduction to Genetic Algorithms》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具