seq2seq 모델의 원리와 구현

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

Supongo que te gusta

Origin blog.csdn.net/tostq/article/details/129801771
Recomendado
Clasificación