AI模型监控:这次怎么落地的

他们去年上线的推荐模型,看起来一切正常:预测延迟在100ms以内,接口错误率接近0,监控面板绿油油的。这次写这篇,想梳理一下这几年在生产环境监控AI模型时踩过的坑和积累的经验。

模型监控到底要监控什么

说到底,模型监控的核心问题只有一个:模型现在的表现,和上线那天相比,还符不符合预期?

这个"预期"可以拆成几个维度:

  • 数据层面:输入数据的分布、特征值范围、缺失率、异常值比例
  • 预测层面:预测结果的分布、类别比例、置信度分布
  • 性能层面:准确率、召回率、F1、AUC这些指标的业务表现
  • 系统层面:预测延迟、吞吐量、资源使用率

模型卡在训练环境和生产基础设施之间:既要业务指标,也得盯延迟、吞吐和资源占用。

实践中最容易犯的错误是只看基础设施监控。Kafka不丢消息、Redis不超时、接口返回200ms,就觉得没问题。但模型可能已经在偷偷地从一个方向漂向另一个方向了。

先给一张整体图,把几个监控维度之间的关系理清楚:

graph LR A[输入数据] --> B[数据质量监控] B --> C[漂移检测] C --> D[预测结果监控] D --> E[模型性能监控] E --> F[告警触发] F --> G[模型再训练/回滚] H[系统资源] --> I[延迟/吞吐量监控] I --> F style C fill:#fff3cd style E fill:#fff3cd

这张图想说明的是:监控不是单向的,数据漂移会影响到预测结果和模型性能,系统层面的问题也可能触发告警。但更重要的是,漂移检测和性能监控是两个独立但相关的信号——数据变了不代表模型一定变差,模型变差也不一定是因为数据变了。

数据漂移:从"看起来正常"到"真的出问题"

数据漂移是模型监控里最经典的问题,也是最容易过度反应的地方。

分类漂移

最简单的情况是分类漂移——输入数据的类别分布变了。

比如一个垃圾邮件分类模型,训练时样本里垃圾邮件占30%,正常邮件占70%。上线半年后发现,垃圾邮件的占比降到了10%。

乍一看是个好事——垃圾变少了。但问题在于,模型在训练时学到的决策边界是针对30%垃圾比例的。现在输入数据的类别分布变了,模型的预测概率分布也会跟着变,即使模型本身没有"退化"。

用一个简单的例子说明:

import numpy as np
from scipy.stats import ks_2samp

# 模拟训练数据和线上数据的特征分布
train_data = np.random.normal(0, 1, 10000)  # 训练数据
online_data = np.random.normal(0.2, 1, 10000)  # 线上数据,均值轻微偏移

# 使用KS检验检测分布差异
statistic, p_value = ks_2samp(train_data, online_data)

print(f"KS统计量: {statistic:.4f}")
print(f"P值: {p_value:.6f}")

if p_value < 0.05:
    print("警告:检测到显著的分布漂移")
else:
    print("分布没有显著漂移")

这个例子用的是KS检验,实践中常用的还有Population Stability Index (PSI)、Jensen-Shannon散度等。但选择哪个指标不那么重要,关键在于阈值怎么设

早年我犯过一个典型的错误:把p值阈值设成0.05。结果每天都能收到漂移告警——因为只要样本量够大,任何微小的分布差异都能被检测出来。但业务上那些差异根本不造成影响。

后来改成了PSI阈值:PSI < 0.1算稳定,0.1-0.25算轻微漂移,>0.25算严重漂移。这是个经验值,不一定适用于所有场景,但至少比死守0.05要实用。

特征漂移

比分类漂移更隐蔽的是特征漂移——单个特征的分布变了。

比如一个用户年龄特征,训练时大部分用户在20-40岁之间,上线后慢慢变成40-60岁。年龄这个特征值的范围没变,但分布中心漂移了。

如果模型对年龄敏感,这就可能是个问题。但反过来想,年龄分布变了本身也许不构成问题——如果用户的年龄结构真的在自然变化,那模型跟着变是合理的,反而是强行保持"训练时分布"有问题。

