基于Transformer实现机器翻译(日译中)

一、实验内容

1.实验介绍

基于Transformer的机器翻译模型通过自注意力机制和编码器-解码器架构,显著提升了翻译的质量和效率。该模型能够捕获句子中的长距离依赖关系,并生成流畅、准确的译文,实现了高效、高质量的自动翻译。在多个翻译任务中,基于Transformer的模型均取得了显著的性能提升。

2.特点

  • 长距离依赖捕获:通过自注意力机制(Self-Attention),Transformer模型能够直接计算输入序列中任意两个位置之间的相关性,从而有效捕获长距离依赖关系,这对于理解复杂句子结构和进行准确翻译至关重要。

  • 并行化计算:Transformer模型中的自注意力机制使得整个序列的计算可以并行进行,相比传统的循环神经网络(RNN)和卷积神经网络(CNN),这大大提高了计算效率,使得模型在训练和解码时可以更快地处理长序列。

  • 位置信息编码:为了弥补自注意力机制中缺乏位置信息的缺点,Transformer引入了位置编码(Positional Encoding),将位置信息嵌入到输入序列中,使得模型能够区分不同位置的词元。

  • 深层堆叠结构:Transformer模型通常使用多层堆叠的编码器和解码器,每一层都能够进一步提取和表示输入序列的深层特征,从而增强模型的表示能力和翻译性能。

  • 可扩展性和灵活性:Transformer模型的设计简洁而灵活,可以方便地扩展到其他自然语言处理任务中,如文本分类、情感分析、命名实体识别等。此外,它还可以与其他模型结合使用,如预训练语言模型(如BERT),以进一步提高翻译性能。

  • 高质量的翻译输出:由于上述特点,基于Transformer的机器翻译模型能够生成流畅、准确、自然的译文,显著提升了翻译质量,特别是在处理长句、复杂句和跨语言现象时表现出色。

3.主要应用领域

  1. 自然语言处理(NLP)中的机器翻译:这是Transformer模型最初被设计并广泛应用的领域。它能够处理各种语言对之间的翻译任务,包括英语到法语、中文到英文等,为跨语言交流提供了极大的便利。

  2. 多语言处理:由于Transformer模型的可扩展性和灵活性,它可以很容易地扩展到多语言环境中。多语言机器翻译系统能够同时处理多种语言之间的翻译,对于促进全球范围内的语言交流和文化交流具有重要意义。

  3. 文本摘要:Transformer模型也可以应用于文本摘要领域,通过生成简洁明了的摘要来总结长篇文章或文档的主要内容。这种技术在新闻报道、学术论文、社交媒体等领域都有广泛的应用。

  4. 问答系统:在问答系统中,Transformer模型可以用于理解用户的查询并生成相关的答案。通过捕捉查询中的关键信息和上下文关系,模型可以准确地匹配问题并返回合适的答案。

  5. 情感分析:情感分析是NLP中的一个重要任务,用于识别文本中的情感倾向(如正面、负面或中性)。基于Transformer的模型可以有效地捕捉文本中的情感信息,并用于各种应用场景,如社交媒体监测、产品评论分析等。

二、实验原理

基于Transformer的机器翻译模型是一种先进的深度学习架构,它通过编码器将源语言句子转换为富含语义信息的中间表示,解码器则利用这些表示以及独特的自注意力机制,不仅捕获句子内部的长距离依赖关系,还能高效地处理并生成目标语言句子。该模型的多头自注意力机制使得模型能够并行处理序列中的每个元素,大大提高了计算效率,同时保证了翻译结果的准确性和流畅性。这种先进的模型设计使得基于Transformer的机器翻译模型在各种跨语言交流场景中表现出色,为全球范围内的语言互译提供了强有力的技术支持。

三、实验过程

1.实验准备

首先,请确保我们的系统中安装了以下软件包,如果发现缺少某些软件包,请确保安装它们。

import math
import torchtext
import torch
import torch.nn as nn
from torch import Tensor
from torch.nn.utils.rnn import pad_sequence
from torch.utils.data import DataLoader
from collections import Counter
from torchtext.vocab import Vocab
from torch.nn import TransformerEncoder, TransformerDecoder, TransformerEncoderLayer, TransformerDecoderLayer
import io
import time
import pandas as pd
import numpy as np
import pickle
import tqdm
import sentencepiece as spm
torch.manual_seed(0)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# print(torch.cuda.get_device_name(0)) ## 如果你有GPU,请在你自己的电脑上尝试运行这一套代码

