之前只学习了seq2seq模型的原理,包括其中的注意力机制,大致原理是了解了,最近想动手实现模型结构,但是在动手落地的时候又遇到一些细节问题,现对疑惑问题的相关理解进行记录。
Q1:seq2seq模型中包含编码器和解码器(RNN/LSTM),编码器的的会对输入句子进行编码得到语义向量,并作为解码器的输入,那么编码器的输出是如何作为解码器的输入呢,在哪里输入到解码器?
A1:编码器编码得到的语义向量(即源句子的语义向量)是指编码器隐藏层向量,将编码向量输入到解码器是指使用编码器的隐藏层向量来初始化解码器隐藏层向量。
Q2:在解码器生成目标句子的时候,会根据之前时刻的生成内容来生成当前时刻的内容,例如以“I am so hungry”为目标句子,在生成"so"的时候会参考“I”和“am”,但是是如何参考的呢,是通过循环的方式来将"so"的语义向量输入到解码器中吗?
A2:原理确实是需要将上一时刻的语义向量输入到解码器,但是这一步操作是无需人为操作的,循环神经网络天然具备这样的功能,这一步已经隐藏在循环神经网络中完成,无需人为额外编写代码。
Q3:训练过程中解码器是以整个句子为单位来计算分类的交叉熵吗?例如训练数据集中包含x个目标句子[sen1,sen2…senx],类比于文本分类任务,就将解码器生成的目标句子作为模型预测的类别,计算“预测类别”和“真实类别”之间的交叉熵
A3:经过查阅资料发现解码器还是将每一个时间步预测的字作为目标类别,类似于NER中的序列标注,对每一个字生成一个实体标签,此处解码器会对输入句子中的每一个字生成一个目标句子中的字。即对RNN模型中的output进行预测,而不是对hidden进行预测。
Q4:seq2seq中的注意力机制是指解码器当前时刻的隐藏层与编码器各个时刻的隐藏层之间分别计算注意力值,但是编码器各个时刻的隐藏层怎么获得呢?
A4:可以将output中的向量作为编码器各个时刻的隐藏层
output,hidden = RNN(inputs)
其中output的维度是(batch,seq_len,embedd_size),seq_len维度中的每一个向量即可作为每个时间步的隐藏层向量。
Q5:seq2seq中的attention机制,到底是什么时候进行attention计算呢?计算attentionhou的语义向量如何使用?
A5:使用解码器当前时间步的隐藏层向量和编码器每一个时间步的隐藏层向量计算attention值,然后依据attention值对编码器每一个时间步的向量进行加权求和,此时即得到了通过注意力机制计算得到的语义向量;该语义向量可以用于预测目标词,也可以用于下一时间步隐藏层向量的计算。
简单记录代码如下
import torch
import torch.nn as nn
class tool():
def process(self,data,label):
'''对数据进行处理'''
words = []
if label == 'ZH':#对中文进行处理
for line in data:
words.extend(list(line))
elif label == 'EN':#对英文进行处理
for line in data:
words.extend(line.split(' '))
dis_words = set(words)
dis_words = list(dis_words)
dis_words.insert(0.'P')#插入补充长度的‘P’
return dis_words
def word2index(self,dis_words,data,max_len,label):
'''将字转换成对应的索引,一一对应,低于最大长度进行补零,超过最大长度直接截断'''
w2i = []
if label == 'CH':
for line in data:
indexes = []
if len(line) > max_len:
line = line[:max_len]#截断
else:
line = line + 'P' * (max_len - len(line))#补零
for i,word in enumerate(line):
indexes.append(dis_words.index(word))
w2i.append(indexes)
if label == 'EN':
for line in data:
indexes = []
line_split = line.split(' ')
if len(line_split) > max_len:
line_split = line_split[:max_len]#截断
else:
line_split = line_split + ['P'] * (max_len - len(line))#补零
for i,word in enumerate(line_split):
indexes.append(dis_words.index(word))
w2i.append(indexes)
class Encoder(nn.Module):
def __init__(self,input_dim,hidden_dim,vocab_size):
super(Encoder,self).__init__()
self.embedding_layer = nn.Embedding(vocab_size,input_dim)
self.lstm_layer = nn.LSTM(input_dim,hidden_dim)
def forward(self,inputs):
embedding = self.embedding_layer(inputs)
lstm_output,lstm_hidden = self.lstm_layer(embedding)
return lstm_output,lstm_hidden
class Decoder(nn.Module):
def __init__(self,input_dim,hidden_dim,num_class):
super(Decoder,self).__init__()
self.lstm_layer = nn.LSTM(input_dim,hidden_dim)
self.linear_layer = nn.Linear(hidden_dim,num_class)
def forward(self,inputs,hidden_states):
lstm_output,lstm_hidden = self.lstm_layer(inputs,hidden_states)
linear_out = self.linear_layer(lstm_output)
return linear_out
source_data = ['今天仍然是晴天','我喜欢吃苹果']
target_data = ['Today is sunny','I like apple']
tool = tool()
dis_words_source = tool.process(data = source_data,label == 'CH')
dis_words_target = tool.process(data = source_data,label == 'EN')
w2i_source = tool.word2index(dis_words = dis_words_source,data=source_data,label='CH',max_len = 10)
w2i_target = tool.word2index(dis_words = dis_words_target,data=target_data,label='EN',max_len = 10)
#继续补充
参考:
https://zhuanlan.zhihu.com/p/47063917