当前位置: 首页 > 工具软件 > Rouge > 使用案例 >

常用的ROUGE得分计算Python库(pyrouge,files2rouge,rouge,py-rouge)

叶声
2023-12-01

前言

ROUGE得分是文本摘要任务中最常用的自动评测指标,关于rouge得分的计算,可以查看原始论文
https://www.aclweb.org/anthology/W04-1013.pdf
要注意一般来说,摘要中包含多个句子,直接把多个句子拼接在一起计算ROUGE得分可能是不准确的,具体可以参考论文中的3.2部分,实际上应该具体考虑摘要中的每个句子。
在实际实验中,处于便利和效率的考虑,我们一般不会手动去实现rouge指标,通常会使用一些第三方的库。在看源码的过程中,我发现比较常用的rouge计算库有三个,pyrouge,files2rouge,rouge,网上关于这三个库的资料不多,在这里我对这三个库进行总结,包括库的安装,使用示例。

pyrouge

这相当于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("<", "&lt;")
    sentence=sentence.replace(">", "&gt;")
    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)

files2rouge

这也是一个常用的ROUGE计算包,也是一个wrapper,安装和使用比起pyrouge简单的多

github链接

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

rouge

和上面的files2rouge一个作者,不过是一个纯python的实现,可以直接用python调用

链接:https://github.com/pltrdy/rouge

安装:

参考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)

py-rouge

这也是一个纯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,用空格分隔

 类似资料: