seq2seq는 이름에서 알 수 있듯이 시퀀스에서 시퀀스를 생성하는 것을 말하며 기계 번역 분야에서 널리 사용되며 RNN으로 구성된 인코더 세트와 디코더 세트로 구성됩니다.
위 그림의 파란 모듈은 인코더로 임베딩 모듈을 이용하여 단어를 단어 벡터로 변환하고 GRU 다층 루프 모듈을 이용하여 은닉층 벡터를 생성하며 그 구조는 다음과 같으며 2계층 GRU이다. 입력이 단어의 원-핫 벡터인 루프 네트워크는 모든 단어를 인코더에 입력한 후 인코더의 최종 숨겨진 레이어 벡터를 디코더 모듈에 출력합니다.
인코더의 네트워크 구조는 다음 패들 코드로 설명할 수 있습니다.
#@save
class Seq2SeqEncoder(nn.Layer):
"""用于序列到序列学习的循环神经网络编码器"""
def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
dropout=0, **kwargs):
super(Seq2SeqEncoder, self).__init__(**kwargs)
weight_ih_attr = paddle.ParamAttr(initializer=nn.initializer.XavierUniform())
weight_hh_attr = paddle.ParamAttr(initializer=nn.initializer.XavierUniform())
# 嵌入层
self.embedding = nn.Embedding(vocab_size, embed_size)
self.rnn = nn.GRU(embed_size,
num_hiddens,
num_layers,
direction="forward", # foward指从序列开始到序列结束的单向GRU网络方向,bidirectional指从序列开始到序列结束,又从序列结束到开始的双向GRU网络方向
dropout=dropout,
time_major=True, # time_major为True,则Tensor的形状为[time_steps,batch_size,input_size],否则为[batch_size,time_steps,input_size]
weight_ih_attr=weight_ih_attr,
weight_hh_attr=weight_hh_attr)
def forward(self, X, *args):
# 输出'X'的形状:(batch_size, num_steps, embed_size)
X = self.embedding(X)
# 在循环神经网络模型中,第一个轴对应于时间步
X = X.transpose([1, 0, 2])
# 如果未提及状态,则默认为0
output, state = self.rnn(X)
# PaddlePaddle的GRU层output的形状:(batch_size, time_steps, num_directions * num_hiddens),
# 需设定time_major=True,指定input的第一个维度为time_steps
# state[0]的形状:(num_layers,batch_size,num_hiddens)
return output, state
디코더도 2계층 GRU 순환 신경망을 주성분으로 구성하는데, 입력 및 임베딩 모듈 외에 단어 벡터를 원-핫 형태로 복원하기 위한 선형 모듈도 출력에 있다. 그 구조는 아래 그림과 같으며 초기의 경우 디코더의 은닉층 벡터를 인코더의 최종 출력 은닉층 벡터로 설정하고 디코더의 입력은 인코더 및 이전 디코더 라운드의 예측된 출력 cocat.
디코더의 네트워크 구조는 다음 패들 코드로 설명할 수 있습니다.
class Seq2SeqDecoder(nn.Layer):
"""用于序列到序列学习的循环神经网络解码器"""
def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
dropout=0, **kwargs):
super(Seq2SeqDecoder, self).__init__(**kwargs)
self.embedding = nn.Embedding(vocab_size, embed_size)
weight_attr = paddle.ParamAttr(initializer=nn.initializer.XavierUniform())
weight_ih_attr = paddle.ParamAttr(initializer=nn.initializer.XavierUniform())
weight_hh_attr = paddle.ParamAttr(initializer=nn.initializer.XavierUniform())
self.rnn = nn.GRU(embed_size + num_hiddens, num_hiddens, num_layers, dropout=dropout,
time_major=True, weight_ih_attr=weight_ih_attr,weight_hh_attr=weight_hh_attr)
self.dense = nn.Linear(num_hiddens, vocab_size, weight_attr=weight_attr)
def init_state(self, enc_outputs, *args):
return enc_outputs[1]
def forward(self, X, state):
# 输出'X'的形状:(batch_size,num_steps,embed_size)
X = self.embedding(X).transpose([1, 0, 2]) # shape: (num_steps,batch_size,embed_size)
# 广播context,使其具有与X相同的num_steps
context = state[-1].tile([X.shape[0], 1, 1])
X_and_context = paddle.concat((X, context), 2)
output, state = self.rnn(X_and_context, state)
output = self.dense(output).transpose([1, 0, 2])
# output的形状:(batch_size,num_steps,vocab_size)
# state[0]的形状:(num_layers,batch_size,num_hiddens)
return output, state
seq2seq 모델을 구성하는 최종 코드는 다음과 같습니다.
#@save
class EncoderDecoder(nn.Layer):
"""编码器-解码器架构的基类"""
def __init__(self, encoder, decoder, **kwargs):
super(EncoderDecoder, self).__init__(**kwargs)
self.encoder = encoder
self.decoder = decoder
def forward(self, enc_X, dec_X, *args):
enc_outputs = self.encoder(enc_X, *args)
dec_state = self.decoder.init_state(enc_outputs, *args)
return self.decoder(dec_X, dec_state)
또 다른 중요한 문제는 seq2seq 모델의 손실 함수입니다. 기계 번역의 손실 함수는 분류와 유사하여 교차 엔트로피 손실 함수로 표현할 수 있지만 동일한 배치의 중복 부분은 마스크를 통해 제거됩니다.구체적인 손실 함수 코드는 다음과 같습니다.
class MaskedSoftmaxCELoss(nn.CrossEntropyLoss):
"""带遮蔽的softmax交叉熵损失函数"""
def sequence_mask(self, X, valid_len, value=0):
"""在序列中屏蔽不相关的项"""
maxlen = X.shape[1]
mask = paddle.arange((maxlen), dtype=paddle.float32)[None, :] < valid_len[:, None]
Xtype = X.dtype
X = X.astype(paddle.float32)
X[~mask] = float(value)
return X.astype(Xtype)
# pred的形状:(batch_size,num_steps,vocab_size)
# label的形状:(batch_size,num_steps)
# valid_len的形状:(batch_size,)
def forward(self, pred, state, label, valid_len):
weights = paddle.ones_like(label)
weights = self.sequence_mask(weights, valid_len)
self.reduction='none'
unweighted_loss = super(MaskedSoftmaxCELoss, self).forward(
pred, label)
weighted_loss = (unweighted_loss * weights).mean(axis=1)
return weighted_loss