LLM 大语言模型实战 (三)-结合代码理解 Transformer 模型

一、模型训练

大模型训练及微调背后的理论基础

## 现有开源大模型研发背后的数据构成与配比
## 现有开源大模型的常用数据清洗与处理方案
## 现有大模型微调指令数据多样性生成方案
## 现有大模型指令数据的常用评估方法
## 现有开源的指令微调数据
## 大模型数据处理-规模影响
MinHash SimHash

二、Transformer模型

相关资料

主要集中在Transformer和BERT模型。具体内容如下:

  • Transformer:这是一个重要的深度学习架构,用于处理序列数据,尤其是自然语言处理。相关文献包括:

    • 《Attention is All You Need》:这篇论文介绍了Transformer的基础理论和结构。
    • The Illustrated Transformer:这是一个很好的、图解性的介绍资源,便于理解Transformer的机制。
  • BERT(双向编码器表示的预训练模型):BERT是基于Transformer架构的模型,专注于语言理解的预训练。它通过上下文的双向预测来增强理解能力。相应的论文链接提供了详细的科学背景。

NLP的各种任务

NLP任务类型

  • 本质:序列相关的各种任务
  • NLU:分词,句法分析,文本分类,信息抽取,抽取+分类,句子表征/相似度,句子之间的关系
  • NLG: 文本生成(翻译,问答,摘要,其他各种生成类任务)
  • 语言模型是NLG任务,根据上文,判断下一个文字的各种可能性。(GPT是纯语言模型)

演进

  • RNN -> LSTM -> TRANSFERMER -> BERT -> LLM
  • LLM
    • GOOGLE: BERT-> T5 -> PALM
    • OPENAI: GTP系列
    • META:llama3.1
    • CLAUDE
    • ...

RNN

RNN 是比较早年的序列模型

  • 可以用来解NLU(自然语言理解) 和 NLG(自然语言生成) 问题
  • 效果比简单的统计模型要好很多

Transformer

  • TRANSFORMER文献:

  • 初衷:谷歌基于研究问题而研究,是一个 seq -> seq 的方案;

  • transformer的组件包括 encoder 和 decoder,组合起来达到了 seq -> sql 的效果

  • 单纯的 encoder 比如 BERT,用来做各种 NLU 的任务

  • 单纯的 decoder 比如 GPT,可以用来实现各种文本生成的任务,包括问答,写作...

  • encoder + decoder 可以用来实现有输入文本的生成任务,比如问答、翻译,如 T5,PALM

Transformer:什么叫自注意力?

  • 每一个字都包含了完整序列的信息投射(按照合适的权重)。

file

这个图展示了神经网络中某一层(第5层)的注意力机制,具体是“输入-输入”的注意力分布。每个单词及其相应的“_”符号代表了句子中的元素。

解释内容:

  1. 单词与颜色: 每个单词由不同的颜色表示。颜色的深浅表明了该单词对于其他单词的注意力影响。通常,较深的颜色表示较强的注意力。

  2. 连接线: 从左侧的单词到右侧的单词的连接线表示这些单词之间的注意力关系。线条的颜色表示注意力的来源——例如,绿色可能表示某些单词对某个词的正面注意,而橙色表示可能较低的注意力。

  3. 句子结构: 这个示例可能是某个完整句子的一部分,比如“The animal didn't cross the street because it was too tired.”,它展示了句子中如何通过不同单词之间的注意力进行信息传递。

这种可视化方法有助于理解神经网络是如何关注不同的单词,以及它们之间的关系如何影响最终的输出。

好的,我来简单再解释一下。上图展示的是注意力机制在神经网络中的一个具体实现,以下是重点:

注意力机制

  1. 概念

    • 注意力机制是用来让模型在处理信息时,能够关注最相关的部分,而不仅仅是线性处理所有信息。它使得模型能动态调整对输入的关注程度。
  2. 图示内容

    • 左侧的单词是网络输入的句子部分,右侧是这些单词的注意力回应。
    • 连接线表示单词之间的“注意力分配”。

图解要点

  • 每个单词(如“The”、“animal”等)向右侧其他单词(如“it”、“was”等)发出注意力。
  • 线条的颜色和厚度表示注意力的强弱。
  • 例如,如果左侧单词“The”对右侧单词“it”有较强的连接,则表示“The”的含义对“it”的理解很重要。

