十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

特征工程核心技法:特征构建与特征拆分如何提升机器学习模型上限

特征工程核心技法:特征构建与特征拆分如何提升机器学习模型上限 这次我们直接进入机器学习特征工程中最容易被忽视、但决定模型上限的两个环节特征构建和特征拆分。很多人在建模前只会把表格原样丢给算法结果模型交叉项、时间周期性、类别组合这些最有价值的信号全部丢掉也有不少人一股脑生成几百维新特征最后模型没变强训练时间翻了几倍还引入了数据泄漏。这篇内容把这两个概念拆开来讲清楚它们分别解决什么问题、常用方法有哪些、代码上怎么落地、组合使用时怎么避免翻车。先给一个最简单的大白话版本特征构建是在已有特征的基础上“造”新的特征特征拆分是把一个复杂特征“拆”成多个更细、更有业务含义的特征。前者做加法后者做分解。两者都是在不新增原始数据的情况下把信息密度提上去。本文会给出完整操作路径和可直接运行的示例代码覆盖时间特征、数值特征、类别特征、交互特征、分箱与聚合等常见动作同时把数据泄漏、稀疏矩阵、过拟合这些坑单独拎出来讲。如果你正在做机器学习入门、准备面试、或者拿真实数据集做比赛和项目这篇文章可以直接收藏。下面按工程化流程来写先建立统一认知再逐个方法上代码最后给出一套可复用的特征处理管线和排查清单。1. 特征构建与特征拆分核心能力速览先看一张速览表把两个概念放在同一个坐标系里对比后面所有细节都围绕这张表展开。能力项特征构建 Feature Construction特征拆分 Feature Splitting核心动作由已有特征组合、变换、聚合出新特征将一个复合特征分解成多个子特征本质做加法扩展特征维度做拆分挖掘已有信息的粒度典型场景捕捉非线性关系、交互效应、领域规律解析时间、地址、文本、编号等富信息字段常用方法多项式特征、交互特征、聚合统计、分箱、领域公式时间拆分、数值区间拆分、类别拆分、文本分词统计数据维度影响维度增加可能引入稀疏性通常维度小幅增加信息更细主要风险过拟合、数据泄漏、计算量上升粒度分裂导致稀疏、语义丢失与特征选择关系生成候选特征后常配合选择/降维拆分出的子特征也要评估有效性工具依赖pandas、numpy、scikit-learnpandas、numpy、scikit-learn表格里的对比不需要死记。实际建模时两个动作往往是交替出现的先拆分出更细的字段再基于拆分结果构建更有表达力的特征。比如日志数据里的“时间戳”字段先拆出小时、星期、是否节假日然后再用“小时 × 星期”构建交互特征这就是典型的“先拆后建”。理解一个关键边界特征拆分和特征提取不一样。特征拆分是把原始字段分解为可解释的组成部分比如把“2025-06-01 14:30:00”拆成日期和时间特征提取是通过PCA、嵌入等算法将数据映射到低维空间结果往往没有直观业务含义。本文讨论的范围限定在可解释的特征工程范畴。2. 适用场景与选择逻辑不是所有数据集都需要做特征构建和特征拆分。先判断数据类型和模型类型再决定投入多少精力。先说特征拆分最适用的场景。第一类是时间字段原始时间戳对树模型和线性模型都不友好模型很难自己学到“周末销量更高”“凌晨3点是低谷”这种规律。第二类是富信息文本字段比如地址、身份证号、设备型号、商品编码这些字段拆开后才真正有意义。第三类是连续值跨度很大的字段比如收入、交易金额、点击量直接输入模型容易被极端值主导拆分或分箱后更稳。第四类是类别字段中的隐藏层级比如“品牌-品类-型号”合在一个编码里不拆就无法建模。特征构建最适用的场景也包括四类需要表达非线性关系时可以用多项式特征或者业务公式场来构造非线性关系需要表达特征之间相互作用时可以做交叉特征可以捕获两个特征的依赖效应需要表达群体统计规律时可以按类目分组做聚合统计特征比如“用户当前城市的总订单量”需要提升树模型或线性模型表现时构造与业务密切相关的领域特征往往比换算法更有效。那什么时候不要强行做数据量很小的表格型数据特征越多越容易过拟合。业务解释要求极高的场景复杂交互特征会让服务方难以解释。如果模型本身是深度学习且接受原始特征对人工特征工程的需求也会明显下降尤其是图像、文本、语音这类非结构化数据。特征构建和特征拆分主要价值区间是结构化表格数据配合线性模型、树模型、梯度提升树使用效果最明显。从材料看CampusX 这类课程内容会把特征工程放在数据预处理和模型选择之间来讲原因也在这里模型只能看到你给它的信息原始字段里的时间、文本、组合关系不会自动变成模型能理解的规律。3. 特征构建的常用方法与代码实现特征构建的方式可以分成四类数学变换、交互特征、分箱离散化、聚合统计。下面分别给出可直接运行的代码思路。3.1 多项式特征与交互特征多项式特征是把单个特征的幂次项、多个特征的乘积项作为新特征加入数据集核心目的是让线性模型能拟合非线性边界也让树模型获得更丰富的分裂候选。先看一个最简单的构造方式。假设有age和income两个特征可以构造age^2、income^2、age * income等新特征。import pandas as pd import numpy as np from sklearn.preprocessing import PolynomialFeatures df pd.DataFrame({ age: [25, 32, 47, 51], income: [50000, 80000, 120000, 90000] }) poly PolynomialFeatures(degree2, include_biasFalse) poly_features poly.fit_transform(df[[age, income]]) feature_names poly.get_feature_names_out([age, income]) df_poly pd.DataFrame(poly_features, columnsfeature_names) print(df_poly.head())输出列会变成age、income、age^2、age*income、income^2。需要注意degree2时维度从 2 变 5degree3时特征数量会明显膨胀所以一般情况下先从 2 阶开始尝试配合特征选择使用。如果不想用PolynomialFeatures也可以手动构造业务上有明确含义的交互特征。最典型的例子是“面积 × 单价 总价”、“点击率 点击量 / 曝光量”。df[price_per_age] df[income] / df[age] df[income_squared] df[income] ** 2这种手动构造的优势是特征有明确业务解释也更容易在后续模型分析中定位特征重要性。但需要注意除零问题对可能为 0 的分母要提前处理。3.2 聚合统计特征聚合特征是把一个群体的统计量作为新特征填充回原始样本在风控、推荐、电商场景里非常常见。它的逻辑是每个样本不只携带自身信息还携带其所属群体的整体表现。df pd.DataFrame({ user_id: [u1, u1, u2, u2, u2], order_amount: [100, 250, 80, 120, 60] }) agg_features df.groupby(user_id)[order_amount].agg( user_total_orderscount, user_avg_amountmean, user_max_amountmax, user_min_amountmin, user_std_amountstd ).reset_index() df df.merge(agg_features, onuser_id, howleft) print(df)这里的关键是一句提醒聚合特征只能在训练集内部计算一定要防止用测试集或全量数据去算统计量否则会出现数据泄漏。解决方案是先把训练集和测试集分开再各自计算聚合特征或者使用交叉验证内部的统计计算。更稳妥的做法是在时间序列场景中只用过去数据算统计量不能用未来数据填进当前样本。3.3 分箱与离散化分箱的常见动机有三个降低连续特征的噪声敏感性、处理长尾分布、让模型学到非线性分段关系。分箱本身既可以看作特征构建中的离散化操作也可以看作数据的数值特征拆分关键在于后续如何编码。直接用示例说明df pd.DataFrame({amount: [10, 200, 1500, 8000, 30000]}) # 等宽分箱 df[amount_bin_width] pd.cut( df[amount], bins4, labels[low, mid, high, very_high] ) # 等频分箱每个箱体样本量接近 df[amount_bin_freq] pd.qcut( df[amount], q4, labels[q1, q2, q3, q4] ) # 自定义分箱业务指定阈值 bins [0, 1000, 5000, 20000, 100000] df[amount_bin_custom] pd.cut( df[amount], binsbins, labels[tiny, small, medium, large] ) print(df)等宽分箱适合分布相对均匀的数据等频分箱适合长尾数据。分箱之后通常还要配合 one-hot 编码或目标编码才能进入模型。分箱数量不宜过多一般 4 到 10 箱过多又会回到稀疏问题。4. 特征拆分的常用方法与代码实现特征拆分的核心是把一个“复合字段”拆成多个“原子字段”。下面按时间、数值、类别文本三个方向给出代码。4.1 时间特征拆分时间字段是最典型的拆分对象。一个完整时间戳可以拆出年份、月份、日期、星期几、小时、分钟、一年中的第几天、是否周末、是否月初月末等维度。这些信息对不同业务的含义完全不同电商关注星期和小时金融风控关注月份和时段工业预测关注周期和班次。df pd.DataFrame({ timestamp: [ 2025-06-01 08:30:00, 2025-06-02 14:45:00, 2025-06-07 23:10:00 ] }) df[timestamp] pd.to_datetime(df[timestamp]) df[year] df[timestamp].dt.year df[month] df[timestamp].dt.month df[day] df[timestamp].dt.day df[weekday] df[timestamp].dt.weekday df[hour] df[timestamp].dt.hour df[minute] df[timestamp].dt.minute df[day_of_year] df[timestamp].dt.dayofyear df[is_weekend] df[weekday].isin([5, 6]).astype(int) print(df[[timestamp, weekday, hour, is_weekend]])拆出hour和weekday后还可以继续用正弦余弦编码表达周期性。比如hour的 23 点和 0 点在数值上距离很远但在时间上其实很近直接输入模型会丢失周期性。一种常见做法是转换到圆周坐标df[hour_sin] np.sin(2 * np.pi * df[hour] / 24) df[hour_cos] np.cos(2 * np.pi * df[hour] / 24)星期几也可以做同样的处理周期为 7。这种编码方式对线性模型和神经网络尤其有效对树模型不一定有增益因为树模型本身可以做分段分裂。只有在采用边时损失较小但是对深度学习特征影响比较大的场景再考虑正弦余弦编码。4.2 数值特征拆分数值特征不只有“拆时间”这一种路径。在很多场景中一个连续值字段可以拆成“业务属性”和“数值属性”。举例来说订单金额字段可以拆出“是否大于阈值”“属于哪个金额档位”“金额的销量占比”等。此外像车牌号、身份证、学号这类编码字段虽然表面是文本或编号内部实际包含地区、出生日期、校验位等多段信息也是拆分的典型对象。对连续数值本身也可以做“趋势”与“波动”的拆分。一根时序上的累计值可以拆出当前值和前一周期差值的差分特征、环比变化率特征这样比直接把原始数值输入模型更有信息量。df pd.DataFrame({ date: pd.date_range(2025-01-01, periods5, freqD), sales: [100, 120, 115, 140, 160] }) df[sales_lag1] df[sales].shift(1) df[sales_diff] df[sales] - df[sales_lag1] df[sales_pct_change] df[sales].pct_change() print(df)这种拆法在时间序列和风控建模里非常常用因为它把一个绝对数值变成了“变化量”模型更容易学到上升趋势和突变点。4.3 类别特征与文本特征拆分类别特征的拆分方向取决于原始字段里是否埋了额外信息。地址字段可以拆成省、市、区、街道产品编码可以拆成品牌、系列、型号日志字符串可以拆出协议类型、状态码、耗时区间。示例把“省份-城市-区域”形式的地址拆分出来。df pd.DataFrame({ address: [北京市-朝阳区-望京街道, 广东省-深圳市-南山区, 浙江省-杭州市-西湖区] }) split_df df[address].str.split(-, expandTrue) split_df.columns [province, city, district] df pd.concat([df, split_df], axis1) print(df)文本特征拆分最常见的操作是把长文本拆成词或 n-gram然后做词频、TF-IDF 特征。这里用一个简单例子演示如何把句子拆成单词并生成词频列。from sklearn.feature_extraction.text import CountVectorizer corpus [ 机器学习 特征工程 特征构建, 机器学习 特征工程 特征拆分, 机器学习 实战 项目 ] vectorizer CountVectorizer() X vectorizer.fit_transform(corpus) feature_names vectorizer.get_feature_names_out() df_tf pd.DataFrame(X.toarray(), columnsfeature_names) print(df_tf)需要注意CountVectorizer默认用空格分词对中文文本通常需要先分词比如使用 jieba。这里只是演示整体流程实际项目中要先做中文分词再进入向量化。5. 特征构建与特征拆分的组合处理流程实际项目中特征构建和特征拆分很少单独使用。这里给出一个表格型数据的标准处理流程示例覆盖“拆分 → 构建 → 选择”的完整链路。5.1 完整处理管线示例假设有一份电商订单表主要字段包括order_id、user_id、category、amount、create_time。目标是预测订单是否会被取消。第一步先做特征拆分create_time拆出hour、weekday、is_weekend、monthcategory保留原始类别同时拆出大类和子类如果字段本身是复合编码则按分隔符拆分第二步再做特征构建构造amount / 用户历史平均金额得到用户消费水平偏移量构造hour × weekday交互特征捕捉特定时段与星期的组合效应按user_id聚合构建用户历史订单数、用户历史取消率按category聚合构建该类目近期平均金额第三步检查特征有效性使用VarianceThreshold过滤近似常数列使用相关性分析删除高度相关特征最后用特征重要性排序或模型系数筛选保留特征这张流程可以串起本文所有方法。实际处理时最重要的原则是所有构建和拆分操作必须放到Pipeline或自定义Transformer中确保训练和测试走完全相同的逻辑避免测试数据被单独加工。5.2 是否需要归一化和标准化标准化和归一化经常和特征构建混在一起说但其实不是一回事。标准化把数据变为均值为 0、方差为 1归一化把数据缩放到 [0,1] 区间它们都属于无量纲化不增加新信息更像数据处理而不是特征构建。需要说明的是线性模型、神经网络、K 近邻等对尺度敏感树模型基本不受影响。做特征构建时要记住顺序先做拆分和构建再做无量纲化。如果先用多项式、交互等方式构造特征再缩放会导致缩放系数计算混乱。5.3 拆分是否等于 one-hot二者有交叉但并不等价。one-hot 把类别特征转成多个 0/1 列本质上也是一种“拆分”——把一个类别字段拆成多个布尔字段。但特征拆分的外延更宽它还包括把时间拆成年月日、把数值拆成差分和比例、把地址拆成省市。one-hot 是类别特征拆分的一种编码实现而不是全部。使用 one-hot 时要注意稀疏问题类别基数很大的字段例如城市、用户 ID、商品 ID直接 one-hot 会产生高维稀疏矩阵训练效率下降。此时优先考虑目标编码、频数编码、嵌入或者先做业务分组合并少见的类别。6. 易错点数据泄漏、稀疏性与过拟合特征工程做得越多踩坑概率越大。下面三个问题是新手最常遇到的。6.1 数据泄漏数据泄漏是指模型在训练时“看到”了本不该看到的信息导致离线指标虚高、上线后效果暴跌。特征构建中最常见的泄漏源有三个。第一个是聚合特征使用了全量数据。比如先对整个数据计算用户平均金额再划分训练集和测试集测试集的信息已经进入了训练集的特征值。第二个是时间序列特征没有做时序切分。比如用未来信息构造当前样本的统计量测试时根本没这个信息。第三个是标签信息被卷进特征。例如用“用户是否取消”去聚合出用户级别统计量再作为特征预测同一个标签离线效果会好到离谱。避免方法拆分训练测试集时特征计算只放在训练集内部完成交叉验证时在每一折内部重新计算特征时间序列按时间顺序切分不能随机切分。6.2 稀疏性每生成一个新类别列或高基数 one-hot 列数据矩阵就稀疏一分。样本总量不大、特征又高维稀疏时线性模型容易学到噪声树模型会偏好取值极少的特征做分裂看起来重要度很高实际上只是偶然命中。应对方法先用频数过滤低频类别不要一次性把多项式扩展到三次以上用线性模型时加 L1 正则或嵌入特征选择对高基数类别用目标编码或计数编码代替 one-hot。6.3 过拟合特征构建是维度扩张的过程特征是模型的“弹药”但弹药过多同样危险。特征数量接近样本数量时任何模型都可能记住训练集噪声。验证方式比较简单粗暴对比构造特征前后、训练集和验证集的指标差。如果训练集指标明显提升、验证集指标基本不变甚至下降就需要缩减特征集或增加正则。还有一种更稳妥的做法把特征工程产生的候选特征全部放进一个标准化流程然后用带交叉验证的特征选择方法做筛选保留真正有增益的特征。特征选择是特征工程闭环的最后一环不能省。7. 特征工程常见问题与排查方法下面把特征工程过程中的常见问题整理成一张排查表方便对照。问题现象可能原因排查方式解决方案增加特征后验证集指标反而下降特征过拟合或数据泄漏对比训练集和验证集指标差距检查特征计算是否只用了训练集删除无关特征改用交叉验证内部特征计算模型训练时间明显变长多项式次数过高one-hot 产生大量稀疏列打印特征矩阵 shape 和稀疏率降低多项式次数使用目标编码或频数编码时间字段拆分后模型没有提升树模型本身可以处理时间分裂或拆分维度与业务无关做特征重要性分析观察新特征排序增加周期性编码或构造滞后、差分特征聚合特征出现极高重要度可能使用了标签或未来信息检查特征计算逻辑确认没有用到标签列做聚合重建特征保证时间和信息边界正确分箱后结果波动大箱体边界不合理样本分布不均查看每个箱体的样本量和目标均值改用等频分箱或自定义业务阈值中文文本向量化后全部是零列没有做分词直接按空格切分中文查看向量化结果的非零列先使用 jieba 分词再进入向量器特征数量爆炸难以管理构建和拆分逻辑没有固化在管线里检查是否每次实验手动拼特征使用 sklearn Pipeline 或自定义 Transformer 统一处理新特征与旧特征高度相关构建了冗余的多项式或统计特征计算相关性矩阵观察高相关对删除一个优先保留业务解释强的特征这张表在项目开发阶段可以直接当 checklist 用。每次加特征前先问三个问题这个特征在预测时是否拿得到计算它是否用了训练集之外的信息它带来的维度增加是否值得8. 最佳实践与工程化建议最后把这套方法落成可执行的工程建议适合直接带入实际项目。第一先定特征边界再做特征编辑。在项目启动时就明确哪些字段是原始可用字段、哪些是衍生字段、标签字段不能参与任何特征计算。最好把原始数据保留一份所有后续操作在副本上完成。第二把特征工程代码固化到管线中。建议把特征构建和特征拆分写成自定义Transformer接收 DataFrame 输入输出处理后的特征矩阵。这样训练、验证、上线都走同一套逻辑避免预测时少拼接一个统计特征导致维度不匹配。from sklearn.base import BaseEstimator, TransformerMixin class FeatureEnricher(BaseEstimator, TransformerMixin): def __init__(self): pass def fit(self, X, yNone): return self def transform(self, X): X X.copy() X[hour] pd.to_datetime(X[create_time]).dt.hour X[weekday] pd.to_datetime(X[create_time]).dt.weekday X[is_weekend] X[weekday].isin([5, 6]).astype(int) X[amount_per_age] X[amount] / (X[age] 1e-6) return X这个示例里的amount_per_age只是一种演示实际要根据业务设计同时要处理除零问题。编写Transformer的核心目的是让特征逻辑可以反复使用而不是每次建模手动复制粘贴一堆 pandas 代码。第三先拆分再构建后选择。项目里优先把原始复合字段拆开因为拆分出来的字段是后续构建的基础然后在拆分结果上构建交互、聚合、差分特征最后用特征选择方法收一下维度。第四特征数量与样本量比例要保持警惕。经验上特征数量不建议超过样本量的 1/10 到 1/5 的合理性边界不能一概而论但一旦出现特征数量大于样本量基本意味着需要强正则和严格特征选择。第五使用验证集评估特征增量。每加入一类新特征不要只看训练集分数要看验证集提升。如果多轮构造的特征对验证集没有正向贡献及时止损而不是继续堆特征。第六合规与安全边界同样适用。如果项目涉及用户隐私、地址、手机号等敏感字段特征工程中不能把这些字段以原始形式直接暴露在模型服务中。拆分和脱敏要同时考虑例如地址拆到省市粒度后敏感的详细街道信息应做脱敏处理不进入模型特征。涉及真实业务数据时先确认数据使用授权再开始特征构造。9. 从 CampusX 学习路径看下一步把特征工程放进完整建模流程特征构建和特征拆分不是孤立的技术点。在 CampusX 这类系统性课程体系里特征工程通常被放在探索性数据分析之后、模型训练之前它与缺失值处理、异常值处理、特征编码共同构成数据预处理的主要环节。理解了拆分和构建下一步要补的能力是特征选择和模型评估的联动。实践中推荐按这样安排优先级先做最小可行模型用原始特征跑一个基线记录指标然后加入时间拆分、类别拆分再添加交互和聚合特征每加一步记录验证集指标变化。这样不会在特征工程里迷失方向也能清楚每一类操作到底带来了多少提升。如果这个数据集是时间序列优先关注滞后特征、差分特征和窗口统计。如果数据集是高基数类别较多的风控或推荐数据优先关注聚合特征、目标编码和交互特征。如果数据集来自传感器或工业生产优先关注均值、方差、斜率、峰峰值这类统计特征以及基于时间窗口的拆分。以上都是比较通用的方向最后还是要用验证集指标来决定取舍。特征工程的价值不是堆出几百个特征证明代码能力而是让模型把你对业务的理解吸收进去。拆得合理、建得克制、选得干净比盲目加特征更接近建模的本质。
返回列表