趋近智
虽然使用对数损失(LogLoss)或均方误差(Mean Squared Error)等标准损失函数 (loss function)来优化模型是常见做法,但模型成功的最终衡量标准通常依赖于领域特定条件或竞赛规则,这些内置目标无法直接体现。训练和验证期间使用的评估指标作为指示器,引导超参数 (parameter) (hyperparameter)调整并通过提前停止来提示何时终止训练。当准确率(Accuracy)、F1分数、AUC或RMSE等标准指标无法完全体现所需性能特征时,实现自定义评估指标就变得有必要。
与必须提供梯度信息以驱动提升过程的自定义损失函数不同,自定义评估指标更为简单。它们的主要作用是在特定时间间隔(例如,每个提升轮次)根据真实标签和预测值量化 (quantization)性能。它们不直接影响梯度计算,但对追踪进展和就模型的充分性做出明智判断不可或缺。
在以下几种情况中,您可能需要自定义评估指标:
大多数流行的梯度提升库(XGBoost、LightGBM、CatBoost)都提供集成自定义评估逻辑的接口。虽然具体的参数 (parameter)名称可能略有不同,但基本结构通常是一致的。一个典型的Python自定义指标函数需要:
y_pred)和真实标签(通常封装在特定库的数据结构中,如XGBoost的DMatrix或LightGBM的Dataset,可以从中提取真实标签y_true)。y_true和y_pred计算指标分数。metric_name:标识指标的字符串(例如,'custom_rmse')。metric_value:计算得到的数值分数。is_higher_better:一个布尔值,表示分数越高是否代表性能越好(例如,AUC为True,RMSE为False)。这一点对提前停止很重要。让我们看看如何在实践中实现这一点。
XGBoost通过其xgb.train函数中的feval参数 (parameter)或使用scikit-learn API时的eval_metric参数(该参数可接受可调用对象)支持自定义评估函数。对于xgb.train,函数签名应接受preds(预测值)和dtrain(一个包含真实标签的xgb.DMatrix对象)。
import numpy as np
import xgboost as xgb
from sklearn.metrics import mean_absolute_percentage_error # 示例指标
# 定义一个自定义评估指标:平均绝对百分比误差(MAPE)
# 注意:XGBoost的预测结果在转换前可能是原始分数
# 根据您的目标函数进行相应调整(例如,对二分类问题应用sigmoid)
def xg_mape(preds: np.ndarray, dtrain: xgb.DMatrix):
"""XGBoost的自定义MAPE指标"""
labels = dtrain.get_label()
# 确保标签没有除以零或接近零的情况
# 裁剪标签以避免不稳定,或适当处理
epsilon = 1e-6
safe_labels = np.maximum(np.abs(labels), epsilon)
mape = np.mean(np.abs((labels - preds) / safe_labels))
return 'MAPE', mape # 默认情况下,如果未另行指定,XGBoost认为自定义指标越低越好
# --- 示例数据(请替换为您的实际数据)---
X_train = np.random.rand(100, 5)
y_train = np.random.rand(100) * 10
X_eval = np.random.rand(50, 5)
y_eval = np.random.rand(50) * 10
dtrain = xgb.DMatrix(X_train, label=y_train)
deval = xgb.DMatrix(X_eval, label=y_eval)
# --- 使用自定义指标进行训练 ---
params = {
'objective': 'reg:squarederror',
'eta': 0.1,
'max_depth': 3
}
evals = [(dtrain, 'train'), (deval, 'eval')]
# 将自定义函数传递给feval
# 要用于提前停止,将maximize设置为False,因为MAPE应最小化
bst = xgb.train(
params,
dtrain,
num_boost_round=100,
evals=evals,
feval=xg_mape,
# 使用自定义指标进行提前停止:
early_stopping_rounds=10,
# 指定maximize=False,因为MAPE越低越好
# 注意:默认情况下,XGBoost在evals_result中查看*最后一个*指标进行停止
# 或者在使用sklearn API的fit方法时明确指定指标。
# 对于xgb.train,它通常隐式使用'eval_metric'列表中的最后一个指标
# 或'maximize'中指定的指标。我们假设我们希望在eval-MAPE上停止。
# 如果存在多个指标,可能需要进行控制。
# 为了明确起见,请查看XGBoost文档中关于feval + early_stopping特定版本行为。
verbose_eval=10 # 每10轮打印一次评估结果
)
print(f"\n验证集上的最佳MAPE: {bst.best_score}")
请注意: 当xgb.train与feval一起使用时,除非将maximize=True传递给xgb.train,否则XGBoost通常会假设指标应最小化。请查阅您特定版本的文档,特别是关于当使用多个标准和自定义指标时,提前停止是如何影响的。对于scikit-learn接口(XGBRegressor/XGBClassifier),您将可调用对象传递给eval_metric,并通过eval_set以及可能的专用提前停止参数来控制最大化。
LightGBM使用与lgb.train中feval参数 (parameter)非常相似的机制。函数签名期望preds和train_data(一个lgb.Dataset对象)。
import numpy as np
import lightgbm as lgb
from sklearn.metrics import matthews_corrcoef # 示例指标
# 定义一个自定义评估指标:Matthews相关系数(MCC)
# 假设是带有概率输出的二分类
def lgbm_mcc(preds: np.ndarray, train_data: lgb.Dataset):
"""LightGBM的自定义MCC指标"""
labels = train_data.get_label()
# 将概率转换为二元预测(阈值为0.5)
pred_labels = (preds > 0.5).astype(int)
mcc = matthews_corrcoef(labels, pred_labels)
# 返回格式:(指标名称, 值, 是否越高越好)
return 'MCC', mcc, True
# --- 示例数据(请替换为您的实际数据)---
X_train = np.random.rand(100, 5)
y_train = np.random.randint(0, 2, size=100)
X_eval = np.random.rand(50, 5)
y_eval = np.random.randint(0, 2, size=50)
lgb_train = lgb.Dataset(X_train, label=y_train)
lgb_eval = lgb.Dataset(X_eval, label=y_eval, reference=lgb_train)
# --- 使用自定义指标进行训练 ---
params = {
'objective': 'binary',
'metric': 'binary_logloss', # 仍可追踪标准指标
'boosting_type': 'gbdt',
'num_leaves': 31,
'learning_rate': 0.05,
'feature_fraction': 0.9
}
evals_result = {} # 用于存储评估结果
# 将自定义函数列表传递给feval
bst = lgb.train(
params,
lgb_train,
num_boost_round=100,
valid_sets=[lgb_train, lgb_eval],
valid_names=['train', 'eval'],
evals_result=evals_result,
feval=[lgbm_mcc], # 作为列表传递
callbacks=[
lgb.early_stopping(stopping_rounds=10, first_metric_only=False, verbose=True),
lgb.log_evaluation(period=10)
]
# 提前停止将使用params['metric']中指定的指标
# 以及由feval函数返回的任何指标。它正确使用'is_higher_better'
# 标志。params中的'metric'参数决定优化目标。
# params中的'metric'和feval指标都会被监控用于提前停止。
)
print("\n包含自定义MCC的评估结果:")
# print(evals_result) # 完整历史记录
print(f"验证集上的最佳MCC: {max(evals_result['eval']['MCC'])}")
LightGBM要求自定义指标函数明确返回is_higher_better布尔值,这使得它与提前停止一起使用起来很简单。您可以在feval列表中传递多个自定义指标函数。
CatBoost也通过其fit方法(针对CatBoostClassifier/CatBoostRegressor类)或train函数中的eval_metric参数 (parameter)支持自定义指标。您可以传递一个表示内置指标的字符串,或一个带有特定方法的Python对象(通常是一个类)。
对于自定义指标,您通常需要定义一个至少包含以下方法的类:
__init__(self):构造函数(可选)。is_max_optimal(self):如果指标值越高越好,则返回True,否则返回False。evaluate(self, approxes, target, weight):计算指标。
approxes:包含每个文档的预测原始分数/值的列表的列表。对于多分类,每个类别一个列表。target:真实标签。weight:样本权重 (weight)(可以是None)。(指标值之和,权重之和)。CatBoost在内部对其进行平均。get_final_error(self, error, weight):从evaluate返回的和中计算最终指标值。import numpy as np
from catboost import CatBoostClassifier, Pool
from sklearn.metrics import f1_score # 示例指标
# 定义一个用于F1分数的自定义指标类(二分类)
class CatBoostF1Metric:
def is_max_optimal(self):
# True,因为F1分数越高越好
return True
def evaluate(self, approxes, target, weight):
# approxes是列表的列表,二分类需要扁平化
# 内部列表包含原始预测分数(例如,对数几率)
# 使用sigmoid将原始分数转换为概率
assert len(approxes) == 1 # 预期二分类只有一个列表
preds_raw = np.array(approxes[0])
preds_prob = 1.0 / (1.0 + np.exp(-preds_raw))
# 将概率转换为二元预测
pred_labels = (preds_prob > 0.5).astype(int)
# 计算F1分数
f1 = f1_score(target, pred_labels)
# CatBoost期望指标值之和与权重之和
# 对于在整个数据集上计算的F1,我们可以返回 (f1 * 计数, 计数)
# 或者,如果提供了权重,则进行处理。这里假设没有权重或权重相等。
count = len(target) if target is not None else 0
if count == 0:
return 0.0, 0.0 # 如果目标为空,避免除以零
# 返回 (sum_metric, sum_weight)
# 这里,权重实际上是用于简单平均的样本数量
return f1 * count, count
def get_final_error(self, error_sum, weight_sum):
# 计算最终的平均误差
if weight_sum == 0:
return 0.0 # 避免除以零
return error_sum / weight_sum
# --- 示例数据(请替换为您的实际数据)---
X_train = np.random.rand(100, 5)
y_train = np.random.randint(0, 2, size=100)
X_eval = np.random.rand(50, 5)
y_eval = np.random.randint(0, 2, size=50)
train_pool = Pool(X_train, label=y_train)
eval_pool = Pool(X_eval, label=y_eval)
# --- 使用自定义指标进行训练 ---
model = CatBoostClassifier(
iterations=100,
learning_rate=0.1,
loss_function='Logloss',
custom_metric=[CatBoostF1Metric()], # 传递自定义指标类实例或列表
eval_metric='Logloss', # 仍可用于优化/报告的标准指标
early_stopping_rounds=10,
verbose=10
)
model.fit(
train_pool,
eval_set=eval_pool
# 默认情况下,CatBoost使用eval_metric中的第一个指标进行优化,
# 但会监控所有指定的指标(包括自定义指标)用于提前停止。
# 它使用自定义指标类中is_max_optimal()方法的结果。
)
print("\n训练期间(在评估集上)的自定义F1指标值:")
eval_metrics = model.get_evals_result()
if 'learn' in eval_metrics and 'CatBoostF1Metric' in eval_metrics['learn']:
print(f"训练集F1: {eval_metrics['learn']['CatBoostF1Metric'][-1]:.4f}")
if 'validation' in eval_metrics and 'CatBoostF1Metric' in eval_metrics['validation']:
print(f"评估集F1: {eval_metrics['validation']['CatBoostF1Metric'][-1]:.4f}")
CatBoost为自定义指标提供的基于类的方法提供了一种结构化的方式来封装指标的逻辑和属性。请记住根据您的任务类型(二分类、多分类、回归)处理approxes的特定格式。
is_higher_better标志(或CatBoost中的is_max_optimal方法)对于提前停止正确工作很重要。请确保它准确反映指标是应最大化还是最小化。提升库将使用此信息来判断验证集上的性能是否正在提升。preds或approxes)的格式。它们可能是原始分数(例如,逻辑回归的对数几率)、概率或最终预测值,具体取决于库、目标函数和特定的API调用。根据需要调整您的指标计算(例如,如果需要,应用sigmoid或softmax)。通过实现自定义评估指标,您可以获得对模型性能如何追踪和评估的更细致控制,从而使评估过程更贴合您的机器学习 (machine learning)任务或业务目标的特定要求。此能力,结合自定义损失函数 (loss function)和可解释性工具,为构建高度定制化且有效的梯度提升模型提供了一个功能强大的工具集。
这部分内容有帮助吗?
© 2026 ApX Machine LearningAI伦理与透明度•