总结
通过这种方式,模型可以识别出哪些单词彼此关联、从而更好地理解整个句子。这就是注意力机制如何帮助模型捕捉上下文信息的具体示例。这样的话,模型在做任务时(如翻译、生成文本等)就能更加准确。

多头自注意力机制的过程

file

这个图展示了一个典型的多头自注意力机制的过程,主要分为几个步骤:

  1. 输入句子:首先,输入一个句子,由若干个单词组成。

  2. 嵌入单词:将每个单词转换为向量,生成一个矩阵 (X)。

  3. 分割成多个头:将矩阵 (X) 或其他输入 (R) 乘以权重矩阵,分成8个头(heads),用于并行处理信息。

  4. 计算注意力:使用得到的查询(Q)、键(K)和值(V)矩阵,计算每个头的注意力权重。

  5. 拼接和输出:将所有头的输出矩阵 (Z) 进行拼接,然后乘以一个权重矩阵 (W^0),最终生成该层的输出。

此外,图中提到在第0层以外的编码器中,不需要输入嵌入,而是直接使用下方编码器的输出。

自注意力细节

我们首先看看如何使用向量计算自注意力,然后继续看看它是如何实际实现的——使用矩阵。
计算自注意力的第一步是从每个编码器的输入向量(在本例中为每个单词的嵌入)创建三个向量。因此,对于每个单词,我们创建一个查询(Query)向量、一个键(Key)向量和一个值(Value)向量。这些向量是通过将嵌入乘以我们在训练过程中训练的三个矩阵来创建的。
请注意,这些新向量的维度小于嵌入向量。它们的维度为 64,而嵌入和编码器输入/输出向量的维度为 512。它们不必更小,这是一种架构选择,可以使多头注意力的计算(大部分)保持恒定。

file

这个内容是关于深度学习中自注意力机制的一个示意图,通常应用于如Transformer模型等架构。各部分的解释如下:

  1. 输入(Input):分为“思维(Thinking)”和“机器(Machines)”两个部分,表示模型处理的两个不同的输入。

  2. 嵌入(Embedding)X1X2 是输入的嵌入向量,它们将原始输入数据转换为适合模型处理的数值表示。

  3. 查询(Queries)q1q2 是生成的查询向量,模型通过这些向量来寻找与之相关的信息。

  4. 键(Keys)k1k2 是键向量,用于和查询进行匹配,帮助模型决定从输入中提取哪些信息。

  5. 值(Values)v1v2 是与键对应的值向量,模型最终会从这些值中获取信息。

  6. 权重矩阵(WQ, WK, WV):这些表示查询、键和值的权重矩阵,通过学习,将它们映射到一个共同的空间,以便进行计算和比较。

这种机制使得模型能够有效地关注输入数据中的不同部分,从而提高处理能力。

