ROUGE与PyROUGE的安装:虚拟机上从头再来
1 写在前面
上一篇文章:ROUGE与PyROUGE的安装:非root权限的尝试与失败,烂尾了。由于没有服务器的root权限,最终也没能成功。但我是不会就这样放弃的,经老师建议,我可以在本地安装虚拟机,在虚拟机中使用ROUGE评价模型。
我使用的软件是VMware Workstation Pro
。还记得大一的暑假参加科技夏令营的时候,我们学习在服务器上部署LNMP
框架,搭建WordPress
的博客。当时为了练习linux命令,在本地搭了一台Ubuntu
和一台CentOS7
,距离现在已经一年半了。所以说,哈哈,我不记得密码了。我蒙对了CentOS7
的密码,所以下文将基于本地的虚拟机:CentOS7
。
2 前期准备
关于下载和安装虚拟机的教程在此不进行详述,改日再成博客。我的这台虚拟机内存分配了2GB,硬盘20GB。我所说的前期准备主要是网络设置。
在我wget的时候发现网络不通,修改网络配置可以参考这篇博客:VMware 虚拟机无法连接网络解决办法。这篇博客写的很详细,但唯一没有提到网络连接模式应该选择WMnet8 NAT模式
。
另外为了能够更好地使用yum
命令,最好对它的安装源进行升级。这个过程往往很缓慢,可以在睡觉前更新,让它安装一夜。
$ yum -y upgrade
3 注意事项
具体的安装过程完全可以参考上一篇文章,有了root权限,安装起来会快很多很多,基本没遇到太复杂的问题。如下是一些需要注意的事项。
3.1 设置perl的执行路径
首先,既然我们已经有了root权限,就要把perl的路径变量设在root的配置文件中。这里首先需要用su
命令进入到root中,然后:
# vim /etc/profile
在文件的最后加上如下内容,其中路径可以在安装ActivePerl的时候得到:
export PATH=$PATH:/opt/ActivePerl-5.26/site/bin:/opt/ActivePerl-5.26/bin
export MANPATH=$MANPATH:/opt/ActivePerl-5.26/site/man:/opt/ActivePerl-5.26/man
然后**当前环境变量:
# source /etc/profile
但是在这时我们使用perl -v
命令查看perl,仍然不是我们新安装的。我又不知道如何卸载系统原有的perl(网上有人问过这个问题,一般无法卸载)。于是破釜沉舟:
whereis perl
得到系统原装的perl的可执行文件路径后,我们删掉它。这时再查看perl -v
,成功的覆盖掉了原始perl,得到了5.26.0版本。关于linux查找文件的命令,find
、whereis
和locate
有什么区别,可以参考:Linux下怎样搜索文件。
3.2 ROUGE-RELEASE-1.5.5
在出现了很多File not found问题之后,我开始反思我的项目是不是不完整。在网上查到的博客多数都是互相抄袭,但这篇博客:Ubuntu安装配置ROUGE教程是原创。其中给出了ROUGE-1.5.5的源码地址:GitHub,我才知道我的项目缺了很多东西,这是它应有的模样:
data docs README.txt RELEASE-NOTE.txt ROUGE-1.5.5.pl runROUGE-test.pl sample-output sample-test
而网上绝大多数博客给出的ROUGE的目录都没有sample-output
和sample-test
这两个文件夹,我都不知道他们明知道结果不对还互相抄袭是什么心理,或者说他们都没做过这个实验?
我们照上节提到的方法将环境变量添加到/etc/profile
中:
export ROUGE_EVAL_HOME=$ROUGE_EVAL_HOME:/home/thomas_atlantis/environments/ROUGE-RELEASE-1.5.5/data
3.3 修改*.pl文件开头声明
一般来说我们可能会遇到这个问题:
Can't locate DB_File.pm in @INC
这我在上篇博客中已经强调过了。由于我们的perl路径已经不是默认的路径了,我们需要将所有的*.pl
文件的首行修改为:
#!/opt/ActivePerl-5.26/bin/perl -w
我们可以使用以下命令查找出ROUGE-RELEASE-1.5.5下的所有*.pl
文件,以免有遗漏:
$ find . -name '*.pl'
得到结果:
./data/WordNet-2.0-Exceptions/buildExeptionDB.pl
./data/WordNet-1.6-Exceptions/buildExeptionDB.pl
./runROUGE-test.pl
./test/runROUGE-test.pl
./ROUGE-1.5.5.pl
3.4 修改*.pl文件执行权限
这个版本的ROUGE中不存在执行权限问题,但还是要检查以下,查看上节中*.pl
文件列表,如果某个文件不是绿色的可执行状态,我们要:
$ chmod +x *.pl
3.5 WordNet-2.0.exc.db问题
Cannot open exception db file for reading: data/WordNet-2.0.exc.db
这是一个常见问题,解决方法:
$ cd ROUGE-RELEASE-1.5.5/data
$ rm WordNet-2.0.exc.db
$ ./WordNet-2.0-Exceptions/buildExeptionDB.pl ./WordNet-2.0-Exceptions $ ./smart_common_words.txt ./WordNet-2.0.exc.db
解决完以上问题,ROUGE就基本配好了,测试命令:
perl runROUGE-test.pl
的结果正确无误输出如下:
我们可以查看其中的一个样例DUC2002-ROUGE.in.26.spl.out
:
---------------------------------------------
26 ROUGE-1 Average_R: 0.33939 (95%-conf.int. 0.32215 - 0.35572)
26 ROUGE-1 Average_P: 0.28013 (95%-conf.int. 0.26712 - 0.29348)
26 ROUGE-1 Average_F: 0.30654 (95%-conf.int. 0.29199 - 0.32107)
---------------------------------------------
26 ROUGE-2 Average_R: 0.07727 (95%-conf.int. 0.06642 - 0.08925)
26 ROUGE-2 Average_P: 0.06356 (95%-conf.int. 0.05484 - 0.07321)
26 ROUGE-2 Average_F: 0.06967 (95%-conf.int. 0.06017 - 0.08050)
---------------------------------------------
26 ROUGE-3 Average_R: 0.02587 (95%-conf.int. 0.01928 - 0.03279)
26 ROUGE-3 Average_P: 0.02119 (95%-conf.int. 0.01587 - 0.02674)
26 ROUGE-3 Average_F: 0.02327 (95%-conf.int. 0.01749 - 0.02950)
---------------------------------------------
26 ROUGE-4 Average_R: 0.01185 (95%-conf.int. 0.00768 - 0.01608)
26 ROUGE-4 Average_P: 0.00970 (95%-conf.int. 0.00625 - 0.01315)
26 ROUGE-4 Average_F: 0.01065 (95%-conf.int. 0.00689 - 0.01445)
---------------------------------------------
26 ROUGE-L Average_R: 0.30064 (95%-conf.int. 0.28601 - 0.31506)
26 ROUGE-L Average_P: 0.24797 (95%-conf.int. 0.23702 - 0.25990)
26 ROUGE-L Average_F: 0.27143 (95%-conf.int. 0.25923 - 0.28387)
4 pyrouge的安装
$ git clone https://github.com/bheinzerling/pyrouge
$ cd pyrouge
$ sudo python setup.py install
$ python -m pyrouge.test
这里注意,一定不要使用pip install pyrouge
的方法安装,那样安装的文件的源码是有BUG在里面的。如有其它问题可以参考以上的GitHub:README。如果测试成功最后可以看到:
----------------------------------------------------------------------
Ran 10 tests in 8.987s
OK
5 pyrouge的使用
参考这篇博客:rouge与pyrouge使用事项。博客中给出的代码需要稍作改动。我假设输入的文件的格式是:reference:每行一段参考标准摘要,一段可以包含多个句子;candidate:与前者相对应,每行一段模型预测的摘要,一段可以包含多个句子。这是在考虑模型输出为多句摘要且未进行分句的情况,属于默认情况。如果模型给出多句且已分好句,那么可以将多句分别写在一行,段与段之间隔一个空行。我将加入一个参数来控制这两种输入格式。
原博主给出的代码中有一句:" ".join(ref[i]).replace(' ', '') + '\n'
,让人很莫名其妙,为什么要先用空格连接然后又去掉空格?我猜想她是想用\n
连接来着,但是空格是真的不应该去。**ROUGE要求每行一句话,单词用空格隔开。**而且最好在评价结束后将程序生成的中间文件删除掉,否则会影响下一次评价的输入。我使用argparse
命令行参数解析工具包,将修改后的程序封装了一下,下面是源代码:
# -*- coding: utf-8
import pyrouge, codecs, os, sys, logging, argparse
from nltk.tokenize import sent_tokenize
def remove_files(path):
for file in os.listdir(path):
path_file = os.path.join(path, file)
os.remove(path_file)
def rouge(ref, hyp, log_path):
assert len(ref) == len(hyp)
ref_dir = log_path + 'reference/'
cand_dir = log_path + 'candidate/'
if not os.path.exists(ref_dir):
os.mkdir(ref_dir)
if not os.path.exists(cand_dir):
os.mkdir(cand_dir)
for i in range(len(ref)):
with codecs.open(ref_dir+"%06d_reference.txt" % i, 'w', 'utf-8') as f:
f.write("\n".join(ref[i]) + '\n')
with codecs.open(cand_dir+"%06d_candidate.txt" % i, 'w', 'utf-8') as f:
f.write("\n".join(hyp[i]).replace('<unk>', 'UNK') + '\n')
r = pyrouge.Rouge155()
r.model_filename_pattern = '#ID#_reference.txt'
r.system_filename_pattern = '(\d+)_candidate.txt'
r.model_dir = ref_dir
r.system_dir = cand_dir
logging.getLogger('global').setLevel(logging.WARNING)
rouge_results = r.convert_and_evaluate()
scores = r.output_to_dict(rouge_results)
recall = [round(scores["rouge_1_recall"] * 100, 2),
round(scores["rouge_2_recall"] * 100, 2),
round(scores["rouge_l_recall"] * 100, 2)]
precision = [round(scores["rouge_1_precision"] * 100, 2),
round(scores["rouge_2_precision"] * 100, 2),
round(scores["rouge_l_precision"] * 100, 2)]
f_score = [round(scores["rouge_1_f_score"] * 100, 2),
round(scores["rouge_2_f_score"] * 100, 2),
round(scores["rouge_l_f_score"] * 100, 2)]
print("F_measure: %s Recall: %s Precision: %s\n"
% (str(f_score), str(recall), str(precision)))
remove_files(ref_dir)
remove_files(cand_dir)
with codecs.open(log_path + "rouge_score", 'a+', 'utf-8') as f:
f.write("F_measure: %s Recall: %s Precision: %s\n"
% (str(f_score), str(recall), str(precision)))
return f_score[:], recall[:], precision[:]
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-r', '--reference', help='reference input file')
parser.add_argument('-c', '--candidate', help='candidate input file')
parser.add_argument('-a', '--auto_tokenize', choices=['true', 'false'], default='true',
help='if true, every paragraph takes one line;'
'if false, every sentence takes one line, paragraphs are split by one blank line')
parser.add_argument('-l', '--log_path', help="log path to extract files and store rouge scores")
args = parser.parse_args()
assert args.reference
assert args.candidate
assert args.log_path
if args.auto_tokenize == 'false':
ref = [para.strip().split('\n') for para in codecs.open(args.reference, 'r', encoding='utf-8').read().strip().split('\n\n')]
can = [para.strip().split('\n') for para in codecs.open(args.candidate, 'r', encoding='utf-8').read().strip().split('\n\n')]
else:
ref = [sent_tokenize(line.strip()) for line in codecs.open(args.reference, 'r', encoding='utf-8').readlines() if line.strip()]
can = [sent_tokenize(line.strip()) for line in codecs.open(args.candidate, 'r', encoding='utf-8').readlines() if line.strip()]
assert len(ref) == len(can)
rouge(ref, can, args.log_path)
if __name__ == '__main__':
main()
可以通过-h
或--help
参数查看帮助文档:
[aaa@qq.com workspace]# rouge -h
usage: rouge_tool.py [-h] [-r REFERENCE] [-c CANDIDATE] [-a {true,false}]
[-l LOG_PATH]
optional arguments:
-h, --help show this help message and exit
-r REFERENCE, --reference REFERENCE
reference input file
-c CANDIDATE, --candidate CANDIDATE
candidate input file
-a {true,false}, --auto_tokenize {true,false}
if true, every paragraph takes one line;if false,
every sentence takes one line, paragraphs are split by
one blank line
-l LOG_PATH, --log_path LOG_PATH
log path to extract files and store rouge scores
两种格式下的使用例子,其中rouge_log是暂存中间文件和存储最终score的路径。
$ python rouge_tool.py -r sent_tokenized_ref -c sent_tokenized_can -l rouge_log/ -a false
$ python rouge_tool.py -r reference -c candidate -l rouge_log/
如果有Assertion Error
那多半是文件格式或命令格式不对,如果出现
subprocess.CalledProcessError: Command ... returned non-zero exit status 255
可以考虑将错误指出的命令运行一下,多半是log_path/reference
huolog_path/candidate
中有其他的文件,删除一下重新运行。我使用两篇论文的摘要作为参考,将其中一部分词替换为<unk>
作为候选,运行结果的正确示例:
F_measure: [95.94, 91.8, 95.94] Recall: [95.94, 91.8, 95.94] Precision: [95.94, 91.8, 95.94]
其中方括号列表内的三个值分别代表ROUGE-1,ROUGE-2和ROUGE-L,单位为%。如果想要更方便的使用脚本,把ROUGE变为一个命令,建议不要使用pyinstaller打包成可执行文件,那样不仅会速度很慢,还会出很多第三方库不兼容的问题。我们只需要为命令起一个别名,在~/.bashrc
中加入alias rouge=python /home/username/some_path/rouge_tool.py
。
关于中文的处理
这部分以及以下第6小节参考了这篇文章:ROUGE脚本对中文的支持。顺便拜访了以下博主鱼虾一整碗(雨下一整晚)的网站,做的很棒。
ROUGE perl 脚本不能直接用在中文上,需要把中文转成英文或数字。具体的,应该把每个字都转成一个唯一对应的英文或数字,同时输出的时候符号与符号之间用空格分隔。 还是要注意中英混合时的tokenize!评估时,英文是词为单位,而中文是字为单位!
训练中,一般自己实现一个简单的
ROUGE-N
或者ROUGE-L
。正确的实现,应该是取COUNT
(当共现的NGRAM
中有多个相同的时候),可以直接使用已有的rouge re-implementation
脚本。
而在最终写论文的时候才用ROUGE perl脚本,较为正式。
6 其他ROUGE工具
- re- implementation, use SET for multi-NGRAM
- the kernal copys the google/seq2seq/metrics/rouge
- wrapper, not in pypi
- wrapper, in pypi
- re-implementation, use COUNT for mult-NGRAM
- re-implementation, use SET for multi-NGRAM, Java
re-implementation
是指用原生Python实现的,wrapper是指在PERL脚本上用Python封装了一下;use SET for multi-NGRAM
和 use COUNT for mult-NGRAM
是指当 gold
和 predict
共现的NGRAM
里,某些NGRAM
出现了多次,有的算的是uniq
个数(SET),有的是COUNT
。我看论文是COUNT
,究竟如何不确定,毕竟google的seq2seq都是set,而我有没看到PERL
里相关部分
上一篇: Python文件I/O