NLP 课设 - 来训练一个的英文 -> 中文的 Transformer 翻译模型。
在做什么?
代码所在:
Math-X-Science/school-homework
我在尝试利用deeplx
翻译英文小说,形成翻译对,然后用这些翻译对微调和从 0 训练 Transformer 模型。
感谢这些仓库:
关于数据集:
- 考虑到我们的数据集一半是翻译模型自动生成的。而模型碰到一些特殊词汇或者有时候算力紧张就会吐原文(英文),所以最重要的反而是把 csv 里面中文栏内含有英文的部分条目都清洗掉。因为这代表着这个段落没有被翻译完全,应该被清洗。
- 关于 csv 存储的分隔问题,我最初制作数据集的时候用的是
,
,英文的,但是实际上这么做存在隐患,存在什么隐患,那就是必须保证我的中文栏目里面一个英文逗号都没有(它也确实是这样的),否则会导致读取错乱。对于这种没有处理特殊符号的数据集,BEST PRACTICE 还是用|
。
因为数据集合并后有点大,存在 github 上面影响 clone 的时间,所以这里我打算把它抽离到网盘里。
它分成这么几个部分:
- origin_dataset/: 它包含原始的英文 txt 和按
\n
切分和翻译后的翻译对csv
,用,
分割。里面存在一些中英文混合的条目,需要清洗。 - cleaned_dataset/: 它包含清洗后的翻译对
csv
,依然是用,
分割,(我意识到地太晚了),但因为中文里面不存在英文,所以不需要额外处理可以直接读取。 - eval.csv: 我随便生成的几个翻译对,用于测试模型的效果。你可以自定义。
- small.csv: 我随便挑出来的几个翻译对,用来跑通模型用的。我建议你也先验证一下模型是否能跑通最终再在大数据集上训练,因为猥琐的模型总是在最后一步才会报错。
- noval.csv: 我把清洗后的数据集简单地合并和叠加到一起的结果。
你可以自己对原始数据集做自己的需要的处理,比如你还可以去除掉所有短句。
以及中英文混合的例子让我想起来以前训练BERT-VITS2
的时候,在文本样例中,混合样例比较少,但是对于说话的场景,那中英文混合的案例就太多了:
- 比如说我们这个 tensor 啊。
- pytorch 真的是太好用了。
- coding 能力要怎么样才能快速提升或者稳步提升?
这样子的样例太多了。
所以我觉得中英文混在一起做tokenize
应该是没有问题的,甚至可能会在上面的问题上表现的不错。后面 BERT-VITS2 也确实出了一个支持中英文混合的模型,但我当时已经没有再跟进了,没看细节。
但那种混合的情况数据集太难找了。把各种音素的混合训练到,哪怕只是单语言就够喝一壶了,混合的话,数据集的均衡性非常难保证。
数据集网盘地址:
english-novel-complete-works
链接: https://pan.baidu.com/s/1eh4D0at0ONT7Ct89V0Mw8g?pwd=1tt3 提取码: 1tt3
分词:
之前训练大模型的时候比较方便,tokenizer 和 embedding 都是由模型开发的人提供的,只需要做好指令微调的数据集喂进去,然后等就行。
但如果自己训练自定义的模型,那么对于 embedding 和 tokenizing 都是自己要考虑的。
因为在大样本集上面的分词和 embedding 通常要很久,所以先在 demo 上面演示.
这里的一个点是:是否要把中英文的 indices 建立在一个词典下?
我选择把 indices 分别建立在两个词典下,因为我的用例中完全不存在:"我喜欢 Python" -> "I like Python" 这样的情况。我翻译的是小说。我更希望在 Embedding 的时候两个字典存在于不同的空间,然后让模型自己学着映射,而不是一开始就存在一些关联。
当然如果字典本身比较小,偏日常用语,且里面也有不少:“这个 to_tensor...”这样的混合用例,那么建立在一个空间应该是不错的选择,至少经常出现在一起的组合在 embedding 之初就有联系。
这里是一些 token 和 indices:
tokens ([这是, 果酱, !], [This, is, jam, !])
indices ([49, 310, 7], [7, 308, 9, 10])
Name: 20, dtype: object
tokens ([两个, 女孩, 拉住, 了, 对方, ,, 一个, 扮演, 绅士, ,, 另一个, ...
indices ([98, 81, 103, 14, 101, 3, 31, 104, 105, 3, 83...
Name: 3, dtype: object

image-20250106213904048
demo 上的中英文非重复分词长度。
如果直接用 BERT 做 Embedding 的话,通常不需要考虑这么多,直接少走弯路。后面也会在第一个模型训练完后改用 BERT 进行一个对比。
Embedding
这里因为之后要和预训练的 BERT 做对比,所以 Embedding 相当水,只是简单投射了一下。
class Embeddings(nn.Module):
def __init__(self, d_model, vocab):
super(Embeddings, self).__init__()
self.lut = nn.Embedding(vocab, d_model)
self.d_model = d_model
def forward(self, x):
return self.lut(x) * math.sqrt(self.d_model)
在初始化的时候,我们的 embedding 是随机初始化的,不过我们把 indices 分开了。相当于到时候会产生两个 Embedding 空间,一个是中文的一个是英文的。
它会随着我们的训练而更新,像是模型的一部分,优点是可以更贴合任务进行参数调整。
缺点就是,可调参数太多对训练的难易度是很大挑战。这些缺点可以用微调预训练模型来进行一个弥补。

image-20250107082628340

image-20250107082722790
这里有一点值得注意的,就是对于embedding lr应该比模型参数大一个数量级,不然会出现更新不动的情况。
似乎数据集太小,二十秒左右就 loss<0.4 了,按理来说 NLP 的任务 Seq2Seq Loss 不应该将太低,这说明模型把所有数据都记住了,而且还没怎么更新到 embedding。于是下一步是,扩大数据集。
一个彩蛋:
前面提到我的 demo 相当小,然后在可视化和测试的时候就出现了很有意思的点。因为我的 embedding 是自定义的,而里面没有 test 这个词。于是乎它就映射到了果酱:(有点幽默)

image-20250106230908256
它来源于训练集:
tokens ([这是, 果酱, !], [This, is, jam, !])
indices ([49, 310, 7], [7, 308, 9, 10])
Name: 20, dtype: object
this is a jam, 这是一个测试。
到目前为止模型在预期的范围内工作。下一步就是扩大数据集然后躺平等训练了。
扩大我们的训练集并且给我们的实验画上句号。
碰到的一些头疼的 bug。
分词爆内存
在做分词的时候,我收集到的所有数据集合并给 spacy 分词的时候,提示超出 spacy 的 maxlen,然后我调高 maxlen,就反复干闪退我的 vscode。
以为在处理的时候,它总是试图先把 maxlen 所有的内容装到内存里,而我的内存不够。经过测试一个人的全集大概分词要 3 分钟,如果全部分词大概要一个小时左右,这个还得考虑后面对字典的一个去重和合并,因为试错成本太高,于是我直接只在一个人的全集上面尝试。
模型过拟合。
在最初只在 15 本的测试里,它总是试图匹配训练集里面的原句,而且 LOSS 降得过于低了。对没见过的语句表现非常差。
这里我扩大了数据集并用了预训练的 BERT 模型。
模型结果评估。
训练集里面的:

alt text
长句表现:
英文原句:“You boys get along and leave us alone,” Mr. Borrow said. He was carving away steadily, his infirm old hands shaking a little between strokes.”
中文翻译:"博洛先生说:"你们这些孩子快走吧,别来烦我们。他稳步地雕刻着,那双虚弱的老手在两笔之间微微颤抖"。
感觉翻译的相当好的一段。
和gpt的对比:

alt text
随机生成段落测试:
英文原句:The sun dipped below the horizon, painting the sky in vibrant hues of orange and purple. The waves crashed against the rocks, a rhythmic sound that filled the air. Sarah stood at the edge of the cliff, her hair blowing in the breeze, her thoughts drifting to distant memories. She clutched the letter tightly in her hand, unsure if she should let it go or keep it as a reminder of what once was.
中文翻译:太阳斜射到地平线以下,给天空上了橙色和紫色的鲜艳的颜色。海浪击打着石头,有节奏的声音在空气中。萨拉站在悬崖边,头发被风吹起,她的思绪飘向遥远的回忆。她紧紧地攥着手中的信,不知道
emmmm,当段落太长的时候会出现截断,推测是我的maxlen设置太小,以及一个中文可能被拆分成多个token导致比英文token长。
短段落中表现正常。
结束。
实际上还可以用Qwen进行一个指令微调,以后有空可以试试。