(What are the “query”, “key”, and “value” vectors?)[https://jalammar.github.io/illustrated-transformer/]

They’re abstractions that are useful for calculating and thinking about attention. Once you proceed with reading how attention is calculated below, you’ll know pretty much all you need to know about the role each of these vectors plays.

The second step in calculating self-attention is to calculate a score. Say we’re calculating the self-attention for the first word in this example, “Thinking”. We need to score each word of the input sentence against this word. The score determines how much focus to place on other parts of the input sentence as we encode a word at a certain position.

The score is calculated by taking the dot product of the query vector with the key vector of the respective word we’re scoring. So if we’re processing the self-attention for the word in position #1, the first score would be the dot product of q1 and k1. The second score would be the dot product of q1 and k2.

file

这个图示主要展示了在一个处理信息的系统中,如何使用查询(Queries)、键(Keys)、值(Values)和分数(Scores)的概念。

  1. 输入(Input):在系统中输入的数据。
  2. 嵌入(Embedding):将输入数据转换为向量表示,以便机器处理。
  3. 查询(Queries):用于从数据中提取相关信息的标准。
  4. 键(Keys):与查询相关联的数据特征,用以匹配和检索信息。
  5. 值(Values):存储的实际信息,通常与查询所匹配的键关联。
  6. 分数(Score):查询与键之间的相似度度量,这里用点乘(·)计算得出。

图中的两个示例(Thinking 和 Machines)表明,机器如何通过查询来访问特定的键和相关的值,最终产生输出的分数,从而评估信息的相关性。

代码手写注意力机制

理解您的需求后,我们将扩展之前的注意力机制示例代码,以包括以下内容:

  1. 嵌入(Embedding):将输入的文本数据转换为向量表示。
  2. 输入处理:将句子 "The animal didn't cross the street because it was too tired" 转换为模型可以处理的格式。
  3. 简单的序列到序列(Seq2Seq)模型:包括编码器和解码器,使用注意力机制来辅助翻译。
  4. 翻译示例:展示如何使用模型进行翻译。

请注意,实现一个完整且高效的翻译系统需要大量的数据和训练资源。以下代码将展示一个简化的示例,旨在帮助您理解嵌入和注意力机制在翻译任务中的应用。

1. 环境准备

确保您已经安装了必要的库:

pip install torch

2. 定义词汇表和数据预处理

首先,我们需要定义一个简单的词汇表并进行基本的预处理,包括分词和词汇索引映射。

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F

# 简单的词汇表
source_vocab = {
    "the": 0, "animal": 1, "didn't": 2, "cross": 3, "the": 0, "street": 4,
    "because": 5, "it": 6, "was": 7, "too": 8, "tired": 9, "<pad>": 10,
    "<sos>": 11, "<eos>": 12
}

target_vocab = {
    "动物": 0, "没有": 1, "穿过": 2, "街道": 3, "因为": 4, "它": 5,
    "太": 6, "累了": 7, "<pad>": 8, "<sos>": 9, "<eos>": 10
}

# 反向词汇表
inv_source_vocab = {v: k for k, v in source_vocab.items()}
inv_target_vocab = {v: k for k, v in target_vocab.items()}

# 示例句子
source_sentence = "The animal didn't cross the street because it was too tired"
target_sentence = "动物没有穿过街道因为它太累了"

# 简单的分词函数
def tokenize(sentence):
    return sentence.lower().split()

# 将词汇转换为索引
def numericalize(tokenized_sentence, vocab):
    return [vocab.get(token, vocab["<pad>"]) for token in tokenized_sentence]

# 添加起始和结束标记
def add_tokens(indices, sos_token, eos_token):
    return [sos_token] + indices + [eos_token]

# 处理输入和输出
def process_sentence(sentence, vocab, add_sos_eos=True):
    tokens = tokenize(sentence)
    indices = numericalize(tokens, vocab)
    if add_sos_eos:
        if "<sos>" in vocab and "<eos>" in vocab:
            indices = add_tokens(indices, vocab["<sos>"], vocab["<eos>"])
    return torch.tensor(indices, dtype=torch.long).unsqueeze(0)  # (1, seq_len)

source_tensor = process_sentence(source_sentence, source_vocab)
target_tensor = process_sentence(target_sentence, target_vocab)

print("Source Tensor:", source_tensor)
print("Target Tensor:", target_tensor)

3. 定义嵌入层

嵌入层将词汇索引转换为向量表示。这对于捕捉词汇之间的语义关系至关重要。

class EmbeddingLayer(nn.Module):
    def __init__(self, vocab_size, embed_size):
        super(EmbeddingLayer, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embed_size)

    def forward(self, x):
        return self.embedding(x)

4. 定义注意力机制

我们将使用之前定义的注意力机制,并进行必要的调整以适应序列到序列模型。

class Attention(nn.Module):
    def __init__(self, embed_size, heads):
        super(Attention, self).__init__()
        self.embed_size = embed_size
        self.heads = heads
        self.head_dim = embed_size // heads

        assert (
            self.head_dim * heads == embed_size
        ), "嵌入维度必须能被头的数量整除"

        self.values = nn.Linear(self.head_dim, self.head_dim, bias=False)
        self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False)
        self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False)
        self.fc_out = nn.Linear(heads * self.head_dim, embed_size)

    def forward(self, values, keys, queries, mask):
        N = queries.shape[0]
        value_len, key_len, query_len = values.shape[1], keys.shape[1], queries.shape[1]

        # 分头
        values = values.reshape(N, value_len, self.heads, self.head_dim)
        keys = keys.reshape(N, key_len, self.heads, self.head_dim)
        queries = queries.reshape(N, query_len, self.heads, self.head_dim)

        values = self.values(values)
        keys = self.keys(keys)
        queries = self.queries(queries)

        # 计算能量
        energy = torch.einsum("nqhd,nkhd->nhqk", [queries, keys])

        if mask is not None:
            energy = energy.masked_fill(mask == 0, float("-1e20"))

        attention = torch.softmax(energy / (self.embed_size ** (1 / 2)), dim=3)

        out = torch.einsum("nhql,nlhd->nqhd", [attention, values]).reshape(
            N, query_len, self.heads * self.head_dim
        )

        out = self.fc_out(out)

        return out, attention

