Task3 基于术语词典干预的机器翻译挑战赛学习笔记03
比赛任务概述:
基于术语词典干预的机器翻译挑战赛选择以英文为源语言,中文为目标语言的机器翻译。本次大赛除英文到中文的双语数据,还提供英中对照的术语词典。参赛队伍需要基于提供的训练数据样本从多语言机器翻译模型的构建与训练,并基于测试集以及术语词典,提供最终的翻译结果,数据包括:
- 训练集:双语数据:中英14万余双语句对
- 开发集:英中1000双语句对
- 测试集:英中1000双语句对
- 术语词典:英中2226条
赛题链接:
https://challenge.xfyun.cn/topic/info?type=machine-translation-2024&option=tjjg&ch=dw24_AtTCK9
Baseline 代码学习
教程文档及BaseLine代码链接
Task3:基于Transformer解决机器翻译任务 - 飞书云文档 (feishu.cn)
本次BaseLine中,将模型换成了Transfomer模型,按照BaseLine代码直接进行训练,能够得到不错的翻译效果(至少翻译出来的中文看上去像一句人话,而不是复读机器)。
数据预处理
由于训练数据量有限,以及训练数据的噪声等影响,模型最终的翻译并不能很好。因此,对数据进行处理是得到一个好的翻译模型的必需操作。以下给出我的一些尝试:(感谢学习群内大佬分享~)
数据清洗
-
对训练数据中文文本中的一些声音词进行清洗去除,避免模型学习到该部分多余内容。
-
将英文缩写进行展开(比如将There’s 变成There is),单纯在训练时使用没有意义,需在验证、测试时也配套使用,该方法也可用于扩充数据集。
# 数据清洗
import re
import contractions
import unicodedata
def unicodeToAscii(text):
return ''.join(c for c in unicodedata.normalize('NFD', text) if unicodedata.category(c) != 'Mn')
def preprocess_en(text):
text = unicodeToAscii(text.strip())
text = contractions.fix(text)
text = re.sub(r'\([^)]*\)', '', text)
text = re.sub(r"[^a-zA-Z0-9.!?]+", r" ", text) # 保留数字
return text
def preprocess_zh(text):
patterns_to_replace = ["(笑声)", "(掌声)", "(口哨声)","口哨声)", "(音乐)", "(鼓掌)", "(笑)",
"(众笑)", "(视频):", "(大笑)", "(录音)", "(消音)", "(欢呼)",
"(视频)", "(叫声)", "(录像):", "(录像)", "(拍手)", "(大喊)",
"(吟唱)", "(噪音)", "(铃声)", "(尖叫)", "(影片)", "(声音)",
"(喇叭)", "(齐唱)", "(混音)", "(音频)", "(影视)", "(噪声)",
"(口哨)", "(击掌)", "(铃铛)", "(小号)", "(歌声)", "(狂笑)",
"(演唱)", "(喝彩)", "(配乐)", "(调音)", "(笑话)", "(叹气)",
"(鸟鸣)", "(鸟鸣)", "(爆炸)", "(枪声)", "(爆笑)", "(滑音)",
"(音调)", "(游戏)", "(笑)", "(淫笑)", "(音译)", "(笑♫)",
"(音乐)", "(咳嗽)", "(咳嗽)", "(马嘶声)", "(音乐声)", "(鼓掌声)",
"(众人笑)", "(喇叭声)","(钢琴声)", "(吹口哨)","(尖叫声)", "(大家笑)",
"(重击声)", "(呼吸声)", "(感叹声)", "(敲打声)", "(背景音)", "(噼啪声)",
"(观众笑)", "(爆炸声)","(歌词:)", "(敲椅声)","(滋滋声)", "(静电声)",
"(笑~~)", "(喝彩声)", "(抨击声)", "(咳嗽声)", "(喊叫声)", "(风雨声)",
"(哭泣声)", "(大笑声)", "(欢呼声)", "(嘀嘀声)", "(闹铃声)", "(拍手声)",
"(讨论声)", "(鼓掌♫)", "(喘息声)", "(打呼声)", "(惊叫声)", "(议论声)",
"(音乐起)", "(小提琴)", "(拍巴掌)", "(众鼓掌)", "(众人鼓掌)", "(众人欢呼)",
"(观众笑声)", "(观众掌声)", "(热烈鼓掌)", "(哄堂大笑)", "(警报噪声)", "(掌声♫♪)",
"(按喇叭声)", "(众人大笑)", "(现场笑声)", "(限频音乐)", "(音乐响起)", "(掌声。 )",
"(观众鼓掌)", "(电话铃声)", "(又是狂笑)", "(电话铃响)", "(音乐和声)", "(笑声,掌声)",
"(频率的声音)", "(众笑+鼓掌)", "(相机快门声)", "(音乐录影带)", "(诺基亚铃声)", "(听众的笑声)",
"(无意义的声音)", "(笑+鼓掌♫♫)", "(发射时的噪音)", "(人群的欢呼声)", "(打喷嚏的声音)"
# "", "","", "","", "","", "",
]
pattern = "|".join(map(re.escape, patterns_to_replace))
pattern1 = r'(.*?)' # 直接替换掉带括号的词
text = re.sub(pattern, "", text)
return text
sen = "我们管它叫做 一个情感工程 它使用最新的 十七世纪的技术- (笑声) 来把名词 变成动词"
text = preprocess_zh(sen)
print(text)
sen = "there's a dog"
text = preprocess_en(sen)
print(text)
# 数据预处理函数
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):
# 将英文缩写拆开 there's -> there is
en = preprocess_zh(en)
# 替换掉中文中的语气如:(笑声)
zh = preprocess_zh(zh)
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 load_terminology_dictionary(dict_file):
terminology = {}
with open(dict_file, 'r', encoding='utf-8') as f:
for line in f:
en_term, ch_term = line.strip().split('\t')
terminology[en_term] = ch_term
return terminology
# 数据加载函数
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)
terminology = load_terminology_dictionary(terminology_path)
for en_term, zh_term in terminology.items():
train_en += (en_term,)
train_zh += (zh_term,)
# 预处理数据
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
训练参数设置
# 定义常量
MAX_LENGTH = 120 # 最大句子长度
BATCH_SIZE = 32
DEVICE = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
N = 150372 # 由于将术语表加入训练数据,训练数据增长,具体数值可在load_data函数中通过print(len(train_processed))查看
# 模型参数
D_MODEL = 512
NHEAD = 8
NUM_ENCODER_LAYERS = 3
NUM_DECODER_LAYERS = 3
DIM_FEEDFORWARD = 1024
DROPOUT = 0.1
提交结果分数
分数较GRU模型已有较大提升,但是由于训练数据质量较差,仍有较大提升空间,需要继续处理数据,以提供更好的学习环境。
测试集异常翻译结果集锦
♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫ ♫
训练文本中仍存在♫符号,需清洗去除
史蒂芬·德·德·德·德·德·德德·德德·德德:战争背后的天才
这正是我们在学校里看到的 青少年和孩子们在学校里做的事情, 在桌子下,在桌子下, 在桌子下,在桌子下, 和朋友们在桌子下。
马克·麦克·麦克·麦克德·麦克德·麦克德·麦克德·麦克德·麦克德·德·德·德·德·德·德·德·德·德:烹饪烹饪
这是一个来自伦敦的 叫约翰·约翰·约翰·约翰·约翰·约翰·约翰·德斯的一句话, 他一直在非洲的 和他的 一个令人着迷的 关于他的神奇的
某些文本不断重复,模型可能存在一定程度过拟合?
翻译结果为空的情况
还未找到原因…