🕷️ Crawler Inspector

URL Lookup

Direct Parameter Lookup

Raw Queries and Responses

1. Shard Calculation

Query:
Response:
Calculated Shard: 46 (from laksa076)

2. Crawled Status Check

Query:
Response:

3. Robots.txt Check

Query:
Response:

4. Spam/Ban Check

Query:
Response:

5. Seen Status Check

ℹ️ Skipped - page is already crawled

📄
INDEXABLE
CRAWLED
5 months ago
🤖
ROBOTS ALLOWED

Page Info Filters

FilterStatusConditionDetails
HTTP statusPASSdownload_http_code = 200HTTP 200
Age cutoffPASSdownload_stamp > now() - 6 MONTH5.7 months ago
History dropPASSisNull(history_drop_reason)No drop reason
Spam/banPASSfh_dont_index != 1 AND ml_spam_score = 0ml_spam_score=0
CanonicalPASSmeta_canonical IS NULL OR = '' OR = src_unparsedNot set

Page Details

PropertyValue
URLhttps://www.cnblogs.com/ysngki/p/17814160.html
Last Crawled2025-10-18 18:42:45 (5 months ago)
First Indexed2023-11-30 05:29:46 (2 years ago)
HTTP Status Code200
Meta TitleFairseq 机器翻译全流程一文速通 (NMT, WMT, translation) - ysngki - 博客园
Meta Description最新编辑于:2024年8月30日 一、摘要 fairseq 是个常用的机器翻译项目。它的优化很好,但代码晦涩难懂,限制了我们的使用。 本文旨在梳理如下流程:1)准备 WMT23 的数据 (其余生成任务皆可类比),2)训练模型,3)用 sacrebleu、COMET-22 评测模型。 不想要 wmt
Meta Canonicalnull
Boilerpipe Text
最新编辑于:2024年8月30日 fairseq 是个常用的机器翻译项目。它的优化很好,但代码晦涩难懂,限制了我们的使用。 本文旨在梳理如下流程:1)准备 WMT23 的数据 (其余生成任务皆可类比),2)训练模型,3)用 sacrebleu、COMET-22 评测模型。 不想要 wmt 的数据,想要自己的数据也完全 ok。反正最终的目标就是为训练、验证、测试准备分别两个文本文件(验证、测试集也可以不准备),每个文本文件的每一行就是一条句子。准备好后,跳到第三章预处理,按照需求修改文件名字和路径就行。 1. 训练数据 我们使用 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.zh 和 train.en 。这两个文件有相同的行数。每一行为一条训练数据。两个文件的每一行一一对应。 或许有人好奇,上述的命令到底下载了那些语料库。我们可以看到,第二行 wget 命令下载了一个 yml 文件。用文本编辑器打开这个文件,有如下的内容: - id: wmt23-jaen langs: jpn-eng #dev: #test: train: &para_jpn_eng - Statmt-news_commentary-16-eng-jpn - KECL-paracrawl-3-eng-jpn - Statmt-wikititles-3-jpn-eng - Facebook-wikimatrix-1-eng-jpn - Statmt-ted-wmt20-eng-jpn - StanfordNLP-jesc_train-1-eng-jpn - Phontron-kftt_train-1-eng-jpn mono_train: - *mono_eng - &mono_jpn - Statmt-news_crawl-2021-jpn - Statmt-news_commentary-17-jpn - Statmt-commoncrawl-wmt22-jpn - Leipzig-web-2020_1m-jpn_JP - Leipzig-comweb-2018_1m-jpn - Leipzig-web_public-2019_1m-jpn_JP - Leipzig-news-2020_100k-jpn - Leipzig-newscrawl-2019_1m-jpn - Leipzig-wikipedia-2021_1m-jpn 对于日语-英语,上述列举的那么多文件都会被下载下来,然后拼接在一起。如果我们只想下载部分文件,进行小规模的实验,不妨按照需求删除一些文件。 好的,训练集到此为止了。在进一步清洗训练数据之前,我们先准备下测试数据和验证数据。 2. 测试集和验证集 根据 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。 如果我们选择用 sacrebleu 来最终评估模型的性能的话,这一步的测试集可以不用下载,不过反手顺手,就下载了吧。 在进行预处理之前,这里提供了 A,B 两种选择。推荐别思考,选第二个。 A、BPE from subword-nmt 使用 fairseq 默认的预处理,具体来说,它包括如下几个步骤: 用 moses (或者 sacremoses)进行 normalization 。具体做了啥,我也很难说清楚。 用 moses 进行 tokenization。主要就是把词语与标点符号用空格分开。 用 subword-nmt 在训练数据上学习 bpe tokenizer。 用 bpe tokenizer 对训练数据和测试数据进行 tokenization。可以理解为把单词进一步拆碎,成为一个或多个token。 过滤训练数据中过短或过长的。 (该选择请阅读接下来的1, 3小节。) B、SentencePiece Tokenizer Sentencepiece 是现在更常用的 tokenizer。它的好处之一,就是说在学习 tokenizer 之前,不需要用 moses 进行 normalization,tokenization: --input : one-sentence-per-line raw corpus file. No need to run tokenizer, normalizer or preprocessor. By default, SentencePiece normalizes the input with Unicode NFKC. You can pass a comma-separated list of files. 而且它可以直接指定 tokenizer 最终会有多少个 token。 (该选择请阅读接下来的1, 2小节。) 1. 文件准备 在开始之前,请将之前下载的文件组织成这样: |-orig --train.tgt --train.src |-test --test.tgt --test.src --dev.tgt --dev.src 其中 orig 表示你存放数据的文件夹,或许是叫做 wmt23-jaen ,其他的也可以。这里的 src 和 tgt 是指源语言和目标语言的两个字母的代码,比如 ja 和 en ,请按照需求修改。 2. SentencePiece 下面的 bash 文件包括了学习 tokenizer,并且处理训练和测试集的流程。 你所要做的,1)确保已经 clone 了 fairseq,然后用 SCRIPTS 指向文件夹。2)检查(用#分割的)前两块的一些变量,设置成自己的。 接着,一键运行吧,就是这么简单! 注意 : character_coverage 这个是可以修改的。对于中文和日文,可以设置为 0.9995 。对于拉丁语言,改成 1.0 比较好。 (参考了 fairseq 提供的这个 例子 ) ############################################################ pip install sentencepiece SCRIPTS=fairseq/scripts SPM_TRAIN=$SCRIPTS/spm_train.py SPM_ENCODE=$SCRIPTS/spm_encode.py BPESIZE=32000 TRAIN_MINLEN=1 # remove sentences with <1 BPE token TRAIN_MAXLEN=250 # remove sentences with >250 BPE tokens TRAIN_SENTENCE_MAXNUM=20000000 ############################################################ CHAR_COVER=1.0 src=ja tgt=en orig=wmt23-jaen CORPORA=( "train" ) OUTDIR=wmt23-${src}${tgt}-sentencepiece prep=$OUTDIR mkdir -p $prep ############################################################ TRAIN_FILES=$(for f in "${CORPORA[@]}"; do echo $orig/$f.${src}; echo $orig/$f.${tgt}; done | tr "\n" ",") echo "learning joint BPE over ${TRAIN_FILES}..." python "$SPM_TRAIN" \ --input=$TRAIN_FILES \ --model_prefix=$prep/sentencepiece.bpe \ --vocab_size=$BPESIZE \ --character_coverage=$CHAR_COVER \ --model_type=bpe \ --input_sentence_size=$TRAIN_SENTENCE_MAXNUM \ --shuffle_input_sentence=true ############################################################ # echo "encoding train/valid/test with learned BPE..." echo "encoding train with learned BPE..." python "$SPM_ENCODE" \ --model "$prep/sentencepiece.bpe.model" \ --output_format=piece \ --inputs $orig/train.${src} $orig/train.${tgt} \ --outputs $prep/train.${src} $prep/train.${tgt} \ --min-len $TRAIN_MINLEN --max-len $TRAIN_MAXLEN echo "encoding valid with learned BPE..." python "$SPM_ENCODE" \ --model "$prep/sentencepiece.bpe.model" \ --output_format=piece \ --inputs $orig/test/dev.${src} $orig/test/dev.${tgt} \ --outputs $prep/valid.${src} $prep/valid.${tgt} echo "encoding test with learned BPE..." python "$SPM_ENCODE" \ --model "$prep/sentencepiece.bpe.model" \ --output_format=piece \ --inputs $orig/test/test.${src} $orig/test/test.${tgt} \ --outputs $prep/test.${src} $prep/test.${tgt} 3. BPE from subword-nmt 喂,用了 SentencePiece,就没必要看这节了! 这一步包括清洗训练数据,学习 bpe tokenizer,然后 tokenize 数据。请放心,我们将用一个 bash 文件搞定。 准备工作完成,请将下述的代码保存在一个 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 pip install subword-nmt 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}..." subword-nmt learn-bpe -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}..." subword-nmt apply-bpe -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 指定训练数据的前缀。你或许会注意到,我们可以在 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 ############################################################ 4. 二进制文件 很好,您可以检查下输出目录下的文件。如果成功的话,训练、测试、验证数据各有两个文件。这时候打开文件,会发现这些文件仍旧是可读的,只不过会有 ▁ 或者 @@ 这种符号。它表示一个单词被切成了一个个 token。 接下来,我们需要将这些文本数据转换成二进制文件,用于 fairseq。这有利于更快的训练。同样的, 根据你之前选择的 tokenizer,这里有两种不同的处理方式。 不过,我仍然推荐按顺序阅读下,更好地理解代码。 a. BPE from subword-nmt TEXT=examples/translation/wmt23-jaen fairseq-preprocess --source-lang ja --target-lang en \ --joined-dictionary \ --trainpref $TEXT/train --validpref $TEXT/valid --testpref $TEXT/test \ --destdir data-bin/wmt23-zhen --workers 10 显然, TEXT 指的是您先前的输出目录,而现在,它成了输入目录。 destdir 则是二进制文件要保存的地方。 其中 --joined-dictionary 是选填的,也就是说输入语言和输出语言要不要共用一个字典。 既然您阅读到了这里,我必须要说明下这个命令在做什么。 初始化一个字典。它的实现在 fairseq/data/dictionary.py 。初始化的时候,会自动加上四个特殊符号:句子结束、开始、padding、unk。 扫描传入文件的每一行,按照空格将 token 分开。然后统计每个 token 出现的次数。 将所有 token 按照出现次数降序排列,然后根据设置的字典大小,将排在前面的 token 加入字典。如果不设置,就是所有 token。 接着扫描所有文件的每一行,按照空格将 token 分开,然后把每个 token 变成字典中的序号。 然后经过 binarize 啥的,大抵就是减少存储空间。 在上面的二进制命令中,用 nwordssrc 和 nwordstgt 可以指定字典的大小。如果是 joined-dictionary ,指定一个就行了。 如果要后续复用字典处理其他文件,可以这么指定 --tgtdict data-bin/iwslt17.de_fr.en.bpe16k/dict.en.txt 。因为是 src 和 tgt 使用同一个字典,所以只要制定 tgt 要用到的字典就行。 好的,很符合直觉,也很奇怪 。 bpe 学习完之后就自带一个字典。而且用 bpe tokenize 之后的文件,里面是不会出现不在 bpe 字典里的词。也就是说,这一步学习到的字典,肯定是和 bpe 自带的一样。 不过我跑了下,居然是不一样的,你说奇怪不奇怪。 --- 更新于 2024年8月30日 我猜测,在上一节 学习 bpe 以及 把数据 tokenize 化,目标是把文本切分开了一个个词。 然后这一步就用之前切分好的词,来维护一个新的字典(是不是有病啊,怀疑显得程度) b. SentencePiece 可以发现,在 SentencePiece 处理完文件后,你能够得到两个额外的东西,一个是 model,一个是 vocab。 处理后的文件用空格分开的所有 token,都能在 vocab 里找到对应(有例外)。所以,接下来我们可以根据这个 vocab,把处理完的文件的变成数字的序列。 如果不在 fairseq-preprocess 里传入字典,那么它会学习一个新的字典 —— 这个字典取决于这个命令处理的数据。这样不好。所以我们观测下 fairseq-preprocess 输出的字典格式,然后把 sentencepiece 得到的字典直接伪装成最终要的字典。 (这里参考了 issue )。 首先要把这个 vocab 转换成 fairseq 能用的格式: # strip the first three special tokens and append fake counts for each vocabulary tail -n +4 sentencepiece.bpe.vocab | cut -f1 | sed 's/$/ 100/g' > fairseq.vocab 接着开始处理: TEXT=wmt23-zhen-sentencepiece DICT=wmt23-zhen-sentencepiece/fairseq.vocab fairseq-preprocess --source-lang zh --target-lang en \ --joined-dictionary --tgtdict $DICT \ --trainpref $TEXT/train --validpref $TEXT/valid --testpref $TEXT/test \ --destdir wmt23-zhen-fairseq-sentencepiece --workers 10 大致上和 BPE from subword-nmt 做的没什么区别,只是跳过了获得字典这一步。 接下来,我们用四个显卡,小训 50 个 epoch,并且只保存在验证集上效果最好的那个 epoch 的 checkpoint。 如果没有准备验证集,只是想先训训看看的话,请传入 --disable-validation CUDA_VISIBLE_DEVICES=0,1,2,3 fairseq-train data-bin/wmt17_en_de/ \ --arch transformer_iwslt_de_en --share-decoder-input-output-embed \ --optimizer adam --adam-betas '(0.9, 0.98)' \ --clip-norm 0.0 --lr 5e-4 --lr-scheduler inverse_sqrt \ --warmup-updates 512 --dropout 0.3 --weight-decay 0.0001 \ --criterion label_smoothed_cross_entropy --label-smoothing 0.1 \ --max-tokens 32768 \ --update-freq 2 \ --max-source-positions 256 \ --max-target-positions 256 \ --max-epoch 10 \ --save-interval-updates 100 \ --keep-interval-updates 10 \ --no-epoch-checkpoints \ --amp \ --eval-bleu \ --eval-bleu-args '{"beam": 5, "max_len_a": 1.2, "max_len_b": 10}' \ --eval-bleu-detok moses \ --eval-bleu-remove-bpe \ --eval-bleu-print-samples \ --best-checkpoint-metric bleu \ --no-epoch-checkpoints \ --maximize-best-checkpoint-metric \ --find-unused-parameters \ --log-interval 1 \ --wandb-project translation_deen \ --skip-invalid-size-inputs-valid-test \ --save-dir checkpoints/test_transformer/ 上面我尽可能列出了常用的命令,大家可以猜测意思修改。 其中 --save-interval-updates 100 keep-interval-updates 10 --no-epoch-checkpoints 意味着,每训练 100 步测试下模型,并保存这个模型,但是只保存最多 10 个,并且我选择不保存每个epoch结束后的模型。 下面是我自用的命令,备份用,大家跳过吧: CUDA_VISIBLE_DEVICES=2,3 fairseq-train /data/yuanhang/wmt23/wmt23-deen-fairseq \ --arch transformer_wmt_en_de --share-decoder-input-output-embed \ --optimizer adam --adam-betas '(0.9, 0.98)' \ --clip-norm 0.0 --lr 5e-4 --lr-scheduler inverse_sqrt \ --warmup-updates 512 --dropout 0.3 --weight-decay 0.0001 \ --criterion label_smoothed_cross_entropy --label-smoothing 0.1 \ --moe-loss-coeff 0.01 \ --max-tokens 16384 \ --update-freq 32 \ --max-source-positions 256 \ --max-target-positions 256 \ --max-epoch 10 \ --save-interval-updates 100 \ --keep-interval-updates 10 \ --no-epoch-checkpoints \ --amp \ --eval-bleu \ --eval-bleu-args '{"beam": 5, "max_len_a": 1.2, "max_len_b": 10}' \ --eval-bleu-detok moses \ --eval-bleu-remove-bpe \ --eval-bleu-print-samples \ --best-checkpoint-metric bleu \ --maximize-best-checkpoint-metric \ --find-unused-parameters \ --log-interval 1 \ --wandb-project translation_deen \ --save-dir checkpoints/wmt23-deen-base/ |& tee logs/wmt23-deen-base.log 机器翻译界有个trick:训练过程中保存多个 checkpoints,训练结束后把它们的权重平均一下,得到新的模型。 这个功能 fairseq 提供了,很简单: CHECKPOINT_DIR= python scripts/average_checkpoints.py \ --num-update-checkpoints 10 \ --inputs $CHECKPOINT_DIR \ --output $CHECKPOINT_DIR/checkpoint.avg10.pt 测试有两种方法,一种是 fairseq 自带的,一种是用 sacrebleu。我个人测试下,差别不是很大。 fairseq-generate data-bin/wmt17_en_de/ --path checkpoints/test_transformer/checkpoint_best.pt --batch-size 128 --beam 5 --remove-bpe=sentencepiece 如果用的是 subword-ntm,请将 --remove-bpe=sentencepiece change to --remove-bpe 。 当然,现在更流行的是 sacrebelu,我参考了这个 链接 和 这个链接 ,得到了下面两个命令,亲测可行: **重要,更新于 2024年8月30日 **:所有的 sacrebleu 命令后面都应该加上 --tokenize $TGTLANG 来指定语言。不然的话 bleu 会低很多。如果 sacreblue 没有支持某个语言,或者是你想要自定义 tokenize 的方法,那么就请先把预测结果和 label 都 tokenize 下,然后再用 sacrebleu 某个参数,明确告诉它输入的是 tokenize 后的版本。 1. BPE from subword-nmt DATASET=wmt22 LANGPAIR=ja-en SRCLANG=ja TGTLANG=en BPECODE= DATABIN= MODEL= sacrebleu -t $DATASET -l $LANGPAIR --echo src \ | sacremoses -q -l $SRCLANG tokenize -a \ | subword-nmt apply-bpe -c $BPECODE \ | fairseq-interactive $DATABIN --path $MODEL \ -s $SRCLANG -t $TGTLANG \ --beam 5 --remove-bpe --buffer-size 1024 --max-tokens 8000 \ | grep ^H- | cut -f 3- \ | sacremoses -q -l $TGTLANG detokenize \ | sacrebleu -t $DATASET -l $LANGPAIR -m bleu chrf --tokenize $TGTLANG 用指令 sacrebleu --list -l en-fr 可以查看当前语言对支持的数据集。 2. SentencePiece DATASET=wmt22 LANGPAIR=ja-en SRCLANG=ja TGTLANG=en SPM_ENCODE=fariseq/scripts/spm_encode.py SPM_MODEL=xxx/sentencepiece.bpe.model DATABIN= MODEL= sacrebleu -t $DATASET -l $LANGPAIR --echo src \ | python $SPM_ENCODE --model $SPM_MODEL \ | fairseq-interactive $DATABIN --path $MODEL \ -s $SRCLANG -t $TGTLANG \ --beam 5 --remove-bpe=sentencepiece --buffer-size 1024 --max-tokens 8000 \ | grep ^H- | cut -f 3- \ | sacrebleu -t $DATASET -l $LANGPAIR -m bleu chrf --tokenize $TGTLANG 其实和 subword-nmt 的区别不大。 3. 基于神经网络的测试: COMET-22 根据这篇文章: Results of WMT22 Metrics Shared Task: Stop Using BLEU – Neural Metrics Are Better and More Robust , COMET-22 是更符合人类偏好的测试方法。 要使用这个方法,我是参照 https://huggingface.co/Unbabel/wmt22-comet-da 里的描述。 流程很简单,首先要安装一个库: pip install --upgrade pip # ensures that pip is current pip install unbabel-comet 然后要准备三个文件,翻译的源文件,我的翻译输出,翻译参考答案。 我们先准备源文件和参考答案: DATASET=wmt22 LANGPAIR=ja-en SRCLANG=ja TGTLANG=en sacrebleu -t $DATASET -l $LANGPAIR --echo src > source-inputs.txt sacrebleu -t $DATASET -l $LANGPAIR --echo ref > references.txt 然后进行翻译(和上一小节一样,为了偷懒,我就不写 subword-nmt的了): SPM_ENCODE=fariseq/scripts/spm_encode.py SPM_MODEL=xxx/sentencepiece.bpe.model DATABIN= MODEL= sacrebleu -t $DATASET -l $LANGPAIR --echo src \ | python $SPM_ENCODE --model $SPM_MODEL \ | fairseq-interactive $DATABIN --path $MODEL \ -s $SRCLANG -t $TGTLANG \ --beam 5 --remove-bpe=sentencepiece --buffer-size 1024 --max-tokens 8000 \ | grep ^H- | cut -f 3- > translation-outputs.txt comet-score -s source-inputs.txt -t translation-outputs.txt -r references.txt --model Unbabel/wmt22-comet-da 最后一行其实就是打分啦,如果是第一次使用,会下载一个 2GB 的模型: comet-score -s source-inputs.txt -t translation-outputs.txt -r references.txt --model Unbabel/wmt22-comet-da Github 的 Interpreting Scores 这一节可以读一下,它推荐,在比较不同的模型时,使用下面这个命令,可以输出 statistical hypothesis test 的结果: comet-compare -s source-inputs.txt -t hyp1.en hyp2.en hyp3.en -r references.txt 祝大家吃好睡好,下次再见! 在处理日语文件时,我遇到了有无法被 unicode 解码,导致 tokenize 失败的问题。要解决这个问题也很简单,只要在预处理的时候,处理掉那些奇怪的字符就好了。 举个例子,我假设我的训练文件名字是 old_train, 新文件是 train,那么处理可以这样: with open("old_train.ja", errors='ignore') as input_ja, open("old_train.en") as input_en, open("train.ja", "w") as output_ja, open("train.en", "w") as output_en: index = 0 try: for ja_row, en_row in zip(input_ja, input_en): output_ja.write(ja_row) output_en.write(en_row) index += 1 if index % 1000000 == 0: print(f"!!!! {index}") except UnicodeDecodeError: print(f"yyh here {index}") 可以看到,在读取日语训练文件时,我指定了 errors='ignore' ,这会忽略导致错误的字符。 虽然我写了 except,但理论上不会被触发。 1. 12 erros detected in ... 参考这个网站 https://github.com/NVIDIA/apex/issues/1735 。 如果安装失败,或许是 apex 当前版本的代码有错误(2023年11月12日01:47:21)。 我是 torch 2.0.1 时安装 apex 有这个问题。 只要安装 apex 之前的版本就行: git checkout 2386a912164b0c5cfcd8be7a2b890fbac5607c82 2. cuda 相关 最近在新机器上安装 fairseq ,会遇到当前 cuda 版本和编译了 pytorch 的 cuda 版本不匹配的问题。但如果不是安装可编辑版本,而是直接用 pip 安装,就没问题。很奇怪。 我安装的全流程是这样的: 根据我的一篇文章安装 cuda,修改环境变量 --> 创建 conda 虚拟环境 --> 安装 torch --> 安装 apex --> 安装 fairseq。 然后就遇到问题了。我一通折腾,改了个流程: 删除 conda 的环境,删除 conda 缓存的包,删除 cuda 的环境变量 --> 创建 conda 虚拟环境 --> 安装 torch --> 安装 fairseq --> 修改 cuda 环境变量 --> 安装 apex。 然后就好了。很奇怪,不是吗。我不设置 cuda 的环境变量,居然能够安装 fairseq,在这时候安装 apex 反而会报错。我也不知道是哪一步生效的。 我又在一台机子上测试了下,在安装 fairseq 的时候,不要有 cuda 的环境变量,就可以安装成功。 值得一提,如果要为了 fairseq 安装 apex,可能需要加几个参数,当然不加也是可以的: pip install -v --disable-pip-version-check --no-cache-dir --no-build-isolation --config-settings "--build-option=--cpp_ext" --config-settings "--build-option=--cuda_ext" --config-settings "--build-option=--deprecated_fused_adam" --config-settings "--build-option=--xentropy" --config-settings "--build-option=--fast_multihead_attn" ./
Markdown
- [![博客园logo](https://assets.cnblogs.com/logo.svg)](https://www.cnblogs.com/ "开发者的网上家园") - [会员](https://cnblogs.vip/) - [众包](https://www.cnblogs.com/cmt/p/18500368) - [新闻](https://news.cnblogs.com/) - [博问](https://q.cnblogs.com/) - [闪存](https://ing.cnblogs.com/) - [赞助商](https://www.cnblogs.com/cmt/p/19081960) - [HarmonyOS](https://harmonyos.cnblogs.com/) - [Chat2DB](https://chat2db-ai.com/) - [![写随笔](https://assets.cnblogs.com/icons/newpost.svg)](https://i.cnblogs.com/EditPosts.aspx?opt=1 "写随笔") [![我的博客](https://assets.cnblogs.com/icons/myblog.svg)](https://passport.cnblogs.com/GetBlogApplyStatus.aspx "我的博客") [![短消息](https://assets.cnblogs.com/icons/message.svg)](https://msg.cnblogs.com/ "短消息") [![简洁模式](https://assets.cnblogs.com/icons/lite-mode-on.svg)]("简洁模式启用,您在访问他人博客时会使用简洁款皮肤展示") [![用户头像](https://assets.cnblogs.com/icons/avatar-default.svg)](https://home.cnblogs.com/) [我的博客](https://passport.cnblogs.com/GetBlogApplyStatus.aspx) [我的园子](https://home.cnblogs.com/) [账号设置](https://account.cnblogs.com/settings/account) [会员中心](https://vip.cnblogs.com/my) [简洁模式 ...]("简洁模式会使用简洁款皮肤显示所有博客") [退出登录]() [注册](https://account.cnblogs.com/signup) [登录]() [![返回主页](https://www.cnblogs.com/skins/custom/images/logo.gif)](https://www.cnblogs.com/ysngki/) # [一个废物的碎碎念](https://www.cnblogs.com/ysngki) ## 我当然会一直更新的,只、只不过有点慢罢了! - [首页](https://www.cnblogs.com/ysngki/) - [新随笔](https://i.cnblogs.com/EditPosts.aspx?opt=1) - [订阅]() - [管理](https://i.cnblogs.com/) # [Fairseq 机器翻译全流程一文速通 (NMT, WMT, translation)](https://www.cnblogs.com/ysngki/p/17814160.html "发布于 2023-11-07 00:35") *最新编辑于:2024年8月30日* # 一、摘要 fairseq 是个常用的机器翻译项目。它的优化很好,但代码晦涩难懂,限制了我们的使用。 本文旨在梳理如下流程:1)准备 WMT23 的数据 (其余生成任务皆可类比),2)训练模型,3)用 sacrebleu、COMET-22 评测模型。 > 不想要 wmt 的数据,想要自己的数据也完全 ok。反正最终的目标就是为训练、验证、测试准备分别两个文本文件(验证、测试集也可以不准备),每个文本文件的每一行就是一条句子。准备好后,跳到第三章预处理,按照需求修改文件名字和路径就行。 # 二、数据下载 ## 1\. 训练数据 我们使用 **[mtdata](https://github.com/thammegowda/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 网站](https://www2.statmt.org/wmt23/mtdata/)。 对于每个语言对,比如 zhen(中文到英文),在 `wmt23-zhen/` 下将会有两个文件,应该叫 `train.zh` 和 `train.en` 。这两个文件有相同的行数。每一行为一条训练数据。两个文件的每一行一一对应。 或许有人好奇,上述的命令到底下载了那些语料库。我们可以看到,第二行`wget`命令下载了一个 yml 文件。用文本编辑器打开这个文件,有如下的内容: ``` - id: wmt23-jaen langs: jpn-eng #dev: #test: train: &para_jpn_eng - Statmt-news_commentary-16-eng-jpn - KECL-paracrawl-3-eng-jpn - Statmt-wikititles-3-jpn-eng - Facebook-wikimatrix-1-eng-jpn - Statmt-ted-wmt20-eng-jpn - StanfordNLP-jesc_train-1-eng-jpn - Phontron-kftt_train-1-eng-jpn mono_train: - *mono_eng - &mono_jpn - Statmt-news_crawl-2021-jpn - Statmt-news_commentary-17-jpn - Statmt-commoncrawl-wmt22-jpn - Leipzig-web-2020_1m-jpn_JP - Leipzig-comweb-2018_1m-jpn - Leipzig-web_public-2019_1m-jpn_JP - Leipzig-news-2020_100k-jpn - Leipzig-newscrawl-2019_1m-jpn - Leipzig-wikipedia-2021_1m-jpn ``` 对于日语-英语,上述列举的那么多文件都会被下载下来,然后拼接在一起。如果我们只想下载部分文件,进行小规模的实验,不妨按照需求删除一些文件。 好的,训练集到此为止了。在进一步清洗训练数据之前,我们先准备下测试数据和验证数据。 ## 2\. 测试集和验证集 根据 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。 **如果我们选择用 sacrebleu 来最终评估模型的性能的话,这一步的测试集可以不用下载,不过反手顺手,就下载了吧。** # 三、预处理 在进行预处理之前,这里提供了 A,B 两种选择。推荐别思考,选第二个。 *A、BPE from subword-nmt* 使用 fairseq 默认的预处理,具体来说,它包括如下几个步骤: 1. 用 moses (或者 sacremoses)进行 normalization 。具体做了啥,我也很难说清楚。 2. 用 moses 进行 tokenization。主要就是把词语与标点符号用空格分开。 3. 用 subword-nmt 在训练数据上学习 bpe tokenizer。 4. 用 bpe tokenizer 对训练数据和测试数据进行 tokenization。可以理解为把单词进一步拆碎,成为一个或多个token。 5. 过滤训练数据中过短或过长的。 *(该选择请阅读接下来的1, 3小节。)* *B、SentencePiece Tokenizer* Sentencepiece 是现在更常用的 tokenizer。它的好处之一,就是说在学习 tokenizer 之前,不需要用 moses 进行 normalization,tokenization: > `--input`: one-sentence-per-line **raw** corpus file. No need to run tokenizer, normalizer or preprocessor. By default, SentencePiece normalizes the input with Unicode NFKC. You can pass a comma-separated list of files. 而且它可以直接指定 tokenizer 最终会有多少个 token。 *(该选择请阅读接下来的1, 2小节。)* ## 1\. 文件准备 在开始之前,请将之前下载的文件组织成这样: ``` |-orig --train.tgt --train.src |-test --test.tgt --test.src --dev.tgt --dev.src ``` 其中 `orig` 表示你存放数据的文件夹,或许是叫做 `wmt23-jaen`,其他的也可以。这里的`src`和`tgt`是指源语言和目标语言的两个字母的代码,比如`ja`和`en`,请按照需求修改。 ## 2\. SentencePiece 下面的 bash 文件包括了学习 tokenizer,并且处理训练和测试集的流程。 你所要做的,1)确保已经 clone 了 fairseq,然后用 `SCRIPTS` 指向文件夹。2)检查(用\#分割的)前两块的一些变量,设置成自己的。 接着,一键运行吧,就是这么简单\! **注意**:`character_coverage` 这个是可以修改的。对于中文和日文,可以设置为 0.9995 。对于拉丁语言,改成 1.0 比较好。 (参考了 fairseq 提供的这个[例子](https://github.com/facebookresearch/fairseq/issues/1080)) ``` ############################################################ pip install sentencepiece SCRIPTS=fairseq/scripts SPM_TRAIN=$SCRIPTS/spm_train.py SPM_ENCODE=$SCRIPTS/spm_encode.py BPESIZE=32000 TRAIN_MINLEN=1 # remove sentences with <1 BPE token TRAIN_MAXLEN=250 # remove sentences with >250 BPE tokens TRAIN_SENTENCE_MAXNUM=20000000 ############################################################ CHAR_COVER=1.0 src=ja tgt=en orig=wmt23-jaen CORPORA=( "train" ) OUTDIR=wmt23-${src}${tgt}-sentencepiece prep=$OUTDIR mkdir -p $prep ############################################################ TRAIN_FILES=$(for f in "${CORPORA[@]}"; do echo $orig/$f.${src}; echo $orig/$f.${tgt}; done | tr "\n" ",") echo "learning joint BPE over ${TRAIN_FILES}..." python "$SPM_TRAIN" \ --input=$TRAIN_FILES \ --model_prefix=$prep/sentencepiece.bpe \ --vocab_size=$BPESIZE \ --character_coverage=$CHAR_COVER \ --model_type=bpe \ --input_sentence_size=$TRAIN_SENTENCE_MAXNUM \ --shuffle_input_sentence=true ############################################################ # echo "encoding train/valid/test with learned BPE..." echo "encoding train with learned BPE..." python "$SPM_ENCODE" \ --model "$prep/sentencepiece.bpe.model" \ --output_format=piece \ --inputs $orig/train.${src} $orig/train.${tgt} \ --outputs $prep/train.${src} $prep/train.${tgt} \ --min-len $TRAIN_MINLEN --max-len $TRAIN_MAXLEN echo "encoding valid with learned BPE..." python "$SPM_ENCODE" \ --model "$prep/sentencepiece.bpe.model" \ --output_format=piece \ --inputs $orig/test/dev.${src} $orig/test/dev.${tgt} \ --outputs $prep/valid.${src} $prep/valid.${tgt} echo "encoding test with learned BPE..." python "$SPM_ENCODE" \ --model "$prep/sentencepiece.bpe.model" \ --output_format=piece \ --inputs $orig/test/test.${src} $orig/test/test.${tgt} \ --outputs $prep/test.${src} $prep/test.${tgt} ``` ## 3\. BPE from subword-nmt **喂,用了 SentencePiece,就没必要看这节了!** 这一步包括清洗训练数据,学习 bpe tokenizer,然后 tokenize 数据。请放心,我们将用一个 bash 文件搞定。 准备工作完成,请将下述的代码保存在一个 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 pip install subword-nmt 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}..." subword-nmt learn-bpe -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}..." subword-nmt apply-bpe -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` 指定训练数据的前缀。你或许会注意到,我们可以在 `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 ############################################################ ``` ## 4\. 二进制文件 很好,您可以检查下输出目录下的文件。如果成功的话,训练、测试、验证数据各有两个文件。这时候打开文件,会发现这些文件仍旧是可读的,只不过会有 `▁` 或者 `@@` 这种符号。它表示一个单词被切成了一个个 token。 接下来,我们需要将这些文本数据转换成二进制文件,用于 fairseq。这有利于更快的训练。同样的,**根据你之前选择的 tokenizer,这里有两种不同的处理方式。**不过,我仍然推荐按顺序阅读下,更好地理解代码。 ### a. BPE from subword-nmt ``` TEXT=examples/translation/wmt23-jaen fairseq-preprocess --source-lang ja --target-lang en \ --joined-dictionary \ --trainpref $TEXT/train --validpref $TEXT/valid --testpref $TEXT/test \ --destdir data-bin/wmt23-zhen --workers 10 ``` 显然,`TEXT` 指的是您先前的输出目录,而现在,它成了输入目录。`destdir` 则是二进制文件要保存的地方。 其中 `--joined-dictionary` 是选填的,也就是说输入语言和输出语言要不要共用一个字典。 既然您阅读到了这里,我必须要说明下这个命令在做什么。 1. 初始化一个字典。它的实现在 `fairseq/data/dictionary.py`。初始化的时候,会自动加上四个特殊符号:句子结束、开始、padding、unk。 2. 扫描传入文件的每一行,按照空格将 token 分开。然后统计每个 token 出现的次数。 3. 将所有 token 按照出现次数降序排列,然后根据设置的字典大小,将排在前面的 token 加入字典。如果不设置,就是所有 token。 4. 接着扫描所有文件的每一行,按照空格将 token 分开,然后把每个 token 变成字典中的序号。 5. 然后经过 binarize 啥的,大抵就是减少存储空间。 在上面的二进制命令中,用 `nwordssrc` 和 `nwordstgt` 可以指定字典的大小。如果是`joined-dictionary`,指定一个就行了。 如果要后续复用字典处理其他文件,可以这么指定 `--tgtdict data-bin/iwslt17.de_fr.en.bpe16k/dict.en.txt`。因为是 src 和 tgt 使用同一个字典,所以只要制定 tgt 要用到的字典就行。 *** *好的,很符合直觉,也很奇怪*。 bpe 学习完之后就自带一个字典。而且用 bpe tokenize 之后的文件,里面是不会出现不在 bpe 字典里的词。也就是说,这一步学习到的字典,肯定是和 bpe 自带的一样。 不过我跑了下,居然是不一样的,你说奇怪不奇怪。 \--- 更新于 2024年8月30日 我猜测,在上一节 学习 bpe 以及 把数据 tokenize 化,目标是把文本切分开了一个个词。 然后这一步就用之前切分好的词,来维护一个新的字典(是不是有病啊,怀疑显得程度) ### b. SentencePiece 可以发现,在 SentencePiece 处理完文件后,你能够得到两个额外的东西,一个是 model,一个是 vocab。 处理后的文件用空格分开的所有 token,都能在 vocab 里找到对应(有例外)。所以,接下来我们可以根据这个 vocab,把处理完的文件的变成数字的序列。 > 如果不在 fairseq-preprocess 里传入字典,那么它会学习一个新的字典 —— 这个字典取决于这个命令处理的数据。这样不好。所以我们观测下 fairseq-preprocess 输出的字典格式,然后把 sentencepiece 得到的字典直接伪装成最终要的字典。 (这里参考了[issue](https://github.com/facebookresearch/fairseq/issues/859#:~:text=To%20be%20more%20specific%20-%20if%20you%20use%20similar%20preprocess))。 首先要把这个 vocab 转换成 fairseq 能用的格式: ``` # strip the first three special tokens and append fake counts for each vocabulary tail -n +4 sentencepiece.bpe.vocab | cut -f1 | sed 's/$/ 100/g' > fairseq.vocab ``` 接着开始处理: ``` TEXT=wmt23-zhen-sentencepiece DICT=wmt23-zhen-sentencepiece/fairseq.vocab fairseq-preprocess --source-lang zh --target-lang en \ --joined-dictionary --tgtdict $DICT \ --trainpref $TEXT/train --validpref $TEXT/valid --testpref $TEXT/test \ --destdir wmt23-zhen-fairseq-sentencepiece --workers 10 ``` 大致上和 BPE from subword-nmt 做的没什么区别,只是跳过了获得字典这一步。 # 四、训练 接下来,我们用四个显卡,小训 50 个 epoch,并且只保存在验证集上效果最好的那个 epoch 的 checkpoint。 > 如果没有准备验证集,只是想先训训看看的话,请传入 --disable-validation ``` CUDA_VISIBLE_DEVICES=0,1,2,3 fairseq-train data-bin/wmt17_en_de/ \ --arch transformer_iwslt_de_en --share-decoder-input-output-embed \ --optimizer adam --adam-betas '(0.9, 0.98)' \ --clip-norm 0.0 --lr 5e-4 --lr-scheduler inverse_sqrt \ --warmup-updates 512 --dropout 0.3 --weight-decay 0.0001 \ --criterion label_smoothed_cross_entropy --label-smoothing 0.1 \ --max-tokens 32768 \ --update-freq 2 \ --max-source-positions 256 \ --max-target-positions 256 \ --max-epoch 10 \ --save-interval-updates 100 \ --keep-interval-updates 10 \ --no-epoch-checkpoints \ --amp \ --eval-bleu \ --eval-bleu-args '{"beam": 5, "max_len_a": 1.2, "max_len_b": 10}' \ --eval-bleu-detok moses \ --eval-bleu-remove-bpe \ --eval-bleu-print-samples \ --best-checkpoint-metric bleu \ --no-epoch-checkpoints \ --maximize-best-checkpoint-metric \ --find-unused-parameters \ --log-interval 1 \ --wandb-project translation_deen \ --skip-invalid-size-inputs-valid-test \ --save-dir checkpoints/test_transformer/ ``` 上面我尽可能列出了常用的命令,大家可以猜测意思修改。 其中 `--save-interval-updates 100 keep-interval-updates 10 --no-epoch-checkpoints` 意味着,每训练 100 步测试下模型,并保存这个模型,但是只保存最多 10 个,并且我选择不保存每个epoch结束后的模型。 *** 下面是我自用的命令,备份用,大家跳过吧: ``` CUDA_VISIBLE_DEVICES=2,3 fairseq-train /data/yuanhang/wmt23/wmt23-deen-fairseq \ --arch transformer_wmt_en_de --share-decoder-input-output-embed \ --optimizer adam --adam-betas '(0.9, 0.98)' \ --clip-norm 0.0 --lr 5e-4 --lr-scheduler inverse_sqrt \ --warmup-updates 512 --dropout 0.3 --weight-decay 0.0001 \ --criterion label_smoothed_cross_entropy --label-smoothing 0.1 \ --moe-loss-coeff 0.01 \ --max-tokens 16384 \ --update-freq 32 \ --max-source-positions 256 \ --max-target-positions 256 \ --max-epoch 10 \ --save-interval-updates 100 \ --keep-interval-updates 10 \ --no-epoch-checkpoints \ --amp \ --eval-bleu \ --eval-bleu-args '{"beam": 5, "max_len_a": 1.2, "max_len_b": 10}' \ --eval-bleu-detok moses \ --eval-bleu-remove-bpe \ --eval-bleu-print-samples \ --best-checkpoint-metric bleu \ --maximize-best-checkpoint-metric \ --find-unused-parameters \ --log-interval 1 \ --wandb-project translation_deen \ --save-dir checkpoints/wmt23-deen-base/ |& tee logs/wmt23-deen-base.log ``` # 五、模型平均(选做) 机器翻译界有个trick:训练过程中保存多个 checkpoints,训练结束后把它们的权重平均一下,得到新的模型。 这个功能 fairseq 提供了,很简单: ``` CHECKPOINT_DIR= python scripts/average_checkpoints.py \ --num-update-checkpoints 10 \ --inputs $CHECKPOINT_DIR \ --output $CHECKPOINT_DIR/checkpoint.avg10.pt ``` # 六、测试 测试有两种方法,一种是 fairseq 自带的,一种是用 sacrebleu。我个人测试下,差别不是很大。 ``` fairseq-generate data-bin/wmt17_en_de/ --path checkpoints/test_transformer/checkpoint_best.pt --batch-size 128 --beam 5 --remove-bpe=sentencepiece ``` 如果用的是 subword-ntm,请将 `--remove-bpe=sentencepiece` change to `--remove-bpe`。 当然,现在更流行的是 sacrebelu,我参考了这个[链接](https://github.com/facebookresearch/fairseq/tree/main/examples/backtranslation) 和 [这个链接](https://github.com/facebookresearch/fairseq/blob/ac66df47b5394e730aa05efa50ed7ec6103388bb/examples/translation/README.md#multilingual-translation:~:text=%23%20Generate%20and%20score%20the%20test%20set%20with%20sacrebleu),得到了下面两个命令,亲测可行: \*\*重要,更新于 2024年8月30日 \*\*:所有的 sacrebleu 命令后面都应该加上 `--tokenize $TGTLANG` 来指定语言。不然的话 bleu 会低很多。如果 sacreblue 没有支持某个语言,或者是你想要自定义 tokenize 的方法,那么就请先把预测结果和 label 都 tokenize 下,然后再用 sacrebleu 某个参数,明确告诉它输入的是 tokenize 后的版本。 ## 1\. BPE from subword-nmt ``` DATASET=wmt22 LANGPAIR=ja-en SRCLANG=ja TGTLANG=en BPECODE= DATABIN= MODEL= sacrebleu -t $DATASET -l $LANGPAIR --echo src \ | sacremoses -q -l $SRCLANG tokenize -a \ | subword-nmt apply-bpe -c $BPECODE \ | fairseq-interactive $DATABIN --path $MODEL \ -s $SRCLANG -t $TGTLANG \ --beam 5 --remove-bpe --buffer-size 1024 --max-tokens 8000 \ | grep ^H- | cut -f 3- \ | sacremoses -q -l $TGTLANG detokenize \ | sacrebleu -t $DATASET -l $LANGPAIR -m bleu chrf --tokenize $TGTLANG ``` 用指令 `sacrebleu --list -l en-fr` 可以查看当前语言对支持的数据集。 ## 2\. SentencePiece ``` DATASET=wmt22 LANGPAIR=ja-en SRCLANG=ja TGTLANG=en SPM_ENCODE=fariseq/scripts/spm_encode.py SPM_MODEL=xxx/sentencepiece.bpe.model DATABIN= MODEL= sacrebleu -t $DATASET -l $LANGPAIR --echo src \ | python $SPM_ENCODE --model $SPM_MODEL \ | fairseq-interactive $DATABIN --path $MODEL \ -s $SRCLANG -t $TGTLANG \ --beam 5 --remove-bpe=sentencepiece --buffer-size 1024 --max-tokens 8000 \ | grep ^H- | cut -f 3- \ | sacrebleu -t $DATASET -l $LANGPAIR -m bleu chrf --tokenize $TGTLANG ``` 其实和 subword-nmt 的区别不大。 ## 3\. 基于神经网络的测试: COMET-22 根据这篇文章:**Results of WMT22 Metrics Shared Task: Stop Using BLEU – Neural Metrics Are Better and More Robust**, COMET-22 是更符合人类偏好的测试方法。 要使用这个方法,我是参照 <https://huggingface.co/Unbabel/wmt22-comet-da> 里的描述。 流程很简单,首先要安装一个库: ``` pip install --upgrade pip # ensures that pip is current pip install unbabel-comet ``` 然后要准备三个文件,翻译的源文件,我的翻译输出,翻译参考答案。 我们先准备源文件和参考答案: ``` DATASET=wmt22 LANGPAIR=ja-en SRCLANG=ja TGTLANG=en sacrebleu -t $DATASET -l $LANGPAIR --echo src > source-inputs.txt sacrebleu -t $DATASET -l $LANGPAIR --echo ref > references.txt ``` 然后进行翻译(和上一小节一样,为了偷懒,我就不写 subword-nmt的了): ``` SPM_ENCODE=fariseq/scripts/spm_encode.py SPM_MODEL=xxx/sentencepiece.bpe.model DATABIN= MODEL= sacrebleu -t $DATASET -l $LANGPAIR --echo src \ | python $SPM_ENCODE --model $SPM_MODEL \ | fairseq-interactive $DATABIN --path $MODEL \ -s $SRCLANG -t $TGTLANG \ --beam 5 --remove-bpe=sentencepiece --buffer-size 1024 --max-tokens 8000 \ | grep ^H- | cut -f 3- > translation-outputs.txt comet-score -s source-inputs.txt -t translation-outputs.txt -r references.txt --model Unbabel/wmt22-comet-da ``` 最后一行其实就是打分啦,如果是第一次使用,会下载一个 2GB 的模型: ``` comet-score -s source-inputs.txt -t translation-outputs.txt -r references.txt --model Unbabel/wmt22-comet-da ``` Github 的 **Interpreting Scores** 这一节可以读一下,它推荐,在比较不同的模型时,使用下面这个命令,可以输出 statistical hypothesis test 的结果: ``` comet-compare -s source-inputs.txt -t hyp1.en hyp2.en hyp3.en -r references.txt ``` # 七、结语 祝大家吃好睡好,下次再见! # 八、番外:utf-8 问题 在处理日语文件时,我遇到了有无法被 unicode 解码,导致 tokenize 失败的问题。要解决这个问题也很简单,只要在预处理的时候,处理掉那些奇怪的字符就好了。 举个例子,我假设我的训练文件名字是 old\_train, 新文件是 train,那么处理可以这样: ``` with open("old_train.ja", errors='ignore') as input_ja, open("old_train.en") as input_en, open("train.ja", "w") as output_ja, open("train.en", "w") as output_en: index = 0 try: for ja_row, en_row in zip(input_ja, input_en): output_ja.write(ja_row) output_en.write(en_row) index += 1 if index % 1000000 == 0: print(f"!!!! {index}") except UnicodeDecodeError: print(f"yyh here {index}") ``` 可以看到,在读取日语训练文件时,我指定了 `errors='ignore'`,这会忽略导致错误的字符。 虽然我写了 except,但理论上不会被触发。 # 九、番外:安装 fairseq 的问题 ## 1\. 12 erros detected in ... *** 参考这个网站 <https://github.com/NVIDIA/apex/issues/1735> 。 如果安装失败,或许是 apex 当前版本的代码有错误(2023年11月12日01:47:21)。 我是 torch 2.0.1 时安装 apex 有这个问题。 只要安装 apex 之前的版本就行: ``` git checkout 2386a912164b0c5cfcd8be7a2b890fbac5607c82 ``` ## 2\. cuda 相关 *** 最近在新机器上安装 fairseq ,会遇到当前 cuda 版本和编译了 pytorch 的 cuda 版本不匹配的问题。但如果不是安装可编辑版本,而是直接用 `pip` 安装,就没问题。很奇怪。 我安装的全流程是这样的: > 根据我的一篇文章安装 cuda,修改环境变量 --\> 创建 conda 虚拟环境 --\> 安装 torch --\> 安装 apex --\> 安装 fairseq。 然后就遇到问题了。我一通折腾,改了个流程: > 删除 conda 的环境,删除 conda 缓存的包,删除 cuda 的环境变量 --\> 创建 conda 虚拟环境 --\> 安装 torch --\> 安装 fairseq --\> 修改 cuda 环境变量 --\> 安装 apex。 然后就好了。很奇怪,不是吗。我不设置 cuda 的环境变量,居然能够安装 fairseq,在这时候安装 apex 反而会报错。我也不知道是哪一步生效的。 我又在一台机子上测试了下,在安装 fairseq 的时候,不要有 cuda 的环境变量,就可以安装成功。 值得一提,如果要为了 fairseq 安装 apex,可能需要加几个参数,当然不加也是可以的: ``` pip install -v --disable-pip-version-check --no-cache-dir --no-build-isolation --config-settings "--build-option=--cpp_ext" --config-settings "--build-option=--cuda_ext" --config-settings "--build-option=--deprecated_fused_adam" --config-settings "--build-option=--xentropy" --config-settings "--build-option=--fast_multihead_attn" ./ ``` posted @ 2023-11-07 00:35 [ysngki](https://www.cnblogs.com/ysngki) 阅读(2012) 评论(1) [收藏]() [举报]() [刷新页面](https://www.cnblogs.com/ysngki/p/17814160.html)[返回顶部](https://www.cnblogs.com/ysngki/p/17814160.html#top) [![](https://img2024.cnblogs.com/blog/35695/202510/35695-20251011174856892-66283873.jpg)](https://developer.huawei.com/consumer/cn/activity/digixActivity/digixcmsdetail/101750143863263087?ha_source=BKYQ3&ha_sourceId=89000408) ### 公告 [![](https://img2024.cnblogs.com/blog/35695/202510/35695-20251013111242110-2029482428.jpg)](https://qoder.com/) [博客园](https://www.cnblogs.com/) © 2004-2025 [![](https://assets.cnblogs.com/images/ghs.png)浙公网安备 33010602011771号](http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=33010602011771) [浙ICP备2021040463号-3](https://beian.miit.gov.cn/)
Readable Markdownnull
Shard46 (laksa)
Root Hash10938660598884985246
Unparsed URLcom,cnblogs!www,/ysngki/p/17814160.html s443