Moses 官网其实是有 macOS 二进制包的,你不需要从源代码编译它们。但总之,由于 Moses 开发者已经不再用 Mac,所以他没办法更新,这导致了目前最新版(4.0)的代码中有一个bug,使得二进制文件不能直接使用,作者说“反正从源码编译也不是很难……”但总之,从 BigSur 上编译 Moses 已经几乎是不可能的了,各种奇怪的报错,令人头疼。
其实,我们是可以直接修正二进制文件中的错误,直接运行的。
修复报错
直接下载的 Moses 二进制文件,执行任意一个都会遇到如下错误:
1 |
'./moses/bin/consolidate' terminated by signal SIGKILL (Forced quit) |
以及:
1 2 3 4 |
dyld: Library not loaded: /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_xmltok.3.39.dylib Referenced from: ~/MosesTest/moses/bin/biconcor Reason: image not found Abort trap: 6 |
我们先处理第二个报错,这个报错有些复杂,显然,这些二进制文件链接到了一个不存在的动态链接库,这就很棘手了,因为代码已经被打包成了二进制文件,我们无法直接修改代码或者编译器参数来修复这个错误……吗?
分析
使用命令 otool -L ./moses/bin/consolidate 来查看 consolidate 这个可执行文件,我们得到如下结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
./moses/bin/consolidate: /opt/local/lib/libbz2.1.0.dylib (compatibility version 1.0.0, current version 1.0.6) /opt/local/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11) /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_xmltok.3.39.dylib (compatibility version 0.0.0, current version 0.0.0) /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_xmlparse.3.39.dylib (compatibility version 0.0.0, current version 0.0.0) /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_util++.8.39.dylib (compatibility version 0.0.0, current version 0.0.0) /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_util.3.39.dylib (compatibility version 0.0.0, current version 0.0.0) /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_server_abyss++.8.39.dylib (compatibility version 0.0.0, current version 0.0.0) /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_server_abyss.3.39.dylib (compatibility version 0.0.0, current version 0.0.0) /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_server++.8.39.dylib (compatibility version 0.0.0, current version 0.0.0) /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_server.3.39.dylib (compatibility version 0.0.0, current version 0.0.0) /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_abyss.3.39.dylib (compatibility version 0.0.0, current version 0.0.0) /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc++.8.39.dylib (compatibility version 0.0.0, current version 0.0.0) /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc.3.39.dylib (compatibility version 0.0.0, current version 0.0.0) /Users/hieu/workspace/cmph-2.0/lib/libcmph.0.dylib (compatibility version 1.0.0, current version 1.0.0) /Users/hieu/workspace/irstlm/irstlm-5.80.08/trunk/lib/libirstlm.0.dylib (compatibility version 1.0.0, current version 1.0.0) /opt/local/lib/libiconv.2.dylib (compatibility version 9.0.0, current version 9.0.0) /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.0.0) |
这就是这个文件链接的所有动态库了,理论上我们只要把那些不存在的路径改成存在的路径就可以让这个程序正常运行,这有两步:
- 找到真正被链接的动态库;
- 改这个二进制文件的链接库地址。
其中第二点我们可以使用 install_name_tool 这个 Xcode 自带命令完成,第一点就是去找到 xmlrpc-c 了,最好是 1.39.07 这个版本(我已经试过用 brew 直接装一个最新版,但由于太新了,有两个动态链接库直接被去掉了,所以最好还是同一个版本,确保具体 API 不变)。好在 xmlrpc-c 有官方提供历史版本,我们可以从这里直接下载 1.39.07 这个版本的源代码,进行编译。
修复错误
要编译 xmlrpc-c,需要使用 gcc-10,如果你没有装的话,可以使用命令 brew install gcc 一键安装,然后就是编译安装了:
1 2 3 |
./configure --prefix=/usr/local/lib/xmlrpc/ make make install |
安装到哪里你随意,但要记得这个地址,一会要去找到这个路径的。
接下来就是修改链接地址了:
1 |
install_name_tool -change /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_xmltok.3.39.dylib /usr/local/lib/xmlrpc/libxmlrpc_xmltok.3.39.dylib ./moses/bin/consolidate |
比如对于 libxmlrpc_xmltok.3.39.dylib 这一条,我们就这样把它替换掉,变成真正可用的动态链接库。但每个二进制文件都有11个错误链接需要替换……手动处理还是有些麻烦,于是我写了一个简单的脚本,你可以把它复制下来写到一个 .sh 文件里,然后使用 sh xxx.sh ./moses/bin 这样的形式来给对应的二进制文件进行替换,这个脚本可以直接将给定目录下的所有可执行文件进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#!/bin/bash updateLink() { install_name_tool -change /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_xmltok.3.39.dylib /usr/local/lib/xmlrpc/libxmlrpc_xmltok.3.39.dylib ${1} install_name_tool -change /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_xmlparse.3.39.dylib /usr/local/lib/xmlrpc/libxmlrpc_xmlparse.3.39.dylib ${1} install_name_tool -change /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_util++.8.39.dylib /usr/local/lib/xmlrpc/libxmlrpc_util++.8.39.dylib ${1} install_name_tool -change /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_util.3.39.dylib /usr/local/lib/xmlrpc/libxmlrpc_util.3.39.dylib ${1} install_name_tool -change /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_server_abyss++.8.39.dylib /usr/local/lib/xmlrpc/libxmlrpc_server_abyss++.8.39.dylib ${1} install_name_tool -change /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_server_abyss.3.39.dylib /usr/local/lib/xmlrpc/libxmlrpc_server_abyss.3.39.dylib ${1} install_name_tool -change /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_server++.8.39.dylib /usr/local/lib/xmlrpc/libxmlrpc_server++.8.39.dylib ${1} install_name_tool -change /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_server.3.39.dylib /usr/local/lib/xmlrpc/libxmlrpc_server.3.39.dylib ${1} install_name_tool -change /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc_abyss.3.39.dylib /usr/local/lib/xmlrpc/libxmlrpc_abyss.3.39.dylib ${1} install_name_tool -change /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc++.8.39.dylib /usr/local/lib/xmlrpc/libxmlrpc++.8.39.dylib ${1} install_name_tool -change /Users/hieu/workspace/xmlrpc-c/xmlrpc-c-1.39.07/lib/libxmlrpc.3.39.dylib /usr/local/lib/xmlrpc/libxmlrpc.3.39.dylib ${1} install_name_tool -change /Users/hieu/workspace/irstlm/irstlm-5.80.08/trunk/lib/libirstlm.0.dylib /usr/local/lib/xmlrpc/libxmlrpc.3.39.dylib ${1} } for file in [crayon-6741aaa417a06101577263 inline="true" ]ls -F ${1} | grep "*" |
do
echo “processing $file …”
updateLink “${1}/${file}”
done
[/crayon]
在把报错的命令进行修复后,我们就可以正常地使用 Moses 了。
这里我们再说第一个错误,是 macOS 新引入的 gatekeeper,这个安全机制会默认阻止你运行任何没有签名的二进制文件……显然,Moses 里的所有二进制文件都是没有签名的……总之,由于我们已经用脚本将这些二进制文件修改了一下,现在系统就认为它们是我们自己生成的,于是就不会再阻挡运行。不过以防万一,如果你遇到了,就用 Finder 去目录里,鼠标右键点击它,选择“打开”。 这样系统就会用自带的终端来运行这个二进制文件了,接下来再回到你的终端重新执行这个命令,就可以正常执行了(每一个新命令都要这样处理一次,好在只是第一次才需要,以后就不要了。)
注意这里有个 libirstlm.0.dylib 我们其实并没有去生成,但没关系,因为我们没有用到这个库,只要把它的依赖替换成任意能找到的路径就行了,只要不用这部分功能,那么理论上不会有任何影响~
准备数据
这里我们用联合国公开平行语料进行实验,由于数据过于庞大,这里仅取中文英文各前 60 万行,下载到的数据是 tar.gz 的分包文件,这里我们用 cat UNv1.0.en-zh.tar.gz.* >>un.en-zh.tar.gz 命令将分包文件进行合并,然后再解压缩。
这里我们将截取出来的数据分别命名为 en60w.txt 和 zh60w.txt ,这两个文件基本上是一句一行,两个文件的格式一致,内容一致,唯一的不同就是语言不同。
分词
我们需要对数据进行分词,注意英文语料也需要分词,这会将一些标点符号与英文词汇或者数字分隔开,方便后续操作。
对中文进行分词,我们使用 jieba:
1 |
python3 -m jieba -d " " zh60w.txt > zh60w_cuted.txt |
注意这里用了 -d " " 这个参数,把 jieba 默认的斜杠分词符号改为空格。这样我们就得到了分词结果 zh60w_cuted.txt 。
对英文进行分词,使用 Moses 自带工具:
1 |
./moses/scripts/tokenizer/tokenizer.perl -l en -lines 20000 -time -threads 6 < en60w.txt > en60w.tok.txt |
这里我用了 -time 来显示最终时间消耗,用 -threads 6 注明使用多线程加速处理,用 -lines 20000 设置每个线程每次处理 20000 行,默认是 2000. 这样我们就得到了英文分词结果 en60w.tok.txt 。
至此, en60w.txt 和 zh60w.txt 就可以删掉了。
处理大小写
把英文数据中的大写都换成小写,这有助于加快翻译速度,我们首先要训练 Truecase,然后再用它来快速处理语料:
1 2 |
./moses/scripts/recaser/train-truecaser.perl --model truecase-model.en --corpus en60w.tok.txt ./moses/scripts/recaser/train-truecaser.perl --model truecase-model.cn --corpus zh60w.tok.txt |
这样我们就得到了 truecase-model.en 和 truecase-model.cn 两个模型,然后我们使用这两个模型对分词后的语料进行处理:
1 2 |
./moses/scripts/recaser/truecase.perl --model truecase-model.en < en60w.tok.txt > en-zh60w.true.en ./moses/scripts/recaser/truecase.perl --model truecase-model.cn < zh60w.tok.txt > en-zh60w.true.cn |
这样我们就得到了处理过的 en-zh60w.true.en 和 en-zh60w.true.cn ,注意从这里开始,我们的命名具有了一定的规则,因为后续的命令会用到。
至此, en60w.tok.txt 和 zh60w.tok.txt 就可以删掉了。
去掉过长语句
最后,我们再来对语料进行一下修剪,比如太长的语句会明显减慢训练速度且影响最终的准确性:
1 |
./moses/scripts/training/clean-corpus-n.perl en-zh60w.true cn en en-zh60w.clean 1 50 |
这样,我们就又得到了 en-zh60w.clean.cn 和 en-zh60w.clean.en 这两个清洗后的语料文件。
生成语言模型
语言模型用来保证翻译后的内容是流利可读的:
1 |
./moses/bin/lmplz -o 3 < en-zh60w.true.cn > en-zh60w.arpa.cn |
然后将生成的模型压缩成二进制,加快查询速度:
1 |
./moses/bin/build_binary en-zh60w.arpa.cn en-zh60w.blm.cn |
这样我们就得到了 en-zh60w.blm.cn 这个模型文件, en-zh60w.arpa.cn 可以删掉了。
使用命令测试模型: echo "我 爱 北京 天安门" | ./moses/bin/query en-zh60w.blm.cn 得到输出:
1 2 3 4 5 6 |
我=23055 2 -2.4262204 爱=3881 1 -6.498771 北京=14065 1 -4.4601955 天安门=33807 1 -6.3538356 </s>=2 1 -2.6495044 Total: -22.388527 OOV: 0 Perplexity including OOVs: 30040.377320675794 Perplexity excluding OOVs: 30040.377320675794 OOVs: 0 Tokens: 5 RSSMax:166760448 kB user:0.002757 sys:0.098377 CPU:0.101134 real:0.092774 |
至此, en-zh60w.true.en 和 en-zh60w.true.cn 就可以删掉了。
训练翻译模型
现在,万事俱备,我们可以开始训练翻译模型了:
1 2 |
mkdir working cd working |
我们先创建一个独立的目录,在这里边执行训练命令:
1 |
../moses/scripts/training/train-model.perl -root-dir train -corpus ../en-zh60w.clean -f en -e cn -alignment grow-diag-final-and -reordering msd-bidirectional-fe -lm 0:3:/【这里需要使用绝对路径】/Downloads/MosesTest/lm/en-zh60w.blm.cn:8 -external-bin-dir ../bin/training-tools -cores 6 -mgiza -mgiza-cpus 6 |
这里注意 -mgiza -mgiza-cpus 6 是必须要加上的,因为 Moses 的 macOS 包中只带有 mgiza 这个工具,如果不使用它,则会默认使用一个单线程的处理工具,最终导致找不到命令而报错。其中 -mgiza-cpus 6 表明了我要使用 6 个线程来进行训练。
调优参数
模型训练完成了,但是现在的超参都是默认值,并不是最优的,我们需要对参数进行调优。
首先从最一开始下载的平行语料中再次截取 10 万语料,这里我截取了第 60 万到 70 万这10万行数据,保存为 opt.en 和 opt.cn 这两个小语料,我们将使用这 10 万数据作为调试集进行参数调优。
分词和处理大小写
还是类似的步骤,将数据进行处理:
1 2 3 4 5 6 7 |
python3 -m jieba -d " " opt.cn > opt.tok.cn ./moses/scripts/tokenizer/tokenizer.perl -l en -lines 2000000 -time < opt.en > opt.tok.en ./moses/scripts/recaser/truecase.perl --model truecase-model.cn < opt.tok.cn > opt.true.cn ./moses/scripts/recaser/truecase.perl --model truecase-model.en < opt.tok.en > opt.true.en rm opt.en opt.cn |
现在我们得到 opt.true.cn 和 opt.true.en ,这下就可以用来给参数调优了。
调优
1 2 |
cd working ../moses/scripts/training/mert-moses.pl ../opt.true.en ../opt.true.cn ../moses/bin/moses train/model/moses.ini --mertdir /【绝对路径】/moses/bin/ --multi-moses --multi-moses --decoder-flags='-threads 6' |
--mertdir /【绝对路径】/moses/bin/ 注意这个参数,要写成绝对路径,虽然写成相对路径程序也可以执行,但到末尾调优结束后无法正确生成导出脚本,会导致实际目录错位。
这里我们使用 --multi-moses参数开启多进程并使用参数 --decoder-flags='-threads 6' 直接给 decoder 传送指令,表示使用 6 个进程,加速处理。
这里参数名虽然是 threads,但实际上格式是这样的 进程数:每个进程的线程数:额外进程的线程数 ,如果你像我这样只给了一个数字,那么意思就是 6:1:0 ,也就是6个进程每个进程1线程,不要额外进程。这是最快也是最消耗内存的方案。
实测不使用 --multi-moses即使设定6线程,实际也只使用2线程,占用内存4.2GB(这个大小是依据不同的模型大小来的,你要根据你的实际情况进行处理,比如我内存是 32GB,得到这个内存占用量后,就可以结束进程,然后使用6个进程重新开始,加速处理)。调优的过程非常非常缓慢,我的建议是你选择一个10的倍数级别的样本进行调优,这样就可以根据数量计算当前进度。
程序会先根据你要测试的数据对模型进行过滤,去掉模型中肯定用不到的条目,这样就会大大提升加载速度而不影响测试结果,但实际使用时请不要使用这个过滤后的专用模型,且更换了测试数据也要重新生成过滤的模型。
注意,调优不会自动停止,它会一遍又一遍的迭代下去,在你觉得差不多的时候自行停止它,并使用最好的那个结果。
二进制模型压缩
生成的模型是文本的,我们可以将模型进行压缩,生成二进制数据,这样能够极大程度提升 Moses 的载入速度。我们创建一个目录来存放生成的二进制模型: mkdir working/binarised-model
然后使用两个命令来生成两个模型文件:
1 2 3 |
../moses/bin/processPhraseTableMin -in train/model/phrase-table.gz -nscores 4 -out binarised-model/phrase-table ../moses/bin/processLexicalTable -in train/model/reordering-table.wbe-msd-bidirectional-fe.gz -out binarised-model/reordering-table |
将 train/model/moses.ini 复制到 binarised-model/moses.ini
编辑它,找到 # feature functions 这一块, LexicalReordering 这个字段中的参数 path= 为 binarised-model/reordering-table 的绝对路径, PhraseDictionaryMemory 这个字段改为 PhraseDictionaryCompact 并将其中的参数 path= 改为 binarised-model/phrase-table.minphr 的绝对路径。
然后我们就可以使用命令 ../moses/bin/moses -f binarised-model/moses.ini 来启动 Moses 了。
批量测试
批量测试也需要对应的平行语料,英文用于翻译,中文用于最后的比对准确率。同样的英文要用 tokenizer.perl 进行分词,中文要用 jieba 等分词库进行分词,然后使用 truecase.perl 进行处理。
准备模型
同样的,我们先将模型针对测试集进行过滤,去掉完全不会用到的条目,这样能大大加快测试速度却不影响结果:
1 2 |
cd working ../moses/scripts/training/filter-model-given-input.pl filtered mert-work/run4.moses.ini ../opt.true.en -Binarizer ../moses/bin/processPhraseTableMin |
这里我直接用调优的数据进行测试了。
批量处理
使用命令让 moses 批量翻译所有内容:
1 |
./moses/bin/moses -f working/filtered/moses.ini -i < opt.true.en > translated.cn 2> test.out |
计算 BLEU
BLEU 是一个评判翻译结果准确度的算法,得到的结果是一个百分比:
1 |
./moses/scripts/generic/multi-bleu.perl -lc opt.true.cn translated.cn |
比如按照本次例子中的语料,我们得到结果如下:
1 |
BLEU = 26.53, 63.3/32.7/19.4/12.3 (BP=1.000, ratio=1.000, hyp_len=2354049, ref_len=2353369) |
参考链接
本文由 落格博客 原创撰写:落格博客 » macOS 运行和训练 Moses
转载请保留出处和原文链接:https://www.logcg.com/archives/3487.html