实践中更有效的方式是分层监控:先看整体分布变化,再看关键特征的变化,最后结合预测结果判断是否真的需要干预。

def monitor_feature_drift(train_features, online_features, threshold=0.1):
    """
    监控特征漂移,返回漂移程度和建议

    Args:
        train_features: 训练数据的特征字典 {feature_name: array}
        online_features: 线上数据的特征字典
        threshold: PSI阈值,超过则告警

    Returns:
        drift_report: 包含每个特征的漂移状态和建议
    """
    from scipy.stats import entropy

    drift_report = {}

    for feature in train_features.keys():
        train_vals = train_features[feature]
        online_vals = online_features[feature]

        # 计算分布(这里简化为直方图)
        train_hist, _ = np.histogram(train_vals, bins=20, density=True)
        online_hist, _ = np.histogram(online_vals, bins=20, density=True)

        # 计算PSI
        psi = np.sum((online_hist - train_hist) * np.log(online_hist / (train_hist + 1e-10) + 1e-10))

        # 判断漂移等级
        if psi < threshold:
            level = "stable"
            suggestion = "正常,无需干预"
        elif psi < threshold * 2.5:
            level = "warning"
            suggestion = "轻微漂移,建议关注"
        else:
            level = "critical"
            suggestion = "严重漂移,考虑重新训练"

        drift_report[feature] = {
            "psi": psi,
            "level": level,
            "suggestion": suggestion
        }

    return drift_report

直方图 bin 数、阈值都得按场景调;代码只是示例,思路是先量化漂移,再给业务建议,别一上来就告警

概念漂移

概念漂移是最坑爹的——输入数据看起来没变,但"正确答案"的映射关系变了。

举个真实的例子:一个电商平台的用户流失预测模型。训练数据里,“最近30天登录次数"是一个强特征——登录次数少意味着流失风险高。

但平台后来做了一个产品改动:把登录入口移到了更显眼的位置,还加了每日签到奖励。结果用户登录次数普遍上升了,但流失率并没有下降。模型开始误判那些"登录频繁但行为单一"的用户为低风险,实际上他们只是机械地点了签到按钮,并没有真的活跃。

这种情况下,特征分布看起来是"稳定的”,但特征与目标的映射关系已经变了。

检测概念漂移的有效方法之一是监控预测置信度的分布变化

def monitor_confidence_drift(predictions_history, window=100):
    """
    监控预测置信度的漂移

    Args:
        predictions_history: 历史预测置信度列表
        window: 滑动窗口大小

    Returns:
        drift_signal: 漂移信号强度 (0-1)
    """
    if len(predictions_history) < window * 2:
        return 0  # 数据不足,无法判断

    # 计算最近窗口和历史窗口的统计差异
    recent = predictions_history[-window:]
    historical = predictions_history[-(window*2):-window]

    # 计算均值差异
    mean_diff = abs(np.mean(recent) - np.mean(historical))

    # 计算方差差异
    var_diff = abs(np.var(recent) - np.var(historical))

    # 归一化漂移信号
    drift_signal = min(1.0, (mean_diff + var_diff) / 2.0)

    return drift_signal

这个方法不能解决概念漂移本身,但至少能在模型开始"自欺欺人"时发个信号。

模型性能监控:准确率只是开始

准确率、召回率这些指标在训练集上跑得再漂亮,在线上也可能一塌糊涂。

离线评估 vs 在线效果

训练时用的评估指标和线上的真实效果往往不是一回事。

一个典型的例子:推荐系统。训练时用AUC评估,模型AUC能达到0.85。上线后发现点击率很低,换了个AUC只有0.82的模型,点击率反而上去了。

原因在于训练时的评估数据是"截断"的——用户看到的只有模型推荐的东西,没看到的被当成负样本。但线上真实场景里,用户没点击可能是因为没看到,也可能是因为真的不感兴趣。这两种情况的负样本含义完全不同。

