seq2seq项目详解

一、seq2seq和encoder-decoder关系

 seq2seq是从解决问题的目的角度来说的,利用的框架是encoder-decoder

 

二、项目例子

 比如我们有两个文件letters_source.txtletters_target.txt,他们行数一致,也就是我们的训练集合,他们每一行互应(这两个文件同一行彼此长度可以不一致:比如中英互译

 我们只展示前10行(我们该项目的目的是训练一个seq2seq模型,最终输入一个原序列,给出对原序列排序的目标序列

'''
原序列	目标序列
bsaqq	abqqs
npy	    npy
lbwuj	bjluw
bqv	    bqv
kial	aikl
tddam	addmt
edxpjpg	degjppx
nspv	npsv
huloz	hlouz
kmclq	cklmq
.............
'''

  1)读取文件

'''
只展示前十个序列
source_seq: ['bsaqq', 'npy', 'lbwuj', 'bqv', 'kial', 'tddam', 'edxpjpg', 'nspv', 'huloz', 'kmclq']
target_seq: ['abqqs', 'npy', 'bjluw', 'bqv', 'aikl', 'addmt', 'degjppx', 'npsv', 'hlouz', 'cklmq']
'''

 2)针对source和target中的所有字符,我们建立一个字典,将字符变成数字

'''
为每个char进行int转换:(这里为了每次转换保持一致,已经进行了字符列表的排序)
source_int_to_letter {0: '<PAD>', 1: '<UNK>', 2: '<GO>', 3: '<EOS>', 4: 'a', 5: 'b', 6: 'c', 7: 'd', 8: 'e', 9: 'f', 10: 'g', 11: 'h', 12: 'i', 13: 'j', 14: 'k', 15: 'l', 16: 'm', 17: 'n', 18: 'o', 19: 'p', 20: 'q', 21: 'r', 22: 's', 23: 't', 24: 'u', 25: 'v', 26: 'w', 27: 'x', 28: 'y', 29: 'z'}
source_letter_to_int {'<PAD>': 0, '<UNK>': 1, '<GO>': 2, '<EOS>': 3, 'a': 4, 'b': 5, 'c': 6, 'd': 7, 'e': 8, 'f': 9, 'g': 10, 'h': 11, 'i': 12, 'j': 13, 'k': 14, 'l': 15, 'm': 16, 'n': 17, 'o': 18, 'p': 19, 'q': 20, 'r': 21, 's': 22, 't': 23, 'u': 24, 'v': 25, 'w': 26, 'x': 27, 'y': 28, 'z': 29}

target_int_to_letter {0: '<PAD>', 1: '<UNK>', 2: '<GO>', 3: '<EOS>', 4: 'a', 5: 'b', 6: 'c', 7: 'd', 8: 'e', 9: 'f', 10: 'g', 11: 'h', 12: 'i', 13: 'j', 14: 'k', 15: 'l', 16: 'm', 17: 'n', 18: 'o', 19: 'p', 20: 'q', 21: 'r', 22: 's', 23: 't', 24: 'u', 25: 'v', 26: 'w', 27: 'x', 28: 'y', 29: 'z'}
target_letter_to_int {'<PAD>': 0, '<UNK>': 1, '<GO>': 2, '<EOS>': 3, 'a': 4, 'b': 5, 'c': 6, 'd': 7, 'e': 8, 'f': 9, 'g': 10, 'h': 11, 'i': 12, 'j': 13, 'k': 14, 'l': 15, 'm': 16, 'n': 17, 'o': 18, 'p': 19, 'q': 20, 'r': 21, 's': 22, 't': 23, 'u': 24, 'v': 25, 'w': 26, 'x': 27, 'y': 28, 'z': 29}

由于原序列和目标序列都是26个英语字母,如果是其他情况,如翻译,他们就是分别是单词或者中文文字库
'''

  3)source和target序列进行数值转换

