在 allennlp 中编写自定义指标

Writing custom metrics in allennlp

我正在写我的第一个 allennlp 项目来检测报纸文章中的特定跨度。我能够在我的数据集上训练它。使用交叉熵计算的损失似乎正确减少,但我的指标存在一些问题。我写了一个自定义指标,它应该可以估计我的模型根据一些基本事实跨度预测跨度的准确性。问题是现在,即使损失正在减少,我们的指标似乎也没有正确更新。

我不确定如何解决这个问题,我猜我的问题如下:

  1. reset()函数在Metricclass中的具体用法是什么?
  2. 除了写__call__()get_metric()reset()函数,还有什么需要注意的地方吗?

下面是我的自定义 Metric class 的快照,以备不时之需。

class SpanIdenficationMetric(Metric):
    def __init__(self) -> None:
        self._s_cardinality = 0 # S: model predicted spans
        self._t_cardinality = 0 # T: article gold spans
        self._s_sum = 0
        self._t_sum = 0
        
    def reset(self) -> None:
        self._s_cardinality = 0
        self._t_cardinality = 0
        self._s_sum = 0
        self._t_sum = 0
            
    def __call__(self, prop_spans: torch.Tensor, gold_spans: torch.Tensor, mask: Optional[torch.BoolTensor] = None):
        for i, article_spans in enumerate(prop_spans):
            if article_spans.numel() == 0:
                continue
            article_gold_spans = gold_spans[i]
            merged_prop_spans = self._merge_intervals(article_spans)
            self._s_cardinality += merged_prop_spans.size(dim=0)
            self._t_cardinality += article_gold_spans.size(dim=0)
            for combination in itertools.product(merged_prop_spans, article_gold_spans):
                sspan = combination[0]
                tspan = combination[1]
                self._s_sum += self._c_function(sspan, tspan, sspan[1].item() - sspan[0].item() + 1)
                self._t_sum += self._c_function(sspan, tspan, tspan[1].item() - tspan[0].item() + 1)

    def get_metric(self, reset: bool = False):
        precision = 0
        recall = 0
        if self._s_cardinality != 0:
            precision = self._s_sum / self._s_cardinality
        if self._t_cardinality != 0:
            recall = self._t_sum / self._t_cardinality
        if reset:
            self.reset()
        return { "si-metric" : (2 * precision * recall) / (precision + recall) if precision + recall > 0 else 0 }

def _c_function(self, s, t, h): {}
def _intersect(self, s, t): {}
def _merge_intervals(self, prop_spans): {}

提前谢谢你。干杯。

在训练期间,训练器将使用每个批次的结果调用指标(使用 Metric.__call__())。当发生这种情况时,指标应该更新其内部状态。培训师希望在调用 Metric.get_metric() 时获得指标的当前值。 Metric.reset() 必须将度量重置为一种状态,就好像它以前从未被调用过一样。当使用 reset = True 调用 get_metric() 时,预计也会重置指标。

据我所知,您的代码正确地完成了所有这些事情。您的代码在分布式设置中不会 运行 正确,但如果您不在多个 GPU 上进行训练,那不是问题。

您所做的与 SQuAD 指标类似:https://github.com/allenai/allennlp-models/blob/main/allennlp_models/rc/metrics/squad_em_and_f1.py SQuAD 指标特意调用原始 SQuAD 评估代码,因此它比您想要的要复杂一点,但也许您可以调整它?主要区别在于您计算整个数据集的 F 分数,而 SQuAD 按文档计算它们,然后计算文档的平均值。

最后,您可以为您的指标编写一个简单的测试,类似于 SQuAD 测试:https://github.com/allenai/allennlp-models/blob/main/tests/rc/metrics/squad_em_and_f1_test.py 这可能有助于缩小问题所在的范围。