前言

本文以训练Transformer-based机器翻译模型为例,通过WMT2014英德实验来介绍fairseq工具的使用方法。

预安装工具或脚本:

1. fairseq

由于直接使用pip安装的fairseq版本(0.9.0)还停留在2019年12月,为了使用更新的特性,我们选择GitHub上的最新版本:

  1. git clone https://github.com/pytorch/fairseq.git
  2. cd fairseq
  3. pip install --editable ./

2. mosesdecoder

  1. git clone https://github.com/moses-smt/mosesdecoder.git

3. subword-nmt

  1. git clone https://github.com/rsennrich/subword-nmt.git

4. 评测脚本

multi-bleu.perl 位于mosesdecoder/scripts/generic/ 路径下
sacreBleu

1. 数据准备:

脚本下载:

使用fairseq提供的脚本进行数据的下载

  1. bash fairseq/examples/translation/prepare-wmt14en2de.sh

这个脚本会下载IWSLT 14 英语和德语的平行数据,并进行分词、BPE等操作,处理的结果为:

  1. wmt17_en_de
  2. ├── code
  3. ├── test.de
  4. ├── test.en
  5. ├── tmp
  6. ├── train.de
  7. ├── train.en
  8. ├── valid.de
  9. └── valid.en

手动下载:

1.1 数据下载及处理

以de-en为例,下载其平行语料数据。
下载训练集数据 http://www.statmt.org/wmt14/translation-task.html
包括三部分数据:Europarl v7、Common Crawl corpus、News Commentary。将三部分数据中的de-en.en和de-en.de文件分别合并train_raw.en(4520620行)train_raw.de(4520620行)
使用命令清洗训练数据,$mosesdecoder 为 clean-corpus-n.perl文件所在目录

  1. perl $mosesdecoder/clean-corpus-n.perl -ratio 1.5 train_raw en de train 1 250

得到清洗后的训练数据 train.en (3907123)
train.de (3907123)

下载开发集数据 http://data.statmt.org/wmt17/translation-task/dev.tgz
采用dev/newstest2013-src.en.sgm 和 dev/newstest2013-src.de.sgm为开发集

  1. grep '<seg id' newstest2013-src.en.sgm | sed -e 's/<seg id="[0-9]*">\s*//g' | sed -e 's/\s*<\/seg>\s*//g' | sed -e "s/\’/\'/g" > valid.en

同样处理de文件,将生成如下两个文件
valid.en (3000行)
valid.de (3000行)

下载测试集数据 http://statmt.org/wmt14/test-full.tgz
以test-full/newstest2014-deen-src.en.sgm文件为例

  1. grep '<seg id' newstest2014-deen-src.en.sgm | sed -e 's/<seg id="[0-9]*">\s*//g' | sed -e 's/\s*<\/seg>\s*//g' | sed -e "s/\’/\'/g" > test.en

同样处理de文件,将生成如下两个文件
test.en (3003行)
test.de (3003行)

1.2 分词

$moses = mosesdecoder/scripts/tokenizer
$file1-6 分别为 train.en train.de valid.en valid.de test.en test.de

  1. cat $file1 | perl $moses/normalize-punctuation.perl | perl $moses/remove-non-printing-char.perl en | perl $moses/tokenizer.perl -threads 8 -a -l en >> train.tok.en -l
  1. cat $file2 | perl $moses/normalize-punctuation.perl | perl $moses/remove-non-printing-char.perl de | perl $moses/tokenizer.perl -threads 8 -a -l de >> train.tok.de -l
  1. cat $file3 | perl $moses/normalize-punctuation.perl | perl $moses/remove-non-printing-char.perl en | perl $moses/tokenizer.perl -threads 8 -a -l en >> valid.tok.en -l
  1. cat $file4 | perl $moses/normalize-punctuation.perl | perl $moses/remove-non-printing-char.perl de | perl $moses/tokenizer.perl -threads 8 -a -l de >> valid.tok.de -l
  1. cat $file5 | perl $moses/normalize-punctuation.perl | perl $moses/remove-non-printing-char.perl en | perl $moses/tokenizer.perl -threads 8 -a -l en >> test.tok.en -l
  1. cat $file6 | perl $moses/normalize-punctuation.perl | perl $moses/remove-non-printing-char.perl de | perl $moses/tokenizer.perl -threads 8 -a -l de >> test.tok.de -l

经过以上操作后,将在train.en等文件所在的目录中生成train.tok.en train.tok.de valid.tok.en valid.tok.de test.tok.en test.tok.de等文件

