本动手练习着重于应用技术,以检测模型训练(离线)时使用的特征值数据集与在线服务时观察到的特征值数据集之间可能存在的偏差。检测此类差异,即 $P_{训练}(X) \ne P_{服务}(X)$,对维持生产环境中模型性能很重要。我们将使用 Pandas、NumPy 和 SciPy 等 Python 库进行统计比较,并使用 Plotly 进行可视化。场景设置假设我们从两个来源记录了特征数据:训练数据集特征: 通过批处理生成,用于训练模型的特征。我们将其命名为 training_features_df。服务日志特征: 在特定时期进行预测之前,从在线系统记录的特征。我们将其命名为 serving_features_df。我们的目标是比较这两个数据集中特定特征的分布,以识别潜在的偏差。生成示例数据首先,让我们使用 Pandas 和 NumPy 模拟这两个数据集。我们将创建两个特征:user_age(数值型)和 product_category(类别型)。我们将在两个数据框之间引入 user_age 和 product_category 分布的微小差异。import pandas as pd import numpy as np from scipy import stats # 模拟训练数据特征 np.random.seed(42) training_data = { 'user_age': np.random.normal(loc=35, scale=10, size=1000).astype(int), 'product_category': np.random.choice(['Electronics', 'Clothing', 'Home Goods', 'Books'], size=1000, p=[0.4, 0.3, 0.2, 0.1]) } training_features_df = pd.DataFrame(training_data) training_features_df['user_age'] = training_features_df['user_age'].clip(lower=18) # 确保年龄符合实际 # 模拟服务日志特征(含偏差) serving_data = { 'user_age': np.random.normal(loc=40, scale=12, size=500).astype(int), # 不同的均值和标准差 'product_category': np.random.choice(['Electronics', 'Clothing', 'Home Goods', 'Books'], size=500, p=[0.35, 0.25, 0.25, 0.15]) # 不同的类别分布 } serving_features_df = pd.DataFrame(serving_data) serving_features_df['user_age'] = serving_features_df['user_age'].clip(lower=18) print("训练特征样本:") print(training_features_df.head()) print("\n服务特征样本:") print(serving_features_df.head()) print("\n训练数据描述:") print(training_features_df.describe(include='all')) print("\n服务数据描述:") print(serving_features_df.describe(include='all'))运行此代码会生成两个不同的数据框。描述性统计信息已暗示存在差异,特别是在平均年龄和主要产品类别的频率方面。比较数值特征分布对于像 user_age 这样的数值特征,比较分布的常用方法是双样本 Kolmogorov-Smirnov (KS) 检验。KS 检验是非参数的,它检查两个样本是否来自相同的底层连续分布。它量化了两个样本的经验累积分布函数 (ECDF) 之间的最大距离。KS 检验的零假设 ($H_0$) 是两个样本来自同一分布。较小的 p 值(通常 < 0.05)提示拒绝 $H_0$,表明分布之间存在统计学上的显著差异。# 提取数值特征列 train_age = training_features_df['user_age'] serve_age = serving_features_df['user_age'] # 执行双样本 KS 检验 ks_statistic, p_value = stats.ks_2samp(train_age, serve_age) print(f"user_age 的 KS 检验:") print(f" KS 统计量: {ks_statistic:.4f}") print(f" P 值: {p_value:.4f}") if p_value < 0.05: print(" 结果:检测到显著差异(拒绝 H0)。存在潜在偏差。") else: print(" 结果:未检测到显著差异(未能拒绝 H0)。") 输出很可能显示一个非常小的 p 值,证实了我们模拟的训练和服务数据集之间 user_age 分布的统计学显著差异。让我们使用直方图来可视化这种差异。{"layout":{"title":"用户年龄分布:训练 vs. 服务","xaxis":{"title":"用户年龄"},"yaxis":{"title":"计数"},"barmode":"overlay","legend":{"traceorder":"reversed"},"autosize":true},"data":[{"type":"histogram","x":[35,45,25,30,40,50,28,38,48,33,43,23,36,46,26,31,41,51,29,39,49,34,44,24,37,47,27,32,42,52,22,18,55,60,20,35,45,25,30,40,50,28,38,48,33,43,23,36,46,26,31,41,51,29,39,49,34,44,24,37,47,27,32,42,52,22,18,55,60,20,35,45,25,30,40,50,28,38,48,33,43,23,36,46,26,31,41,51,29,39,49,34,44,24,37,47,27,32,42,52,22,18,55,60,20,35,45,25,30,40,50,28,38,48,33,43,23,36,46,26,31,41,51,29,39,49,34,44,24,37,47,27,32,42,52,22,18,55,60,20],"name":"训练","opacity":0.75,"marker":{"color":"#4263eb"}},{"type":"histogram","x":[40,50,30,35,45,55,32,42,52,38,48,28,41,51,31,36,46,56,33,43,53,39,49,29,42,52,32,37,47,57,27,20,60,65,25,40,50,30,35,45,55,32,42,52,38,48,28,41,51,31,36,46,56,33,43,53,39,49,29,42,52,32,37,47,57,27,20,60,65,25],"name":"服务","opacity":0.75,"marker":{"color":"#f06595"}}]}用户年龄分布的比较,显示服务数据中年龄向更高龄段偏移,与训练数据相比。比较类别特征分布对于像 product_category 这样的类别特征,我们可以使用卡方 ($\chi^2$) 独立性检验。此检验有助于确定数据集来源(训练 vs. 服务)与类别分布之间是否存在显著关联。首先,我们需要创建一个列联表(交叉表),显示两个数据集中每个类别的计数。# 创建带有来源标识符的组合数据框 training_features_df['source'] = 'Training' serving_features_df['source'] = 'Serving' combined_df = pd.concat([training_features_df, serving_features_df], ignore_index=True) # 创建列联表 contingency_table = pd.crosstab(combined_df['product_category'], combined_df['source']) print("product_category 的列联表:") print(contingency_table) # 执行卡方检验 chi2_stat, p_value, dof, expected = stats.chi2_contingency(contingency_table) print(f"\nproduct_category 的卡方检验:") print(f" 卡方统计量: {chi2_stat:.4f}") print(f" P 值: {p_value:.4f}") print(f" 自由度: {dof}") if p_value < 0.05: print(" 结果:检测到显著差异(拒绝 H0)。存在潜在偏差。") else: print(" 结果:未检测到显著差异(未能拒绝 H0)。")同样,较小的 p 值表明 product_category 的分布在训练数据集和服务数据集之间存在显著差异。让我们可视化比例。{"layout":{"title":"产品类别比例:训练 vs. 服务","xaxis":{"title":"产品类别"},"yaxis":{"title":"比例","tickformat":".0%"},"barmode":"group","autosize":true},"data":[{"type":"bar","x":["Electronics","Clothing","Home Goods","Books"],"y":[0.40,0.30,0.20,0.10],"name":"训练","marker":{"color":"#1c7ed6"}},{"type":"bar","x":["Electronics","Clothing","Home Goods","Books"],"y":[0.35,0.25,0.25,0.15],"name":"服务","marker":{"color":"#e64980"}}]}产品类别比例的比较,强调了训练数据和服务数据之间相对频率的差异。解释与行动在此次实践中,我们模拟了已知偏差的数据,并使用统计检验(数值特征使用 KS 检验,类别特征使用卡方检验)来检测这些差异。可视化帮助证实了偏差的性质。在 MLOps 流程中,这些步骤将自动化执行:数据收集: 抽样近期服务日志并获取相应的训练数据特征统计信息(如果可行,则获取完整数据集)。比较: 对相关特征运行统计检验。设置阈值: 将检验统计量或 p 值与预设阈值进行比较。这些阈值很重要且特定于领域。统计学上的显著差异(小的 p 值)在实际中可能不总是显著的。您可以直接在 KS 统计量上设置阈值,或者要求 p 值低于更严格的显著性水平(例如 0.01)。警报/行动: 如果阈值被突破,触发警报以进行调查。行动可能包括使用更新的数据重新训练模型、调查上游数据管道问题,或调整特征工程逻辑。此实践提供了一个基本框架。更复杂的做法包括随时间跟踪多个分布指标,使用漂移检测算法(如漂移检测方法 - DDM,或 Page Hinkley),以及使用专门的数据验证库(great_expectations、pandera、evidently.ai、deepchecks),这些库提供了更结构化的方式来定义期望并检测违规,包括偏差。这些工具通常提供更丰富的可视化效果和 MLOps 工作流集成。