解决方案:别指望换一个指标就搞定,离线指标和业务指标分开盯,两层监控一起上。

class ModelPerformanceMonitor:
    def __init__(self, metrics_window=1000):
        self.metrics_window = metrics_window
        self.predictions = []
        self.labels = []
        self.business_metrics = []

    def record_prediction(self, prediction, label, business_value):
        """
        记录单次预测

        Args:
            prediction: 模型预测值
            label: 真实标签(如果有)
            business_value: 业务指标(点击/转化/订单金额等)
        """
        self.predictions.append(prediction)
        if label is not None:
            self.labels.append(label)
        self.business_metrics.append(business_value)

        # 保持窗口大小
        if len(self.predictions) > self.metrics_window:
            self.predictions.pop(0)
            if self.labels:
                self.labels.pop(0)
            self.business_metrics.pop(0)

    def evaluate(self):
        """
        评估当前窗口的模型性能
        """
        results = {}

        # 离线指标(如果有标签)
        if self.labels:
            from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score

            binary_preds = [1 if p > 0.5 else 0 for p in self.predictions]

            results['accuracy'] = accuracy_score(self.labels, binary_preds)
            results['precision'] = precision_score(self.labels, binary_preds, zero_division=0)
            results['recall'] = recall_score(self.labels, binary_preds, zero_division=0)
            results['f1'] = f1_score(self.labels, binary_preds, zero_division=0)

        # 在线业务指标
        if self.business_metrics:
            results['business_conversion'] = np.mean(self.business_metrics)
            results['business_std'] = np.std(self.business_metrics)

        # 预测置信度分布
        results['prediction_mean'] = np.mean(self.predictions)
        results['prediction_std'] = np.std(self.predictions)

        return results

这个监控器同时跟踪离线指标和在线业务指标,你可以清楚地看到:离线指标很好但业务指标很差,或者反过来。

滑动窗口 vs 全量数据

另一个常见问题是监控窗口怎么设。

太短的窗口(比如最近1小时)对突发情况敏感,但容易被随机波动误触发。太长的窗口(比如最近30天)反应迟钝,等告警时模型已经烂了很久。

我的做法是分层窗口

  • 实时窗口:最近1小时,用于检测突发问题
  • 短期窗口:最近1天,用于评估短期趋势
  • 长期窗口:最近7天,用于判断基线偏移
class MultiWindowMonitor:
    def __init__(self):
        self.realtime_data = []  # 1小时窗口
        self.short_term_data = []  # 1天窗口
        self.long_term_data = []   # 7天窗口

    def add_data_point(self, value, timestamp):
        """
        添加数据点,自动分配到不同窗口
        """
        now = datetime.now()

        self.long_term_data.append((value, timestamp))
        if timestamp > now - timedelta(days=1):
            self.short_term_data.append((value, timestamp))
        if timestamp > now - timedelta(hours=1):
            self.realtime_data.append((value, timestamp))

        # 清理过期数据
        self.realtime_data = [(v, t) for v, t in self.realtime_data if t > now - timedelta(hours=1)]
        self.short_term_data = [(v, t) for v, t in self.short_term_data if t > now - timedelta(days=1)]
        self.long_term_data = [(v, t) for v, t in self.long_term_data if t > now - timedelta(days=7)]

    def evaluate(self):
        """
        评估不同窗口的性能
        """
        results = {}

        for name, data in [('realtime', self.realtime_data),
                          ('short_term', self.short_term_data),
                          ('long_term', self.long_term_data)]:
            if not data:
                continue

            values = [v for v, t in data]

            results[f'{name}_mean'] = np.mean(values)
            results[f'{name}_std'] = np.std(values)
            results[f'{name}_min'] = np.min(values)
            results[f'{name}_max'] = np.max(values)

        return results

这样你可以在不同时间尺度上观察模型表现:实时窗口如果突然变差,可能服务有问题;短期窗口持续下降,可能是数据漂移;长期窗口稳定但近期变差,可能是业务场景变了。