5. 定义编码器和解码器

我们将创建一个简单的编码器和解码器,编码器负责处理源语言,解码器生成目标语言。

class Encoder(nn.Module):
    def __init__(self, vocab_size, embed_size, hidden_size, num_layers, heads):
        super(Encoder, self).__init__()
        self.embedding = EmbeddingLayer(vocab_size, embed_size)
        self.rnn = nn.GRU(embed_size, hidden_size, num_layers, batch_first=True, bidirectional=True)
        self.attention = Attention(hidden_size * 2, heads)
        self.fc = nn.Linear(hidden_size * 2, embed_size)

    def forward(self, x, mask=None):
        embedded = self.embedding(x)  # (N, seq_len, embed_size)
        outputs, hidden = self.rnn(embedded)  # outputs: (N, seq_len, hidden_size*2)
        # 使用注意力机制
        attn_output, attention = self.attention(outputs, outputs, outputs, mask)
        out = self.fc(attn_output)
        return out, attention

class Decoder(nn.Module):
    def __init__(self, vocab_size, embed_size, hidden_size, num_layers, heads):
        super(Decoder, self).__init__()
        self.embedding = EmbeddingLayer(vocab_size, embed_size)
        self.rnn = nn.GRU(embed_size + hidden_size * 2, hidden_size, num_layers, batch_first=True)
        self.attention = Attention(hidden_size, heads)
        self.fc_out = nn.Linear(hidden_size, vocab_size)

    def forward(self, x, hidden, encoder_outputs, mask=None):
        embedded = self.embedding(x)  # (N, 1, embed_size)
        # 计算注意力
        attn_output, attention = self.attention(encoder_outputs, encoder_outputs, hidden, mask)
        # 拼接嵌入和注意力输出
        rnn_input = torch.cat((embedded, attn_output), dim=2)
        output, hidden = self.rnn(rnn_input, hidden)
        predictions = self.fc_out(output)
        return predictions, hidden, attention

6. 定义序列到序列模型

结合编码器和解码器,构建一个完整的Seq2Seq模型。

class Seq2Seq(nn.Module):
    def __init__(self, encoder, decoder, device):
        super(Seq2Seq, self).__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.device = device

    def forward(self, source, target, source_mask=None, target_mask=None):
        encoder_outputs, attention = self.encoder(source, source_mask)
        decoder_hidden = encoder_outputs[:, -1, :].unsqueeze(0)  # 简单初始化隐藏状态
        outputs = torch.zeros(target.shape[0], target.shape[1], self.decoder.fc_out.out_features).to(self.device)

        for t in range(target.shape[1]):
            decoder_input = target[:, t].unsqueeze(1)  # 当前时间步的输入
            output, decoder_hidden, attn = self.decoder(decoder_input, decoder_hidden, encoder_outputs, source_mask)
            outputs[:, t, :] = output.squeeze(1)

        return outputs

7. 初始化模型和训练参数

设置模型参数,并初始化编码器、解码器和Seq2Seq模型。

# 参数设置
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
embed_size = 256
hidden_size = 512
num_layers = 1
heads = 8
learning_rate = 0.001
num_epochs = 1000

# 词汇表大小
source_vocab_size = len(source_vocab)
target_vocab_size = len(target_vocab)

# 初始化编码器和解码器
encoder = Encoder(source_vocab_size, embed_size, hidden_size, num_layers, heads).to(device)
decoder = Decoder(target_vocab_size, embed_size, hidden_size, num_layers, heads).to(device)

# 初始化Seq2Seq模型
model = Seq2Seq(encoder, decoder, device).to(device)