2.获取数据

在本教程中,我们将使用从JParaCrawl下载的日英平行数据集![http://www.kecl.ntt.co.jp/icl/lirg/jparacrawl],该数据集被描述为“NTT创建的最大公开可用的英语-日语平行语料库。它是通过大规模爬取网页和自动对齐平行句子来创建的。”您还可以在这里查看相关论文。

df = pd.read_csv('./zh-ja/zh-ja.bicleaner05.txt', sep='\\t', engine='python', header=None)
trainen = df[2].values.tolist()#[:10000]
trainja = df[3].values.tolist()#[:10000]
# trainen.pop(5972)
# trainja.pop(5972)

在导入所有日语及其对应的英语句子后,我删除了数据集中最后一组数据,因为它有缺失值。总的来说,trainen和trainja中的句子数量都是5,973,071句,但是为了学习目的,通常建议在一次性使用所有数据之前,先对数据进行抽样并确保一切正常,以节省时间。

3.分词器

与英语或其他字母语言不同,日语句子不包含空格来分隔单词。我们可以使用JParaCrawl提供的分词器,该分词器为日语和英语都使用了SentencePiece进行创建。你可以访问JParaCrawl网站来下载它们,或者点击这里下载。

# 加载英文tokenizer模型
en_tokenizer = spm.SentencePieceProcessor(model_file='enja_spm_models/spm.en.nopretok.model')

# 加载日文tokenizer模型
ja_tokenizer = spm.SentencePieceProcessor(model_file='enja_spm_models/spm.ja.nopretok.model')

在加载分词器之后,你可以通过执行下面的代码来测试它们,例如。

en_tokenizer.encode("All residents aged 20 to 59 years who live in Japan must enroll in public pension system.", out_type='str')
ja_tokenizer.encode("年金 日本に住んでいる20歳~60歳の全ての人は、公的年金制度に加入しなければなりません。", out_type='str')

4.构建TorchText词汇对象,并将句子转换为Torch张量

这个过程可能需要几秒钟或几分钟,具体取决于数据集的大小和计算能力。不同的分词器也可能影响构建词汇表所需的时间。我尝试了几种其他日语分词器,但SentencePiece对我来说似乎工作得很好且速度足够快。

接下来,我们将使用这些分词器和原始句子将句子转换为Torch张量(tensors)。

def build_vocab(sentences, tokenizer):
    counter = Counter()
    # 统计所有句子中的词频
    for sentence in sentences:
        counter.update(tokenizer.encode(sentence, out_type=str))
    
    # 创建词汇表,包括特殊标记'<unk>', '<pad>', '<bos>', '<eos>'
    return Vocab(counter, specials=['<unk>', '<pad>', '<bos>', '<eos>'])

# 使用build_vocab函数构建日文文本的词汇表
ja_vocab = build_vocab(trainja, ja_tokenizer)

# 使用build_vocab函数构建英文文本的词汇表
en_vocab = build_vocab(trainen, en_tokenizer)
def data_process(ja, en):
    data = []
    # 遍历日文和英文句子对
    for (raw_ja, raw_en) in zip(ja, en):
        # 对日文句子进行编码并转换为PyTorch tensor
        ja_tensor_ = torch.tensor([ja_vocab[token] for token in ja_tokenizer.encode(raw_ja.rstrip("\n"), out_type=str)],
                                  dtype=torch.long)
        # 对英文句子进行编码并转换为PyTorch tensor
        en_tensor_ = torch.tensor([en_vocab[token] for token in en_tokenizer.encode(raw_en.rstrip("\n"), out_type=str)],
                                  dtype=torch.long)
        # 将编码后的日文和英文tensor组成一个数据样本,并添加到data列表中
        data.append((ja_tensor_, en_tensor_))
    
    return data

# 对训练数据进行处理,生成训练数据集
train_data = data_process(trainja, trainen)

5.在训练过程中创建将被迭代的DataLoader对象

在这里,我将BATCH_SIZE设置为16以防止“cuda内存不足”的错误,但这取决于各种因素,如你的机器内存容量、数据大小等,因此你可以根据需要自由更改批次大小(注意:PyTorch的教程在使用Multi30k德英数据集时,将批次大小设置为128)。

BATCH_SIZE = 8
PAD_IDX = ja_vocab['<pad>']
BOS_IDX = ja_vocab['<bos>']
EOS_IDX = ja_vocab['<eos>']

def generate_batch(data_batch):
    ja_batch, en_batch = [], []
    # 遍历一个batch的数据样本
    for (ja_item, en_item) in data_batch:
        # 在日文句子前后添加起始和结束标记,并合并为一个tensor
        ja_batch.append(torch.cat([torch.tensor([BOS_IDX]), ja_item, torch.tensor([EOS_IDX])], dim=0))
        # 在英文句子前后添加起始和结束标记,并合并为一个tensor
        en_batch.append(torch.cat([torch.tensor([BOS_IDX]), en_item, torch.tensor([EOS_IDX])], dim=0))
    
    # 对日文句子进行padding,使其长度相同
    ja_batch = pad_sequence(ja_batch, padding_value=PAD_IDX)
    # 对英文句子进行padding,使其长度相同
    en_batch = pad_sequence(en_batch, padding_value=PAD_IDX)
    
    return ja_batch, en_batch

# 使用DataLoader将训练数据集train_data划分为batch,并进行shuffle和padding
train_iter = DataLoader(train_data, batch_size=BATCH_SIZE, shuffle=True, collate_fn=generate_batch)

6.序列到序列的Transformer模型

接下来的一些代码和文本解释(用斜体书写)来自PyTorch的原始教程[https://pytorch.org/tutorials/beginner/translation_transformer.html]。除了将BATCH_SIZE进行了调整,并将de_vocab改为ja_vocab之外,我没有做任何更改。

Transformer是一个Seq2Seq模型,它在“Attention is all you need”论文中被引入,用于解决机器翻译任务。Transformer模型由一个编码器和一个解码器块组成,每个块都包含固定数量的层。

编码器通过一系列的多头注意力和前馈网络层来处理输入序列。编码器输出的结果,被称为“记忆”,与目标张量一起被传递给解码器。编码器和解码器使用教师强迫(teacher forcing)技术以端到端的方式进行训练。

import torch.nn as nn
from torch import Tensor
from positional_encoding import PositionalEncoding
from token_embedding import TokenEmbedding

class Seq2SeqTransformer(nn.Module):
    def __init__(self, num_encoder_layers: int, num_decoder_layers: int,
                 emb_size: int, src_vocab_size: int, tgt_vocab_size: int,
                 dim_feedforward:int = 512, dropout:float = 0.1):
        super(Seq2SeqTransformer, self).__init__()

        # 初始化Transformer编码器层
        encoder_layer = TransformerEncoderLayer(d_model=emb_size, nhead=NHEAD,
                                                dim_feedforward=dim_feedforward)
        # 创建Transformer编码器
        self.transformer_encoder = TransformerEncoder(encoder_layer, num_layers=num_encoder_layers)

        # 初始化Transformer解码器层
        decoder_layer = TransformerDecoderLayer(d_model=emb_size, nhead=NHEAD,
                                                dim_feedforward=dim_feedforward)
        # 创建Transformer解码器
        self.transformer_decoder = TransformerDecoder(decoder_layer, num_layers=num_decoder_layers)

        # 线性层用于生成输出词汇
        self.generator = nn.Linear(emb_size, tgt_vocab_size)

        # 初始化TokenEmbedding层,用于词嵌入
        self.src_tok_emb = TokenEmbedding(src_vocab_size, emb_size)
        self.tgt_tok_emb = TokenEmbedding(tgt_vocab_size, emb_size)

        # 初始化位置编码层,用于处理位置信息
        self.positional_encoding = PositionalEncoding(emb_size, dropout=dropout)

    def forward(self, src: Tensor, trg: Tensor, src_mask: Tensor,
                tgt_mask: Tensor, src_padding_mask: Tensor,
                tgt_padding_mask: Tensor, memory_key_padding_mask: Tensor):
        # 对源语言和目标语言进行位置编码和词嵌入
        src_emb = self.positional_encoding(self.src_tok_emb(src))
        tgt_emb = self.positional_encoding(self.tgt_tok_emb(trg))

        # 编码阶段,将源语言句子编码为内部表示
        memory = self.transformer_encoder(src_emb, src_mask, src_padding_mask)

        # 解码阶段,根据内部表示生成目标语言句子
        outs = self.transformer_decoder(tgt_emb, memory, tgt_mask, None,
                                        tgt_padding_mask, memory_key_padding_mask)

        # 通过线性层生成最终的输出词汇
        return self.generator(outs)

    def encode(self, src: Tensor, src_mask: Tensor):
        # 编码阶段,将源语言句子编码为内部表示
        return self.transformer_encoder(self.positional_encoding(
                            self.src_tok_emb(src)), src_mask)

    def decode(self, tgt: Tensor, memory: Tensor, tgt_mask: Tensor):
        # 解码阶段,根据内部表示生成目标语言句子
        return self.transformer_decoder(self.positional_encoding(
                          self.tgt_tok_emb(tgt)), memory,
                          tgt_mask)
class PositionalEncoding(nn.Module):
    def __init__(self, emb_size: int, dropout, maxlen: int = 5000):
        super(PositionalEncoding, self).__init__()

        # 计算位置编码矩阵
        den = torch.exp(- torch.arange(0, emb_size, 2) * math.log(10000) / emb_size)
        pos = torch.arange(0, maxlen).reshape(maxlen, 1)
        pos_embedding = torch.zeros((maxlen, emb_size))
        pos_embedding[:, 0::2] = torch.sin(pos * den)
        pos_embedding[:, 1::2] = torch.cos(pos * den)
        pos_embedding = pos_embedding.unsqueeze(-2)

        # 初始化Dropout层和位置编码矩阵
        self.dropout = nn.Dropout(dropout)
        self.register_buffer('pos_embedding', pos_embedding)

    def forward(self, token_embedding: Tensor):
        # 将位置编码矩阵和词嵌入相加,并应用Dropout
        return self.dropout(token_embedding + self.pos_embedding[:token_embedding.size(0), :])

class TokenEmbedding(nn.Module):
    def __init__(self, vocab_size: int, emb_size):
        super(TokenEmbedding, self).__init__()

        # 初始化词嵌入层
        self.embedding = nn.Embedding(vocab_size, emb_size)
        self.emb_size = emb_size

    def forward(self, tokens: Tensor):
        # 返回词嵌入结果,并乘以根号下词嵌入维度
        return self.embedding(tokens.long()) * math.sqrt(self.emb_size)

我们创建一个后续词掩码,以防止目标词关注到其后续的词汇。我们还创建了掩码,用于掩盖源语言和目标语言中的填充标记。

def generate_square_subsequent_mask(sz):
    # 生成一个上三角矩阵
    mask = (torch.triu(torch.ones((sz, sz), device=device)) == 1).transpose(0, 1)
    # 将矩阵转换为浮点型,并将为0的位置填充为负无穷,为1的位置填充为0
    mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
    return mask

def create_mask(src, tgt):
    src_seq_len = src.shape[0]
    tgt_seq_len = tgt.shape[0]

    # 生成目标语言的mask
    tgt_mask = generate_square_subsequent_mask(tgt_seq_len)
    # 生成源语言的mask,全零矩阵
    src_mask = torch.zeros((src_seq_len, src_seq_len), device=device).type(torch.bool)

    # 生成源语言和目标语言的padding mask
    src_padding_mask = (src == PAD_IDX).transpose(0, 1)
    tgt_padding_mask = (tgt == PAD_IDX).transpose(0, 1)

    return src_mask, tgt_mask, src_padding_mask, tgt_padding_mask

这里我们服务器实在是计算能力有限,按照以下配置可以训练但是效果应该是不行的。如果想要看到训练的效果请使用你自己的带GPU的电脑运行这一套代码。

当你使用自己的GPU的时候,NUM_ENCODER_LAYERS 和 NUM_DECODER_LAYERS 设置为3或者更高,NHEAD设置8,EMB_SIZE设置为512。

SRC_VOCAB_SIZE = len(ja_vocab)
TGT_VOCAB_SIZE = len(en_vocab)
EMB_SIZE = 512
NHEAD = 8
FFN_HID_DIM = 512
BATCH_SIZE = 16
NUM_ENCODER_LAYERS = 3
NUM_DECODER_LAYERS = 3
NUM_EPOCHS = 16

# 初始化Seq2SeqTransformer模型
transformer = Seq2SeqTransformer(NUM_ENCODER_LAYERS, NUM_DECODER_LAYERS,
                                 EMB_SIZE, SRC_VOCAB_SIZE, TGT_VOCAB_SIZE,
                                 FFN_HID_DIM)

# 对模型参数进行Xavier初始化
for p in transformer.parameters():
    if p.dim() > 1:
        nn.init.xavier_uniform_(p)

# 将模型移动到指定的设备上
transformer = transformer.to(device)

# 定义交叉熵损失函数,忽略填充符号PAD_IDX
loss_fn = torch.nn.CrossEntropyLoss(ignore_index=PAD_IDX)

# 定义Adam优化器
optimizer = torch.optim.Adam(
    transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9
)

def train_epoch(model, train_iter, optimizer):
    model.train()
    losses = 0
    for idx, (src, tgt) in enumerate(train_iter):
        src = src.to(device)
        tgt = tgt.to(device)

        tgt_input = tgt[:-1, :]

        src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)

        logits = model(src, tgt_input, src_mask, tgt_mask,
                       src_padding_mask, tgt_padding_mask, src_padding_mask)

        optimizer.zero_grad()

        tgt_out = tgt[1:,:]
        loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))
        loss.backward()

        optimizer.step()
        losses += loss.item()
    return losses / len(train_iter)

