📜  机器学习中的 seq2seq 模型(1)

📅  最后修改于: 2023-12-03 15:26:32.949000             🧑  作者: Mango

机器学习中的 Seq2Seq 模型

介绍

Seq2Seq模型(Sequence-to-Sequence Model)是一种可以用来处理序列转化问题的深度学习模型。它由两个循环神经网络(RNN)组成,一个是Encoder RNN,用来将输入序列转化为一个上下文向量,另一个是Decoder RNN,用来将这个上下文向量转化为输出序列。Seq2Seq模型广泛应用于机器翻译、对话系统、自动摘要等领域。

原理
Encoder

Encoder RNN将输入序列 $X={x_1,x_2,\cdots,x_T}$ 转化为一个上下文向量 $C$,定义如下:

$$ h_t = f(x_t, h_{t-1}) \ C = g(h_1, h_2, \cdots, h_T) $$

其中 $h_t$ 是Encoder RNN在时刻 $t$ 的隐藏状态,$f$ 和 $g$ 分别是Encoder RNN的输入函数和输出函数。常用的输入函数包括LSTM(Long Short-Term Memory)和GRU(Gated Recurrent Unit),而输出函数通常是平均池化或只采用最后一个隐藏状态。

Decoder

Decoder RNN将上下文向量 $C$ 转化为输出序列 $Y={y_1,y_2,\cdots,y_T}$,定义如下:

$$ s_0 = h_T \ s_t = h(y_{t-1}, s_{t-1}) \ P(y_t|y_{<t}, X) = softmax(Ws_t+b) $$

其中 $s_t$ 是Decoder RNN在时刻 $t$ 的隐藏状态,$h$ 是Decoder RNN的输入函数,用来输入上一个时刻的输出和当前时刻的隐藏状态,$W$ 和 $b$ 是模型参数。最后一步是计算输出的概率分布,可以使用softmax函数。

Training

模型的训练过程通常采用最大似然估计,具体步骤如下:

  • 将输入序列送入Encoder RNN,计算上下文向量 $C$;
  • 将开始符号(比如"")送入Decoder RNN,计算第一个输出 $y_1$;
  • 将 $y_1$ 送入Decoder RNN,计算第二个输出 $y_2$,以此类推,直到输出结束符号(比如"")或达到最大输出长度;
  • 计算标准答案和模型输出之间的交叉熵损失,使用反向传播算法更新模型参数。
Python实现

以下是使用Python和PyTorch实现Seq2Seq模型的代码片段:

import torch
import torch.nn as nn

class Encoder(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers=1):
        super(Encoder, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        self.embedding = nn.Embedding(input_size, hidden_size)
        self.rnn = nn.LSTM(hidden_size, hidden_size, num_layers, batch_first=True)

    def forward(self, input):
        embedded = self.embedding(input)  # [batch_size, sequence_length, hidden_size]
        output, (hidden, cell) = self.rnn(embedded)  # output: [batch_size, sequence_length, hidden_size]
        return hidden, cell

class Decoder(nn.Module):
    def __init__(self, output_size, hidden_size, num_layers=1):
        super(Decoder, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        self.embedding = nn.Embedding(output_size, hidden_size)
        self.rnn = nn.LSTM(hidden_size, hidden_size, num_layers, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, input, hidden, cell):
        input = input.unsqueeze(1)  # [batch_size, 1]
        embedded = self.embedding(input)  # [batch_size, 1, hidden_size]
        output, (hidden, cell) = self.rnn(embedded, (hidden, cell))
        output = output.squeeze(1)  # [batch_size, hidden_size]
        output = self.fc(output)  # [batch_size, output_size]
        return output, hidden, cell

class Seq2Seq(nn.Module):
    def __init__(self, input_size, output_size, hidden_size, num_layers=1):
        super(Seq2Seq, self).__init__()
        self.encoder = Encoder(input_size, hidden_size, num_layers)
        self.decoder = Decoder(output_size, hidden_size, num_layers)

    def forward(self, input, target, teacher_forcing_ratio=0.5):
        batch_size = input.size(0)
        target_length = target.size(1)
        output_vocab_size = self.decoder.fc.out_features
        outputs = torch.zeros(batch_size, target_length, output_vocab_size).to(self.device)
        hidden, cell = self.encoder(input)
        input = target[:, 0]
        for t in range(1, target_length):
            output, hidden, cell = self.decoder(input, hidden, cell)
            outputs[:, t] = output
            teacher_force = torch.rand(1).item() < teacher_forcing_ratio
            top1 = output.max(1)[1]
            input = (target[:, t] if teacher_force else top1)
        return outputs

这个代码片段定义了一个Seq2Seq类,包含了Encoder和Decoder两个子类。在训练过程中,通过输入和目标序列计算交叉熵损失并更新模型参数;在测试过程中,通过输入序列和已知的开始符号预测输出序列。