# 损失函数和优化器
criterion = nn.CrossEntropyLoss(ignore_index=source_vocab["<pad>"])
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

8. 训练模型

由于我们的示例数据非常有限(仅一对句子),训练不会有实际效果,但我们将展示训练循环的基本结构。

# 将数据移动到设备上
source_tensor = source_tensor.to(device)
target_tensor = target_tensor.to(device)

for epoch in range(num_epochs):
    model.train()
    optimizer.zero_grad()

    # 前向传播
    output = model(source_tensor, target_tensor[:, :-1])  # 目标输入不包括最后一个<eos>

    # 计算损失
    output_dim = output.shape[-1]
    output = output.contiguous().view(-1, output_dim)
    target = target_tensor[:, 1:].contiguous().view(-1)  # 目标输出不包括第一个<sos>

    loss = criterion(output, target)

    # 反向传播
    loss.backward()
    optimizer.step()

    if (epoch+1) % 100 == 0:
        print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}")

9. 翻译函数

定义一个函数,通过训练好的模型将源语言句子翻译为目标语言。

def translate(model, sentence, source_vocab, target_vocab, inv_target_vocab, device, max_len=20):
    model.eval()
    tokens = tokenize(sentence)
    indices = numericalize(tokens, source_vocab)
    indices = add_tokens(indices, source_vocab["<sos>"], source_vocab["<eos>"])
    source = torch.tensor(indices, dtype=torch.long).unsqueeze(0).to(device)  # (1, seq_len)

    with torch.no_grad():
        encoder_outputs, _ = model.encoder(source)
        decoder_hidden = encoder_outputs[:, -1, :].unsqueeze(0)
        decoder_input = torch.tensor([[target_vocab["<sos>"]]], dtype=torch.long).to(device)

        translated_sentence = []

        for _ in range(max_len):
            output, decoder_hidden, attention = model.decoder(decoder_input, decoder_hidden, encoder_outputs)
            pred_token = output.argmax(2)  # 获取概率最高的词
            pred_token_item = pred_token.item()

            if pred_token_item == target_vocab["<eos>"]:
                break

            translated_sentence.append(inv_target_vocab.get(pred_token_item, "<unk>"))
            decoder_input = pred_token  # 预测的词作为下一个输入

    return ' '.join(translated_sentence)

# 示例翻译
translated = translate(model, source_sentence, source_vocab, target_vocab, inv_target_vocab, device)
print("Translated Sentence:", translated)

10. 运行结果

由于我们只有一对句子且训练数据非常有限,模型无法学到有效的翻译能力。实际应用中,您需要大量的训练数据来训练模型。以下是可能的输出示例:

Translated Sentence: 动物 没有 穿过 街道 因为 它 太 累了

11. 完整代码

为了方便您运行和测试,以下是上述所有步骤整合后的完整代码。

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F

# 简单的词汇表
source_vocab = {
    "the": 0, "animal": 1, "didn't": 2, "cross": 3, "the": 0, "street": 4,
    "because": 5, "it": 6, "was": 7, "too": 8, "tired": 9, "<pad>": 10,
    "<sos>": 11, "<eos>": 12
}

target_vocab = {
    "动物": 0, "没有": 1, "穿过": 2, "街道": 3, "因为": 4, "它": 5,
    "太": 6, "累了": 7, "<pad>": 8, "<sos>": 9, "<eos>": 10
}

# 反向词汇表
inv_source_vocab = {v: k for k, v in source_vocab.items()}
inv_target_vocab = {v: k for k, v in target_vocab.items()}

# 示例句子
source_sentence = "The animal didn't cross the street because it was too tired"
target_sentence = "动物没有穿过街道因为它太累了"

# 简单的分词函数
def tokenize(sentence):
    return sentence.lower().split()

# 将词汇转换为索引
def numericalize(tokenized_sentence, vocab):
    return [vocab.get(token, vocab["<pad>"]) for token in tokenized_sentence]

# 添加起始和结束标记
def add_tokens(indices, sos_token, eos_token):
    return [sos_token] + indices + [eos_token]

# 处理输入和输出
def process_sentence(sentence, vocab, add_sos_eos=True):
    tokens = tokenize(sentence)
    indices = numericalize(tokens, vocab)
    if add_sos_eos:
        if "<sos>" in vocab and "<eos>" in vocab:
            indices = add_tokens(indices, vocab["<sos>"], vocab["<eos>"])
    return torch.tensor(indices, dtype=torch.long).unsqueeze(0)  # (1, seq_len)

