Fairseq 机器翻译数据处理 (NMT, WMT, translation)

发布时间 2023-11-07 00:36:40作者: ysngki

摘要

fairseq是个常用的机器翻译项目。它的优化很好,但代码晦涩难懂,限制了我们的使用。

翻译数据的准备,是训练的第一步。但 fairseq 关于翻译数据的准备流程散布在零星的 bash 脚本中。本文旨在梳理如下流程:1)准备 WMT23 的数据,2)训练模型,3)用 sacrebleu 评测模型。

训练数据

我们使用 mtdata 这个库来准备我们需要的数据。这个库是 WMT 官方钦定的。

接下来下载数据:

pip install mtdata==0.4.0
wget https://www.statmt.org/wmt23/mtdata/mtdata.recipes.wmt23-constrained.yml
for ri in wmt23-{enzh,zhen,ende,deen,enhe,heen,enja,jaen,enru,ruen,encs,csuk,enuk,uken}; do
  mtdata get-recipe -ri $ri -o $ri
done

上面的代码拷贝自 WMT23 网站

对于每个语言对,比如 zhen(中文到英文),在 wmt23-zhen 下将会有两个文件,应该叫 train.zhtrain.en 。这两个文件有相同的行数。每一行为一条训练数据。两个文件的每一行一一对应。

好的,训练数据就先到这,在进一步清洗数据之前,我们先准备下测试数据和验证数据。

测试集和验证集

(首先声明,本文准备的数据是训练和评估机器翻译模型,而不是参与 WMT 榜单。)

根据 WMT 官方推荐,应该用历年的测试数据来当做验证集。

To evaluate your system during development, we suggest using test sets from past WMT years...

由于今年的测试数据是没有标签的。所以,我们选择用一部分往年的测试数据当验证集,用另一部分的往年的测试数据当测试集。

那么,同样用 mtdata 来下载数据:

cd wmt23-zhen

# 验证集
mtdata get -l zho-eng --out test/ --merge --dev Statmt-newstest_zhen-20{18,19,20}-zho-eng

# 测试集
mtdata get -l zho-eng --out test/ --merge --test Statmt-newstest_zhen-2021-zho-eng

可以看到,我们使用18-20年的数据当做验证集,21年的数据当做测试集。如果好奇有多少年的数据,可以使用这个命令查看:mtdata list -l zho-eng | cut -f1

验证集和测试集都被保存在了 test/ 文件夹中,但各自的名字不同。两者都有两个文件。其中,验证集的名字是 dev.zho 以及 dev.eng,后缀是语言的三字母缩写。测试集以此类推。

为了后续的处理,请重命名文件,将后缀改成语言的二字母缩写。比如将 zho 修改为 zh。

预处理

这一步包括清洗训练数据,学习 bpe tokenizer,tokenize 数据。请放心,我们将用一个 bash 文件搞定。

在开始之前,请确保你的文件结构是这样的:

--orig
  --train.tgt
  --train.src
  --test
    --test.tgt
    --test.src
    --dev.tgt
    --dev.src

其中 orig 表示你存放数据的文件夹,或许是叫做 wmt23-zhen,其他的也可以。

准备工作完成,请将下述的代码保存在一个 sh 文件中。然后,再回来看后续内容:

#!/bin/bash
# Adapted from https://github.com/facebookresearch/MIXER/blob/master/prepareData.sh

echo 'Cloning Moses github repository (for tokenization scripts)...'
git clone https://github.com/moses-smt/mosesdecoder.git

echo 'Cloning Subword NMT repository (for BPE pre-processing)...'
git clone https://github.com/rsennrich/subword-nmt.git

SCRIPTS=mosesdecoder/scripts
TOKENIZER=$SCRIPTS/tokenizer/tokenizer.perl
CLEAN=$SCRIPTS/training/clean-corpus-n.perl
NORM_PUNC=$SCRIPTS/tokenizer/normalize-punctuation.perl
REM_NON_PRINT_CHAR=$SCRIPTS/tokenizer/remove-non-printing-char.perl
BPEROOT=subword-nmt/subword_nmt
BPE_TOKENS=40000

if [ ! -d "$SCRIPTS" ]; then
    echo "Please set SCRIPTS variable correctly to point to Moses scripts."
    exit
fi

############################################################

src=zh
tgt=en
lang=zh-en

OUTDIR=wmt23-zhen
prep=$OUTDIR
tmp=$prep/tmp
TRAIN=$tmp/train.zh-en

CORPORA=(
    "train"
)
orig=/data/yuanhang/mtdata/wmt23-zhen

mkdir -p $tmp $prep

############################################################

echo "pre-processing train data..."
for l in $src $tgt; do
    rm $tmp/train.$l
    for f in "${CORPORA[@]}"; do
        cat $orig/$f.$l | \
            perl $NORM_PUNC $l | \
            perl $REM_NON_PRINT_CHAR | \
            perl $TOKENIZER -threads 8 -a -l $l >> $tmp/train.$l
    done
done

############################################################

echo "pre-processing test and dev data..."
for l in $src $tgt; do
    cat $orig/test/dev.$l | \
        sed -e "s/\’/\'/g" | \
    perl $TOKENIZER -threads 8 -a -l $l > $tmp/valid.$l
    echo ""

    cat $orig/test/test.$l | \
        sed -e "s/\’/\'/g" | \
    perl $TOKENIZER -threads 8 -a -l $l > $tmp/test.$l
    echo ""
done

############################################################

BPE_CODE=$prep/code
rm -f $TRAIN
for l in $src $tgt; do
    cat $tmp/train.$l >> $TRAIN
done

echo "learn_bpe.py on ${TRAIN}..."
python $BPEROOT/learn_bpe.py -s $BPE_TOKENS < $TRAIN > $BPE_CODE

for L in $src $tgt; do
    for f in train.$L valid.$L test.$L; do
        echo "apply_bpe.py to ${f}..."
        python $BPEROOT/apply_bpe.py -c $BPE_CODE < $tmp/$f > $tmp/bpe.$f
    done
done

perl $CLEAN -ratio 1.5 $tmp/bpe.train $src $tgt $prep/train 1 250

for L in $src $tgt; do
    cp $tmp/bpe.test.$L $prep/test.$L
	cp $tmp/bpe.valid.$L $prep/valid.$L
done

您所需要修改的地方很少,仅为下面这些地方,用来制定下语言 src, tgt, lang、输出的文件夹 OUTDIR ,要处理的文件所在路径 orig,和 CORPORA 指定训练数据的前缀。你或许会注意到,我们可以指定多个训练数据。他们会在处理中被合并。

############################################################

src=zh
tgt=en
lang=zh-en

OUTDIR=final-wmt23-zhen
prep=$OUTDIR
tmp=$prep/tmp
TRAIN=$tmp/train.zh-en

CORPORA=(
    "train"
)
orig=/data/yuanhang/mtdata/wmt23-zhen

mkdir -p $tmp $prep

############################################################