hscredit.core.models.losses.functional 源代码

"""将普通概率评估函数转换为统一指标,无需编写指标子类。"""

from copy import deepcopy
from inspect import Parameter, signature

import numpy as np

from .base import BaseMetric, _validate_binary_inputs


[文档] class CallableMetric(BaseMetric): """包装返回标量的概率评估函数。 **参数** :param function: ``function(y_true, probability, **kwargs) -> float``。 第一项为0/1标签,第二项为坏样本概率;不接受 sklearn scorer 或框架回调协议。 :param name: 中文显示名称,默认“自定义指标”。多指标时请使用不同名称。 :param greater_is_better: 必须明确指定 True(最大化)或 False(最小化)。 :param kwargs: 固定业务参数,例如阈值或收益参数;创建时复制。 样本权重请在评价时通过 ``sample_weight`` 提供,不可绑定整个数据集。 **属性** ``direction``、``evaluate``、``to_scorer`` 和框架适配方法继承自 BaseMetric。 **参考样例** >>> import numpy as np >>> def error(y, p): ... return float(np.mean((y - p) ** 2)) >>> metric = CallableMetric(error, name="概率均方误差", greater_is_better=False) >>> round(metric([0, 1], [0.1, 0.9]), 2) 0.01 """ def __init__(self, function, *, name="自定义指标", greater_is_better, **kwargs): if not callable(function): raise TypeError("评估函数必须可调用,签名为 (真实标签, 坏样本概率) -> 标量") if not isinstance(greater_is_better, (bool, np.bool_)): raise ValueError("greater_is_better 必须是 True 或 False") if not isinstance(name, str) or not name.strip(): raise ValueError("指标名称必须是非空字符串") if "sample_weight" in kwargs: raise ValueError("样本权重不能固定绑定到指标,请在 evaluate 时传入 sample_weight") try: parameters = signature(function).parameters # bind_partial 允许 sample_weight 等必需参数在调用时提供,同时拒绝误写参数。 signature(function).bind_partial(None, None, **kwargs) except (TypeError, ValueError) as exc: raise ValueError("评估函数需接收 (y_true, probability),且固定参数必须与函数签名一致") from exc if any(key in parameters for key in ("estimator", "X")) and hasattr(function, "_score_func"): raise TypeError("请提供概率评估函数;sklearn scorer 请用于 cross_val_score,不可作为普通指标") super().__init__(name, bool(greater_is_better)) self.function = function self.kwargs = deepcopy(kwargs) self.supports_sample_weight = "sample_weight" in parameters or any( item.kind == Parameter.VAR_KEYWORD for item in parameters.values() ) def __call__(self, y_true, y_pred, sample_weight=None): """校验并计算原始指标值,不按方向改变正负号。 :param y_true: 0/1真实标签,长度为样本数。 :param y_pred: 坏样本概率;可为一维或两列 ``predict_proba``。 :param sample_weight: 可选非负有限权重,函数须支持该参数。 :return: 单个有限浮点数;列表、框架三元组、缺失值会报中文错误。 >>> metric = make_metric(lambda y, p: np.mean((y-p)**2), greater_is_better=False) >>> metric([0, 1], [0.2, 0.8]) < 0.05 True """ y, probability, weight = _validate_binary_inputs(y_true, y_pred, sample_weight) kwargs = dict(self.kwargs) if weight is not None: if not self.supports_sample_weight: raise ValueError(f"指标 {self.name} 的函数没有 sample_weight 参数,不能进行加权评价") kwargs["sample_weight"] = weight result = self.function(y, probability, **kwargs) if isinstance(result, (bool, np.bool_)) or np.ndim(result) != 0: raise ValueError(f"指标 {self.name} 必须返回单个有限数值,不能返回标签、列表或框架回调元组") try: value = float(result) except (TypeError, ValueError, OverflowError) as exc: raise ValueError(f"指标 {self.name} 必须返回单个有限数值") from exc if not np.isfinite(value): raise ValueError(f"指标 {self.name} 返回了非有限值") return value
[文档] def make_metric(function, *, name="自定义指标", greater_is_better, **kwargs): """用一行代码定义离线、训练及调参共用的自定义指标。 :param function: ``(y_true, probability, **kwargs) -> float`` 的函数。 输入是坏样本概率,不能传 sklearn scorer 或 XGBoost/LightGBM 回调。 :param name: 显示名称,默认“自定义指标”。 :param greater_is_better: True 表示越大越好,False 表示越小越好;必须显式提供。 :param kwargs: 转交函数的固定业务参数,不能包含样本数组权重。 :return: CallableMetric,可直接传 ``ModelTuner(metric=...)`` 或 ``LightGBM(eval_metric=...)``;CV 使用返回对象的 ``to_scorer()``。 **参考样例** >>> from sklearn.metrics import log_loss >>> metric = make_metric(log_loss, name="交叉熵", greater_is_better=False, labels=[0, 1]) >>> metric.direction 'minimize' >>> round(metric([0, 1], [0.1, 0.9]), 3) 0.105 """ return CallableMetric(function, name=name, greater_is_better=greater_is_better, **kwargs)