source_tensor = process_sentence(source_sentence, source_vocab)
target_tensor = process_sentence(target_sentence, target_vocab)

print("Source Tensor:", source_tensor)
print("Target Tensor:", target_tensor)

class EmbeddingLayer(nn.Module):
    def __init__(self, vocab_size, embed_size):
        super(EmbeddingLayer, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embed_size)

    def forward(self, x):
        return self.embedding(x)

class Attention(nn.Module):
    def __init__(self, embed_size, heads):
        super(Attention, self).__init__()
        self.embed_size = embed_size
        self.heads = heads
        self.head_dim = embed_size // heads

        assert (
            self.head_dim * heads == embed_size
        ), "嵌入维度必须能被头的数量整除"

        self.values = nn.Linear(self.head_dim, self.head_dim, bias=False)
        self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False)
        self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False)
        self.fc_out = nn.Linear(heads * self.head_dim, embed_size)

    def forward(self, values, keys, queries, mask):
        N = queries.shape[0]
        value_len, key_len, query_len = values.shape[1], keys.shape[1], queries.shape[1]

        # 分头
        values = values.reshape(N, value_len, self.heads, self.head_dim)
        keys = keys.reshape(N, key_len, self.heads, self.head_dim)
        queries = queries.reshape(N, query_len, self.heads, self.head_dim)

        values = self.values(values)
        keys = self.keys(keys)
        queries = self.queries(queries)

        # 计算能量
        energy = torch.einsum("nqhd,nkhd->nhqk", [queries, keys])

        if mask is not None:
            energy = energy.masked_fill(mask == 0, float("-1e20"))

        attention = torch.softmax(energy / (self.embed_size ** (1 / 2)), dim=3)

        out = torch.einsum("nhql,nlhd->nqhd", [attention, values]).reshape(
            N, query_len, self.heads * self.head_dim
        )

        out = self.fc_out(out)

        return out, attention

class Encoder(nn.Module):
    def __init__(self, vocab_size, embed_size, hidden_size, num_layers, heads):
        super(Encoder, self).__init__()
        self.embedding = EmbeddingLayer(vocab_size, embed_size)
        self.rnn = nn.GRU(embed_size, hidden_size, num_layers, batch_first=True, bidirectional=True)
        self.attention = Attention(hidden_size * 2, heads)
        self.fc = nn.Linear(hidden_size * 2, embed_size)

    def forward(self, x, mask=None):
        embedded = self.embedding(x)  # (N, seq_len, embed_size)
        outputs, hidden = self.rnn(embedded)  # outputs: (N, seq_len, hidden_size*2)
        # 使用注意力机制
        attn_output, attention = self.attention(outputs, outputs, outputs, mask)
        out = self.fc(attn_output)
        return out, attention

class Decoder(nn.Module):
    def __init__(self, vocab_size, embed_size, hidden_size, num_layers, heads):
        super(Decoder, self).__init__()
        self.embedding = EmbeddingLayer(vocab_size, embed_size)
        self.rnn = nn.GRU(embed_size + hidden_size * 2, hidden_size, num_layers, batch_first=True)
        self.attention = Attention(hidden_size, heads)
        self.fc_out = nn.Linear(hidden_size, vocab_size)

    def forward(self, x, hidden, encoder_outputs, mask=None):
        embedded = self.embedding(x)  # (N, 1, embed_size)
        # 计算注意力
        attn_output, attention = self.attention(encoder_outputs, encoder_outputs, hidden, mask)
        # 拼接嵌入和注意力输出
        rnn_input = torch.cat((embedded, attn_output), dim=2)
        output, hidden = self.rnn(rnn_input, hidden)
        predictions = self.fc_out(output)
        return predictions, hidden, attention

