自定义指标

除了使用飞桨框架内置的指标外,飞桨框架还支持用户根据自己的实际场景,完成指标的自定义。

一、自定义Loss

有时你会遇到特定任务的Loss计算方式在框架既有的Loss接口中不存在,或算法不符合自己的需求,那么期望能够自己来进行Loss的自定义。这里介绍如何进行Loss的自定义操作,首先来看下面的代码:

  1. class SelfDefineLoss(paddle.nn.Layer):
  2. """
  3. 1. 继承paddle.nn.Layer
  4. """
  5. def __init__(self):
  6. """
  7. 2. 构造函数根据自己的实际算法需求和使用需求进行参数定义即可
  8. """
  9. super(SelfDefineLoss, self).__init__()
  10. def forward(self, input, label):
  11. """
  12. 3. 实现forward函数,forward在调用时会传递两个参数:input和label
  13. - input:单个或批次训练数据经过模型前向计算输出结果
  14. - label:单个或批次训练数据对应的标签数据
  15. 接口返回值是一个Tensor,根据自定义的逻辑加和或计算均值后的损失
  16. """
  17. # 使用Paddle中相关API自定义的计算逻辑
  18. # output = xxxxx
  19. # return output

接下来是一个具体的例子,在图像分割示例代码中写的一个自定义Loss,当时主要是使用自定义的softmax计算维度。

  1. class SoftmaxWithCrossEntropy(paddle.nn.Layer):
  2. def __init__(self):
  3. super(SoftmaxWithCrossEntropy, self).__init__()
  4. def forward(self, input, label):
  5. loss = F.softmax_with_cross_entropy(input,
  6. label,
  7. return_softmax=False,
  8. axis=1)
  9. return paddle.mean(loss)

二、自定义Metric

和Loss一样,你也可以来通过框架实现自定义的评估方法,具体的实现如下:

  1. class SelfDefineMetric(paddle.metric.Metric):
  2. """
  3. 1. 继承paddle.metric.Metric
  4. """
  5. def __init__(self):
  6. """
  7. 2. 构造函数实现,自定义参数即可
  8. """
  9. super(SelfDefineMetric, self).__init__()
  10. def name(self):
  11. """
  12. 3. 实现name方法,返回定义的评估指标名字
  13. """
  14. return '自定义评价指标的名字'
  15. def compute(self, ...)
  16. """
  17. 4. 本步骤可以省略,实现compute方法,这个方法主要用于`update`的加速,可以在这个方法中调用一些paddle实现好的Tensor计算API,编译到模型网络中一起使用低层C++ OP计算。
  18. """
  19. return 自己想要返回的数据,会做为update的参数传入。
  20. def update(self, ...):
  21. """
  22. 5. 实现update方法,用于单个batch训练时进行评估指标计算。
  23. - 当`compute`类函数未实现时,会将模型的计算输出和标签数据的展平作为`update`的参数传入。
  24. - 当`compute`类函数做了实现时,会将compute的返回结果作为`update`的参数传入。
  25. """
  26. return acc value
  27. def accumulate(self):
  28. """
  29. 6. 实现accumulate方法,返回历史batch训练积累后计算得到的评价指标值。
  30. 每次`update`调用时进行数据积累,`accumulate`计算时对积累的所有数据进行计算并返回。
  31. 结算结果会在`fit`接口的训练日志中呈现。
  32. """
  33. # 利用update中积累的成员变量数据进行计算后返回
  34. return accumulated acc value
  35. def reset(self):
  36. """
  37. 7. 实现reset方法,每个Epoch结束后进行评估指标的重置,这样下个Epoch可以重新进行计算。
  38. """
  39. # do reset action