告警策略:从"狼来了"到"该出手时就出手"

告警设置是模型监控里最容易翻车的地方。

误报陷阱

早期的告警策略很粗暴:准确率下降超过5%就告警。结果经常半夜被叫起来,发现准确率只是从92%掉到87%,还在可接受范围内。

另一个常见问题是数据波动引发的误报。周末的流量和工作日不同,节假日的用户行为和平时不一样,这些正常波动都可能触发告警。

后来我学乖了:告警要考虑上下文,而不是绝对阈值

class ContextAwareAlert:
    def __init__(self, history_window=7):
        self.history_window = history_window
        self.history_data = []

    def add_data_point(self, value, timestamp):
        """
        添加历史数据点
        """
        self.history_data.append((value, timestamp))

        # 清理过期数据
        now = datetime.now()
        self.history_data = [(v, t) for v, t in self.history_data
                            if t > now - timedelta(days=self.history_window)]

    def should_alert(self, current_value, timestamp):
        """
        判断是否应该告警

        Args:
            current_value: 当前指标值
            timestamp: 当前时间

        Returns:
            (should_alert, reason)
        """
        if len(self.history_data) < 24:  # 至少需要1天数据
            return False, "历史数据不足"

        # 计算同一时间段的历史均值和标准差
        hour_of_day = timestamp.hour
        day_of_week = timestamp.weekday()

        historical_values = []
        for v, t in self.history_data:
            if t.hour == hour_of_day and t.weekday() == day_of_week:
                historical_values.append(v)

        if len(historical_values) < 3:
            return False, "同一时段历史样本不足"

        mean = np.mean(historical_values)
        std = np.std(historical_values)

        # 判断当前值是否偏离历史均值超过2个标准差
        z_score = abs(current_value - mean) / (std + 1e-10)

        if z_score > 2:
            return True, f"当前值{current_value:.2f}偏离历史均值{mean:.2f}超过2个标准差"
        else:
            return False, f"当前值{current_value:.2f}在正常范围内"

这个告警策略考虑了时间上下文——同样是85%的准确率,在凌晨2点可能是正常的,但在下午2点可能就异常了。

告警分级

不是所有异常都值得立刻叫醒人。

实践中我把告警分成三级:

  • P0(紧急):模型完全不可用,预测准确率暴跌到随机水平,或者服务超时、错误率飙升
  • P1(重要):模型性能下降超过10%,但还在可用范围内
  • P2(关注):轻微性能下降,或者检测到数据漂移但业务指标还正常
class PriorityAlert:
    def __init__(self):
        self.alert_handlers = {
            'P0': self.handle_p0_alert,
            'P1': self.handle_p1_alert,
            'P2': self.handle_p2_alert
        }

    def evaluate_alert_priority(self, metrics):
        """
        评估告警优先级

        Args:
            metrics: 包含各种监控指标的字典

        Returns:
            priority: 'P0', 'P1', 'P2' 或 None
            reason: 告警原因
        """
        # P0条件:完全不可用
        if (metrics.get('error_rate', 0) > 0.1 or
            metrics.get('latency_p99', 0) > 5000 or
            metrics.get('accuracy', 1) < 0.6):
            return 'P0', '模型服务不可用或性能严重下降'

        # P1条件:性能下降显著
        if (metrics.get('accuracy', 1) < metrics.get('baseline_accuracy', 1) * 0.9 or
            metrics.get('conversion_rate', 1) < metrics.get('baseline_conversion', 1) * 0.9):
            return 'P1', '模型性能下降超过10%'

        # P2条件:轻微异常
        if (metrics.get('data_drift', 0) > 0.1 or
            metrics.get('confidence_drift', 0) > 0.1):
            return 'P2', '检测到轻微数据或置信度漂移'

        return None, '正常'

    def handle_p0_alert(self, reason):
        """处理P0告警"""
        # 立即通知on-call人员
        self.send_immediate_notification(reason)
        # 尝试自动回滚到上一个稳定版本
        self.attempt_auto_rollback()

    def handle_p1_alert(self, reason):
        """处理P1告警"""
        # 发送邮件和Slack通知
        self.send_email_notification(reason)
        self.send_slack_notification(reason)
        # 记录到监控看板
        self.log_to_dashboard(reason)

    def handle_p2_alert(self, reason):
        """处理P2告警"""
        # 只记录到监控看板,不主动通知
        self.log_to_dashboard(reason)

    def send_immediate_notification(self, reason):
        """立即通知on-call人员"""
        # 实现细节取决于你的通知系统
        pass

    def attempt_auto_rollback(self):
        """尝试自动回滚"""
        # 实现细节取决于你的部署系统
        pass

    # ... 其他通知方法的实现