def evaluate(model, val_iter):
    model.eval()
    losses = 0
    for idx, (src, tgt) in enumerate(valid_iter):
        src = src.to(device)
        tgt = tgt.to(device)

        tgt_input = tgt[:-1, :]

        src_mask, tgt_mask, src_padding_mask, tgt_padding_mask = create_mask(src, tgt_input)

        logits = model(src, tgt_input, src_mask, tgt_mask,
                       src_padding_mask, tgt_padding_mask, src_padding_mask)
        tgt_out = tgt[1:,:]
        loss = loss_fn(logits.reshape(-1, logits.shape[-1]), tgt_out.reshape(-1))
        losses += loss.item()
    return losses / len(val_iter)

7.开始训练

最后,在准备好必要的类和函数之后,我们就可以开始训练我们的模型了。不用说,完成训练所需的时间可能会因很多因素而有很大差异,比如计算能力、参数和数据集的大小。

当我使用JParaCrawl中的完整句子列表(每种语言大约有590万个句子)训练模型时,使用单个NVIDIA GeForce RTX 3070 GPU,每个epoch大约需要5个小时。

下面是相关代码:

for epoch in tqdm.tqdm(range(1, NUM_EPOCHS+1)):
    start_time = time.time()
    # 训练一个epoch,返回训练损失
    train_loss = train_epoch(transformer, train_iter, optimizer)
    end_time = time.time()
    # 打印当前epoch的训练损失和训练时间
    print((f"Epoch: {epoch}, Train loss: {train_loss:.3f}, "
           f"Epoch time = {(end_time - start_time):.3f}s"))

