ROUGE得分是文本摘要任务中最常用的自动评测指标,关于rouge得分的计算,可以查看原始论文
https://www.aclweb.org/anthology/W04-1013.pdf
要注意一般来说,摘要中包含多个句子,直接把多个句子拼接在一起计算ROUGE得分可能是不准确的,具体可以参考论文中的3.2部分,实际上应该具体考虑摘要中的每个句子。
在实际实验中,处于便利和效率的考虑,我们一般不会手动去实现rouge指标,通常会使用一些第三方的库。在看源码的过程中,我发现比较常用的rouge计算库有三个,pyrouge,files2rouge,rouge,网上关于这三个库的资料不多,在这里我对这三个库进行总结,包括库的安装,使用示例。
这相当于ROUGE 的一个python接口,因此要安装配置ROUGE
https://github.com/bheinzerling/pyrouge
要使用pyrouge需要预先安装ROUGE,而ROUGE的安装非常麻烦,需要编译一大堆东西,可以参考下面的链接进行安装
https://blog.csdn.net/Hay54/article/details/78744912
安装配置好ROUGE后就可以安装pyrouge了,安装命令如下,注意这里不能直接pip install pyrouge安装
pip install -U git+https://github.com/pltrdy/pyrouge
pyrouge采用正则匹配的方法找到对应的参考摘要和生成摘要,因此比较麻烦,需要先保存成相应格式的文件,代码如下
这里我们有一个列表refs和一个列表preds,refs保存了所有的参考摘要,preds保存了所有生成的摘要。显然,preds和refs列表的长度相同。值得注意的是每个摘要中是一个长序列,可能包含了若个句子,这里假定每个摘要中的句子以".“分隔(一般英文数据集都以”.“分隔,当然也可以采用其他分隔符,只要后面分隔对话的时候注意即可)
首先保存成pyrouge所要求的格式,参考摘要和生成摘要保存在不同的文件夹下面,每个摘要保存在一个文件中,有多少摘要就有多少文件。
用ID命名每个文件,从而区分不同的摘要。如生成摘要文件命名为”000001_hyper.txt“,”000002_hyper.txt”…,参考摘要文件命名为"000001_ref.txt”…一个摘要是一个文本序列,通常包含若干句子,pyrouge要求每个文件中的每一行只能有一个句子,因此这里首先将每个摘要序列进行分句,这里我是根据".“分句,将分句后的摘要写入文件中,具体细节参考下面的代码。
还有一点需要注意,pyrouge要求保存的句子中不能包含“<”,">"这种符号,否则会报错,所以我这里有一个convert函数,进行相应的转换。
具体代码如下:
import os
import pyrouge
def convert(sentence):
sentence=sentence.strip()
sentence=sentence.replace("<unk>","UNK")
sentence=sentence.replace("<", "<")
sentence=sentence.replace(">", ">")
return sentence
def write_for_rouge(reference, pred, ex_index,
_rouge_ref_dir, _rouge_dec_dir):
references=[]
preds=[]
while len(reference) > 0:
try:
fst_period_idx = reference.index(".")
except ValueError:
fst_period_idx = len(reference)
sent = reference[:fst_period_idx + 1]
reference=reference[fst_period_idx + 1:]
references.append(sent)
while len(pred) > 0:
try:
fst_period_idx = pred.index(".")
except ValueError:
fst_period_idx = len(pred)
sent = pred[:fst_period_idx + 1]
pred = pred[fst_period_idx + 1:]
preds.append(sent)
references=[convert(w) for w in references]
preds=[convert(w) for w in preds]
ref_file = os.path.join(_rouge_ref_dir, "%06d_reference.txt" % ex_index)
pred_file = os.path.join(_rouge_dec_dir, "%06d_hyper.txt" % ex_index)
with open(ref_file, "w") as f:
for idx, sent in enumerate(references):
f.write(sent) if idx == len(references) - 1 else f.write(sent + "\n")
with open(pred_file, "w") as f:
for idx, sent in enumerate(preds):
f.write(sent) if idx == len(preds) - 1 else f.write(sent + "\n")
def cal_rouge(refs,hypers,ref_dir,hyper_dir):
hypers=[]
refs=[]
assert(len(refs)==len(hypers)),print("length is not equal")
for i in range(1,len(refs)+1):
write_for_rouge(refs[i-1],hypers[i-1],i,ref_dir,hyper_dir)
results=rouge_eval(ref_dir,hyper_dir)
return results
保存成相应的格式后,就可以调用pyrouge进行计算了,代码如下:
这部分就是固定格式,注意一下参考摘要和生成摘要是根据文件名进行正则匹配,model_file_name_pattern是参考摘要文件名的模式,system_filename_pattern是生成摘要文件名的模式,model_dir是参考摘要文件所在的文件夹,system_dir是生成摘要文件所在的文件夹,调用convert_and_evaluate计算,更详细的使用可以参考pyrouge的github
最后返回结果得到一个dict
def rouge_eval(ref_dir, hyper_dir):
r = pyrouge.Rouge155()
r.model_filename_pattern = '#ID#_reference.txt'
r.system_filename_pattern = '(\d+)_hyper.txt'
r.model_dir = ref_dir
r.system_dir = hyper_dir
rouge_results = r.convert_and_evaluate()
return r.output_to_dict(rouge_results)
上面返回一个字典,保存很多的rouge得分,可以从中提取出需要的结果,一般常用的rouge得分有ROUGE-1,ROUGE-2,ROUGE-L,可以参考下面的代码提取保存:
def rouge_log(results_dict, dir_to_write):
log_str = ""
for x in ["1","2","l"]:
log_str += "\nROUGE-%s:\n" % x
for y in ["f_score", "recall", "precision"]:
key = "rouge_%s_%s" % (x,y)
key_cb = key + "_cb"
key_ce = key + "_ce"
val = results_dict[key]
val_cb = results_dict[key_cb]
val_ce = results_dict[key_ce]
log_str += "%s: %.4f with confidence interval (%.4f, %.4f)\n" % (key, val, val_cb, val_ce)
print(log_str)
results_file = os.path.join(dir_to_write, "ROUGE_results.txt")
print("Writing final ROUGE results to %s..."%(results_file))
with open(results_file, "w") as f:
f.write(log_str)
这也是一个常用的ROUGE计算包,也是一个wrapper,安装和使用比起pyrouge简单的多
https://github.com/pltrdy/files2rouge
参考github的readme一步一步来即可,注意目前只支持ubuntu
直接在命令行里调用即可,需要给出保存参考摘要的文件reference.txt和保存生成摘要的文件hyper.txt,一行保存一个摘要序列,每个摘要可能包含若干个句子,这里我用".“分隔句子。 -s参数为保存得分的文件路径 ,-e参数为句子分隔符,默认为”.",因为一个摘要往往包含多个句子,需要正确指定分隔符,否则会影响ROUGE-L的计算。
files2rouge "./reference.txt" "./hyper.txt" -s "./scores.txt" -e "."
更多用法参考github
和上面的files2rouge一个作者,不过是一个纯python的实现,可以直接用python调用
参考github的readme即可
参考github,可以在文件中调用
from rouge import FilesRouge
files_rouge = FilesRouge()
scores = files_rouge.get_scores(hyp_path, ref_path)
scores = files_rouge.get_scores(hyp_path, ref_path, avg=True)
也可以调用列表计算:
这里的hypers保存所有生成摘要,refers保存所有参考摘要,句子默认用”."分隔,不用自己分句
from rouge import Rouge
rouge = Rouge()
print(rouge.get_scores(hypers, refers, avg = True))
files_rouge = FilesRouge()
scores = files_rouge.get_scores("pred.txt", "ref.txt", avg=True)
这也是一个纯python的库, 注意和pyrouge区分
https://github.com/Diego999/py-rouge
直接pip安装即可,或者参考github上的readme手动安装
按照官方示例来,根据官方示例,py-rouge应该是通过/n分隔句子,这里还是假定我们有两个列表,preds保存所有生成摘要,每个摘要包含若干句子,用“.”分隔,references是保存所有参考摘要,也是包含若干句子
首先处理成用"/n"分隔的形式,然后调用py-rouge计算,具体参数可以参考github
def convert(sentence):
sents=[]
while len(sentence) > 0:
try:
fst_period_idx = sentence.index(".")
except ValueError:
fst_period_idx = len(sentence)
sent = sentence[:fst_period_idx + 1]
sentence = sentence[fst_period_idx + 1:]
sents.append(sent)
sents="\n".join(sents)
#sents=sents+'\n'
sents=sents.replace("\n\n","\n")
return sents
refers=[convert(refer) for refer in refers]
hypers=[convert(hyper) for hyper in hypers]
import rouge
def prepare_results(m, p, r, f):
return '\t{}:\t{}: {:5.2f}\t{}: {:5.2f}\t{}: {:5.2f}'.format(m, 'P', 100.0 * p, 'R', 100.0 * r, 'F1', 100.0 * f)
for aggregator in ['Avg', 'Best']:
print('Evaluation with {}'.format(aggregator))
apply_avg = aggregator == 'Avg'
apply_best = aggregator == 'Best'
print(apply_avg)
print(apply_best)
evaluator = rouge.Rouge(metrics=['rouge-n', 'rouge-l', 'rouge-w'],
max_n=4,
limit_length=False,
length_limit_type='words',
apply_avg=apply_avg,
apply_best=apply_best,
alpha=0.5, # Default F1_score
weight_factor=1.2,
stemming=True)
scores = evaluator.get_scores(hypers, refers)
for metric, results in sorted(scores.items(), key=lambda x: x[0]):
print(prepare_results(metric, results['p'], results['r'], results['f']))
不过py-rouge计算出的结果与前面几个包的结果有比较大的相差,可能是我对分词,分隔的理解有误
几个包的rouge-1,rouge-2的结果基本是一致的,rouge-l的结果有所不同,可能是由于不同包的分词方式,分句方式不同导致的,前3个包的rouge-l结果基本是接近的,py-rouge我做的结果不太一样,可能是我理解有无。
对于中文数据,这些包可能不能很好的识别中文字符,可以转化为index,用空格分隔