这样你不会因为凌晨3点检测到轻微数据漂移被叫起来,但真正出问题时能第一时间知道。

实践中的坑和解决方案

讲完了理论,聊聊这几年踩过的真实坑。

坑一:监控模型但不监控数据源

一次线上事故,模型预测突然变得很奇怪:大部分预测结果都集中在0.8-0.9之间,置信度分布非常不自然。

查了半天发现不是模型问题,是上游的数据管道出了bug——某些特征被错误地填充成了默认值,导致模型输入变得很单一。

教训:模型监控的同时要监控数据管道。至少要监控:

  • 特征缺失率的变化
  • 特征值范围的异常
  • 数据更新频率
  • 数据新鲜度
class DataQualityMonitor:
    def __init__(self, feature_specs):
        """
        Args:
            feature_specs: 特征规格字典,包含每个特征的预期范围和类型
            例如:
            {
                'age': {'min': 0, 'max': 120, 'type': 'numeric'},
                'gender': {'values': ['M', 'F'], 'type': 'categorical'}
            }
        """
        self.feature_specs = feature_specs
        self.history = {}

    def check_data_quality(self, data):
        """
        检查数据质量

        Args:
            data: 输入数据字典

        Returns:
            quality_report: 质量报告,包含问题和建议
        """
        issues = []

        for feature, spec in self.feature_specs.items():
            if feature not in data:
                issues.append(f"特征缺失: {feature}")
                continue

            value = data[feature]

            # 检查数值范围
            if spec.get('type') == 'numeric':
                if 'min' in spec and value < spec['min']:
                    issues.append(f"{feature}{value}低于最小值{spec['min']}")
                if 'max' in spec and value > spec['max']:
                    issues.append(f"{feature}{value}高于最大值{spec['max']}")

            # 检查分类值
            elif spec.get('type') == 'categorical':
                if value not in spec.get('values', []):
                    issues.append(f"{feature}{value}不在允许的值列表中")

            # 记录历史
            if feature not in self.history:
                self.history[feature] = []
            self.history[feature].append(value)

            # 检测分布异常(简化版)
            if len(self.history[feature]) > 100:
                recent_values = self.history[feature][-100:]
                if value not in recent_values[-20:]:
                    issues.append(f"{feature}{value}与最近20个样本差异较大")

        return {
            'passed': len(issues) == 0,
            'issues': issues
        }

坑二:监控滞后导致回滚不及时

模型上线后性能逐渐下降,但告警阈值设置得比较宽松,等触发告警时已经烂了一个多星期。这时候回滚成本已经很高了——用户已经开始习惯新结果,回滚反而会被当成故障。

教训:当前值和趋势线一起看,趋势往下走时往往比单次掉点更该警惕。