8.使用训练好的模型来翻译一个日语句子

首先,我们创建用于翻译新句子的函数,这些步骤包括获取日语句子、分词、转换为张量、进行推理,然后将结果解码回句子,但这次是用英语。

def greedy_decode(model, src, src_mask, max_len, start_symbol):
    # 将输入数据和掩码移动到指定的设备上
    src = src.to(device)
    src_mask = src_mask.to(device)
    
    # 编码源语言句子,得到编码信息memory
    memory = model.encode(src, src_mask)
    
    # 初始化目标语言句子,填充起始符号
    ys = torch.ones(1, 1).fill_(start_symbol).type(torch.long).to(device)
    
    # 进行解码,直到达到最大长度或者遇到终止符号
    for i in range(max_len-1):
        # 将memory移动到指定的设备上
        memory = memory.to(device)
        
        # 创建memory_mask,用于解码过程中的掩码
        memory_mask = torch.zeros(ys.shape[0], memory.shape[0]).to(device).type(torch.bool)
        
        # 创建目标语言句子的掩码
        tgt_mask = (generate_square_subsequent_mask(ys.size(0))
                                    .type(torch.bool)).to(device)
        
        # 解码得到输出
        out = model.decode(ys, memory, tgt_mask)
        out = out.transpose(0, 1)
        
        # 通过generator得到概率分布,并选择概率最大的词作为下一个词
        prob = model.generator(out[:, -1])
        _, next_word = torch.max(prob, dim = 1)
        next_word = next_word.item()
        
        # 将预测的词加入到目标语言句子中
        ys = torch.cat([ys,
                        torch.ones(1, 1).type_as(src.data).fill_(next_word)], dim=0)
        
        # 如果预测的词是终止符号,则停止解码
        if next_word == EOS_IDX:
            break
    
    return ys

