Seq2seq模型是一种many to many结构,它实现了从一个序列到另一个序列的转换,其基本思想就是利用两个RNN,一个RNN作为恩code人,另一个作为decoder。Encoder负责将输入序列压缩成指定长度向量,这个向量可以看出序列的语义,而decoder则是负责根据语义将语义向量转化为指定的序列,这个过程称为解码。
一、RNN
RNN循环神经网络,主要用来处理输入前后具有关联的序列数据,传统的神经网络中,输入层到隐藏层再到输出层,层与层之间的连接是全连接的,每层节点是无连接的,而RNN的隐藏节点之间的连接是有连接的,从而隐藏节点的输入包括输入层的输入,还包括上一时刻隐藏层的输入,一个典型的RNNs结构如下:
图中的W,U,V是共享的,并且在梯度下降算法中,每一步的输出不仅依赖当前步的网络,还依赖于前面若干步的状态。常用的RNN模型有GRU和LSTM网络。
RNN在实际问题中常用的模型结构如下:
A 输入为一串序列数据,输出为分类类别,那么输出不需要一个序列,只需要单个输出(n vs 1)
B 输入为单个输入,输出为序列(1 vs n)
C 输入是序列,输出不随着序列变换而变换(n vs n)
输入是序列,而输出是一个可变序列(n vs m),则称这种结构为encoder-decoder模型,也称为seq2seq模型
1.1GRU
其改进一是序列中不同位置处的单词对当前的隐藏状态的影响不同,越前面的影响越小;二是在产生误差时,误差可能由某一个或者几个单词引起的,从而仅仅对对应的单词进行更新,因此其设置了两个门,分别是重置门和更新门;重置门主要是对应改进一,强制隐藏状态遗忘一些历史信息,并利用当前的输入的信息,这可以令隐藏状态遗忘任何在未来与预测不相关的信息,同事也允许构建更加紧致的表征;更新门主要是对应改进二,控制前面隐藏状态的信息有多少回传递到当期状态,这个与LSTM网络中的记忆单元(cell)非常的相似。
1.2 LSTM
LSTM与一般的RNN结构本质上没有什么不同,只是使用了不同的函数去计算隐藏层;在LSTM中,除了RNN具有的隐藏状态ht之外,还多了另一个隐藏状态,这个隐藏状态一般称之为细胞状态ct,它类似于传送带,lstm通过精心设计的“门”结构来去除或者增加信息到细胞状态的能力。Lstm主要有如下三个门:
遗忘门:以一定的概率控制是否遗忘上一层的细胞状态
输入门:负责处理当前序列位置的输入,以确定什么样的新信息存放在细胞状态中,先用sigmod函数决定什么值要进行更新,然后通过tanh激活层,( tanh主要是创建一个候选的值,sigomd主要是产生0、1值,用于决定什么值放弃,什么值保留)产生一个新的候选值从而将后选值在细胞状态中进行更新,
所以在细胞更新状态的时候,主要做的两件事儿就是决定哪些历史信息该流入当前细胞中(遗忘门控制)(主要受到哪些单词的影响),哪些新的信息该流入细胞中(输入门控制)(误差是由什么引起的)
输出门:
根据新的细胞状态ct,进行输出
输入门控制这当前输入值有多少信息流入到当前的计算中,遗忘门控制着历史信息中有多少信息流入到当前计算中,输出门控制着输出值中有多少信息流入到隐层中。
二、Seq2seq用于翻译的模型代码如下:
# seq2seq的模型训练,利用keras from keras.models import Model from keras.layers import Input,LSTM,Dense import numpy as np batch_size = 64 epochs = 10 latent_dim = 256 num_samples = 10000# 训练的样本数量 data_path = "D:\workspace\project\\NLPcase\\seq2seq\data\\translation_fra_en.txt" input_path = 'D:\workspace\project\\NLPcase\\seq2seq\\model\\source_words'# 输入所用到的字符 output_path = 'D:\workspace\project\\NLPcase\\seq2seq\\model\\target_words'# 输出所用到的字符 model_path = 'D:\workspace\project\\NLPcase\\seq2seq\\model\\seq2seq.h5' def build_data(): input_texts = [] target_texts = [] input_characters = set() target_characters = set() with open(data_path,encoding='utf-8') as f: lines = f.read().split('\n') for line in lines[:min(num_samples,len(lines)-1)]: input_text,target_text = line.split('\t') target_text = '\t'+target_text + '\n' input_texts.append(input_text) target_texts.append(target_text) for char in input_text: if char not in input_characters: input_characters.add(char) for char in target_text: if char not in target_characters: target_characters.add(char) with open(input_path,'w') as f: f.write("*".join([item for item in input_characters])) f.close() with open(output_path,'w') as f: f.write("*".join([item for item in target_characters])) f.close() # 多少个输入输出字符 num_encoder_tokens = len(input_characters) num_decoder_tokens = len(target_characters) input_token_index = dict([(char, i) for i, char in enumerate(input_characters)]) target_token_index = dict([(char, i) for i, char in enumerate(target_characters)]) # 输入中最大的序列 max_endoder_seq_length = max([len(x) for x in input_texts]) max_decoder_seq_length = max([len(x) for x in target_texts]) # 定义数据格式 encoder_input_data = np.zeros((len(input_texts),max_endoder_seq_length,num_encoder_tokens),dtype='float32') decoder_input_data = np.zeros((len(input_texts), max_decoder_seq_length,num_decoder_tokens),dtype='float32') decoder_target_data = np.zeros((len(input_texts),max_decoder_seq_length,num_decoder_tokens),dtype='float32') for i,(input_text,target_text) in enumerate(zip(input_texts,target_texts)): for t,char in enumerate(input_text): encoder_input_data[i,t,input_token_index[char]] = 1 for t,char in enumerate(target_text): decoder_input_data[i,t,target_token_index[char]] = 1 if t>0: decoder_target_data[i,t-1,target_token_index[char]] = 1 return num_encoder_tokens,num_decoder_tokens,encoder_input_data,decoder_input_data,decoder_target_data num_encoder_tokens,num_decoder_tokens,encoder_input_data,decoder_input_data,decoder_target_data = build_data() # 构建模型,并进行训练 def build_model(): # 输入层 encoder_inputs = Input(shape=(None,num_encoder_tokens)) #定义lstm层 encoder = LSTM(latent_dim,return_state=True) # 语义向量 encoder_outputs,stateh,stateC = encoder(encoder_inputs) encoder_states = [stateh,stateC] # 设置decoder decoder_inputs = Input(shape=(None,num_decoder_tokens)) # 定义lstm层 decoder_lstm = LSTM(latent_dim,return_state=True,return_sequences=True) decoder_outputs,_,_ = decoder_lstm(decoder_inputs,initial_state=encoder_states) # 全连接层 decoder_dense = Dense(num_decoder_tokens,activation='softmax') decoder_outputs = decoder_dense(decoder_outputs) model = Model([encoder_inputs,decoder_inputs],decoder_outputs) model.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['accuracy']) return model # 训练模型 model = build_model() model.fit([encoder_input_data,decoder_input_data],decoder_target_data, batch_size=batch_size,epochs=epochs,validation_split=0.2) model.save(model_path)
预测推理代码如下
from keras.models import Model,load_model from keras.layers import Input import numpy as np latent_dim = 256 input_path = 'D:\workspace\project\\NLPcase\\seq2seq\\model\\source_words'# 输入所用到的字符 output_path = 'D:\workspace\project\\NLPcase\\seq2seq\\model\\target_words'# 输出所用到的字符 model_path = 'D:\workspace\project\\NLPcase\\seq2seq\\model\\seq2seq.h5' max_encoder_seq_length = 16 max_decoder_seq_length = 56 input_characters = [item for item in open(input_path,encoding='utf-8').read().split('*')] target_characters = [item for item in open(output_path,encoding='utf-8').read().split('*')] input_token_index = dict([(char,i) for i,char in enumerate(input_characters)]) target_token_index = dict([(char,i) for i,char in enumerate(target_characters)]) reverse_input_char_index = dict((i,char)for i,char in input_token_index.items()) reverse_target_char_index = dict((i,char)for i,char in target_characters.items()) # 加载模型,不需要fit的过程 def load(): model = load_model(model_path) encoder_inputs = model.input[0]# 获得input_1 encoder_outputs, state_h_enc, state_c_enc = model.layers[2].output # lstm_1 encoder_states = [state_h_enc, state_c_enc] encoder_model = Model(encoder_inputs,encoder_states)# 输出encoder_states,输入为encoder_inputs #-------------------------- decoder_inputs = model.input[1] #input2 decoder_state_input_h = Input(shape=(latent_dim,),name='input3') decoder_state_input_c = Input(shape=(latent_dim,),name='input4') decoder_states_inputs = [decoder_state_input_h,decoder_state_input_c] decoder_lstm = model.layers[3] decoder_outputs, state_h_dec, state_c_dec = decoder_lstm(decoder_inputs, initial_state=decoder_states_inputs) decoder_states = [state_h_dec, state_c_dec] decoder_dense = model.layers[4] decoder_outputs = decoder_dense(decoder_outputs) decoder_model = Model([decoder_inputs] + decoder_states_inputs, [decoder_outputs] + decoder_states)#之所以做加法,是根据lstm的初始状态来的 return encoder_model,decoder_model # 解码 def decode_sequence(input_seq): encoder_model,decoder_model = load() states_value = encoder_model.predict(input_seq) #------------ target_seq = np.zeros(1,1,len(target_characters)) target_seq[0, 0, target_token_index['\t']] = 1. stop_condition = False decoded_sentence = '' while not stop_condition: output_tokens, h, c = decoder_model.predict([target_seq] + states_value) sampled_token_index = np.argmax(output_tokens[0, -1, :]) sampled_char = reverse_target_char_index[sampled_token_index] decoded_sentence += sampled_char # 控制循环条件 if (sampled_char == '\n' or len(decoded_sentence) > max_decoder_seq_length): stop_condition = True target_seq = np.zeros((1, 1, len(target_characters))) target_seq[0, 0, sampled_token_index] = 1. states_value = [h, c] return decoded_sentence # 新句子向量的表示 def encode_sentence(input_text): encode_input = np.zeros(1,max_encoder_seq_length,len(input_characters),dtype='float32') for index,char in enumerate(input_text): print(index,char) encode_input[0,index,input_token_index[char]] = 1 return encode_input
参考资料
https://blog.csdn.net/jichangzhen/article/details/82940728
https://blog.csdn.net/zhaojc1995/article/details/80572098
https://www.sohu.com/a/224046540_100011708
https://www.cnblogs.com/jiangxinyang/p/9362922.html
https://github.com/liuhuanyong/Seq2SeqTranslation
https://blog.csdn.net/wangyangzhizhou/article/details/77883152