1.3 bpe操作

bpe操作有个重要的参数bpe_size,为将数据集划分为字词的最大个数,具体设置与数据集有关系,在这里我设置$bpe_size = 30000
由于英德同源,所以这里生成一个的bpe分词文件。
$subword = subword-nmt/subword_nmt

  1. cat train.en.tok train.de.tok | python $subword/learn_bpe.py -s $bpe_size > train.bpe

生成bpe文件之后才可以继续分词,使用方法为:

  1. python $subword/apply_bpe.py -c train.bpe < $data_dir/train.tok.en > new_bpe/train.bpe.en
  1. python $subword/apply_bpe.py -c train.bpe < $data_dir/train.tok.de > new_bpe/train.bpe.de
  1. python $subword/apply_bpe.py -c train.bpe < $data_dir/valid.tok.en > new_bpe/valid.bpe.en
  1. python $subword/apply_bpe.py -c train.bpe < $data_dir/valid.tok.de > new_bpe/valid.bpe.de
  1. python $subword/apply_bpe.py -c train.bpe < $data_dir/test.tok.en > new_bpe/test.bpe.en
  1. python $subword/apply_bpe.py -c train.bpe < $data_dir/test.tok.de > new_bpe/test.bpe.de

完成上述操作后,将在$data_dir目录下生成 train.bpe.entrain.bpe.de valid.bpe.en valid.bpe.de test.bpe.en test.bpe.de六个文件

2. 数据预处理

数据二进制化:

使用fairseq-preprocess命令将文本数据转为二进制文件
$data 为第1步中处理好的数据所在目录

  1. fairseq-preprocess --source-lang de --target-lang en --trainpref $data/train --validpref $data/valid --testpref $data/test

预处理命令首先会从训练文本数据中构建词表。在默认情况下,会将所有出现过的单词根据词频排序,并将这个排序后的单词列表作为最终的词表。默认保存位置为/fairseq/data-bin

3. 模型训练

$gpu_id为使用的GPU编号,训练所得模型默认保存至 /fairseq/checkpoints 目录下。
fairseq-train提供了大量的训练参数,从而进行定制化的训练过程,其中主要的参数可以分为数据(data)、模型(model)、优化(optimizing)、训练(分布式和多GPU等)、日志(log)和模型保存(checkpointing)等。详细参数设置参照文档 https://fairseq.readthedocs.io/en/latest/command_line_tools.html

  1. CUDA_VISIBLE_DEVICES=$gpu_id fairseq-train data-bin --arch transformer --max-tokens 4096 --max-update 30000 --optimizer adam --lr-scheduler inverse_sqrt --lr 0.0007 --criterion label_smoothed_cross_entropy --label-smoothing 0.1 --no-progress-bar --save-interval-updates 1000 1>log/translate.log 2>&1 &

4. 解码

4.1 生成式解码 fairseq-generate

$data-bin 为二进制化处理结果目录
$checkpoints 为训练结果目录
$res 为解码结果要保存到的目录

  1. fairseq-generate $data-bin --path $checkpoints/checkpoint_best.pt --batch-size 128 --beam 8 > $res/predict_test.txt

默认情况下,这个命令会从预处理的数据中,解码测试数据。通过—gen-subset可以指定解码其他部分,如—gen-subset train就会翻译整个训练数据。可以添加—quiet参数,只显示翻译进度和最后打分。可以添加—remove-bpe参数,使得在生成时就去掉bpe符号(@@)。

4.2 交互式解码 fairseq-interactive

$data 为要解码的数据所在目录
$checkpoints 为训练结果目录
fairseq-interactive可以进行交互式逐句解码,其参数和fairseq-generate基本一致。例如逐行翻译test.de文件中的句子:

  1. cat $data/test.de | fairseq-interactive data-bin --path $checkpoints/checkpoint_best.pt --remove-bpe

5. 译文处理

5.1 将得到的译文进行抽取处理

  1. grep ^H predict_test.txt | cut -f3- > predict_test.txt

5.2 去除bpe符号

如果在解码过程中未使用—remove-bpe参数,则可以在抽取译文后删除bpe符号

  1. sed -r 's/(@@ )| (@@ ?$)//g' < predict_test.txt > predict_test_rs.txt

5.3 评价

  1. perl /mosesdecoder/scripts/generic/multi-bleu.perl test.en < predict_test_rs.tran

参考资料

https://www.yuque.com/docs/share/5d13c886-8479-4b7b-961e-e9eb84a9f5bd?#
https://zhuanlan.zhihu.com/p/194176917