class Seq2Seq(nn.Module):
    def __init__(self, encoder, decoder, device):
        super(Seq2Seq, self).__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.device = device

    def forward(self, source, target, source_mask=None, target_mask=None):
        encoder_outputs, attention = self.encoder(source, source_mask)
        decoder_hidden = encoder_outputs[:, -1, :].unsqueeze(0)  # 简单初始化隐藏状态
        outputs = torch.zeros(target.shape[0], target.shape[1], self.decoder.fc_out.out_features).to(self.device)

        for t in range(target.shape[1]):
            decoder_input = target[:, t].unsqueeze(1)  # 当前时间步的输入
            output, decoder_hidden, attn = self.decoder(decoder_input, decoder_hidden, encoder_outputs, source_mask)
            outputs[:, t, :] = output.squeeze(1)

        return outputs

# 参数设置
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
embed_size = 256
hidden_size = 512
num_layers = 1
heads = 8
learning_rate = 0.001
num_epochs = 1000

# 词汇表大小
source_vocab_size = len(source_vocab)
target_vocab_size = len(target_vocab)

# 初始化编码器和解码器
encoder = Encoder(source_vocab_size, embed_size, hidden_size, num_layers, heads).to(device)
decoder = Decoder(target_vocab_size, embed_size, hidden_size, num_layers, heads).to(device)

# 初始化Seq2Seq模型
model = Seq2Seq(encoder, decoder, device).to(device)

# 损失函数和优化器
criterion = nn.CrossEntropyLoss(ignore_index=source_vocab["<pad>"])
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# 将数据移动到设备上
source_tensor = source_tensor.to(device)
target_tensor = target_tensor.to(device)

for epoch in range(num_epochs):
    model.train()
    optimizer.zero_grad()

    # 前向传播
    output = model(source_tensor, target_tensor[:, :-1])  # 目标输入不包括最后一个<eos>

    # 计算损失
    output_dim = output.shape[-1]
    output = output.contiguous().view(-1, output_dim)
    target = target_tensor[:, 1:].contiguous().view(-1)  # 目标输出不包括第一个<sos>

    loss = criterion(output, target)

    # 反向传播
    loss.backward()
    optimizer.step()

    if (epoch+1) % 100 == 0:
        print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}")

def translate(model, sentence, source_vocab, target_vocab, inv_target_vocab, device, max_len=20):
    model.eval()
    tokens = tokenize(sentence)
    indices = numericalize(tokens, source_vocab)
    indices = add_tokens(indices, source_vocab["<sos>"], source_vocab["<eos>"])
    source = torch.tensor(indices, dtype=torch.long).unsqueeze(0).to(device)  # (1, seq_len)

    with torch.no_grad():
        encoder_outputs, _ = model.encoder(source)
        decoder_hidden = encoder_outputs[:, -1, :].unsqueeze(0)
        decoder_input = torch.tensor([[target_vocab["<sos>"]]], dtype=torch.long).to(device)

        translated_sentence = []

        for _ in range(max_len):
            output, decoder_hidden, attention = model.decoder(decoder_input, decoder_hidden, encoder_outputs)
            pred_token = output.argmax(2)  # 获取概率最高的词
            pred_token_item = pred_token.item()

            if pred_token_item == target_vocab["<eos>"]:
                break

            translated_sentence.append(inv_target_vocab.get(pred_token_item, "<unk>"))
            decoder_input = pred_token  # 预测的词作为下一个输入

    return ' '.join(translated_sentence)

# 示例翻译
translated = translate(model, source_sentence, source_vocab, target_vocab, inv_target_vocab, device)
print("Translated Sentence:", translated)

12. 进一步扩展

上述示例展示了一个极其简化的翻译模型的结构。为了构建一个实际可用的翻译系统,您需要:

  1. 更大的词汇表:处理更多的词汇和不同的语言。
  2. 大量的数据:训练模型需要大量的源-目标句子对。
  3. 更复杂的模型架构:如Transformer模型,它在实际应用中表现更好。
  4. 优化训练过程:包括学习率调度、正则化、批处理等技术。

13. 结论

通过上述代码,您可以理解如何将嵌入层和注意力机制集成到序列到序列模型中,以实现简单的翻译功能。尽管这个示例非常基础,但它为进一步深入理解和实现更复杂的翻译模型打下了基础。如果您有更多问题或需要进一步的帮助,请随时告知!

BERT 与GPT的区别

BERT 预测固定位置的字,而GPT 判断/预测这个位置的下一个字是什么
file

file


相关文章:
《Attention is All You Need》
The Illustrated Transformer (一篇很好的介绍)

为者常成,行者常至