'''
有了上面的序列字符组成的字符库,我们需要对原序列和目标序列转换成对应的数字序列,这里分别列出前十个:
source_int = [[5, 22, 4, 20, 20], [17, 19, 28], [15, 5, 26, 24, 13], [5, 20, 25], [14, 12, 4, 15], [23, 7, 7, 4, 16], [8, 7, 27, 19, 13, 19, 10], [17, 22, 19, 25], [11, 24, 15, 18, 29], [14, 16, 6, 15, 20], ......](如序列bsaqq对应的数值序列为[5, 22, 4, 20, 20])
target_int = [[4, 5, 20, 20, 22, 3], [17, 19, 28, 3], [5, 13, 15, 24, 26, 3], [5, 20, 25, 3], [4, 12, 14, 15, 3], [4, 7, 7, 16, 23, 3], [7, 8, 10, 13, 19, 19, 27, 3], [17, 19, 22, 25, 3],  [11, 15, 18, 24, 29, 3], [6, 14, 15, 16, 20, 3], ......](target序列abqqs对应的数值序列就是排序[4, 5, 20, 20, 22],我们为每个target序列加上EOS,也就是数字3)
综上,原序列和target序列就被数值化
'''

 

  下面我们讲几个概念:

  • Epoch(时期):
    当一个完整的数据集通过了神经网络一次并且返回了一次,这个过程称为一次epoch。(也就是说,所有训练样本在神经网络中进行了一次正向传播一次反向传播
    再通俗一点,一个Epoch就是将所有训练样本训练一次的过程。

  然而,当一个Epoch的样本(也就是所有的训练样本)数量可能太过庞大(对于计算机而言),就需要把它分成多个小块,也就是就是分成多个Batch 来进行训练

  • Batch(批 / 一批样本):
    将整个训练样本分成若干个Batch

  • Batch_Size(批大小):
    每批样本的大小

  • Iteration(一次迭代):
    训练一个Batch就是一次Iteration(这个概念跟程序语言中的迭代器相似)

  这里我们把epoch设置为60,batch_size=128

# 将数据集分割为train和validation
train_source = source_int[batch_size:]
train_target = target_int[batch_size:]
# 留出一个batch进行验证
valid_source = source_int[:batch_size]
valid_target = target_int[:batch_size]

  这样我们把source序列和target(分别有10000条序列)拆分为训练集和验证集(前128条为验证序列

 

 4)encoder层

  下面我们看看encoder部分干了什么事情:

# Encoder                                                                                                                          
# 在Encoder端,我们需要进行两步,第一步要对我们的输入进行Embedding,再把Embedding以后的向量传给RNN进行处理                         
# 在Embedding中,我们使用tf.contrib.layers.embed_sequence,它会对每个batch执行embedding操作                                        
def get_encoder_layer(input_data, rnn_size, num_layers, source_sequence_length, source_vocab_size, encoding_embedding_size): 
	"""                                                                                                                        
	构造Encoder层                                                                                                              
	参数说明:                                                                                                                 
	- input_data: 输入tensor                                                                                                   
	- rnn_size: rnn隐层结点数量                                                                                                
	- num_layers: 堆叠的rnn cell数量                                                                                           
	- source_sequence_length: 源数据的序列长度,也就是有效长度                                                                 
	- source_vocab_size: 源数据的词典大小                                                                                      
	- encoding_embedding_size: embedding的大小                                                                                 
	"""                                                                                                                        
	# Encoder embedding,这里进行embedding原理是什么呢?我只提供了序列数字,以及词汇数量,就完成了向量化                       
	encoder_embed_input = tf.contrib.layers.embed_sequence(input_data, source_vocab_size, encoding_embedding_size)             
                                                                                                                                   
	# RNN cell                                                                                                                 
	def get_lstm_cell(rnn_size):                                                                                               
		lstm_cell = tf.contrib.rnn.LSTMCell(rnn_size, initializer=tf.random_uniform_initializer(-0.1, 0.1, seed=2))        
		return lstm_cell                                                                                                   
                                                                                                                                   
	cell = tf.contrib.rnn.MultiRNNCell([get_lstm_cell(rnn_size) for _ in range(num_layers)])                                   
	encoder_output, encoder_state = tf.nn.dynamic_rnn(                                                                         
		cell, encoder_embed_input, sequence_length=source_sequence_length, dtype=tf.float32)                               
                                                                                                                                   
	return encoder_output, encoder_state                                                                                       

  我们假如输入的一个batch是128 * 7的tensor(即:128个句子样本,每个样本句子长度为7),rnn_size = 60,num_layers = 2,source_sequence_length为一个list,保留了128个句子样本中每个句子样本的原长,encoding_embedding_size = 15,即每个字符嵌入向量长度15,我们演示上面的encoder过程,别眨眼啊:

  我们可以根据每个batch中最长序列的长度,对其他序列进行pad填充:

'''只展示前六个
valid_sources_batch:
[[5,22,4,20,20,0,0], [17,19,28,0,0,0,0], [15,5,26,24,13,0,0], [,5,20,25,0,0,0,0],[14,12,4,15,0,0,0], [23,7,7,4,16,0,0]]

valid_targets_batch:
[[4,5,20,20,22,3,0,0], [17,19,28,3,0,0,0,0], [5,13,15,24,26,3,0,0], [,5,20,25,3,0,0,0,0], [4,12,14,15,3,0,0,0], [4,7,7,16,23,3,0,0]]

valid_targets_lengths: [6, 4, 6, 4, 5, 6]
valid_sources_lengths: [5, 3, 5, 3, 4, 5]
'''

  上面是验证集的展示,训练集从129行开始

  同理,每个batch处理方式相同,我们以第一个epoch的第一个batch为例,讲述训练过程其中第一个batch是从source_int[batch_size:256]和target_int[batch_size:256],也就说source_int和target_int的下标:128到256,以前几行为例:

  我们看一下转数字并pading之后:sources_batch[0:10]:这里是训练集的第一batch,也就说从原序列129行开始(前128行是验证集),这里只是展示129-138行的序列

'''
sources_batch[0:10]
[[24 9 17 0 0 0 0]  ufn对应的数字进行pad后
[14 13 0 0 0 0 0]  kj对应的数字进行pad后
[20 26 23 19 10 20 0] qwtpgq对应的数字进行pad后
[26 23 15 0 0 0 0]
[11 25 0 0 0 0 0]
[17 25 17 0 0 0 0]
[13 6 22 0 0 0 0]
[21 29 0 0 0 0 0]
[ 8 29 27 21 0 0 0]
[23 13 8 0 0 0 0]] tje对应的数字进行pad后

[3, 2, 6, 3, 2, 3, 3, 2, 4, 3]这个保存的是他们未进行pad时各自序列长度

targets_batch[0:10]:
[[ 9 17 24 3 0 0 0 0]
[13 14 3 0 0 0 0 0]
[10 19 20 20 23 26 3 0]
[15 23 26 3 0 0 0 0]
[11 25 3 0 0 0 0 0]
[17 17 25 3 0 0 0 0]
[ 6 13 22 3 0 0 0 0]
[21 29 3 0 0 0 0 0]
[ 8 21 27 29 3 0 0 0]
[ 8 13 23 3 0 0 0 0]]

[4, 3, 7, 4, 3, 4, 4, 3, 5, 4] 与上面的sources_batch对应,注意,每个target序列都进行了EOS(数字3)的添加,所以每个target序列都比原序列长度多1
'''

  OK,我们至此已经有了一个batch 的input(128个序列),其中第一对训练序列如:source-[24 9 17 0 0 0 0]长度为3,已经pad处理,target-[ 9 17 24 3 0 0 0 0]长度为4,加了EOS和PAD

  其实我们每一batch的source是二维矩阵:$128 * 7$这里的7不是固定的,因为第一batch中source最长是7,所以这里是7,这个列数由每一批中序列最长的序列长度决定(但要注意这个序列不会太长哦)

  然后我们可以对其进行encode:

  在Encoder端,我们需要进行两步,第一步要对我们的输入进行Embedding,再把Embedding以后的向量传给LSTM进行处理。

  在Embedding中,我们使用tf.contrib.layers.embed_sequence,它会对每个batch执行embedding操作,我们设置encoding_embedding_size = 15,也就说每个序列中的每个字符(也就是每个字母)从一维扩张到15维,我们的sources_batch也就是我们的input,会被embedding:

  sources_batch:一batch为128,每个序列长度为7(比如哈,可能其他batch中有条序列长度大于7,那么该批次就每个序列长度pad后就为最长序列长度)

  同时,我们要知道我们的source序列总共有多少种字符(本项目中为26个字母外加四种特殊字符,也就是30种)

  我们的source_batch本来是128 * 7,被embeding后就变成了128 * 7 * 15,我们把第一batch训练集的第一个序列[24 9 17 0 0 0 0]ufn进行embeding后展示出来:(7*15)

# Encoder embedding
encoder_embed_input = tf.contrib.layers.embed_sequence(input_data, source_vocab_size, encoding_embedding_size)

#下面是一行embeding后结果展示
'''
[[-0.36065042 -0.17114215 -0.05008167  0.21896338 -0.18541089  0.19533116
  -0.12902524  0.07539368 -0.23459466  0.21035886 -0.18695556  0.11775276
  -0.0019387  -0.12290165  0.02873832]
 [-0.25419667 -0.10173807  0.22848481 -0.0533388  -0.00930399 -0.02172154
   0.13963962  0.02923623  0.09819978 -0.12081417 -0.23042265 -0.28695342
   0.1045948   0.04559454  0.09481269]
 [ 0.19500643  0.28019369  0.10704392  0.03675491  0.35994822  0.08842304
  -0.15914266  0.33308589 -0.12992106 -0.13675191 -0.17976578 -0.20379627
  -0.29855976  0.03020796 -0.30255833]
 [-0.06587648 -0.05019096 -0.10603911  0.33219868  0.36303657  0.16047823
  -0.0563249  -0.13622303 -0.24733457 -0.30284318  0.03862795 -0.07534647
   0.28796619 -0.07870796  0.16098654]
 [-0.06587648 -0.05019096 -0.10603911  0.33219868  0.36303657  0.16047823
  -0.0563249  -0.13622303 -0.24733457 -0.30284318  0.03862795 -0.07534647
   0.28796619 -0.07870796  0.16098654]
 [-0.06587648 -0.05019096 -0.10603911  0.33219868  0.36303657  0.16047823
  -0.0563249  -0.13622303 -0.24733457 -0.30284318  0.03862795 -0.07534647
   0.28796619 -0.07870796  0.16098654]
 [-0.06587648 -0.05019096 -0.10603911  0.33219868  0.36303657  0.16047823
  -0.0563249  -0.13622303 -0.24733457 -0.30284318  0.03862795 -0.07534647
   0.28796619 -0.07870796  0.16098654]]
'''

  这样我们一个batch的Input就是128*7*15,也就是我们已经获得encoder_embed_input,下面我们可以进行encode了,我们先定义一个LSTM单元

# RNN cell
rnn_size = 50
def get_lstm_cell(rnn_size):
    lstm_cell = tf.contrib.rnn.LSTMCell(rnn_size, initializer=tf.random_uniform_initializer(-0.1, 0.1, seed=2))
    return lstm_cell

  我们先说说这个rnn_size = 50是什么意思,假设我们当前x输入是某个字符-15维向量,那么这个rnn_size也就是num_units就说下面的h,即就是一个50维的向量,h和x进行concatenate,就得到了65维的向量作为input

 

  下面我们参考下面的链接,继续讲解,在使用Tensorflow跑LSTM的试验中, 有个num_units(也就是上面所说的rnn_size)的参数,这个参数是什么意思呢?

  先总结一下,num_units这个参数的大小就是LSTM输出结果的维度。例如num_units=128(上面的例子rnn_size=50), 那么LSTM网络最后输出就是一个128维的向量

 

  我们先换个角度举个例子,最后再用公式来说明。

  假设在我们的训练数据中,每一个样本 x(在这里指每一行序列) 是 $7*15$(上面的例子是7*15:7表示一个序列长度,15表示每个元素(这里为一个字符)被embeding成15维向量) 维的一个矩阵,那么将这个样本的每一行当成一个输入,通过7个时间步骤展开LSTM,在每一个LSTM单元,我们输入一行维度为15的向量,如下图所示(修改别人的图片了,自己PS的,不要被row image字样影响,就当作一个向量就行)

  那么,对每一个LSTM单元,参数num_units=50的话,就是每一个单元的输出为 50*1 的向量,在展开的网络维度来看,如下图所示,对于每一个输入15维的向量,LSTM单元都把它映射到50维的维度, 在下一个LSTM单元时,LSTM会接收上一个50维的输出,和新的15维的输入,处理之后再映射成一个新的50维的向量输出,就这么一直处理下去,知道网络中最后一个LSTM单元,输出一个50维的向量

  从LSTM的公式的角度看是什么原理呢?我们先看一下LSTM的结构和公式:

  参数num_units=50的话,(在机器学习领域,我们说的向量都是列向量)

  对于公式 (1) ,h=50∗1维, x=15∗1维,[h,x]便等于65∗1维,(由于h要输出同样是50维),W=50*65维,W∗[h,x]=50∗65∗65∗1=50∗1维,b=50*1维,所以f=50*1维

  对于公式 (2) 和 (3),同上可分析得 i=50∗1维,$\hat C$=50*1维

  对于公式(4),f(t)=50*1维,C(t−1)=50∗1,f(t).∗C(t−1) = 50*1 点乘 50*1 = 50*1,i点乘$\hat C$=50*1点乘50*1=50*1维,所以$C_t$=50*1维

  对于公式 (5) 和 (6) , 同理可得 O=50∗1=50∗1 维,h=O点乘tanh(C)=50∗1

  所以最后LSTM单元输出的h就是 50*1 的向量

  以上就是 num_units 参数的含义

 

  我们接着讲:上面我们只是讲了单隐层的LSTM,比如现在输入的是ufn序列中的u(u已经被embeding成15维向量),我们希望对应的target为fnu,即希望输出f(u -> f),状态向量继续流入下一个LSTM单元,同时输出也流入下一个单元与新的输入f合并成新的65维向量,继续进行上面公式(1)到(6)的操作,请注意:我们目前只是单隐层的LSTM单元前后连接,我们也可以创建多隐层的LSTM,上下叠加(这里前后和上下叠加是形象化解释,便于理解),请参考

  我们使用tensorflow可以轻松的创建多隐藏的LSTM:

# RNN cell,这里rnn_size=50,num_layers=2,就是说我们创建的是双隐层的LISTM
#这里注意:我们的两个隐层,他们的rnn_size都是50,我们也可以为每个隐层设置不同的rnn_size
#这里的rnn_size,就说tensorflow该函数官方参数num_units
def get_lstm_cell(rnn_size):
    lstm_cell = tf.contrib.rnn.LSTMCell(rnn_size, initializer=tf.random_uniform_initializer(-0.1, 0.1, seed=2))
    return lstm_cell

cell = tf.contrib.rnn.MultiRNNCell([get_lstm_cell(rnn_size) for _ in range(num_layers)])

  

  目前,我们已经可以为每一batch(128个样本,也就是128行序列)数据进行embeding(从2维变成了3维:如输入Input是128*7,字嵌入后变成128*7*15),并且已经创建了双隐层的LSTM,而且还已经知道了每个序列的长度(未进行PAD的长度,也就是我们序列的有效长度,因为进行PAD后长度都是7了),我们可以利用tf.nn.dynamic_rnn()获取output和state

def get_encoder_layer(input_data, rnn_size, num_layers,
                   source_sequence_length, source_vocab_size, 
                   encoding_embedding_size):

    '''
    构造Encoder层
    
    参数说明:
    - input_data: 输入tensor
    - rnn_size: rnn隐层结点数量
    - num_layers: 堆叠的rnn cell数量
    - source_sequence_length: 源数据的序列长度
    - source_vocab_size: 源数据的词典大小
    - encoding_embedding_size: embedding的大小
    '''
    # Encoder embedding
    encoder_embed_input = tf.contrib.layers.embed_sequence(input_data, source_vocab_size, encoding_embedding_size)

    # RNN cell
    def get_lstm_cell(rnn_size):
        lstm_cell = tf.contrib.rnn.LSTMCell(rnn_size, initializer=tf.random_uniform_initializer(-0.1, 0.1, seed=2))
        return lstm_cell

    cell = tf.contrib.rnn.MultiRNNCell([get_lstm_cell(rnn_size) for _ in range(num_layers)])
    
    encoder_output, encoder_state = tf.nn.dynamic_rnn(cell, encoder_embed_input, 
                                                      sequence_length=source_sequence_length, dtype=tf.float32)
    
    return encoder_output, encoder_state

  这里需要记录一下encoder_output和encoder_state的shape,划重点了,我们的Input是一个128* 7 * 15的矩阵,我们开始讲解上面的encoder过程:如下图

  有了上面的图,我们可以测试一下 encoder_output, encoder_state这两个结果的维度,看是否和上图维度一致(是一致的)

  这里的data下载,请查看下载链接,提取码:9e6d

import sys
import numpy as np
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

import tensorflow as tf


class Processing:
	def __init__(self, source_file='data/letters_source.txt', target_file='data/letters_target.txt'):
		self.source_data = self.safe_open(source_file)
		self.target_data = self.safe_open(target_file)

		# 构造映射表
		self.source_int_to_letter, self.source_letter_to_int = self.extract_character_vocab(self.source_data)
		self.target_int_to_letter, self.target_letter_to_int = self.extract_character_vocab(self.target_data)

		# 对字母进行转换
		self.source_int = self.char_to_int('source')
		self.target_int = self.char_to_int('target')

	def safe_open(self, file_name):
		with open(file_name, 'r', encoding='utf-8') as f:
			data = f.read().strip()
		return data

	def extract_character_vocab(self, data):
		"""
		构造映射表
		< PAD>: 补全字符。
		< EOS>: 解码器端的句子结束标识符。
		< UNK>: 低频词或者一些未遇到过的词等。
		< GO>: 解码器端的句子起始标识符
		"""
		special_words = ['<PAD>', '<UNK>', '<GO>', '<EOS>']
		set_words = sorted(list(set([character for line in data.split('\n') for character in line])))
		# 这里要把四个特殊字符添加进词典
		int_to_vocab = {idx: word for idx, word in enumerate(special_words + set_words)}
		vocab_to_int = {word: idx for idx, word in int_to_vocab.items()}
		return int_to_vocab, vocab_to_int

	def char_to_int(self, sequence):
		if sequence == 'source':
			return [[self.source_letter_to_int.get(letter, self.source_letter_to_int['<UNK>']) for letter in line]
					for line in self.source_data.split('\n')]
		elif sequence == 'target':
			return [[self.target_letter_to_int.get(letter, self.target_letter_to_int['<UNK>']) for letter in line] + [self.target_letter_to_int['<EOS>']]
					for line in self.target_data.split('\n')]
		else:
			print('Please input the sequence you  want to convert into int!')
			sys.exit()

	def pad_sentence_batch(self, sentence_batch, pad_int):
		"""
		对batch中的序列进行补全,保证batch中的每行都有相同的sequence_length
		参数:
		- sentence batch
		- pad_int: <PAD>对应索引号
		"""
		max_sentence = max([len(sentence) for sentence in sentence_batch])
		return [sentence + [pad_int] * (max_sentence - len(sentence)) for sentence in sentence_batch]

	def get_batches(self, targets, sources, batch_size, source_pad_int, target_pad_int):
		"""
		定义生成器,用来获取batch
		"""
		print('sources length in get_batches:', len(sources), len(sources) // batch_size)
		for batch_i in range(0, len(sources) // batch_size):
			start_i = batch_i * batch_size
			sources_batch = sources[start_i:start_i + batch_size]
			targets_batch = targets[start_i:start_i + batch_size]
			# 补全序列
			pad_sources_batch = np.array(self.pad_sentence_batch(sources_batch, source_pad_int))
			pad_targets_batch = np.array(self.pad_sentence_batch(targets_batch, target_pad_int))

			# 记录每条记录的长度
			targets_lengths = []
			for target in targets_batch:
				targets_lengths.append(len(target))

			source_lengths = []
			for source in sources_batch:
				source_lengths.append(len(source))

			yield pad_targets_batch, pad_sources_batch, targets_lengths, source_lengths



class EncodeDecodeModelBuild:
	def __init__(self):
		pass

	# Encoder
	# 在Encoder端,我们需要进行两步,第一步要对我们的输入进行Embedding,再把Embedding以后的向量传给RNN进行处理
	# 在Embedding中,我们使用tf.contrib.layers.embed_sequence,它会对每个batch执行embedding操作
	def get_encoder_layer(self, input_data, rnn_size, num_layers, source_sequence_length, source_vocab_size, encoding_embedding_size):
		"""
		构造Encoder层
		参数说明:
		- input_data: 输入tensor
		- rnn_size: rnn隐层结点数量
		- num_layers: 堆叠的rnn cell数量
		- source_sequence_length: 源数据的序列长度,也就是有效长度
		- source_vocab_size: 源数据的词典大小
		- encoding_embedding_size: embedding的大小
		"""
		# Encoder embedding,这里进行embedding原理是什么呢?我只提供了序列数字,以及词汇数量,就完成了向量化
		encoder_embed_input = tf.contrib.layers.embed_sequence(input_data, source_vocab_size, encoding_embedding_size)

		# RNN cell
		def get_lstm_cell(rnn_size):
			lstm_cell = tf.contrib.rnn.LSTMCell(rnn_size, initializer=tf.random_uniform_initializer(-0.1, 0.1, seed=2))
			return lstm_cell

		cell = tf.contrib.rnn.MultiRNNCell([get_lstm_cell(rnn_size) for _ in range(num_layers)])
		encoder_output, encoder_state = tf.nn.dynamic_rnn(
			cell, encoder_embed_input, sequence_length=source_sequence_length, dtype=tf.float32)

		return encoder_output, encoder_state

if __name__ == '__main__':
	process = Processing()
	print('source_seq:', process.source_data.split('\n')[0:10])
	print('target_seq:', process.target_data.split('\n')[0:10])

	print('source_int_to_letter', process.source_int_to_letter)
	print('source_letter_to_int', process.source_letter_to_int)

	print('target_int_to_letter', process.target_int_to_letter)
	print('target_letter_to_int', process.target_letter_to_int)

	source_letter_to_int = process.source_letter_to_int
	target_letter_to_int = process.target_letter_to_int

	encode_decode = EncodeDecodeModelBuild()  # 实例化,没什么初始化操作

	# 超参数
	# Batch Size
	batch_size = 128
	# RNN Size
	rnn_size = 50
	# Number of Layers
	num_layers = 2
	# Embedding Size
	encoding_embedding_size = 15

	valid_source = process.source_int[:batch_size]
	valid_target = process.target_int[:batch_size]
	print('valid_source[0:10]:', valid_source[0:10])
	print('valid_target[0:10]:', valid_target[0:10])

	(valid_targets_batch, valid_sources_batch, valid_targets_lengths, valid_sources_lengths) = next(
		process.get_batches(
			valid_target, valid_source, batch_size,
			process.source_letter_to_int['<PAD>'],
			process.target_letter_to_int['<PAD>'])
		)

	print('valid_sources_batch[0:10]:', valid_sources_batch[0:10])
	print('valid_targets_batch[0:10]:', valid_targets_batch[0:10])
	print('valid_sources_lengths[0:10]:', valid_sources_lengths[0:10])
	print('valid_targets_lengths[0:10]:', valid_targets_lengths[0:10])

	input_data = valid_sources_batch
	targets = valid_targets_batch

	source_sequence_length = valid_sources_lengths
	target_sequence_length = valid_targets_lengths

	max_target_sequence_length = 7
	source_vocab_size = len(source_letter_to_int)

	encoder_output, encoder_state = encode_decode.get_encoder_layer(
		input_data, rnn_size, num_layers, source_sequence_length, source_vocab_size, encoding_embedding_size)


	with tf.Session() as sess:
		sess.run(tf.global_variables_initializer())
	
		print('encoder_output.shape:', sess.run(encoder_output).shape)
		print('\n')
		print(sess.run(encoder_state[0]))
		print('0-c:', sess.run(encoder_state[0][0]).shape)
		print('0-h:', sess.run(encoder_state[0][1]).shape)
	
		print('\n')
		print(sess.run(encoder_state[1]))
		print('1-c:', sess.run(encoder_state[1][0]).shape)
		print('1-h:', sess.run(encoder_state[1][1]).shape)

'''
encoder_output.shape: (128, 7, 50)
LSTMStateTuple(c=array([[-0.04304752, -0.03509561,  0.08023848, ...,  0.00869391,
        -0.05691399, -0.0037792 ],
       [ 0.00383653, -0.00961697, -0.00571259, ..., -0.06620879,
        -0.03612325, -0.05058336],
       [-0.01471607, -0.00054711, -0.00437702, ..., -0.04058436,
        -0.08962905, -0.00147169],
       ...,
       [ 0.05962741,  0.03080438, -0.01021096, ...,  0.03458264,
        -0.01859237, -0.00854286],
       [ 0.10512877,  0.05675253, -0.05238747, ...,  0.08558378,
         0.03848773,  0.0153399 ],
       [-0.00535806, -0.00980609, -0.00647817, ...,  0.01386383,
         0.00361691, -0.01494723]], dtype=float32), h=array([[-0.02223058, -0.01775076,  0.03938275, ...,  0.00433571,
        -0.02808682, -0.00186137],
       [ 0.00186763, -0.00502355, -0.00283817, ..., -0.0320057 ,
        -0.01752974, -0.02522181],
       [-0.00723557, -0.00028466, -0.00215382, ..., -0.02022631,
        -0.04316105, -0.00071936],
       ...,
       [ 0.02942469,  0.01522003, -0.00491344, ...,  0.01708112,
        -0.00908282, -0.00431359],
       [ 0.0540871 ,  0.02825295, -0.02745056, ...,  0.04257125,
         0.01909489,  0.00735466],
       [-0.00273725, -0.00494143, -0.00335784, ...,  0.00702268,
         0.00177948, -0.00720975]], dtype=float32))
0-c: (128, 50)
0-h: (128, 50)


LSTMStateTuple(c=array([[-1.5820017e-02, -2.6942336e-03, -1.0050132e-03, ...,
         8.5409246e-03,  6.6550612e-03,  8.1391260e-03],
       [ 7.7295029e-03,  4.1437177e-03,  4.6428009e-03, ...,
         1.1356347e-02,  8.1963371e-05, -7.8776069e-03],
       [ 1.0825472e-02,  4.3439609e-03,  1.2341643e-02, ...,
         1.4018469e-02,  1.0256685e-02,  8.7942984e-03],
       ...,
       [-3.0825241e-03, -2.2337481e-05, -6.5338850e-04, ...,
        -6.1642872e-03, -1.1415123e-03,  7.1417345e-03],
       [-1.2705816e-02, -1.6099531e-03,  2.2869529e-02, ...,
        -2.3452278e-02, -7.6105753e-03,  7.6716589e-03],
       [-1.0074907e-03,  3.4129494e-05, -1.8248253e-03, ...,
        -5.8616372e-04, -1.6492521e-03, -8.0834865e-04]], dtype=float32), h=array([[-7.8813164e-03, -1.3356666e-03, -5.0444109e-04, ...,
         4.2508096e-03,  3.3232903e-03,  4.0544183e-03],
       [ 3.8475350e-03,  2.0742973e-03,  2.3326257e-03, ...,
         5.6687519e-03,  4.0923649e-05, -3.9418442e-03],
       [ 5.4338807e-03,  2.1625140e-03,  6.1921040e-03, ...,
         6.9652013e-03,  5.1008761e-03,  4.3697306e-03],
       ...,
       [-1.5396455e-03, -1.1132359e-05, -3.2613808e-04, ...,
        -3.0813138e-03, -5.6903326e-04,  3.5529216e-03],
       [-6.3411035e-03, -8.0282643e-04,  1.1467443e-02, ...,
        -1.1772511e-02, -3.8120230e-03,  3.8370693e-03],
       [-5.0305878e-04,  1.7079228e-05, -9.1262913e-04, ...,
        -2.9348588e-04, -8.2494150e-04, -4.0417191e-04]], dtype=float32))
1-c: (128, 50)
1-h: (128, 50)
'''

 

  说完了encode,我们说一下decode,先对target进行处理

# Decoder
# 先对target数据进行预处理
def process_decoder_input(self, data, vocab_to_int, batch_size):
        """
        补充<GO>,并移除最后一个字符
        """
        # cut掉最后一个字符
        data = tf.constant(data, dtype=tf.int32)
        ending = tf.strided_slice(data, [0, 0], [batch_size, -1], [1, 1])
        #  vocab_to_int['<GO>']在本例中是2,经过在列维度上的合并,每个序列都是以GO(对应数值为2)开头
        decoder_input = tf.concat([tf.fill([batch_size, 1], vocab_to_int['<GO>']), ending], 1)

        return decoder_input

 

  前面我们已经有了该batch的targets序列,target_letter_to_int以及batch_size=128,那么我们看看上面的process_decoder_input做了什么骚操作:

'''
valid_targets_batch[0:10]: 
[[ 4  5 20 20 22  3  0  0]
 [17 19 28  3  0  0  0  0]
 [ 5 13 15 24 26  3  0  0]
 [ 5 20 25  3  0  0  0  0]
 [ 4 12 14 15  3  0  0  0]
 [ 4  7  7 16 23  3  0  0]
 [ 7  8 10 13 19 19 27  3]
 [17 19 22 25  3  0  0  0]
 [11 15 18 24 29  3  0  0]
 [ 6 14 15 16 20  3  0  0]]

经过process_decoder_input(targets, target_letter_to_int, batch_size)骚操作后
就在target首位添加了数字2:'GO',并去掉了原来target序列补充的一个数字0

decoder_input: 
[[ 2  4  5 20 20 22  3  0]
 [ 2 17 19 28  3  0  0  0]
 [ 2  5 13 15 24 26  3  0]
 [ 2  5 20 25  3  0  0  0]
 [ 2  4 12 14 15  3  0  0]
 [ 2  4  7  7 16 23  3  0]
 [ 2  7  8 10 13 19 19 27]
 [ 2 17 19 22 25  3  0  0]
 [ 2 11 15 18 24 29  3  0]
 [ 2  6 14 15 16 20  3  0]]

'''

  tf.strided_slice,tf.fill,tf.concat感兴趣的可以参考这个例子

 

  我们先进行embedding,即对decoder_input进行embedding,这样我们就获得了一个三维矩阵:128 * 8 * 15

# 1. Embedding
target_vocab_size = len(target_letter_to_int)
decoder_embeddings = tf.Variable(tf.random_uniform([target_vocab_size, decoding_embedding_size]))
decoder_embed_input = tf.nn.embedding_lookup(decoder_embeddings, decoder_input)

  如何理解呢?不难,请参考

  然后构建decoder中RNN单元,代码和encoder一样,不做解释

# 1. Embedding
target_vocab_size = len(target_letter_to_int)
# 创建一个shape为[target_vocab_size, decoding_embedding_size]的矩阵变量,这里为[30, 15]
decoder_embeddings = tf.Variable(tf.random_uniform([target_vocab_size, decoding_embedding_size]))
decoder_embed_input = tf.nn.embedding_lookup(decoder_embeddings, decoder_input)

# 2. 构造Decoder中的RNN单元
def get_decoder_cell(rnn_size):
        decoder_cell = tf.contrib.rnn.LSTMCell(rnn_size, initializer=tf.random_uniform_initializer(-0.1, 0.1, seed=  2      
        return decoder_cell

cell = tf.contrib.rnn.MultiRNNCell([get_decoder_cell(rnn_size) for _ in range(num_layers)])

 

  然后构建输出全连接层(获得每个字符的输出概率)

from tensorflow.python.layers.core import Dense
output_layer = Dense(target_vocab_size,
    kernel_initializer = tf.truncated_normal_initializer(mean = 0.0, stddev=0.1))

  全连接层例子,自己没找到合适的Dense()函数例子,但可以参考这个理解

 

 

posted @ 2024-03-19 20:19  1直在路上1  阅读(21)  评论(0编辑  收藏  举报