课程9. 机器翻译,Seq2Seq与Attention
课程9. 机器翻译,Seq2Seq与Attention
- 机器翻译的任务. Seq2Seq 架构
- 通过实战理解
- 加载和预处理数据
- 构建 Seq2Seq 模型
- 编码器
- 解码器
- Seq2Seq网络
- 训练
- Seq2Seq 架构问题
- 注意力机制(Attention)
- 注意选项
- Transformer 架构介绍——BERT
课程计划
- 机器翻译的任务. Seq2Seq 架构
- 实践:机器翻译
- 注意力机制
- (可选)Transformer 架构介绍. BERT.
机器翻译的任务. Seq2Seq 架构
之前,我们考虑了一个语言建模问题,我们必须根据先前标记的使用情况预测后续标记的概率分布。
今天,我们将尝试通过预测整个标记序列而不是单个标记来概括这个问题。在这种情况下,预测序列将以某种方式与输入序列相关。具体来说,我们将考虑机器翻译的问题。
因此,机器翻译的任务是在给定一系列标记(源句)的情况下找到最可能的标记序列(翻译句):
猫 坐在地板上
( x 1 , x 2 , x 3 , x 4 x_1, \ \ \ \ \ x_2, \ \ x_3, \ x_4 x1, x2, x3, x4)
一只 猫 正坐在地板上
( y 1 , y 2 , y 3 , y 4 , y 5 y 6 y 7 y_1, y_2, y_3, \ \ y_4, \ y_5 \ y_6\ \ y_7 y1,y2,y3, y4, y5 y6 y7)
机器翻译任务:
Y ^ = a r g m a x Y P ( Y ∣ X , θ ) \widehat{Y} = argmax_{Y} P(Y|X, \theta) Y =argmaxYP(Y∣X,θ)
在这种情况下,它被描述为
P ( Y ∣ X , θ ) = P ( y 1 ∣ X , θ ) ⋅ P ( y 2 ∣ y 1 , X , θ ) ⋅ … P ( y n ∣ y n − 1 , y n − 2 , … , y 1 , X , θ ) P(Y|X, \theta) = P(y_1 | X, \theta) \cdot P(y_2 |y_1, X, \theta) \cdot \dots P(y_n | y_{n-1}, y_{n-2}, \dots, y_1, X, \theta) P(Y∣X,θ)=P(y1∣X,θ)⋅P(y2∣y1,X,θ)⋅…P(yn∣yn−1,yn−2,…,y1,X,θ)
我们看到,机器翻译问题的公式与语言建模问题的公式类似:模型也必须自回归地生成一个标记序列。不同之处在于,在机器翻译的情况下,添加了一个条件,考虑到必须生成输出序列。因此,机器翻译任务可以称为条件语言建模任务。
我们来谈谈如何使用 RNN 解决机器翻译问题。为此,让我们简要回顾一下语言建模模型的结构。
语言模型 - RNN 在每个时间时刻将一个标记作为输入,并生成下一个标记的概率分布作为输出。
在上一课中,我们讨论了如何根据前面的标记生成文本。本质上,我们还生成了一系列标记。但随后我们考虑到这个序列是由相同语言模型生成的(因为它是相同的语言、相同的文本风格等)。
在机器翻译任务中,我们本质上有两个模型:源语言模型和目标语言模型。它们可能有所不同,因为它们可以使用不同的语言。可能存在不同的语法规则。
因此,让我们尝试为机器翻译任务建立一个 RNN 模型。它将由两部分组成:
- 编码器部分,对源句子进行逐个标记的处理。这部分将是一个 RNN 网络,其最后不会有完全连接层。
- 解码器部分,将逐个标记地生成翻译句子标记。这部分也将是一个RNN网络。它将以与语言建模网络相同的方式构建。它与实际的语言建模网络会有一个区别:隐藏状态向量不会随机设置,而是使用编码器的隐藏状态向量,该向量是在编码器处理完原句后获得的。
编码器在处理完原句之后的隐藏状态向量会包含原句的相关信息。并根据这些信息,解码器会生成翻译句子。
这种类型的模型称为序列到序列(Seq2Seq),因为它以序列作为输入并产生一个序列作为输出。
让我们看一下幻灯片,我们将在其中详细讨论该模型的工作原理。
这里我们注意以下几点:假设我们有一个以这种方式训练的模型。然后可以使用该模型的编码器部分来获取句子嵌入。
针对其他任务训练的其他模型也可用于获取对象嵌入(文本、图像等)。例如,ELMO 嵌入的思想基于训练语言模型并根据输入的文本获取其层的输出。
通过实战理解
让我们尝试解决从德语到英语的机器翻译问题。
加载和预处理数据
让我们安装必要的库:
! pip install torch==2.1.0
! pip install torchtext==0.16.0
! pip install portalocker>=2.0.0
! pip install numpy==1.25.0
特别注意版本对应问题:
torch、python、torchdata、torchtext、numpy都要对应上才行
import torch
import torchtext
import portalocker
import torchdata
import numpy as npprint(f"NumPy version: {np.__version__}")
print(f"torch version: {torch.__version__}")
print(f"torchtext version: {torchtext.__version__}")
print(f"portalocker version: {portalocker.__version__}")
print(f"torchdata version: {torchdata.__version__}")
输出:
NumPy version: 1.25.0
torch version: 2.1.0+cu121
torchtext version: 0.16.0+cpu
portalocker version: 3.1.1
torchdata version: 0.7.0
让我们加载一个平行文本语料库。这是 Multi30k 语料库,它是一个包含约 30,000 个英语和德语平行句子的数据集。我们将解决德语到英语的机器翻译问题。
from torchtext.datasets import Multi30ktrain_iter = Multi30k(split="train")# torchtext.datasets.Multi30k 生成 IterableDataset 类的对象。
# 将其转换为简单的元素数组
train_data = list(train_iter)print(f"Amount of training data: {len(train_data)}")
print(train_data[0])
输出:
Amount of training data: 29001
(‘Zwei junge weiße Männer sind im Freien in der Nähe vieler Büsche.’, ‘Two young, White males are outside near many bushes.’)
导入标记器:
from nltk.tokenize import WordPunctTokenizertokenizer = WordPunctTokenizer()
print(tokenizer.tokenize("Good morning!"))
输出:
[‘Good’, ‘morning’, ‘!’]
我们将编写一个 tokenize 函数,用它将数据中的所有句子分解成标记:
def tokenize(sent):return tokenizer.tokenize(sent.rstrip().lower()) # 转换为小写并拆分成标记src, trg = train_data[0] # 取第一对“源语言”-“目标语言”
# 来自训练集print(tokenize(src))
print(tokenize(trg))
输出:
[‘zwei’, ‘junge’, ‘weiße’, ‘männer’, ‘sind’, ‘im’, ‘freien’, ‘in’, ‘der’, ‘nähe’, ‘vieler’, ‘büsche’, ‘.’]
[‘two’, ‘young’, ‘,’, ‘white’, ‘males’, ‘are’, ‘outside’, ‘near’, ‘many’, ‘bushes’, ‘.’]
让我们得到英语和德语的词典。为此,我们将使用 torchtext.vocab.vocab
from collections import Counterfrom torchtext.vocab import vocab as Vocabsrc_counter = Counter()
trg_counter = Counter()
for src, trg in train_data:src_counter.update(tokenize(src))trg_counter.update(tokenize(trg))src_vocab = Vocab(src_counter, min_freq=2) # 最小出现频率 - 2
trg_vocab = Vocab(trg_counter, min_freq=2)
让我们向字典中添加 4 个附加标记:
- UNK — 我们将用它来替换字典中没有的单词;
- BOS — 句子开始;
- EOS — 句子结束;
- PAD 是用于填充的标记。
unk_token, bos_token, eos_token, pad_token = "<UNK>", "<BOS>", "<EOS>", "<PAD>"for vocab in [src_vocab, trg_vocab]:vocab.insert_token(unk_token, index=0)vocab.set_default_index(0) # 我们相信<UNC>标记是默认使用的-它将用于字典中没有的新单词。for vocab in [src_vocab, trg_vocab]:for token in [bos_token, eos_token, pad_token]:if token not in vocab:vocab.append_token(token)
print(f"Unique tokens in source (de) vocabulary: {len(src_vocab)}")
print(f"Unique tokens in target (en) vocabulary: {len(trg_vocab)}")
输出:
Unique tokens in source (de) vocabulary: 7892
Unique tokens in target (en) vocabulary: 5903
我们将句子的所有预处理都合并到编码函数中。它将对产品进行标记,并向其中添加 BOS 和 EOS 实用代币。
def encode(sent, vocab): # 函数接受任何短语和与其对应的词典# 即源语言或目标语言的词典tokenized = [bos_token] + tokenize(sent) + [eos_token]return [vocab[tok] for tok in tokenized] # 返回一个标记索引序列,而不是一个短语src, trg = train_data[100]
print(encode(src, src_vocab))
print(encode(trg, trg_vocab))
输出:
[7889, 421, 422, 8, 26, 160, 199, 46, 157, 193, 55, 26, 423, 424, 13, 7890]
[5900, 434, 232, 14, 18, 163, 149, 289, 33, 111, 362, 435, 11, 5901]
剩下的就是编写一个函数,从一组句子中创建批次。我们记得,对于 RNN,一批中的所有元素必须具有相同的长度。与上一课一样,我们将修复此长度并将 PAD 标记添加到批次中的短句中。这样我们将确保每个批次包含相同长度的元素。
import torch
from torch.nn.utils.rnn import pad_sequence
from torch.utils.data import DataLoader# 该函数用于指定如何从数据集构建批次
def collate_batch(batch):# 初始化两个列表,分别用于存储源序列和目标序列src_list, trg_list = [], []# 遍历批次中的每个样本for src, trg in batch:# 使用源词汇表对源序列进行编码src_encoded = encode(src, src_vocab)# 将编码后的源序列转换为张量,并添加到源序列列表中src_list.append(torch.tensor(src_encoded))# 使用目标词汇表对目标序列进行编码trg_encoded = encode(trg, trg_vocab)# 将编码后的目标序列转换为张量,并添加到目标序列列表中trg_list.append(torch.tensor(trg_encoded))# 使用填充标记对源序列列表进行填充,使其长度一致src_padded = pad_sequence(src_list, padding_value=src_vocab[pad_token])# 使用填充标记对目标序列列表进行填充,使其长度一致trg_padded = pad_sequence(trg_list, padding_value=trg_vocab[pad_token])return src_padded, trg_padded
好吧,最后,让我们使用 collate_batch 创建一个基于 train_data 的训练 DataLoader:
batch_size = 256
train_dataloader = DataLoader(train_data, batch_size, shuffle=True, collate_fn=collate_batch)
src_batch, trg_batch = next(iter(train_dataloader))
src_batch.shape, trg_batch.shape
输出:
(torch.Size([36, 256]), torch.Size([37, 256]))
我们还将为数据的验证部分创建一个数据加载器:
val_data = list(Multi30k(split="valid"))
val_dataloader = DataLoader(val_data, batch_size, collate_fn=collate_batch)
构建 Seq2Seq 模型
现在我们将构建一个Seq2Seq模型。它由两部分组成——编码器和解码器。
编码器
编码器模型将是具有 GRU 层的单层双向 RNN。 GRU 是一个稍微修改过的 RNN 层,它可以让 RNN 记忆机制更好地工作(但仍然不是完美的)。
双向 RNN 层本质上是两层,其中一层从左到右“读取”文本,另一层从右到左“读取”。可以用如下方式来说明:
为了清楚起见,我们再次说明一下 RNN 结构:
import torch.nn as nn
import torch.nn.functional as F# 编码器,接收一批令牌索引序列作为输入,并输出隐藏状态
class Encoder(nn.Module):def __init__(self, input_dim, emb_dim, enc_hid_dim, dec_hid_dim, dropout):super().__init__()# 嵌入层self.embedding = nn.Embedding(input_dim, emb_dim)# 对嵌入层使用 dropoutself.dropout = nn.Dropout(dropout)# 单层双向 GRUself.rnn = nn.GRU(emb_dim, enc_hid_dim, bidirectional = True)# 全连接层self.fc = nn.Linear(enc_hid_dim * 2, dec_hid_dim)def forward(self, src):# 应用嵌入层和 dropoutembedded = self.dropout(self.embedding(src))# 通过 GRU 层outputs, hidden = self.rnn(embedded)# 获取 GRU 两个方向的最后隐藏状态# 每个状态的维度为 (256, 512)hidden = torch.tanh(self.fc(torch.cat((hidden[0,:,:], hidden[1,:,:]), dim = 1)))return outputs, hidden
解码器
解码器将是单层 GRU。解码器和编码器之间的主要区别在于,解码器的前向不仅接受标记作为输入,还接受来自编码器的隐藏状态作为输入。
class Decoder(nn.Module):def __init__(self, output_dim, emb_dim, enc_hid_dim, dec_hid_dim, dropout):super().__init__()# 保存输出维度self.output_dim = output_dim# 解码器的嵌入层self.embedding = nn.Embedding(output_dim, emb_dim)# 对嵌入层使用 dropoutself.dropout = nn.Dropout(dropout)# 单层普通 GRUself.rnn = nn.GRU(emb_dim, dec_hid_dim)# 全连接层self.fc_out = nn.Linear(dec_hid_dim, output_dim)def forward(self, input, hidden):# 将输入从 (batch_size) 调整为 (1, batch_size)input = input.unsqueeze(0)# 经过嵌入层和 dropout 后,形状为 (1, batch_size, emb_dim)embedded = self.dropout(self.embedding(input))# 通过 GRU 层,输入嵌入后的张量和隐藏状态(添加一维)output, hidden = self.rnn(embedded, hidden.unsqueeze(0))# 将 output 从 (1, batch_size, emb_dim) 调整为 (batch_size, emb_dim)output = output.squeeze(0)# 通过全连接层得到预测结果prediction = self.fc_out(output)# 返回下一个令牌的概率预测以及当前步骤的隐藏状态return prediction, hidden.squeeze(0)
Seq2Seq网络
最后,我们将编码器和解码器组装成一个大的 Seq2Seq 网络:
import randomclass Seq2Seq(nn.Module):def __init__(self, encoder, decoder, device):super().__init__()# 编码器self.encoder = encoder# 解码器self.decoder = decoder# 设备(如 CPU 或 GPU)self.device = devicedef forward(self, src, trg, teacher_forcing_ratio = 0.5):# 获取输入批次的大小batch_size = src.shape[1]# 获取目标序列的长度trg_len = trg.shape[0]# 获取目标词汇表的大小trg_vocab_size = self.decoder.output_dim# 创建一个用于存储解码器输出的零张量,形状为 (trg_len, batch_size, trg_vocab_size)outputs = torch.zeros(trg_len, batch_size, trg_vocab_size).to(self.device)# 调用编码器,得到编码器的输出和隐藏状态encoder_outputs, hidden = self.encoder(src)# 将目标序列的第一个标记(通常是 <bos> 开始标记)作为解码器的初始输入input = trg[0, :]# 遍历目标序列,从第二个标记开始for t in range(1, trg_len):# 将输入和之前的隐藏状态传入解码器,得到当前步骤的输出和新的隐藏状态output, hidden = self.decoder(input, hidden)# 将当前步骤的输出存储到 outputs 张量中outputs[t] = output# 决定是否使用教师强制(teacher forcing)# teacher_force = random.random() < teacher_forcing_ratio# 获取当前步骤输出中概率最大的标记的索引top1 = output.argmax(1)# 准备下一次迭代的输入# 如果使用教师强制,则使用目标序列中的下一个标记作为输入;否则,使用当前步骤预测的标记input = trg[t] # if teacher_force else top1# 返回解码器的所有输出return outputs
我们启动Encoder、Decoder和Seq2Seq模型:
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")# 创建编码器
enc = Encoder(len(src_vocab), emb_dim=256, enc_hid_dim=512, dec_hid_dim=512, dropout=0.5).to(device)
# 创建解码器
dec = Decoder(len(trg_vocab), emb_dim=256, enc_hid_dim=512, dec_hid_dim=512, dropout=0.5).to(device)
# 创建 Seq2Seq 模型
model = Seq2Seq(enc, dec, device=device)
optimizer = torch.optim.Adam(model.parameters())
# 损失不会在填充标记上计算
criterion = nn.CrossEntropyLoss(ignore_index=trg_vocab[pad_token])
训练
我们训练模型:
from torch.nn.utils import clip_grad_norm_
from tqdm.auto import tqdm, trange# 训练的总轮数
n_epochs = 10
# 梯度裁剪的阈值
clip = 1
# 开始训练的轮次循环
for epoch in trange(n_epochs, desc="Epochs"):# 将模型设置为训练模式model.train()# 初始化训练损失为 0train_loss = 0# 遍历训练数据集for src, trg in tqdm(train_dataloader, desc="Train", leave=False):# 将源序列和目标序列移动到指定设备(如 GPU)上src, trg = src.to(device), trg.to(device)# 将源序列和目标序列输入模型,得到模型的输出# 对于目标序列的每个前缀,模型会预测下一个标记output = model(src, trg)# 去掉输出和目标序列中的第一个元素(通常是 <bos> 开始标记),用于损失计算output = output[1:].view(-1, output.shape[-1])trg = trg[1:].view(-1)# 计算损失loss = criterion(output, trg)# 反向传播,计算梯度loss.backward()# 对模型的参数进行梯度裁剪,防止梯度爆炸clip_grad_norm_(model.parameters(), clip)# 根据计算得到的梯度更新模型的参数optimizer.step()# 清空梯度,为下一次迭代做准备optimizer.zero_grad()# 累加当前批次的损失到总训练损失中train_loss += loss.item()# 计算平均训练损失train_loss /= len(train_dataloader)# 打印当前轮次的训练损失print(f"Epoch {epoch} train loss = {train_loss} ")# 将模型设置为评估模式model.eval()# 初始化验证损失为 0val_loss = 0# 不计算梯度,减少内存消耗,提高推理速度with torch.no_grad():# 遍历验证数据集for src, trg in tqdm(val_dataloader, desc="Val", leave=False):# 将源序列和目标序列移动到指定设备上src, trg = src.to(device), trg.to(device)# 将源序列和目标序列输入模型,得到模型的输出output = model(src, trg)# 去掉输出和目标序列中的第一个元素,用于损失计算output = output[1:].view(-1, output.shape[-1])trg = trg[1:].view(-1)# 计算损失loss = criterion(output, trg)# 累加当前批次的损失到总验证损失中val_loss += loss.item()# 计算平均验证损失val_loss /= len(val_dataloader)# 打印当前轮次的验证损失print(f"Epoch {epoch} val loss = {val_loss} ")
输出:
# 获取目标词汇表的索引到词的映射列表
trg_itos = trg_vocab.get_itos()
# 将模型设置为评估模式
model.eval()
# 生成的最大长度
max_len = 50# 不计算梯度,以提高推理速度并减少内存消耗
with torch.no_grad():# 遍历验证数据集中的前 10 个样本for src, trg in val_data[:10]:# 对源句子进行编码,将其转换为词汇表中的索引序列encoded = encode(src, src_vocab)# 将编码后的序列转换为 PyTorch 张量,并添加一个维度以模拟批量输入encoded = torch.tensor(encoded)[:, None].to(device)# 将源句子通过模型的编码器,得到编码器的输出和隐藏状态encoder_outputs, hidden = model.encoder(encoded)# 设置第一个预测的标记为起始标记(bos)pred_tokens = [trg_vocab[bos_token]]# 循环生成翻译后的句子,一次生成一个标记for _ in range(max_len):# 将上一个预测的标记作为当前解码器的输入decoder_input = torch.tensor([pred_tokens[-1]]).to(device)# 将输入和隐藏状态传入解码器,得到预测结果和新的隐藏状态pred, hidden = model.decoder(decoder_input, hidden)# 获取预测结果中概率最大的标记的索引_, pred_token = pred.max(dim=1)# 如果预测的标记是结束标记(eos),则停止生成if pred_token == trg_vocab[eos_token]:# 为了输出更简洁,不将结束标记添加到预测结果中break# 将预测的标记添加到预测标记列表中pred_tokens.append(pred_token.item())# 打印源句子,转换为小写并去除右侧的空白字符print(f"src: '{src.rstrip().lower()}'")# 打印目标句子,转换为小写并去除右侧的空白字符print(f"trg: '{trg.rstrip().lower()}'")# 打印预测的句子,将预测标记列表中的索引转换为对应的单词print(f"pred: '{' '.join(trg_itos[i] for i in pred_tokens[1:])}'")print()
输出:
Seq2Seq 架构问题
上述机器翻译的网络架构是2014-2015年的标准。然而,当时它的质量不如统计(基于短语的)翻译模型。这主要是由于该架构存在的一个问题:有关原始句子的所有信息都由 RNN 层学习,并最终包含在单个向量 z 中。
为什么这是一个问题:
- RNN 容易被“遗忘”。尽管 RNN 具有“记忆”机制,但随着时间的推移,这种记忆中的信息会被抹去。即使更强大的 LSTM 仍然无法正确处理非常大的序列(此外,处理大序列需要花费大量时间);
- 不同长度的句子由相同的机制处理。即使最长的句子的所有信息都被收集在一个隐藏状态向量 z 中。事实证明,可以传输到解码器的信息量受到向量 z 的体积的限制。
- 在解码过程中,解码器在每一步都会根据其隐藏状态向量生成一个新的标记。在解码过程中,这个隐藏状态向量会丢失来自编码器的信息,最终生成的标记可能与原始句子关系不大。
长期以来,研究人员一直在试图找出如何克服这一限制。人们发明了许多方法来提高质量,但问题并未得到彻底解决。而在2015年,注意力机制的思想被提出,使得克服这个问题成为可能。现在我们来见见他。
注意力机制(Attention)
我们来看一下演示文稿,详细讨论注意力机制。
注意力机制让我们能够解读模型的工作:
注意选项
令 h e 1 , . . . , h e N h_{e1},...,h_{eN} he1,...,heN 为编码器的隐藏状态, h d t h_{dt} hdt 为解码器在时间 t t t 的状态。
那么时刻 t t t的注意力向量(注意力分数) a d t a_{dt} adt可以表示如下:
a d t = [ a i , t , . . . , a N , t ] a_{dt} = [a_{i,t},...,a_{N, t}] adt=[ai,t,...,aN,t].
其中每个 a i , t = f ( h e i , h d t ) a_{i, t}=f(h_{ei}, h{dt}) ai,t=f(hei,hdt)。
那些。为了计算每个当前时刻 t t t的注意力向量,需要针对编码器的每个状态和解码器的当前状态计算某个注意力函数(Attention)的值。
要计算Attention值,可以使用不同的操作(不同的函数f)。让我们看看最受欢迎的功能。
1.点积注意力机制。这是最经典的Attention类型,在大多数解决方案中都会用到。它被计算为标量积:
f ( h e i , h d t ) = h d t T h e i f(h_{ei}, h{dt}) = h_{dt}^Th_{ei} f(hei,hdt)=hdtThei
2.乘法注意力。这里我们添加一些固定的中间权重矩阵 W W W:
f ( h e i , h d t ) = h d t T ∗ W ∗ h e i f(h_{ei}, h{dt}) = h_{dt}^T*W*h_{ei} f(hei,hdt)=hdtT∗W∗hei
- 附加注意力。在这里,我们通过使用具有某些激活函数(例如双曲正切)的完全连接层的某些类似物来进一步使计算复杂化,将编码器和解码器的隐藏状态向量的某些线性组合作为输入。我们将结果乘以某个权重向量 v v v:
f ( h e i , h d t ) = v T ∗ t a n h ( W 1 ∗ h e i + W 2 ∗ h d t ) f(h_{ei}, h{dt}) = v^T*tanh(W_1*h_{ei}+W_2*h_{dt}) f(hei,hdt)=vT∗tanh(W1∗hei+W2∗hdt)
通常,点积被用作注意函数,因为这种运算在计算速度方面非常高效。加法和乘法注意力是这种方法的概括,允许在计算注意力时考虑每个状态的一些权重(这就是辅助矩阵 W W W、 W 1 W_1 W1 和 W 2 W_2 W2 的用途)。
Transformer 架构介绍——BERT
上面讨论的注意力机制是目前大多数现代解决方案(ChatGPT、BERT 等)中使用的强大 Transformer 模型中的关键工具。
在这些架构中,除了机制之外,还使用了一些附加特征(例如,对于每个标记,除了其向量表示之外,还考虑标记的序数)。但我们不会详细讨论这一点。
我们只想说,这样的架构使我们能够有效地解决已经考虑的问题,例如文本分类问题。
例如,BERT(来自 Transformer 的双向编码器表示)架构基于以下假设:
- 这是一个利用Attention机制的语言模型。
- 在训练过程中,解决了一个 masked 语言建模问题(首先根据所有前面的 token 预测某个 token 的概率——正向语言建模,然后根据所有后续单词计算某个 token 的概率——反向语言建模的任务,然后根据这些概率基于整个上下文计算出最终某个 token 的概率)。
- 对于分类任务,使用特殊标记 <CLF>,该标记添加到句子的开头。因此,在预测这个标记的过程中,我们得到了模型的隐藏状态向量,该向量考虑了原始句子中的所有标记。正是这个标记可以作为整个文本的向量表示,以解决文本数据分析问题(分类、回归等)。
- 因此,作为文本的向量表示,我们可以使用 <CLF> 标记的向量表示,它将包含有关原始文本的所有信息。这意味着我们可以将这个向量输入到一个简单的分类算法中。
正如你所看到的,注意力机制是一个相当复杂的过程。需要计算编码器和解码器所有隐藏状态对的注意力值。因此,就训练时间而言,Transformer 是一种相当昂贵的架构。因此,通常使用预先训练的模型。
特别是,有一个库“transformers”,它已经包含各种预先训练的模型,包括各种 BERT。
相关文章:
课程9. 机器翻译,Seq2Seq与Attention
课程9. 机器翻译,Seq2Seq与Attention 机器翻译的任务. Seq2Seq 架构通过实战理解加载和预处理数据构建 Seq2Seq 模型编码器解码器Seq2Seq网络训练 Seq2Seq 架构问题注意力机制(Attention)注意选项Transformer 架构介绍——BERT 课程计划 机器…...
ASP.NET MVC 入门指南二
9. 表单处理与提交 9.1 创建表单视图 在视图文件夹下创建一个用于创建产品的视图,如 Create.cshtml: html model YourNamespace.Product{ViewBag.Title "创建产品"; }<h2>创建产品</h2>using (Html.BeginForm()) {Html.Anti…...
JavaWeb学习打卡-Day3-MyBatis相关
MyBatis 什么是MyBatis? MyBatis是一款优秀的持久层框架,用于简化JDBC的开发。MyBatis免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。 JDBC JDBC(Java DataBase Connectivity):使用Java语言操作关系型数据库的…...
浅谈AI Agent 演进之路
1、了解下 AI Agent 的定义 AI Agent(人工智能代理)简单来说是一种能够感知环境、进行决策和执行动作的智能实体。与传统的人工智能相比,AI Agent 具备独立思考和调用工具逐步完成目标的能力。 例如:当要求 AI Agent 帮助下单外…...
佳博票据和标签打印:Web网页端与打印机通信 | iOS
文章目录 引言I Web网页端与打印机通信webSDK(包含示例页)打印测试II iOS与佳博打印机通信引言 佳博工具下载ESC是票据打印指令,TSC是标签打印指令 工业打印机:佳博GP-H430F工业机标签条码打印机物流快递电子面单条码机碳带机 应用场景:打印商品价格标签、打印交易小票 I…...
视频噪点多,如何去除画面噪点?
你是否遇到过这样的困扰?辛辛苦苦拍摄的视频,导出后却满屏 “雪花”,夜景变 “噪点盛宴”,低光环境秒变 “马赛克现场”? 无论是日常拍摄的vlog、珍贵的家庭录像,还是专业制作的影视作品,噪点问…...
微信小程序直传阿里云 OSS 实践指南(V4 签名 · 秒传支持 · 高性能封装)
文章目录 前言一、为什么要使用直传 OSS?二、整体架构与实现思路三、阿里云 OSS 配置(V4 签名)1. 权限设置2. 后端生成签名参数(返回给小程序) 四、微信小程序端上传流程(功能模块拆解与封装)第…...
云原生--核心组件-容器篇-1-Docker和云原生关系(Docker是云原生的基石)
1、基本概念 (1)、云原生(Cloud Native) 是一种构建和运行应用程序的方法论,旨在充分利用云计算环境(公有云、私有云、混合云)的特性,通过容器化、微服务、服务网格、声明式API等技…...
GAEA情感坐标背后的技术原理
基于GAEA的去中心化物理基础设施网络(DePIN),用户有机会在GAEA平台上获得宝贵的数据共享积分。为了提升这些洞察的丰富性,用户必须花费一定数量的积分,将过去的网络数据与当前的情感数据绑定,从而产生一种新…...
day01_编程语言介绍丶Java语言概述丶开发环境搭建丶常用DOS命令
编程语言介绍 编程语言是一种用于人与计算机之间通信的语言,允许程序员编写代码,这些代码告诉计算机要执行哪些操作。编程语言可以被视为计算机可以理解并执行的指令集合,它是一种标准化的交流技巧,用于向计算机发出指令。…...
STM32系列官方标准固件库的完整下载流程
一、官网导航与版本确认 访问ST官网 打开浏览器进入 ST官网,点击左侧 “工具与软件” 标签,展开后选择 “嵌入式软件” 。若页面未直接显示,可在搜索框输入“STM32 Standard Peripheral Libraries”查找。 → “STM32标准外设库”࿰…...
Android 14 系统统一修改app启动时图标大小和圆角
Android 14 统一修改app启动时图标大小和圆角 修改如下: 目录:frameworks/base/core/java/android/window/SplashScreenView.java frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.ja…...
MySQL 详解之函数:数据处理与计算的利器
在 MySQL 中,函数可以接受零个或多个输入参数,并返回一个值。这些函数可以在 SELECT 语句的字段列表、WHERE 子句、HAVING 子句、ORDER BY 子句以及 UPDATE 和 INSERT 语句中使用。合理利用函数,可以简化 SQL 语句,提高开发效率。 MySQL 提供了大量的内置函数 (Built-in F…...
Tailwind CSS 实战:基于 Kooboo 构建企业官网页面(一)
目录 一、技术选型:为什么选择Tailwind Kooboo? 二、CDN方案 vs 传统安装 三、CDN方式实战步骤 一、技术选型:为什么选择Tailwind Kooboo? 1.1 黄金组合优势 Tailwind CSS:原子化CSS框架,提供&#x…...
Java基础 — 条件结构与随机数
介绍 Java条件结构与随机数是程序逻辑控制的重要工具。条件结构通过if-else和switch实现分支判断:if(条件){代码}用于单分支,else if添加多条件判断,switch则基于固定值匹配不同case。随机数生成常用两种方式:Math.random()方法返…...
AI网络渗透kali应用(gptshell)
kali安装gptshell 一、shellGPT 工具介绍 ShellGPT是一款由AI大型语言模型(LLM)驱动的终端命令行工具。它能帮助用户直接在终端与AI交互,自动生成、解释、执行各类 Linux 命令,大大提升了运维和开发效率。ShellGPT 支持接入 O…...
如何实现Android屏幕和音频采集并启动RTSP服务?
技术背景 在移动直播和视频监控领域,实现高效的屏幕和音频采集并提供流媒体服务是关键技术之一。本文将详细介绍如何基于大牛直播SDK实现Android屏幕和麦克风/扬声器采集,并启动轻量级RTSP服务以对外提供拉流的RTSP URL。在Android平台上,轻…...
大模型提示词如何编写
一、提示词的核心三要素 明确目标(What) 告诉 AI「你要它做什么」,越具体越好。 ❌ 模糊:写一篇文章 ✅ 清晰:写一篇 800 字的高考作文,主题 “坚持与创新”,结构分引言、三个论点(…...
Serverless 在云原生后端的实践与演化:从函数到平台的革新
📝个人主页🌹:慌ZHANG-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、引言:从服务器到“无服务器”的后端演变 在传统后端开发中,我们需要为服务配置并维护服务器资源,无论是物理机、虚拟机还是容器化服务,都需要: 管理系统运行环境 监控负载与扩缩容 保证高可用与安…...
反爬虫机制中的验证码识别:类型、技术难点与应对策略
在互联网数据抓取领域,验证码识别是爬虫过程中的关键环节之一。下面对常见验证码类型、技术难点及应对策略进行详细解析,并提供多种场景下的代码实现示例。 一、验证码类型与技术难点 (一)图形验证码 1. 字符验证码 特征&#…...
.NET 10 中的新增功能
.NET 运行时 .NET 10 运行时引入了新功能和性能改进。 关键更新包括: 数组接口方法反虚拟化:JIT 现在可以取消虚拟化和内联数组接口方法,从而提高数组枚举的性能。数组枚举去抽象化:改进功能以通过枚举器减少数组迭代的抽象开销…...
通过音频的pcm数据格式利用canvas绘制音频波形图
上面是一个完整的音频的波形图,可以大概知道音频整个的简略信息 数据准备:需要有这个音频的pcm数据,也就是时域采样值,每个数字代表某一时刻音频波形的振幅。 <!DOCTYPE html> <html lang"en"> <head&…...
Dubbo负载均衡策略深度解析
互联网大厂Java求职者面试:Dubbo负载均衡策略详解 第一轮提问: 面试官:马架构,您好!请问您了解Dubbo的负载均衡策略吗?Dubbo支持哪些负载均衡策略呢? 马架构:您好!Dub…...
【高频考点精讲】async/await原理剖析:Generator和Promise的完美结合
async/await原理剖析:Generator和Promise的完美结合 今天咱们聊聊async/await,这玩意儿用起来是真香,但你知道它背后是怎么运作的吗?其实它就是Generator和Promise的"爱情结晶"。 1. 先搞懂Generator Generator&…...
量子加密通信技术及其应用:构建无条件安全的通信网络
一、引言 在数字化时代,信息安全成为全球关注的焦点。随着量子计算技术的快速发展,传统的加密算法面临着前所未有的挑战。量子加密通信技术应运而生,它利用量子力学的基本原理,如量子叠加态和量子纠缠,实现了无条件安全…...
软考中级-软件设计师 知识点速过1(手写笔记)
第一章:数值及其转换 没什么可说的,包括二进制转八进制和十六进制 第二章:计算机内部数据表示 真值和机器数: 原码(后面都拿x 19举例) : 反码: 补码: 移码: 定点数&…...
【prompt是什么?有哪些技巧?】
Prompt(提示词)是什么? Prompt 是用户输入给AI模型(如ChatGPT、GPT-4等)的指令或问题,用于引导模型生成符合预期的回答。它的质量直接影响AI的输出效果。 Prompt 的核心技巧 1. 明确目标(Clar…...
C++进阶----多态
目录 引言1.多态的概念2.多态的定义及实现2.1 多态的构成条件2.2虚函数2.3 虚函数的重写2.4 关键字override和final2.5 重载、覆盖(重写)、隐藏对比 3.抽象类3.1 抽象类概念 4.多态的原理4.1 虚函数表4.2虚函数表的底层4.3多态的原理4.4 动态绑定和静态绑…...
银发科技:AI健康小屋如何破解老龄化困局
随着全球人口老龄化程度的不断加深,如何保障老年人的健康、提升他们的生活质量,成为了社会各界关注的焦点。 在这场应对老龄化挑战的战役中,智绅科技顺势而生,七彩喜智慧养老系统构筑居家养老安全网。 而AI健康小屋作为一项创新…...
【黑马 微服务面试篇】
分布式事务 cap定理-Availability CAP定理-Partition tolerance BASE理论 BASE理论是对CAP的一种解决思路,包含三个思想: BasicallyAvailable(基本可用):分布式系统在出现故障时,允许损失部分可用性&#…...
斗鱼娱乐电玩平台源码搭建实录
在本篇文章中,我们将以技术人的角度详细拆解一款风格接近850平台的斗鱼娱乐电玩系统源码,包含完整服务器端、前台补全资源和双端APP构建流程。基于七月最新更新内容,本教程将突出技术关键点、实战配置与代码示范,旨在为开发者和搭…...
缓存与数据库一致性深度解析与解决方案
缓存与数据库一致性深度解析与解决方案 一、一致性问题本质与挑战 1. 核心矛盾分析 缓存与数据库一致性问题源于数据存储的异步性与分布性,核心挑战包括: 读写顺序不确定性:并发场景下写操作顺序可能被打乱(如先写缓存后写数据…...
Flutter 弹窗队列管理:支持优先级的线程安全通用弹窗队列系统
在复杂的 Flutter 应用开发中,弹窗管理是一个常见难题。手动管理弹窗的显示顺序和条件判断不仅繁琐,还容易出错。为此,我们实现了一个支持优先级的线程安全通用弹窗队列管理系统。它能够自动管理弹窗的显示顺序,支持条件判断&…...
【Langchain】RAG 优化:提高语义完整性、向量相关性、召回率--从字符分割到语义分块 (SemanticChunker)
RAG 优化:提高语义完整性、向量相关性、召回率–从字符分割到语义分块 (SemanticChunker) 背景:提升 RAG 检索质量 在构建基于知识库的问答系统(RAG)时,如何有效地将原始文档分割成合适的文本块(Chunks&a…...
Linux 官方蓝牙协议栈 BlueZ 第一篇:入门与架构概览
Linux 官方蓝牙协议栈 BlueZ,包含内核驱动、用户态守护进程和 DBus 接口,支持 Classic Bluetooth 和 BLE。本篇将从协议栈演进、架构组件、安装调试、核心流程和开发入门五个角度,结合 PlantUML 图、C/Python 代码示例,帮助你全面掌握 BlueZ 基础。 目录 协议栈演进与概念 …...
多层级的对象如何修改、或json格式
场景: 对象有多层级,一层套一层,list套对象,对象套list。 现在需要修改期中一个list的内容,怎么弄呢? 注:每一层都new一个新list再set不可取,太麻烦,看起来乱而且还容易错。 最好…...
产品动态|千眼狼sCMOS科学相机捕获单分子荧光信号
单分子荧光成像技术,作为生物分子动态研究的关键工具,对捕捉微弱信号要求严苛。传统EMCCD相机因成本高昂,动态范围有限,满阱容量低等问题,制约单分子研究成果产出效率。 千眼狼精准把握科研需求与趋势,自研…...
VsCode如何使用默认程序打开word Excel pdf等文件
如何使用在VsCode使用默认程序打开文件? 1.在插件市场里搜open插件并安装 2.安装完后,右键文件打开,点击Open with default application,就可以了 是不是非常方便!!!...
【T-MRMSM】文本引导多层次交互多尺度空间记忆融合多模态情感分析
在特征提取的部分用了k-means abstract (背景) 近年来,随着多模态数据量的迅速增加,多模态情感分析(MSA)越来越受到关注.该方法通过整合不同数据模态间的信息,提高了情感极性提取的准确性,从而实现了信息的全面融合,提高了情感分析的精度。 (针对创新处的不足) …...
python pymysql如何保证数据库更新成功
python pymysql如何保证数据库更新成功 在使用Python的PyMySQL库与MySQL数据库交互时,确保数据库更新操作成功执行,可以通过以下几种方式: 使用execute()和commit() 当执行一个更新(UPDATE)、插入(INSERT)或删除(DELETE)操作时,你需要调用execute()方法来执行SQL语句…...
Redis是单线程的,如何提高多核CPU的利用率?
一句话回答: Redis 是单线程处理客户端命令,但可以通过 多实例部署、I/O 多路复用、后台线程 Redis 6 的 I/O Thread 支持,来充分利用多核 CPU。 一、Redis 单线程 ≠ 整个 Redis 都是单线程! Redis 主要的 网络事件 命令执行 …...
01.oracle SQL基础
SQL是结构化查询语言 SQL分类 数据定义语言(DDL --- create/alter/drop) sysdate --- 可以拿到当前系统时间 案例:创建学生表,教师表,课程表 -- 学生表 create table t_student(sid number(11) primary key,sname n…...
BEVPoolv2:A Cutting-edge Implementation of BEVDet Toward Deployment
背景 该论文是在BEVDet的基础上进行了一个调整优化,传统的方法是将特征图与深度预测进行外积得到视椎特征图,再将它与预处理好的体素索引结合,将每个视椎特征分类到每个voxel中进行累加和的操作。BEVFusion与BEVDepth等方法是避免了累加和&a…...
FreeRTOS学习笔记【10】-----任务上下文切换
1 概念性内容 开机到调度需要经历的步骤有: 系统初始化任务创建启动调度器上下文切换时间分片任务执行 1.1 任务本质 FreeRTOS 的 任务(Task)本质上就是一个运行在任务自己的栈区中无限循环的函数 一段上下文(context&#x…...
PDFMathTranslate:基于LLM的PDF文档翻译及双语对照的工具【使用教程】
1.简介 PDFMathTranslate 是一个用于科学 PDF 文档翻译及双语对照的工具,是一个功能强大且灵活的科学文档翻译工具,适合科研人员、学生和专业人士使用,能够有效提高文档翻译的效率和质量。其具有以下特点和功能: 核心功能 保留格…...
CSS 入门全解析
CSS 入门全解析:从选择器到布局的全面教学 一、CSS 是什么?二、CSS 的基本语法结构三、常见选择器讲解四、盒模型讲解(重点)五、字体与颜色样式六、布局方式6.1 浮动布局(了解)6.2 Flex 弹性布局࿰…...
用户案例--慧眼科技
作者:算力魔方创始人/英特尔创新大使刘力 每个行业都有其独特的需求,算力魔方推出了全面的定制化服务,从概念到产品化,满足各行各业,用户可以根据具体应用需求定制更多接口或更强图形处理的需求,且算力魔方…...
面试中被问到mybatis与jdbc有什么区别怎么办
1. 核心区别 维度JDBCMyBatis抽象层级底层API,直接操作数据库高层持久层框架,封装JDBC细节代码量需要手动编写大量样板代码(连接、异常处理等)通过配置和映射减少冗余代码SQL管理SQL嵌入Java代码,维护困难SQL与Java代…...
科技与商业动态简报
睿创咨询 聚焦与深耕IPD领域长达20年,联合多名企业经营实战专家和前高管,睿创咨询借力IPD,为企业全方面提高产品竞争力,让增长从偶然变为必然!...
Flutter Dart中的类 对象
Dart 基本特征 私有属性/私有方法 import test88.dart;main() {var home new MainHome();home.execRun(); //间接的调用私有方法 }class MainHome {String _name "张三";//私有属性int age 10;main() {_run();print(_name);}void _run() {print("私有方法&qu…...