Seq2seq模型

  Seq2seq模型是一种many to many结构,它实现了从一个序列到另一个序列的转换,其基本思想就是利用两个RNN,一个RNN作为恩code人,另一个作为decoder。Encoder负责将输入序列压缩成指定长度向量,这个向量可以看出序列的语义,而decoder则是负责根据语义将语义向量转化为指定的序列,这个过程称为解码。

一、RNN

  RNN循环神经网络,主要用来处理输入前后具有关联的序列数据,传统的神经网络中,输入层到隐藏层再到输出层,层与层之间的连接是全连接的,每层节点是无连接的,而RNN的隐藏节点之间的连接是有连接的,从而隐藏节点的输入包括输入层的输入,还包括上一时刻隐藏层的输入,一个典型的RNNs结构如下:

Seq2seq模型_第1张图片

  图中的W,U,V是共享的,并且在梯度下降算法中,每一步的输出不仅依赖当前步的网络,还依赖于前面若干步的状态。常用的RNN模型有GRU和LSTM网络。

  RNN在实际问题中常用的模型结构如下:

  A 输入为一串序列数据,输出为分类类别,那么输出不需要一个序列,只需要单个输出(n vs 1)

Seq2seq模型_第2张图片

  B 输入为单个输入,输出为序列(1 vs n)

Seq2seq模型_第3张图片

  C 输入是序列,输出不随着序列变换而变换(n vs n)

Seq2seq模型_第4张图片

  输入是序列,而输出是一个可变序列(n vs m),则称这种结构为encoder-decoder模型,也称为seq2seq模型

Seq2seq模型_第5张图片

Seq2seq模型_第6张图片

 

1.1GRU

  其改进一是序列中不同位置处的单词对当前的隐藏状态的影响不同,越前面的影响越小;二是在产生误差时,误差可能由某一个或者几个单词引起的,从而仅仅对对应的单词进行更新,因此其设置了两个门,分别是重置门和更新门;重置门主要是对应改进一,强制隐藏状态遗忘一些历史信息,并利用当前的输入的信息,这可以令隐藏状态遗忘任何在未来与预测不相关的信息,同事也允许构建更加紧致的表征;更新门主要是对应改进二,控制前面隐藏状态的信息有多少回传递到当期状态,这个与LSTM网络中的记忆单元(cell)非常的相似。

Seq2seq模型_第7张图片

1.2 LSTM

  LSTM与一般的RNN结构本质上没有什么不同,只是使用了不同的函数去计算隐藏层;在LSTM中,除了RNN具有的隐藏状态ht之外,还多了另一个隐藏状态,这个隐藏状态一般称之为细胞状态ct,它类似于传送带,lstm通过精心设计的“门”结构来去除或者增加信息到细胞状态的能力。Lstm主要有如下三个门:

  遗忘门:以一定的概率控制是否遗忘上一层的细胞状态

Seq2seq模型_第8张图片

  输入门:负责处理当前序列位置的输入,以确定什么样的新信息存放在细胞状态中,先用sigmod函数决定什么值要进行更新,然后通过tanh激活层,( tanh主要是创建一个候选的值,sigomd主要是产生0、1值,用于决定什么值放弃,什么值保留)产生一个新的候选值从而将后选值在细胞状态中进行更新,

Seq2seq模型_第9张图片

  所以在细胞更新状态的时候,主要做的两件事儿就是决定哪些历史信息该流入当前细胞中(遗忘门控制)(主要受到哪些单词的影响),哪些新的信息该流入细胞中(输入门控制)(误差是由什么引起的)

Seq2seq模型_第10张图片

  输出门:

  根据新的细胞状态ct,进行输出

Seq2seq模型_第11张图片

  输入门控制这当前输入值有多少信息流入到当前的计算中,遗忘门控制着历史信息中有多少信息流入到当前计算中,输出门控制着输出值中有多少信息流入到隐层中。

二、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

你可能感兴趣的:(文本挖掘)