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查找文件的命令,findwhereislocate有什么区别,可以参考: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-outputsample-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参数查看帮助文档:

[root@localhost 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/referencehuolog_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是指用原生Python实现的,wrapper是指在PERL脚本上用Python封装了一下;use SET for multi-NGRAMuse COUNT for mult-NGRAM 是指当 goldpredict 共现的NGRAM里,某些NGRAM出现了多次,有的算的是uniq个数(SET),有的是COUNT。我看论文是COUNT,究竟如何不确定,毕竟google的seq2seq都是set,而我有没看到PERL里相关部分

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值