def translate(model, src, src_vocab, tgt_vocab, src_tokenizer):
    # 设置模型为评估模式
    model.eval()
    
    # 对源语言进行tokenization,并添加起始符号和终止符号
    tokens = [BOS_IDX] + [src_vocab.stoi[tok] for tok in src_tokenizer.encode(src, out_type=str)]+ [EOS_IDX]
    num_tokens = len(tokens)
    
    # 将token转换为PyTorch张量并添加维度
    src = (torch.LongTensor(tokens).reshape(num_tokens, 1) )
    
    # 创建源语言句子的掩码
    src_mask = (torch.zeros(num_tokens, num_tokens)).type(torch.bool)
    
    # 进行贪婪解码,生成目标语言句子的token
    tgt_tokens = greedy_decode(model,  src, src_mask, max_len=num_tokens + 5, start_symbol=BOS_IDX).flatten()
    
    # 将token转换为目标语言句子并去除特殊符号
    return " ".join([tgt_vocab.itos[tok] for tok in tgt_tokens]).replace("<bos>", "").replace("<eos>", "")

那么,我们可以直接调用翻译函数并传入所需的参数

translate(transformer, "HSコード 8515 はんだ付け用、ろう付け用又は溶接用の機器(電気式(電気加熱ガス式を含む。)", ja_vocab, en_vocab, ja_tokenizer)