class TrendMonitor:
    def __init__(self, window=7):
        self.window = window
        self.history = []

    def add_data_point(self, value, timestamp):
        self.history.append((value, timestamp))

    def calculate_trend(self):
        """
        计算趋势

        Returns:
            trend: 'increasing', 'decreasing', 'stable'
            slope: 趋势斜率
            confidence: 趋势置信度
        """
        if len(self.history) < self.window * 2:
            return 'stable', 0, 0

        # 提取最近window天的数据
        now = datetime.now()
        recent = [(v, t) for v, t in self.history
                 if t > now - timedelta(days=self.window)]

        if len(recent) < 3:
            return 'stable', 0, 0

        # 简单线性回归
        times = [(t - recent[0][1]).total_seconds() for v, t in recent]
        values = [v for v, t in recent]

        slope, intercept = np.polyfit(times, values, 1)

        # 计算R方(拟合优度)
        predictions = [slope * t + intercept for t in times]
        ss_res = sum((values[i] - predictions[i])**2 for i in range(len(values)))
        ss_tot = sum((values[i] - np.mean(values))**2 for i in range(len(values)))
        r_squared = 1 - (ss_res / ss_tot)

        # 判断趋势
        if r_squared < 0.5:
            return 'stable', slope, r_squared
        elif slope > 0.01:
            return 'increasing', slope, r_squared
        elif slope < -0.01:
            return 'decreasing', slope, r_squared
        else:
            return 'stable', slope, r_squared

    def should_alert_on_trend(self):
        """
        基于趋势判断是否需要告警
        """
        trend, slope, confidence = self.calculate_trend()

        # 如果趋势是下降且置信度较高
        if trend == 'decreasing' and confidence > 0.6:
            return True, f"检测到下降趋势(斜率{slope:.4f},置信度{confidence:.2f})"

        return False, f"趋势{trend},无需告警"

趋势监控的好处是能在问题变得严重之前就发信号,给团队留出响应时间。

坑三:监控太多指标反而没人看

早期恨不得监控所有指标:准确率、精确率、召回率、F1、AUC、各种漂移指标、延迟、吞吐量、错误率、GPU使用率、内存使用率…

结果监控面板变成了一堆曲线和数字,没人看得懂,更没人知道出问题时该先看哪个。

教训:指标堆满面板没人看,不如只留 accuracy/转化率、稳定性、延迟错误率这几条主线。

现在我通常只监控这几个核心指标:

  • 准确性:模型预测准确率或业务转化率
  • 稳定性:预测结果的方差或置信度分布
  • 可用性:预测延迟和错误率
  • 效率:资源使用率(可选)

其他指标只在需要时深入查看,不是日常监控的对象。

坑四:监控但不行动,变成"告警疲劳"

有一段时间告警系统很"勤奋",每天都能收到十几条告警信息。但大部分都只是轻微漂移,业务上影响不大。慢慢地大家开始忽略告警,直到真正出问题时没人第一时间响应。

教训:告警要可行动,否则不如不告警。

每个告警信息都要包含:

  • 问题描述:具体是什么问题
  • 影响范围:影响了哪些用户或业务
  • 可能原因:初步分析的可能原因
  • 建议措施:建议采取的措施
class ActionableAlert:
    def generate_alert_message(self, alert_type, metrics, context):
        """
        生成可行动的告警信息

        Args:
            alert_type: 告警类型 ('P0', 'P1', 'P2')
            metrics: 监控指标
            context: 上下文信息(数据版本、模型版本等)

        Returns:
            alert_message: 结构化的告警信息
        """
        message = {
            'priority': alert_type,
            'timestamp': datetime.now().isoformat(),
            'summary': '',
            'details': {},
            'impact': '',
            'possible_causes': [],
            'recommended_actions': [],
            'context': context
        }

        if alert_type == 'P0':
            if metrics.get('error_rate', 0) > 0.1:
                message['summary'] = '模型服务错误率过高'
                message['impact'] = '用户请求大量失败,严重影响用户体验'
                message['possible_causes'] = [
                    '模型服务崩溃',
                    '依赖服务不可用',
                    '资源耗尽'
                ]
                message['recommended_actions'] = [
                    '立即检查模型服务状态',
                    '尝试自动回滚到上一个稳定版本',
                    '检查依赖服务(数据库、缓存等)',
                    '增加资源分配'
                ]

            elif metrics.get('accuracy', 1) < 0.6:
                message['summary'] = '模型预测准确率严重下降'
                message['impact'] = '模型预测结果几乎不可用'
                message['possible_causes'] = [
                    '输入数据异常',
                    '模型文件损坏',
                    '业务场景发生重大变化'
                ]
                message['recommended_actions'] = [
                    '检查输入数据质量',
                    '尝试回滚到上一个模型版本',
                    '检查数据管道是否正常',
                    '评估是否需要紧急重新训练'
                ]

        elif alert_type == 'P1':
            message['summary'] = '模型性能下降'
            message['impact'] = '用户体验受到一定影响,但不完全不可用'
            message['possible_causes'] = [
                '数据分布发生漂移',
                '模型性能自然衰减',
                '用户行为模式变化'
            ]
            message['recommended_actions'] = [
                '分析数据漂移情况',
                '评估是否需要重新训练',
                '考虑A/B测试新模型',
                '与业务团队确认是否有产品变更'
            ]

        elif alert_type == 'P2':
            message['summary'] = '模型轻微异常'
            message['impact'] = '当前影响较小,但需要关注趋势'
            message['possible_causes'] = [
                '正常的数据波动',
                '轻微的数据漂移',
                '暂时的环境因素'
            ]
            message['recommended_actions'] = [
                '持续监控趋势',
                '记录异常现象',
                '定期评估模型性能'
            ]

        return message

