在Task3中,我们使用了Transformer模型来进行机器翻译,以提升翻译的准确度和翻译的效率。Transformer模型摒弃了循环结构,并完全通过注意力机制完成对源语言序列和目标语言序列全局依赖进行建模。在抽取每个单词的上下文特征时,Transformer 通过自注意力机制(self-attention)衡量上下文中每一个单词对当前单词的重要程度,在这个过程当中没有任何的循环单元参与计算。这种高度可并行化的编码过程使得模型的运行变得十分高效。
Transformer的主要组件包括编码器(Encoder)、解码器(Decoder)和注意力层。其核心是利用多头自注意力机制(Multi-Head Self-Attention),使每个位置的表示不仅依赖于当前位置,还能够直接获取其他位置的表示。自从提出以来,Transformer模型在机器翻译、文本生成等自然语言处理任务中均取得了突破性进展,成为NLP领域新的主流模型。
Transformer模型是机器翻译领域一个非常重要的知识点,是各种笔试面试必考的地方,面试题可以参考transformer模型— 20道面试题自我检测
Transformer模型的首次提出是在论文《Attention Is All You Need》上,有关于论文的讲解和解读可以参考李沐老师的视频Transformer论文逐段精读【论文精读】 ,更多有关论文精读的资料可以见深度学习论文精读
优化后代码
主要的优化部分是将模型结构的代码改成使用Transformer模型。
!mkdir ../model
!mkdir ../results
!pip install torchtext
!pip install jieba
!pip install sacrebleu
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.nn.utils import clip_grad_norm_
from torchtext.data.metrics import bleu_score
from torch.utils.data import Dataset, DataLoader
from torchtext.data.utils import get_tokenizer
from torchtext.vocab import build_vocab_from_iterator
from typing import List, Tuple
import jieba
import random
from torch.nn.utils.rnn import pad_sequence
import sacrebleu
import time
import math
!pip install -U pip setuptools wheel -i https://mirrors.aliyun.com/pypi/simple
!pip install -U 'spacy[cuda12x]' -i https://mirrors.aliyun.com/pypi/simple
!pip install ../dataset/en_core_web_trf-3.7.3-py3-none-any.whl
# !python -m spacy download en_core_web_sm
# 定义tokenizer
en_tokenizer = get_tokenizer('spacy', language='en_core_web_trf')
zh_tokenizer = lambda x: list(jieba.cut(x)) # 使用jieba分词
# 读取数据函数
def read_data(file_path: str) -> List[str]:
with open(file_path, 'r', encoding='utf-8') as f:
return [line.strip() for line in f]
# 数据预处理函数
def preprocess_data(en_data: List[str], zh_data: List[str]) -> List[Tuple[List[str], List[str]]]:
processed_data = []
for en, zh in zip(en_data, zh_data):
en_tokens = en_tokenizer(en.lower())[:MAX_LENGTH]
zh_tokens = zh_tokenizer(zh)[:MAX_LENGTH]
if en_tokens and zh_tokens: # 确保两个序列都不为空
processed_data.append((en_tokens, zh_tokens))
return processed_data
# 构建词汇表
def build_vocab(data: List[Tuple[List[str], List[str]]]):
en_vocab = build_vocab_from_iterator(
(en for en, _ in data),
specials=['<unk>', '<pad>', '<bos>', '<eos>']
)
zh_vocab = build_vocab_from_iterator(
(zh for _, zh in data),
specials=['<unk>', '<pad>', '<bos>', '<eos>']
)
en_vocab.set_default_index(en_vocab['<unk>'])
zh_vocab.set_default_index(zh_vocab['<unk>'])
return en_vocab, zh_vocab
class TranslationDataset(Dataset):
def __init__(self, data: List[Tuple[List[str], List[str]]], en_vocab, zh_vocab):
self.data = data
self.en_vocab = en_vocab
self.zh_vocab = zh_vocab
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
en, zh = self.data[idx]
en_indices = [self.en_vocab['<bos>']] + [self.en_vocab[token] for token in en] + [self.en_vocab['<eos>']]
zh_indices = [self.zh_vocab['<bos>']] + [self.zh_vocab[token] for token in zh] + [self.zh_vocab['<eos>']]
return en_indices, zh_indices
def collate_fn(batch):
en_batch, zh_batch = [], []
for en_item, zh_item in batch:
if en_item and zh_item: # 确保两个序列都不为空
# print("都不为空")
en_batch.append(torch.tensor(en_item))
zh_batch.append(torch.tensor(zh_item))
else:
print("存在为空")
if not en_batch or not zh_batch: # 如果整个批次为空,返回空张量
return torch.tensor([]), torch.tensor([])
# src_sequences = [item[0] for item in batch]
# trg_sequences = [item[1] for item in batch]
en_batch = nn.utils.rnn.pad_sequence(en_batch, batch_first=True, padding_value=en_vocab['<pad>'])
zh_batch = nn.utils.rnn.pad_sequence(zh_batch, batch_first=True, padding_value=zh_vocab['<pad>'])
# en_batch = pad_sequence(en_batch, batch_first=True, padding_value=en_vocab['<pad>'])
# zh_batch = pad_sequence(zh_batch, batch_first=True, padding_value=zh_vocab['<pad>'])
return en_batch, zh_batch
# 数据加载函数
def load_data(train_path: str, dev_en_path: str, dev_zh_path: str, test_en_path: str):
# 读取训练数据
train_data = read_data(train_path)
train_en, train_zh = zip(*(line.split('\t') for line in train_data))
# 读取开发集和测试集
dev_en = read_data(dev_en_path)
dev_zh = read_data(dev_zh_path)
test_en = read_data(test_en_path)
# 预处理数据
train_processed = preprocess_data(train_en, train_zh)
dev_processed = preprocess_data(dev_en, dev_zh)
test_processed = [(en_tokenizer(en.lower())[:MAX_LENGTH], []) for en in test_en if en.strip()]
# 构建词汇表
global en_vocab, zh_vocab
en_vocab, zh_vocab = build_vocab(train_processed)
# 创建数据集
train_dataset = TranslationDataset(train_processed, en_vocab, zh_vocab)
dev_dataset = TranslationDataset(dev_processed, en_vocab, zh_vocab)
test_dataset = TranslationDataset(test_processed, en_vocab, zh_vocab)
from torch.utils.data import Subset
# 假设你有10000个样本,你只想用前1000个样本进行测试
indices = list(range(N))
train_dataset = Subset(train_dataset, indices)
# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, collate_fn=collate_fn, drop_last=True)
dev_loader = DataLoader(dev_dataset, batch_size=BATCH_SIZE, collate_fn=collate_fn, drop_last=True)
test_loader = DataLoader(test_dataset, batch_size=1, collate_fn=collate_fn, drop_last=True)
return train_loader, dev_loader, test_loader, en_vocab, zh_vocab
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout=0.1, max_len=5000):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(p=dropout)
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0).transpose(0, 1)
self.register_buffer('pe', pe)
def forward(self, x):
x = x + self.pe[:x.size(0), :]
return self.dropout(x)
class TransformerModel(nn.Module):
def __init__(self, src_vocab, tgt_vocab, d_model, nhead, num_encoder_layers, num_decoder_layers, dim_feedforward, dropout):
super(TransformerModel, self).__init__()
self.transformer = nn.Transformer(d_model, nhead, num_encoder_layers, num_decoder_layers, dim_feedforward, dropout)
self.src_embedding = nn.Embedding(len(src_vocab), d_model)
self.tgt_embedding = nn.Embedding(len(tgt_vocab), d_model)
self.positional_encoding = PositionalEncoding(d_model, dropout)
self.fc_out = nn.Linear(d_model, len(tgt_vocab))
self.src_vocab = src_vocab
self.tgt_vocab = tgt_vocab
self.d_model = d_model
def forward(self, src, tgt):
# 调整src和tgt的维度
src = src.transpose(0, 1) # (seq_len, batch_size)
tgt = tgt.transpose(0, 1) # (seq_len, batch_size)
src_mask = self.transformer.generate_square_subsequent_mask(src.size(0)).to(src.device)
tgt_mask = self.transformer.generate_square_subsequent_mask(tgt.size(0)).to(tgt.device)
src_padding_mask = (src == self.src_vocab['<pad>']).transpose(0, 1)
tgt_padding_mask = (tgt == self.tgt_vocab['<pad>']).transpose(0, 1)
src_embedded = self.positional_encoding(self.src_embedding(src) * math.sqrt(self.d_model))
tgt_embedded = self.positional_encoding(self.tgt_embedding(tgt) * math.sqrt(self.d_model))
output = self.transformer(src_embedded, tgt_embedded,
src_mask, tgt_mask, None, src_padding_mask, tgt_padding_mask, src_padding_mask)
return self.fc_out(output).transpose(0, 1)
def initialize_model(src_vocab, tgt_vocab, d_model=512, nhead=8, num_encoder_layers=6, num_decoder_layers=6, dim_feedforward=2048, dropout=0.1):
model = TransformerModel(src_vocab, tgt_vocab, d_model, nhead, num_encoder_layers, num_decoder_layers, dim_feedforward, dropout)
return model
# 定义优化器
def initialize_optimizer(model, learning_rate=0.001):
return optim.Adam(model.parameters(), lr=learning_rate)
# 运行时间
def epoch_time(start_time, end_time):
elapsed_time = end_time - start_time
elapsed_mins = int(elapsed_time / 60)
elapsed_secs = int(elapsed_time - (elapsed_mins * 60))
return elapsed_mins, elapsed_secs
def train(model, iterator, optimizer, criterion, clip):
model.train()
epoch_loss = 0
for i, batch in enumerate(iterator):
src, tgt = batch
if src.numel() == 0 or tgt.numel() == 0:
continue
src, tgt = src.to(DEVICE), tgt.to(DEVICE)
optimizer.zero_grad()
output = model(src, tgt[:, :-1])
output_dim = output.shape[-1]
output = output.contiguous().view(-1, output_dim)
tgt = tgt[:, 1:].contiguous().view(-1)
loss = criterion(output, tgt)
loss.backward()
clip_grad_norm_(model.parameters(), clip)
optimizer.step()
epoch_loss += loss.item()
return epoch_loss / len(iterator)
def evaluate(model, iterator, criterion):
model.eval()
epoch_loss = 0
with torch.no_grad():
for i, batch in enumerate(iterator):
src, tgt = batch
if src.numel() == 0 or tgt.numel() == 0:
continue
src, tgt = src.to(DEVICE), tgt.to(DEVICE)
output = model(src, tgt[:, :-1])
output_dim = output.shape[-1]
output = output.contiguous().view(-1, output_dim)
tgt = tgt[:, 1:].contiguous().view(-1)
loss = criterion(output, tgt)
epoch_loss += loss.item()
return epoch_loss / len(iterator)
def translate_sentence(src_indexes, src_vocab, tgt_vocab, model, device, max_length=50):
model.eval()
src_tensor = src_indexes.unsqueeze(0).to(device) # 添加批次维度
with torch.no_grad():
encoder_outputs = model.transformer.encoder(model.positional_encoding(model.src_embedding(src_tensor) * math.sqrt(model.d_model)))
trg_indexes = [tgt_vocab['<bos>']]
for i in range(max_length):
trg_tensor = torch.LongTensor(trg_indexes).unsqueeze(0).to(device)
with torch.no_grad():
output = model(src_tensor, trg_tensor)
pred_token = output.argmax(2)[:, -1].item()
trg_indexes.append(pred_token)
if pred_token == tgt_vocab['<eos>']:
break
trg_tokens = [tgt_vocab.get_itos()[i] for i in trg_indexes]
return trg_tokens[1:-1] # 移除<bos>和<eos>标记
def calculate_bleu(dev_loader, src_vocab, tgt_vocab, model, device):
model.eval()
translations = []
references = []
with torch.no_grad():
for src, tgt in dev_loader:
src = src.to(device)
for sentence in src:
translated = translate_sentence(sentence, src_vocab, tgt_vocab, model, device)
translations.append(' '.join(translated))
for reference in tgt:
ref_tokens = [tgt_vocab.get_itos()[idx] for idx in reference if idx not in [tgt_vocab['<bos>'], tgt_vocab['<eos>'], tgt_vocab['<pad>']]]
references.append([' '.join(ref_tokens)])
bleu = sacrebleu.corpus_bleu(translations, references)
return bleu.score
# 主训练循环
def train_model(model, train_iterator, valid_iterator, optimizer, criterion, N_EPOCHS=10, CLIP=1, save_path = '../model/best-model_transformer.pt'):
best_valid_loss = float('inf')
for epoch in range(N_EPOCHS):
start_time = time.time()
#print(f"Starting Epoch {epoch + 1}")
train_loss = train(model, train_iterator, optimizer, criterion, CLIP)
valid_loss = evaluate(model, valid_iterator, criterion)
end_time = time.time()
epoch_mins, epoch_secs = epoch_time(start_time, end_time)
if valid_loss < best_valid_loss:
best_valid_loss = valid_loss
torch.save(model.state_dict(), save_path)
print(f'Epoch: {epoch+1:02} | Time: {epoch_mins}m {epoch_secs}s')
print(f'\tTrain Loss: {train_loss:.3f} | Train PPL: {math.exp(train_loss):7.3f}')
print(f'\t Val. Loss: {valid_loss:.3f} | Val. PPL: {math.exp(valid_loss):7.3f}')
# 定义常量
MAX_LENGTH = 100 # 最大句子长度
BATCH_SIZE = 32
DEVICE = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
N = 148363 # 采样训练集的数量,最多148363
train_path = '../dataset/train.txt'
dev_en_path = '../dataset/dev_en.txt'
dev_zh_path = '../dataset/dev_zh.txt'
test_en_path = '../dataset/test_en.txt'
train_loader, dev_loader, test_loader, en_vocab, zh_vocab = load_data(
train_path, dev_en_path, dev_zh_path, test_en_path
)
print(f"英语词汇表大小: {len(en_vocab)}")
print(f"中文词汇表大小: {len(zh_vocab)}")
print(f"训练集大小: {len(train_loader.dataset)}")
print(f"开发集大小: {len(dev_loader.dataset)}")
print(f"测试集大小: {len(test_loader.dataset)}")
# 主函数
if __name__ == '__main__':
# 模型参数
D_MODEL = 256
NHEAD = 8
NUM_ENCODER_LAYERS = 3
NUM_DECODER_LAYERS = 3
DIM_FEEDFORWARD = 512
DROPOUT = 0.1
N_EPOCHS = 5
CLIP = 1
# 初始化模型
model = initialize_model(en_vocab, zh_vocab, D_MODEL, NHEAD, NUM_ENCODER_LAYERS, NUM_DECODER_LAYERS, DIM_FEEDFORWARD, DROPOUT).to(DEVICE)
print(f'The model has {sum(p.numel() for p in model.parameters() if p.requires_grad):,} trainable parameters')
# 定义损失函数
criterion = nn.CrossEntropyLoss(ignore_index=zh_vocab['<pad>'])
# 初始化优化器
optimizer = optim.Adam(model.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)
# 训练模型
save_path = '../model/best-model_transformer.pt'
train_model(model, train_loader, dev_loader, optimizer, criterion, N_EPOCHS, CLIP, save_path=save_path)
print(f"训练完成!模型已保存到:{save_path}")
# 加载最佳模型
model.load_state_dict(torch.load('../model/best-model_transformer.pt'))
save_dir = '../results/submit_task3.txt'
with open(save_dir, 'w') as f:
translated_sentences = []
for batch in test_loader: # 遍历所有数据
src, _ = batch
src = src.to(DEVICE)
translated = translate_sentence(src[0], en_vocab, zh_vocab, model, DEVICE) #翻译结果
results = "".join(translated)
f.write(results + '\n') # 将结果写入文件
print(f"翻译完成,结果已保存到{save_dir}")
代码运行结果如下图所示:
比赛提交分数如下图所示:
改进方向
最简单的就是调参,将 epochs 调大一点,使用全部训练集,以及调整模型的参数,如head、layers等。如果数据量允许,增加模型的深度(更多的编码器/解码器层)或宽度(更大的隐藏层尺寸),这通常可以提高模型的表达能力和翻译质量,尤其是在处理复杂或专业内容时。
加入术语词典,这是在此竞赛中比较有效的方法,加入术语词典的方法策略也有很多,如:
在模型生成的翻译输出中替换术语,这是最简单的方法
整合到数据预处理流程,确保它们在翻译中保持一致
在模型内部动态地调整术语的嵌入,这涉及到在模型中加入一个额外的层,该层负责查找术语词典中的术语,并为其生成专门的嵌入向量,然后将这些向量与常规的词嵌入结合使用
认真做数据清洗,当前训练集可能会存在脏数据,影响我们的模型训练
数据扩增:
回译(back-translation):将源语言文本先翻译成目标语言,再将目标语言文本翻译回源语言,生成的新文本作为额外的训练数据
同义词替换:随机选择句子中的词,并用其同义词替换
使用句法分析和语义解析技术重新表述句子,保持原意不变
将文本翻译成多种语言后再翻译回原语言,以获得多样化翻译
采用更精细的学习率调度策略(Task1使用的是固定学习率):
Noam Scheduler:结合了warmup阶段和衰减阶段
Step Decay:最简单的一种学习率衰减策略,每隔一定数量的epoch,学习率按固定比例衰减
Cosine Annealing:学习率随周期性变化,通常从初始值下降到接近零,然后再逐渐上升
自己训练一个小的预训练模型,尽量选择 1B 以下小模型,对 GPU 资源要求比较高,仅仅使用魔搭平台可能就满足不了
将训练集上训练出来的模型拿到开发集(dev dataset)上 finetune 可以提高测试集(test dataset)的得分,因为开发集与测试集的分布比较相近
在开发集和测试集上训一个语言模型,用这个语言模型给训练集中的句子打分,选出一些高分句子
集成学习:训练多个不同初始化或架构的模型,并使用集成方法(如投票或平均)来产生最终翻译。这可以减少单一模型的过拟合风险,提高翻译的稳定性。
改进代码——调整参数
1、优化后代码(部分)
# 定义常量
MAX_LENGTH = 100 # 最大句子长度
BATCH_SIZE = 64
DEVICE = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
N = 148363 # 采样训练集的数量,最多148363
# 模型参数
D_MODEL = 256
NHEAD = 8
NUM_ENCODER_LAYERS = 5
NUM_DECODER_LAYERS = 5
DIM_FEEDFORWARD = 512
DROPOUT = 0.1
N_EPOCHS = 10
CLIP = 1
2、优化后结果
Transformer模型的介绍
1、原理
Transformer模型的核心在于自注意力机制(Self-Attention),其通过允许每个位置上的单词与序列中其他位置的单词进行关联,从而捕捉全局上下文信息。这种机制克服了传统循环神经网络中的长期依赖问题,并且可以并行处理序列中的每个元素,极大地提高了计算效率。
(1)自注意力机制(Self-Attention)
在Transformer中,输入序列被分为多个维度表示,例如词嵌入(Word Embeddings)。自注意力机制的计算过程如下:
- 输入向量:每个词嵌入向量被拆分为三个部分:查询向量(Query)、键向量(Key)、值向量(Value)。
- 计算注意力权重:通过计算查询向量和键向量之间的点积,然后应用softmax函数得到注意力权重。这些权重决定了每个位置对当前位置的重要性。
- 加权求和:将值向量乘以注意力权重,并对所有位置的加权值求和,得到自注意力机制的输出。
(2)位置编码(Positional Encoding)
由于Transformer没有像RNN那样的序列位置信息,所以需要添加位置编码来表示每个词的位置。位置编码通常是通过添加不同频率的正弦和余弦函数来实现的,使得模型可以理解输入序列中的顺序信息。
2、结构
Transformer模型通常由多个堆叠的模块组成,主要包括编码器(Encoder)和解码器(Decoder):
(1)编码器(Encoder)
- 输入:编码器接收输入序列的词嵌入和位置编码。
- 自注意力层:处理输入序列并计算每个位置的自注意力表示。
- 前馈神经网络(Feedforward Network):在每个位置应用全连接的前馈神经网络。
- 残差连接和层归一化:每个子层后都应用残差连接和层归一化,有助于训练更深的模型并加速收敛。
(2)解码器(Decoder)
- 输入和编码器-解码器注意力:解码器接收编码器的输出作为输入,并且也包含自注意力机制和编码器-解码器注意力机制,后者帮助解码器对输入序列进行加权表示。
- 自注意力层和前馈网络:与编码器类似,但解码器还包括对上下文的理解和生成下一个词的任务。
(3)输出层
最终,解码器的输出会经过线性变换和softmax操作,生成最终的预测输出序列。
3、特性和优势
- 并行性:Transformer可以并行处理序列中的每个元素,相比于传统的RNN和LSTM模型,在处理长序列时具有更高的效率。
- 远距离依赖:通过自注意力机制,Transformer能够捕捉长距离的依赖关系,这对于语言建模和文本生成等任务尤为重要。
- 可扩展性:Transformer模型结构简单且高度灵活,适用于不同规模和不同任务的应用,如BERT、GPT等模型的成功应用即是例证。
4、应用领域
Transformer模型广泛应用于自然语言处理领域,如机器翻译、文本生成、问答系统、命名实体识别等任务。它也被扩展到其他领域,如计算机视觉和语音处理,展示了其在序列建模和生成任务中的广泛适用性和效果。
拓展:(选学)利用大模型解决NLP任务
使用大模型进行机器翻译,要比自己训练一个任务简单许多,主要有两种解决方案:
-
直接调用大模型 API 来解决
-
本地部署大模型进行推理
1、直接调用大模型API来解决
可以参考Datawhale 的llm-universe 项目https://github.com/datawhalechina/llm-universe/tree/main/notebook/C2%20%E4%BD%BF%E7%94%A8%20LLM%20API%20%E5%BC%80%E5%8F%91%E5%BA%94%E7%94%A8 实现了ChatGPT、百度文心、讯飞星火、智谱AI等大模型API的调用,包括调用原生 API、封装为 LangChain LLM、封装为 Fastapi 等调用方式。
2、本地部署大模型进行推理
(Qwen2-7B-Instruct 模型需要显存 16GB +,如果没有这么大可以尝试其他小一点的模型)
(1)加载模型
import os
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
from transformers import AutoModelForCausalLM, AutoTokenizer
device = "cuda" # the device to load the model onto
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2-7B-Instruct", #"/mnt/data/xuhu/qwen/Qwen2-7B-Instruct"
torch_dtype="auto",
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained("/mnt/data/xuhu/qwen/Qwen2-7B-Instruct")
-
环境变量设置: 代码中设置了一个环境变量
HF_ENDPOINT
,值为https://hf-mirror.com
。这可能用于指定模型或数据下载时的镜像或服务端点,确保从指定的地址获取模型或数据。 -
导入库: 通过导入
transformers
库中的AutoModelForCausalLM
和AutoTokenizer
类,代码准备加载预训练的语言模型和其分词器。这些类提供了方便的接口,使得加载和使用预训练模型变得更加简单和高效。 -
设备选择: 代码指定了设备为
cuda
,这意味着希望将模型加载到CUDA设备上进行运算。CUDA通常是NVIDIA GPU的计算能力,用于加速深度学习模型的训练和推理过程。如果没有CUDA设备,可以选择使用cpu
作为替代。 -
加载预训练模型: 使用
AutoModelForCausalLM.from_pretrained
方法加载预训练模型。这个方法接受模型的名称或路径作为参数,并且可以自动选择合适的张量数据类型和设备映射,以优化模型在给定硬件上的性能表现。 -
加载分词器: 代码加载与模型对应的分词器,通过
AutoTokenizer.from_pretrained
方法实现。分词器的作用是将输入的文本数据转换为模型能够理解的标记序列,这是使用预训练模型进行文本处理和生成的必要步骤。
(2)加载数据
import pandas as pd
dc = pd.read_csv('../dataset/en-zh.dic', sep='\t', header=None).set_index(0).to_dict()[1]
from tqdm import tqdm
lines = open('../dataset/test_en.txt').readlines()
- 读取词典文件:
pd.read_csv('../dataset/en-zh.dic', sep='\t', header=None)
: 使用Pandas库的read_csv
函数读取文件../dataset/en-zh.dic
,这是一个以制表符分隔的文本文件,没有列标题(header=None)。.set_index(0)
: 将读取的数据框的第一列设置为索引,这里假设第一列包含英文单词或短语。.to_dict()[1]
: 将数据框转换为字典,并选择字典的第二个值列(即第二列,索引为1),这里假设第二列包含对应的中文翻译。- 结果是
dc
变量成为一个字典,其中英文单词或短语是键,对应的中文翻译是值。
- 读取英文文本文件:
open('../dataset/test_en.txt').readlines()
: 打开文件../dataset/test_en.txt
并读取所有行,将每一行作为一个字符串存储在lines
列表中。from tqdm import tqdm
: 导入tqdm
库,用于显示进度条,可能在处理大量文本时特别有用。
(3)撰写prompts并加入术语词典进行推理
result = []
for line in tqdm(lines):
sp_words = [x for x in line.lower().split() if x in dc.keys()]
sp_words_meaning = [dc[x] for x in sp_words]
sp_prompt = '文章字符为:'
if len(sp_words) > 0:
for x, y in zip(sp_words, sp_words_meaning):
sp_prompt += f'{x} 翻译为 {y}; '
# 主要任务
messages = [
{"role": "system", "content": "将英文翻译为中文,不要有其他输出,直接输出翻译后的文本。保留特殊单词的翻译。"},
]
# 人工的词典的规则
if len(sp_prompt) > 0:
messages.append({"role": "user", "content": sp_prompt})
messages.append({"role": "user", "content": f"待翻译文本(从英文翻译为中文):{line}"})
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
model_inputs = tokenizer([text], return_tensors="pt").to(device)
generated_ids = model.generate(
model_inputs.input_ids,
max_new_tokens=512
)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
result_line = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
result.append(result_line)
with open('../results/qwen_submit.txt', 'w') as f:
for line in result:
line = line.strip().replace('\n', '')
print(line)
f.write(line + '\n')
-
循环处理每一行文本:使用
for
循环逐行处理lines
列表中的每一行英文文本。tqdm(lines)
用于在处理过程中显示进度条,方便查看处理进度。 -
提取特定单词和翻译:对当前行的文本进行小写处理,并根据在词典
dc
中存在的单词提取它们及其对应的中文翻译。这里使用列表推导式来过滤和映射符合条件的单词和翻译。 -
构建特殊单词提示:
sp_words_meaning): sp_prompt += f'{x} 翻译为 {y}; '
如果存在特殊单词(即在词典
dc
中存在的单词),则将其构建成一条提示信息sp_prompt
,格式为“英文单词 翻译为 中文翻译;”。 -
构建消息列表:构建一个消息列表
messages
,其中包含了系统的指导信息和用户的输入信息。如果存在特殊单词的提示信息,将其作为用户输入的一部分添加到消息列表中。 -
生成模型输入:使用Tokenizer对象的
apply_chat_template
方法生成聊天模板的文本,并将其转换为模型的输入。tokenizer
对象被用来对文本进行预处理,并将其转换为适合模型输入的张量表示形式,同时确保在CUDA设备上进行计算。 -
生成文本:使用预训练的语言生成模型(
model
)生成文本。generate
方法接受模型输入的input_ids
和生成的最大令牌数(max_new_tokens
),生成对应的文本输出。 -
处理生成的文本:将生成的文本通过Tokenizer对象的
batch_decode
方法转换为可读的文本,并将结果存储在result_line
中。 -
将结果写入文件:将每一行处理后的结果
result_line
添加到result
列表中,并将最终的翻译结果写入到文件qwen_submit.txt
中。每行结果去除首尾空白并写入文件。
关于更多部署微调大模型的知识,可以学习:
开源大模型食用指南https://github.com/datawhalechina/self-llm
hahaha都看到这里了,要是觉得有用的话就辛苦动动小手点个赞吧!