trainen.pop(5)

 

trainja.pop(5)

9.保存词汇对象和训练好的模型

 最后, 培训完成后, 我们将首先使用Pickle保存Vocab对象(en_vocab和ja_vocab)

import pickle

# 打开一个文件,用于存储数据
file = open('en_vocab.pkl', 'wb')

# 将英文词汇表(en_vocab)信息存储到文件中
pickle.dump(en_vocab, file)

# 关闭文件
file.close()

# 打开一个文件,用于存储数据
file = open('ja_vocab.pkl', 'wb')

# 将日文词汇表(ja_vocab)信息存储到文件中
pickle.dump(ja_vocab, file)

# 关闭文件
file.close()

最后,我们还可以使用PyTorch的保存和加载函数来保存模型以备以后使用。通常,根据我们以后想要使用模型的方式,有两种保存模型的方法。第一种是仅用于推断,可以稍后加载模型并将其用于从日语翻译成英语。

# save model for inference
torch.save(transformer.state_dict(), 'inference_model')

第二个参数也用于推断,但是还用于当我们想要稍后加载模型并恢复训练时。

# save model + checkpoint to resume training later
torch.save({
    'epoch': NUM_EPOCHS,  # 保存当前训练的轮数
    'model_state_dict': transformer.state_dict(),  # 保存模型的状态字典
    'optimizer_state_dict': optimizer.state_dict(),  # 保存优化器的状态字典
    'loss': train_loss,  # 保存训练损失
}, 'model_checkpoint.tar')  # 将这些信息保存到名为model_checkpoint.tar的文件中

四、小结

在进行基于Transformer的机器翻译模型实验后,我们获得了显著的结果。该模型凭借其强大的自注意力机制和多头注意力结构,成功捕捉了源语言句子中的长距离依赖关系,并生成了高质量的目标语言翻译。实验结果表明,Transformer模型在翻译准确性、流畅性和效率方面都优于传统方法。同时,该模型的可扩展性和灵活性也使其能够轻松适应不同的语言对和翻译任务。通过这次实验,我们深刻体会到了Transformer模型在自然语言处理领域的强大潜力和应用价值。

  • 42
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值