工具选型:不是越复杂越好

最后聊聊工具选择。市场上监控工具很多,但不是越复杂越好。

Prometheus + Grafana:基础设施监控

如果你的模型部署在K8s上,Prometheus + Grafana几乎是标配。它们擅长监控基础设施层面的指标:延迟、错误率、资源使用率。

但它们不太适合监控业务层面的指标:准确率、漂移、转化率这些。这些指标需要你自己在业务代码里埋点。

Evidently AI:开源模型监控

Evidently AI是一个开源的Python库,专门用于机器学习模型的监控。它提供了数据漂移检测、模型性能评估、数据质量检查等功能。

from evidently import ColumnMapping
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset, ClassificationPerformancePreset

# 配置列映射
column_mapping = ColumnMapping(
    target='target',
    prediction='prediction',
    numerical_features=['feature1', 'feature2'],
    categorical_features=['feature3']
)

# 生成数据漂移报告
drift_report = Report(metrics=[DataDriftPreset()])
drift_report.run(reference_data=train_data, current_data=online_data, column_mapping=column_mapping)

# 生成模型性能报告
performance_report = Report(metrics=[ClassificationPerformancePreset()])
performance_report.run(reference_data=train_data, current_data=online_data, column_mapping=column_mapping)

# 导出报告
drift_report.save_html('drift_report.html')
performance_report.save_html('performance_report.html')

Evidently的好处是开箱即用,不需要自己实现各种统计检验。但它的报告是静态HTML,不太适合集成到实时告警系统里。

Arize / WhyLabs:商业SaaS方案

如果你不想自己维护监控基础设施,可以考虑商业SaaS方案,比如Arize或WhyLabs。它们提供了完整的模型监控平台,包括漂移检测、性能监控、异常检测、告警通知等。

但这类方案的缺点也很明显:成本高、数据要上传到第三方、定制化程度有限。

自研方案:灵活但成本高

如果你的场景比较特殊,或者对数据隐私有要求,可能需要自研监控方案。

自研的好处是可以完全按照需求定制,但成本也很高——你需要实现数据采集、指标计算、漂移检测、告警通知等所有组件。

我的建议是:先从开源方案开始,如果确实满足不了需求再考虑自研。

结语

模型上线那天,监控才算真正开始。我后来只盯几件事:业务指标有没有悄悄变差、漂移告警是不是狼来了、趋势往下走时有没有留够回滚窗口。

面板做得再漂亮,业务还在出问题,说明盯错指标了。工具帮不上忙的地方,还是对数据和业务的熟悉——监控只是把那种直觉量化出来。

版权声明: 本文首发于 指尖魔法屋-AI模型监控:这次怎么落地的https://blog.thinkmoon.cn/post/189-ai-model-monitoring-deployment-runtime-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!