215 lines
11 KiB
Python
215 lines
11 KiB
Python
# Copyright 2020-2021 Huawei Technologies Co., Ltd
|
||
#
|
||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
# you may not use this file except in compliance with the License.
|
||
# You may obtain a copy of the License at
|
||
#
|
||
# http://www.apache.org/licenses/LICENSE-2.0
|
||
#
|
||
# Unless required by applicable law or agreed to in writing, software
|
||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
# See the License for the specific language governing permissions and
|
||
# limitations under the License.
|
||
# ============================================================================
|
||
# Recall类,用于计算召回率(Recall)
|
||
"""Recall."""
|
||
# 用于访问与Python解释器相关的变量和函数
|
||
import sys
|
||
# 导入了所需的库和模块,如numpy和mindspore
|
||
import numpy as np
|
||
# 从mindspore._checkparam模块中导入了一个名为validator的类,用于验证参数
|
||
from mindspore._checkparam import Validator as validator
|
||
# Recall类继承自EvaluationBase类
|
||
from .metric import EvaluationBase, rearrange_inputs, _check_onehot_data
|
||
# 从mindspore.metric模块中导入了一个名为EvaluationBase的类和三个函数,分别是rearrange_inputs、_check_onehot_data
|
||
|
||
|
||
class Recall(EvaluationBase):
|
||
# 用于计算召回率(Recall)。Recall类继承自EvaluationBase类。在计算召回率时,会使用两个本地变量true_positive和false_negative来存储真实阳性和假负例的数量。
|
||
# 需要注意的是,在多分类 cases 中,元素的y和y_pred必须为0或1
|
||
r"""
|
||
Calculates recall for classification and multilabel data.
|
||
|
||
The recall class creates two local variables, :math:`\text{true_positive}` and :math:`\text{false_negative}`,
|
||
that are used to compute the recall. The calculation formula is:
|
||
|
||
.. math::
|
||
\text{recall} = \frac{\text{true_positive}}{\text{true_positive} + \text{false_negative}}
|
||
|
||
Note:
|
||
In the multi-label cases, the elements of :math:`y` and :math:`y_{pred}` must be 0 or 1.
|
||
|
||
Args:
|
||
eval_type (str): 'classification' or 'multilabel' are supported. Default: 'classification'.
|
||
Default: 'classification'.
|
||
|
||
Supported Platforms:
|
||
``Ascend`` ``GPU`` ``CPU``
|
||
|
||
Examples:
|
||
>>> import numpy as np
|
||
>>> from mindspore import nn, Tensor
|
||
>>>
|
||
>>> x = Tensor(np.array([[0.2, 0.5], [0.3, 0.1], [0.9, 0.6]]))
|
||
>>> y = Tensor(np.array([1, 0, 1]))
|
||
>>> metric = nn.Recall('classification')
|
||
>>> metric.clear()
|
||
>>> metric.update(x, y)
|
||
>>> recall = metric.eval()
|
||
>>> print(recall)
|
||
[1. 0.5]
|
||
"""
|
||
# 在定义Recall类时,需要初始化一个eval_type参数,默认为'classification'
|
||
def __init__(self, eval_type='classification'):
|
||
# 然后,调用父类的构造函数,将self传递给父类
|
||
super(Recall, self).__init__(eval_type)
|
||
# 接着,初始化一个eps属性,用于存储一个极小值
|
||
self.eps = sys.float_info.min
|
||
# 最后,调用clear方法来清空内部变量
|
||
self.clear()
|
||
|
||
def clear(self):
|
||
# 用于清空内部评估结果
|
||
"""Clears the internal evaluation result."""
|
||
# 首先,将_class_num属性设置为0
|
||
self._class_num = 0
|
||
# 如果_type属性为"multilabel"
|
||
if self._type == "multilabel":
|
||
# 则将_true_positives和_actual_positives属性设置为空数组
|
||
self._true_positives = np.empty(0)
|
||
self._actual_positives = np.empty(0)
|
||
# 并将_true_positives_average和_actual_positives_average属性设置为0
|
||
self._true_positives_average = 0
|
||
self._actual_positives_average = 0
|
||
else:
|
||
# 否则,将_true_positives和_actual_positives属性设置为0
|
||
self._true_positives = 0
|
||
self._actual_positives = 0
|
||
|
||
@rearrange_inputs
|
||
def update(self, *inputs):
|
||
# 用于更新内部评估结果。接收一个或多个输入参数,分别是y_pred和y。对于'classification'评估类型,y_pred通常是一个浮点数列表,范围在0到1之间,
|
||
# 形状为(N, C),其中N是样本数量,C是类别数量。对于'multilabel'评估类型,y_pred和y必须是one-hot编码的数组,值全为0或1。
|
||
# indices中值为1的索引表示正类别。y_pred和y的形状都是(N, C)
|
||
"""
|
||
Updates the internal evaluation result with `y_pred` and `y`.
|
||
|
||
Args:
|
||
inputs: Input `y_pred` and `y`. `y_pred` and `y` are a `Tensor`, a list or an array.
|
||
For 'classification' evaluation type, `y_pred` is in most cases (not strictly) a list
|
||
of floating numbers in range :math:`[0, 1]`
|
||
and the shape is :math:`(N, C)`, where :math:`N` is the number of cases and :math:`C`
|
||
is the number of categories. Shape of `y` can be :math:`(N, C)` with values 0 and 1 if one-hot
|
||
encoding is used or the shape is :math:`(N,)` with integer values if index of category is used.
|
||
For 'multilabel' evaluation type, `y_pred` and `y` can only be one-hot encoding with
|
||
values 0 or 1. Indices with 1 indicate positive category. The shape of `y_pred` and `y`
|
||
are both :math:`(N, C)`.
|
||
|
||
|
||
Raises:
|
||
ValueError: If the number of inputs is not 2.
|
||
"""
|
||
# 首先检查输入参数的数量是否为2
|
||
if len(inputs) != 2:
|
||
# 如果不是,则抛出一个ValueError异常
|
||
raise ValueError("For 'Recall.update', it needs 2 inputs (predicted value, true value), "
|
||
"but got {}.".format(len(inputs)))
|
||
# 然后,将输入的y_pred和true value转换为适当的数据格式
|
||
y_pred = self._convert_data(inputs[0])
|
||
y = self._convert_data(inputs[1])
|
||
# 如果评估类型为'classification',并且y_pred的形状与y相同,并且是one-hot编码
|
||
if self._type == 'classification' and y_pred.ndim == y.ndim and _check_onehot_data(y):
|
||
# 那么将y转换为argmax轴1的值
|
||
y = y.argmax(axis=1)
|
||
# 最后,检查y_pred和y的形状和值是否符合要求
|
||
self._check_shape(y_pred, y)
|
||
self._check_value(y_pred, y)
|
||
|
||
# 检查_class_num属性是否为0
|
||
if self._class_num == 0:
|
||
# 如果是,则将其设置为y_pred的形状[1]
|
||
self._class_num = y_pred.shape[1]
|
||
# 如果y_pred的形状[1]与_class_num不同
|
||
elif y_pred.shape[1] != self._class_num:
|
||
# 则抛出一个ValueError异常
|
||
raise ValueError("For 'Recall.update', class number not match, last input predicted data contain {} "
|
||
"classes, but current predicted data contain {} classes, please check your predicted "
|
||
"value(inputs[0]).".format(self._class_num, y_pred.shape[1]))
|
||
|
||
# 首先获取_class_num属性,然后根据评估类型进行相应的处理
|
||
class_num = self._class_num
|
||
# 如果评估类型为'classification'
|
||
if self._type == "classification":
|
||
# 首先检查y的最大值是否大于_class_num
|
||
if y.max() + 1 > class_num:
|
||
# 如果是,则抛出一个ValueError异常
|
||
raise ValueError("For 'Recall.update', predicted value (input[0]) should have the same classes number "
|
||
"as true value (input[1]), but got predicted value classes {}, true value classes {}."
|
||
.format(class_num, y.max() + 1))
|
||
# 接着,将y转换为one-hot编码
|
||
y = np.eye(class_num)[y.reshape(-1)]
|
||
# 并获取y_pred中每个类别概率最大的索引
|
||
indices = y_pred.argmax(axis=1).reshape(-1)
|
||
# 最后,将y_pred转换回one-hot编码
|
||
y_pred = np.eye(class_num)[indices]
|
||
# 如果评估类型为'multilabel'
|
||
elif self._type == "multilabel":
|
||
# 则将y_pred和y交换轴
|
||
y_pred = y_pred.swapaxes(1, 0).reshape(class_num, -1)
|
||
# 并reshape为(class_num, -1)
|
||
y = y.swapaxes(1, 0).reshape(class_num, -1)
|
||
|
||
# 首先,使用sum函数将y沿axis=0轴求和,得到actual_positives
|
||
actual_positives = y.sum(axis=0)
|
||
# 然后使用点乘运算将y_pred和y相乘,再沿axis=0轴求和,得到true_positives
|
||
true_positives = (y * y_pred).sum(axis=0)
|
||
|
||
# 如果评估类型为'multilabel'
|
||
if self._type == "multilabel":
|
||
# 那么将true_positives除以(actual_positives + self.eps)后求和,然后累加到self._true_positives_average中
|
||
self._true_positives_average += np.sum(true_positives / (actual_positives + self.eps))
|
||
# 接着,将actual_positives累加到self._actual_positives_average中
|
||
self._actual_positives_average += len(actual_positives)
|
||
# 最后,将true_positives和actual_positives拼接在一起,存储在self._true_positives和self._actual_positives中
|
||
self._true_positives = np.concatenate((self._true_positives, true_positives), axis=0)
|
||
self._actual_positives = np.concatenate((self._actual_positives, actual_positives), axis=0)
|
||
else:
|
||
# 如果评估类型为'classification',那么直接将true_positives和actual_positives累加在一起
|
||
self._true_positives += true_positives
|
||
# 存储在self._true_positives和self._actual_positives中
|
||
self._actual_positives += actual_positives
|
||
|
||
def eval(self, average=False):
|
||
# 用于计算召回率。方法接收一个名为average的布尔参数,用于指定是否计算平均召回率
|
||
"""
|
||
Computes the recall.
|
||
|
||
Args:
|
||
average (bool): Specify whether calculate the average recall. Default: False.
|
||
|
||
Returns:
|
||
numpy.float64, the computed result.
|
||
"""
|
||
# 检查输入参数是否为空
|
||
if self._class_num == 0:
|
||
# 如果是,则抛出一个RuntimeError异常
|
||
raise RuntimeError("The 'Recall' can not be calculated, because the number of samples is 0, please check "
|
||
"whether your inputs (predicted value, true value) are empty, or has called update "
|
||
"method before calling eval method.")
|
||
|
||
# 检查输入参数average是否为布尔值,如果不是,则抛出一个TypeError异常
|
||
validator.check_value_type("average", average, [bool], self.__class__.__name__)
|
||
# 然后,计算召回率
|
||
result = self._true_positives / (self._actual_positives + self.eps)
|
||
|
||
# 如果average为True,则计算平均结果
|
||
if average:
|
||
# 如果type为多标签,则计算多标签评估下的召回率
|
||
if self._type == "multilabel":
|
||
result = self._true_positives_average / (self._actual_positives_average + self.eps)
|
||
# 否则,计算正确结果的平均值
|
||
return result.mean()
|
||
# 否则,返回计算得到的召回率
|
||
return result
|