接下来看一个框架中的具体例子,是框架中已提供的一个评估指标计算接口,这里就是按照上述说明中的方法完成了实现。

  1. from paddle.metric import Metric
  2. class Precision(Metric):
  3. """
  4. Precision (also called positive predictive value) is the fraction of
  5. relevant instances among the retrieved instances. Refer to
  6. https://en.wikipedia.org/wiki/Evaluation_of_binary_classifiers
  7. Noted that this class manages the precision score only for binary
  8. classification task.
  9. ......
  10. """
  11. def __init__(self, name='precision', *args, **kwargs):
  12. super(Precision, self).__init__(*args, **kwargs)
  13. self.tp = 0 # true positive
  14. self.fp = 0 # false positive
  15. self._name = name
  16. def update(self, preds, labels):
  17. """
  18. Update the states based on the current mini-batch prediction results.
  19. Args:
  20. preds (numpy.ndarray): The prediction result, usually the output
  21. of two-class sigmoid function. It should be a vector (column
  22. vector or row vector) with data type: 'float64' or 'float32'.
  23. labels (numpy.ndarray): The ground truth (labels),
  24. the shape should keep the same as preds.
  25. The data type is 'int32' or 'int64'.
  26. """
  27. if isinstance(preds, paddle.Tensor):
  28. preds = preds.numpy()
  29. elif not _is_numpy_(preds):
  30. raise ValueError("The 'preds' must be a numpy ndarray or Tensor.")
  31. if isinstance(labels, paddle.Tensor):
  32. labels = labels.numpy()
  33. elif not _is_numpy_(labels):
  34. raise ValueError("The 'labels' must be a numpy ndarray or Tensor.")
  35. sample_num = labels.shape[0]
  36. preds = np.floor(preds + 0.5).astype("int32")
  37. for i in range(sample_num):
  38. pred = preds[i]
  39. label = labels[i]
  40. if pred == 1:
  41. if pred == label:
  42. self.tp += 1
  43. else:
  44. self.fp += 1
  45. def reset(self):
  46. """
  47. Resets all of the metric state.
  48. """
  49. self.tp = 0
  50. self.fp = 0
  51. def accumulate(self):
  52. """
  53. Calculate the final precision.
  54. Returns:
  55. A scaler float: results of the calculated precision.
  56. """
  57. ap = self.tp + self.fp
  58. return float(self.tp) / ap if ap != 0 else .0
  59. def name(self):
  60. """
  61. Returns metric name
  62. """
  63. return self._name

三、自定义Callback

fit接口的callback参数支持传入一个Callback类实例,用来在每轮训练和每个batch训练前后进行调用,可以通过callback收集到训练过程中的一些数据和参数,或者实现一些自定义操作。

  1. class SelfDefineCallback(paddle.callbacks.Callback):
  2. """
  3. 1. 继承paddle.callbacks.Callback
  4. 2. 按照自己的需求实现以下类成员方法:
  5. def on_train_begin(self, logs=None) 训练开始前,`Model.fit`接口中调用
  6. def on_train_end(self, logs=None) 训练结束后,`Model.fit`接口中调用
  7. def on_eval_begin(self, logs=None) 评估开始前,`Model.evaluate`接口调用
  8. def on_eval_end(self, logs=None) 评估结束后,`Model.evaluate`接口调用
  9. def on_predict_begin(self, logs=None) 预测测试开始前,`Model.predict`接口中调用
  10. def on_predict_end(self, logs=None) 预测测试结束后,`Model.predict`接口中调用
  11. def on_epoch_begin(self, epoch, logs=None) 每轮训练开始前,`Model.fit`接口中调用
  12. def on_epoch_end(self, epoch, logs=None) 每轮训练结束后,`Model.fit`接口中调用
  13. def on_train_batch_begin(self, step, logs=None) 单个Batch训练开始前,`Model.fit`和`Model.train_batch`接口中调用
  14. def on_train_batch_end(self, step, logs=None) 单个Batch训练结束后,`Model.fit`和`Model.train_batch`接口中调用
  15. def on_eval_batch_begin(self, step, logs=None) 单个Batch评估开始前,`Model.evalute`和`Model.eval_batch`接口中调用
  16. def on_eval_batch_end(self, step, logs=None) 单个Batch评估结束后,`Model.evalute`和`Model.eval_batch`接口中调用
  17. def on_predict_batch_begin(self, step, logs=None) 单个Batch预测测试开始前,`Model.predict`和`Model.test_batch`接口中调用
  18. def on_predict_batch_end(self, step, logs=None) 单个Batch预测测试结束后,`Model.predict`和`Model.test_batch`接口中调用
  19. """
  20. def __init__(self):
  21. super(SelfDefineCallback, self).__init__()
  22. # 按照需求定义自己的类成员方法

看一个框架中的实际例子,这是框架自带的ModelCheckpoint回调函数,可以在fit训练模型时自动存储每轮训练得到的模型。

  1. class ModelCheckpoint(Callback):
  2. def __init__(self, save_freq=1, save_dir=None):
  3. self.save_freq = save_freq
  4. self.save_dir = save_dir
  5. def on_epoch_begin(self, epoch=None, logs=None):
  6. self.epoch = epoch
  7. def _is_save(self):
  8. return self.model and self.save_dir and ParallelEnv().local_rank == 0
  9. def on_epoch_end(self, epoch, logs=None):
  10. if self._is_save() and self.epoch % self.save_freq == 0:
  11. path = '{}/{}'.format(self.save_dir, epoch)
  12. print('save checkpoint at {}'.format(os.path.abspath(path)))
  13. self.model.save(path)
  14. def on_train_end(self, logs=None):
  15. if self._is_save():
  16. path = '{}/final'.format(self.save_dir)
  17. print('save checkpoint at {}'.format(os.path.abspath(path)))
  18. self.model.save(path)