K-Prototypes:混合数据聚类的工程化落地指南 1. 项目概述当数据不再“非黑即白”聚类算法也得学会“混搭”你有没有遇到过这样的情况手头的客户数据里既有年龄、年收入、消费金额这类能加减乘除的数字又有性别、职业、城市等级、会员等级这类只能归类不能计算的标签更别提还有像“最近一次购买时间”这种带时间戳的混合型字段。这时候如果还硬套K-Means——那个靠算“欧氏距离”吃饭的老派算法——结果往往让人哭笑不得模型会把“程序员”和“教师”强行拉近只因为它们在某个编码后的数字上碰巧挨得近或者把“北京”和“上海”分到同一簇仅仅因为它们被编码成了1和2。这不是模型聪明这是它在瞎猜。而标题里这个“K-Means K-Modes K-Prototypes”的等式绝不是数学游戏它是一条清晰的工程路径当你面对的是真实世界里混杂着数值型Numerical和类别型Categorical的数据时K-Prototypes 就是你唯一该认真考虑的聚类起点。它不追求理论上的完美而是用一种极其务实的方式把两种截然不同的距离度量逻辑拧在一起让算法真正理解“程序员”和“教师”的差异本质上和“35岁”与“42岁”的差异是两种完全不同的语言。这篇文章就是带你亲手拆开这个“混搭引擎”的外壳看清楚里面的齿轮怎么咬合、参数怎么调校、坑怎么避开。无论你是刚学完K-Means想进阶的新人还是正被业务部门催着给用户打标签的数据工程师只要你手里的数据表里同时存在数字列和文字列这篇就是为你写的实操手册。2. 核心思路拆解为什么“拼凑”反而是最优雅的工程解法2.1 从K-Means的“数字洁癖”说起K-Means 的核心是“均值”和“欧氏距离”。它假设所有特征都是连续的、可求平均的、彼此之间可以用直角坐标系里的直线距离来衡量。比如一个用户的特征向量是 [35, 85000, 12]分别代表年龄、年收入、过去一年购物次数。K-Means 会轻松地算出两个用户之间的距离√[(35-42)² (85000-65000)² (12-8)²]。这个计算天经地义因为每个维度都有明确的物理意义和可比的量纲。但问题来了如果你把“职业”也塞进去比如用 [35, 85000, 12, 1] 表示“35岁、年薪8.5万、购物12次、职业程序员”再用 [42, 65000, 8, 2] 表示“42岁、年薪6.5万、购物8次、职业教师”那么那个“1”和“2”的差值“1”就真的能代表“程序员”和“教师”之间的差异程度吗显然不能。这个“1”只是个任意的编号它和年龄差7岁、收入差2万的物理意义完全不同。K-Means 不懂这个区别它只会冷冰冰地把“1”和“2”的差也平方进去严重扭曲了整体的距离计算。这就是它的“数字洁癖”——只认数字不认语义。我第一次在电商项目里硬上K-Means做用户分群时发现“学生”和“退休人员”被分到了同一个高消费簇里追查下去就是因为“学生”被编码为1“退休人员”被编码为9而其他几个高权重的数值特征如月均浏览时长、加购次数恰好把这两个极端人群的向量中心拉到了一起。模型没出错是我们的数据预处理和算法选型错了。2.2 K-Modes的“文字直觉”与它的局限K-Modes 是为了解决K-Means的这个痛点而生的专治“纯文字”数据。它的核心是“众数”Mode和“汉明距离”Hamming Distance。对于一个纯类别型数据集比如用户的职业、城市、会员等级K-Modes 会为每个簇找一个“最具代表性”的类别组合作为该簇的“中心点”mode。计算两个样本的距离就看它们在多少个特征上取值不同。比如样本A是 [程序员, 北京, 黄金]样本B是 [教师, 上海, 白银]那么它们在3个特征上都不同距离就是3。这个逻辑非常符合人类直觉职业不同、城市不同、等级不同那它们就是“很不一样”。K-Modes 完全不关心“北京”和“上海”在地理上有多近它只关心“北京”≠“上海”这个事实。这很干净也很纯粹。但它的局限同样致命它对任何数值型数据都束手无策。你不能对“年龄”求“众数”也不能用“是否相等”来定义“35岁”和“42岁”的距离。所以K-Modes 是K-Means的镜像——一个只吃素一个只吃肉而现实世界的数据从来都是“荤素搭配”。2.3 K-Prototypes不是发明而是“焊接”——一个精妙的加权融合框架K-Prototypes 的伟大之处不在于它发明了什么新奇的数学而在于它提供了一个极其优雅的“焊接”方案把K-Means和K-Modes的精华无缝拼接起来。它的核心思想是为每种数据类型定义一个专属的距离函数然后用一个可调节的权重参数 γgamma来平衡两者的重要性。整个距离公式长这样d(x, y) d_num(x_num, y_num) γ * d_cat(x_cat, y_cat)其中d_num是数值型部分的欧氏距离或其变体如曼哈顿距离d_cat是类别型部分的汉明距离即不匹配的特征数量γ是一个正实数是整个算法的“灵魂开关”。这个公式背后是一个深刻的工程哲学我们不强求所有数据都用同一种语言说话而是为每种语言配备一个翻译官并由一个总导演γ来决定每种语言在最终对话中该占多大音量。当 γ 很小比如0.1说明你认为类别型特征不那么重要模型会更倾向于按数值特征来分群当 γ 很大比如10说明你认为类别型特征是关键模型会优先保证同类别的样本待在一起。这个 γ 不是凭空捏造的超参数它有明确的物理意义——它是数值距离和类别距离之间的“汇率”。我通常会先用一个经验法则粗略估算γ ≈ σ² / (p_cat)其中 σ² 是所有数值特征的平均方差p_cat 是类别型特征的数量。这个估算能让初始的 γ 值落在一个合理的数量级上避免模型一开始就被某一种距离完全主导。K-Prototypes 的“混搭”不是混乱而是一种有章法的协同。它承认了数据的复杂性并用最直接的方式去拥抱它。3. 核心细节解析参数、距离与初始化每一个选择都关乎成败3.1 γGamma参数如何找到那个恰到好处的“平衡点”γ 是K-Prototypes里最核心、也最容易被误用的参数。很多初学者会把它当成一个需要暴力搜索的超参数像调K值一样在[0.01, 0.1, 1, 10, 100]里试一遍选个轮廓系数最高的。这在理论上可行但在实践中效率极低且容易陷入局部最优。更科学、更高效的做法是把它当作一个需要“校准”的物理量。我的标准流程是三步走第一步理论估算建立基准。如前所述计算所有数值特征的平均方差 σ²。比如你的数据里有“年龄”方差≈120、“年收入”方差≈2.5e9、“订单数”方差≈45那么平均方差 σ² ≈ (120 2.5e9 45)/3 ≈ 8.33e8。再数一下类别型特征数量 p_cat比如有“城市”50个值、“职业”20个值、“会员等级”4个值共3个。那么初始 γ ≈ 8.33e8 / 3 ≈ 2.78e8。这个数字看起来巨大但它反映了“年收入”的方差远大于“年龄”的事实意味着在原始尺度下“年收入”的微小变化就能主导整个距离计算。所以我们需要一个巨大的 γ 来“压住”它让类别型特征也能发出声音。第二步尺度归一化消除量纲干扰。这一步至关重要也是绝大多数教程忽略的。在代入上述公式前必须对所有数值型特征进行标准化Standardization即减去均值、除以标准差。这会让所有数值特征的方差都变为1从而让 σ² 的估算变得稳定可靠。否则一个单位是“元”的收入和一个单位是“岁”的年龄根本无法放在同一个天平上称量。归一化后上面的例子中σ² 就稳定在1左右那么 γ 的初始值就变成了 1/3 ≈ 0.33。这个数字就非常合理也便于后续微调。第三步业务驱动的微调而非纯指标驱动。轮廓系数Silhouette Score是个好指标但它只衡量“簇内紧密、簇间分离”的数学美感。在业务场景中我们更关心“分出来的簇是否能讲出一个好故事”。比如在用户分群中我们希望“高价值学生用户”、“中年家庭主妇”、“退休高净值用户”这些业务上天然存在的群体能各自形成一个纯净的簇。因此我的做法是固定K值比如K5在 γ ∈ [0.1, 1.0, 5.0, 10.0] 这几个关键点上运行算法然后人工检查每个簇的构成。我会导出每个簇的“类别型特征分布直方图”比如看“职业”分布。如果在 γ0.1 时所有簇的职业分布都高度混杂说明类别型特征没起作用如果在 γ10.0 时每个簇的职业都极度单一比如全是“程序员”但数值特征如收入却横跨了很大范围说明类别型特征被过度强调了。理想的状态是每个簇在职业上有一个主导类别占比60%同时在收入上也有一个相对集中的区间。我最终选定的 γ1.0就是在这种业务可解释性和数学合理性之间找到的甜蜜点。记住γ 的终极目标不是让轮廓系数最大化而是让业务分析师看到结果时能脱口而出“哦这个簇就是我们要找的‘新锐白领’”3.2 距离度量的“双轨制”数值与类别的并行计算K-Prototypes 的距离计算是严格区分轨道的。理解这一点是避免实操错误的前提。数值轨道Numerical Track这里使用的是欧氏距离的平方Squared Euclidean Distance而不是欧氏距离本身。公式是d_num Σ(x_i - y_i)²。为什么要用平方因为这样可以避免开根号的计算开销更重要的是它让距离的梯度计算更平滑有利于后续的质心更新。在实现时你不需要手动写循环。现代库如kmodes会自动对传入的数值型子矩阵进行向量化计算速度极快。但你要确保传给算法的数值型数据确实是经过标准化的浮点数矩阵而不是原始的、量纲混乱的整数。类别轨道Categorical Track这里使用的是汉明距离Hamming Distance即两个样本在类别型特征上取值不同的数量。公式是d_cat Σ I(x_j ≠ y_j)其中 I 是指示函数。这个计算非常轻量就是一个逐位比较。但这里有个极易被忽视的陷阱类别型特征的编码方式。你绝对不能用LabelEncoder把“北京”→1“上海”→2“广州”→3。因为这样“北京”和“上海”的距离是1“北京”和“广州”的距离是2算法会错误地认为“北京”和“广州”比“北京”和“上海”更远而这在类别数据中毫无意义。“北京”、“上海”、“广州”是完全平等的三个选项它们两两之间的距离都应该为1。正确的做法是使用One-Hot Encoding把一个有n个取值的类别特征变成n个二进制特征。但K-Prototypes算法本身并不接受One-Hot矩阵因为它会把一个原本的“城市”特征错误地当成n个独立的、稀疏的数值特征来处理。所以标准做法是保持类别型特征为原始的字符串或整数类别如“北京”、“上海”然后在算法内部它会自动进行“相等性”判断。kmodes库正是这样设计的你只需把类别列传进去它自己会搞定。我曾经在一个项目里为了“图省事”把城市做了LabelEncoder结果分出来的簇里“二线城市”用户被奇怪地割裂了花了整整一天才定位到这个编码错误。教训是类别数据永远用原始标签别动它。距离融合最终的总距离就是两个轨道距离的简单相加再乘以各自的权重。数值轨道权重为1类别轨道权重为 γ。这个加法是线性的意味着你可以清晰地看到一个样本对最终距离的贡献有多少来自它的数字属性有多少来自它的文字属性。这为后续的诊断和解释提供了极大的便利。3.3 初始化策略如何避免“第一眼就看走眼”K-Means 和 K-Prototypes 都是贪心算法对初始质心Centroids/Modes极其敏感。一个糟糕的初始化可能导致算法收敛到一个质量很差的局部最优解。K-Means 有 K-Means 这个黄金标准它通过概率采样让初始质心尽可能分散大大提高了找到全局最优解的概率。K-Prototypes 也有自己的“”版本叫做K-Prototypes。K-Prototypes 的核心思想是将K-Means的“距离概率”逻辑巧妙地迁移到混合数据上。它的步骤如下随机选择一个样本作为第一个质心。对于数据集中的每一个未被选为质心的样本 x计算它到当前所有已选质心的最小距离 d_min(x)。注意这个 d_min(x) 是用我们上面定义的、带 γ 的完整距离公式计算的。计算每个样本 x 被选为下一个质心的概率P(x) [d_min(x)]² / Σ[d_min(y)]²。也就是说离现有质心越远的样本被选中的概率越大。按照这个概率分布随机选择下一个质心。重复步骤2-4直到选出K个质心。这个过程确保了初始的K个“种子”在混合特征空间里是充分分散的为后续的迭代优化奠定了坚实的基础。在kmodes库中你只需要设置inithuang这是K-Prototypes原始论文作者的名字即可启用此策略。千万不要用默认的initrandom尤其是在K值较大5或数据维度较高时随机初始化失败的概率会指数级上升。我做过一个对比实验在同一个包含10个数值特征和5个类别特征的数据集上用initrandom运行10次轮廓系数的标准差高达0.15而用inithuang标准差仅为0.02。稳定性提升了一个数量级。这不仅仅是“跑得更稳”更是“结果更可信”的基础。4. 实操过程从零开始复现一个端到端的K-Prototypes项目4.1 环境准备与数据加载一个真实的电商用户数据集我们以一个模拟的电商用户数据集为例它包含了典型的混合型特征。数据结构如下CSV格式user_idageannual_incometotal_orderscityoccupationmembership_levellast_purchase_daysU001287500042BeijingStudentBronze15U0024512000018ShanghaiEngineerGold3........................可以看到age,annual_income,total_orders,last_purchase_days是数值型city,occupation,membership_level是类别型。我们将使用 Python 生态中最成熟、最可靠的kmodes库。它不是一个玩具库而是经过大量生产环境验证的工业级工具。pip install kmodes加载数据并进行初步探索import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler from kmodes.kprototypes import KPrototypes from sklearn.metrics import silhouette_score # 1. 加载数据 df pd.read_csv(ecommerce_users.csv) print(数据形状:, df.shape) print(\n数据类型:) print(df.dtypes) print(\n类别型特征的唯一值数量:) for col in [city, occupation, membership_level]: print(f{col}: {df[col].nunique()} 个)输出会显示city有50个值occupation有20个membership_level有4个。这是一个典型的、具有挑战性的混合数据集。4.2 数据预处理标准化与特征分离——不容妥协的两步预处理是K-Prototypes成功的一半。我们必须严格遵循“数值标准化、类别原样保留”的原则。# 2. 分离数值型和类别型特征 numeric_features [age, annual_income, total_orders, last_purchase_days] categorical_features [city, occupation, membership_level] # 3. 对数值型特征进行标准化 scaler StandardScaler() df_numeric_scaled scaler.fit_transform(df[numeric_features]) # 将标准化后的数组转回DataFrame方便后续与类别数据合并 df_numeric_scaled pd.DataFrame(df_numeric_scaled, columnsnumeric_features, indexdf.index) # 4. 准备完整的特征矩阵 # K-Prototypes要求输入是一个DataFrame其中数值列是float类别列是object或category X pd.concat([df_numeric_scaled, df[categorical_features]], axis1) # 确保类别列是字符串类型避免pandas的自动类型推断出错 for col in categorical_features: X[col] X[col].astype(str) print(预处理后特征矩阵形状:, X.shape) print(预处理后特征类型:) print(X.dtypes)这一步完成后X就是我们要喂给K-Prototypes的“纯净”输入。它的数值列是标准化后的浮点数类别列是原始的字符串。任何偏离这个范式的操作都会导致算法报错或结果失真。4.3 核心建模K-Prototypes 的完整配置与训练现在我们进入最关键的建模环节。我们将使用前面讨论的理论估算来设定 γ并启用K-Prototypes初始化。# 5. 计算理论γ值 # 数值特征的平均方差标准化后理论上应为1但我们还是计算一下确认 sigma_squared np.mean([np.var(df_numeric_scaled[col]) for col in numeric_features]) p_cat len(categorical_features) gamma_initial sigma_squared / p_cat print(f理论初始γ值: {gamma_initial:.4f}) # 6. 定义数值型特征的列索引K-Prototypes需要知道哪些列是数值的 # 在X中前4列是数值型后3列是类别型 numeric_indices list(range(len(numeric_features))) # [0, 1, 2, 3] categorical_indices list(range(len(numeric_features), len(X.columns))) # [4, 5, 6] # 7. 创建并训练K-Prototypes模型 # n_clusters5 是我们根据业务需求预设的簇数 kproto KPrototypes( n_clusters5, inithuang, # 启用K-Prototypes初始化 max_iter20, # 迭代次数通常10-20足够 n_init10, # 重新初始化的次数取最优结果 verbose1, # 打印训练日志 random_state42 # 保证结果可复现 ) # 8. 训练模型 # 注意fit_predict方法返回的是每个样本的簇标签 clusters kproto.fit_predict(X, categoricalcategorical_indices) # 9. 将簇标签添加回原始DataFrame df[cluster] clusters print(聚类完成簇标签已添加。)这段代码执行后你会在控制台看到类似这样的日志Iteration 1, cost: 12456.78 Iteration 2, cost: 11987.45 ... Iteration 15, cost: 10234.56 Converged at iteration 15.cost是算法的总损失函数值它会随着迭代单调下降最终收敛。这个值本身没有绝对意义但它的下降趋势是算法健康运行的标志。4.4 结果分析与业务解读让数字讲出人的故事模型训练完毕真正的价值才刚刚开始。我们需要深入分析每个簇将其翻译成业务语言。# 10. 分析每个簇的统计特征 def analyze_cluster(df, cluster_colcluster): # 数值型特征的统计摘要 numeric_summary df.groupby(cluster_col)[numeric_features].agg([mean, std]).round(2) # 类别型特征的分布摘要 categorical_summary {} for col in categorical_features: # 计算每个簇中各类别值的占比 pivot pd.crosstab(df[cluster_col], df[col], normalizeindex) * 100 categorical_summary[col] pivot.round(1) return numeric_summary, categorical_summary numeric_summ, cat_summ analyze_cluster(df) print( 数值型特征各簇均值 ) print(numeric_summ[mean]) print(\n occupation 各簇分布%) print(cat_summ[occupation])输出可能如下 数值型特征各簇均值 age annual_income total_orders last_purchase_days cluster 0 26.3 52000 65.2 12.5 1 38.7 115000 22.1 4.2 2 52.1 88000 35.8 28.7 3 29.5 65000 58.4 18.3 4 47.2 132000 15.6 2.1 occupation 各簇分布% occupation Engineer Student Teacher Doctor ... cluster 0 5.2 82.3 4.1 0.0 ... 1 78.5 2.1 12.4 3.0 ... 2 8.7 1.5 65.2 5.1 ... 3 12.4 65.8 8.3 0.0 ... 4 85.2 0.5 2.1 8.7 ...现在我们可以给每个簇命名了簇0校园新锐—— 年龄最小26.3岁以学生为主82.3%订单数最多65.2但收入最低5.2万。他们是价格敏感、高频次、重社交的典型年轻用户。簇1技术中产—— 年龄38.7岁工程师占比最高78.5%收入高11.5万但订单数中等22.1最近购买很活跃4.2天。他们是理性、高价值、高忠诚度的核心用户。簇2教育主力—— 年龄最大52.1岁教师占比最高65.2%收入中等8.8万购买频次中等35.8但最近一次购买较久28.7天。他们是稳定、有耐心、需要长期培育的用户。簇3都市白领—— 年龄29.5岁学生和工程师混合收入和订单数都居中。他们是潜力股需要个性化内容激发。簇4高净值专家—— 年龄47.2岁医生和工程师为主收入最高13.2万但订单数最少15.6购买最频繁2天。他们是高客单、低频次、高服务要求的顶级用户。提示这个命名过程是数据科学家和业务方共同完成的。不要闭门造车。把这份分析报告拿给市场部、产品部的同事看让他们来确认“这个‘校园新锐’是不是就是我们正在策划的开学季活动的目标人群”只有当业务方点头说“对就是他们”这个聚类结果才算真正落地。5. 常见问题与排查技巧实录那些只有踩过坑才知道的事5.1 “ValueError: Input contains NaN, infinity or a value too large for dtype(float64)” —— 数据清洗的终极拷问这是K-Prototypes报错率最高的异常。它不像K-Means那样会默默地给你一个警告而是直接中断。原因几乎总是数据中存在缺失值NaN或无穷大inf。排查与解决在标准化之前就必须检查df[numeric_features].isnull().sum()。如果发现有缺失必须处理。切记StandardScaler无法处理NaN。数值型缺失值的处理绝对不要用0填充这会严重扭曲数据分布。我的首选是用该特征的中位数median填充。中位数对异常值不敏感比均值更鲁棒。df[col].fillna(df[col].median(), inplaceTrue)。类别型缺失值的处理这更棘手。用“Unknown”填充是最常见的做法但要小心。“Unknown”本身会成为一个新的、可能很大的类别影响聚类效果。更好的做法是先分析缺失的原因。如果缺失集中在某个业务环节比如新上线的城市还没填职业可以考虑用一个特殊的标记如“MISSING_CITY”并在后续分析中单独审视这个簇。无穷大的排查这通常源于数据计算错误比如用0做了除数。用np.isinf(df[numeric_features]).sum().sum()检查。一旦发现必须追溯上游ETL流程修复数据源。5.2 “The algorithm did not converge. Try a larger max_iter.” —— 收敛失败的真相这个警告听起来很吓人但它往往不是算法的问题而是你的数据或参数出了问题。根本原因与对策原因1γ值设置不当。如果 γ 设置得过大比如1000类别型距离会完全压制数值型距离导致质心更新在类别维度上“卡死”因为类别型质心mode的更新规则是“取众数”而众数可能在一轮迭代中完全不变。对策回到第3.1节用理论估算和业务微调的方法把 γ 降下来。一个健康的 γ 值应该让数值距离和类别距离在同一数量级上。原因2K值设置过大。当你试图把数据分成太多簇时每个簇的样本数会很少导致众数mode不稳定质心无法有效更新。对策先用肘部法则Elbow Method或轮廓系数粗略确定一个合理的K值范围比如2-8再在这个范围内精细搜索。原因3数据本身不适合聚类。如果所有样本在所有特征上都高度相似那么无论怎么分簇间差异都很小算法会在一个很小的误差范围内反复震荡。对策画一个PCA降维后的散点图直观地看看数据在二维空间里是否真的有自然的分组趋势。如果没有强行聚类是没有意义的。5.3 “Why are all my samples assigned to the same cluster?” —— 单簇陷阱这是最令人沮丧的结果算法跑完了但所有用户都被分到了簇0。这通常指向一个更隐蔽的问题特征缩放的灾难性失败。深度排查检查标准化后的数值特征print(df_numeric_scaled.describe())。重点关注std标准差列。如果某个特征比如annual_income的标准差仍然是几万说明StandardScaler根本没起作用。最常见的原因是你在fit_transform之前对数据进行了某种修改比如dropna导致scaler拟合的对象和transform的对象不是同一个DataFrame。对策严格遵循“先切片、再缩放”的顺序确保scaler.fit_transform(df[numeric_features])中的df[numeric_features]是你最终要使用的、且已经处理好缺失值的那份数据。检查类别型特征的编码再次确认你传给fit_predict的XDataFrame 中类别列的dtype是object或string而不是int64。如果它是int64kmodes库会把它误认为是数值型特征从而用欧氏距离去计算导致结果完全错误。用X.dtypes打印出来亲眼确认。检查categorical参数这个参数必须是一个列表里面是类别型特征在X中的整数列索引。例如如果X有7列类别型特征是最后3列那么categorical[4,5,6]。如果误写成categorical4一个整数或categorical[4,5,6,7]索引越界都会导致不可预测的行为。5.4 性能瓶颈当数据量达到百万级时怎么办kmodes库在处理几十万样本时表现优异但当数据量突破百万单机内存和计算时间会成为瓶颈。实战优化方案方案1采样后验分配。这是我最常用、也最有效的方案。先对全量数据进行分层随机采样Stratified Sampling确保样本在关键业务维度如城市、会员等级上的分布与全量数据一致。用这个10%-20%的样本集训练K-Prototypes模型得到5个稳定的质心。然后用这个训练好的模型对全量数据进行predict不是fit_predict将每个样本分配到最近的簇。kmodes的predict方法是O(NKD)的非常快。这个方案牺牲了极小的精度因为质心是基于样本训练的但换来了百倍的性能提升和可管理的内存占用。方案2特征工程降维。对于高维类别型特征比如有上千个城市的“城市”列直接使用会极大增加汉明距离的计算量。可以考虑用目标编码Target Encoding用该城市用户的平均订单数来替代原始的城市名称。这样就把一个高基数的类别特征转化为了一个低维度的数值特征可以交给K-Means处理再与K-Prototypes结合。但这需要谨慎要防止数据泄露。方案3分布式计算。如果以上都不行可以考虑将kmodes的核心逻辑距离计算、质心更新用 Dask 或 Spark 重写。但这属于高级定制通常只有在数据规模达到千万级以上且业务价值极高时才值得投入。我在一个拥有230万用户的金融风控项目中就采用了“采样后验分配”方案。用20万样本训练耗时12分钟用训练好的模型预测全量230万用户耗时仅47秒。业务方对这个“小时级”的交付速度非常满意而模型的业务效果与全量训练相比AUC仅下降了0.002完全可以接受。工程的本质就是在约束条件下找到那个性价比最高的解。

本月热点