机器学习特征工程:缩放、编码、聚合、嵌入与自动化

简介: 本文系统讲解特征工程核心技巧:数值缩放与变换、类别编码、时间周期处理、文本统计与嵌入、地理距离特征、聚合与滞后特征、自动化生成及科学筛选方法。强调“好模型源于好特征”,而非复杂算法,突出实用性与防坑指南。

好模型的秘诀不在于更花哨的算法,而在于更好的特征。

🔢 第1部分:数值 特征

1、1 缩放

多数机器学习算法对尺度敏感。一个取值范围在0到1,000,000的列,会在训练中压制一个取值范围仅0到1的列。

常用的三种缩放器各有适用场景:StandardScaler适合近似正态分布的数据,也是最常见的选择;MinMaxScaler将值压缩到0和1之间,适合神经网络;RobustScaler基于中位数和四分位距(IQR)而非均值,在数据中存在明显异常值时更为稳健。

 from sklearn.preprocessing import RobustScaler  
 df['salary_scaled'] = RobustScaler().fit_transform(df[['salary']])

⚠️ 缩放器只能在训练集上拟合。在完整数据集上拟合会引入信息泄漏。

1、2 对数变换

数值列严重右偏时——收入、价格、营收都是典型例子——对数变换可以拉平分布。

 import numpy as np  
 df['revenue_log'] = np.log1p(df['revenue'])   # log1p可以安全处理零值

1、3 分箱

连续数值有时转换为类别反而更有用。

pd.cut()

生成等宽分箱,适合分布均匀的数据;

pd.qcut()

按分位数切分,每个箱中样本量相等,更适合偏斜分布。

 df['age_group'] = pd.cut(df['age'], bins=[0, 18, 35, 55, 100],  
                           labels=['teen', 'young_adult', 'adult', 'senior'])

1、4 交互特征

两个特征组合后的表达能力往往超过各自单独使用。

 df['price_per_sqft'] = df['price'] / df['sqft']  
 df['debt_to_income']  = df['debt'] / df['income']

线性模型中,多项式特征有助于捕获非线性关系:

 from sklearn.preprocessing import PolynomialFeatures  
 poly = PolynomialFeatures(degree=2, include_bias=False)  
 # Creates: age, salary, age², salary², age × salary

1、5 裁剪异常值

与其删除异常值,不如将它们截断到合理的百分位范围。

 lower = df['salary'].quantile(0.01)  
 upper = df['salary'].quantile(0.99)  
 df['salary_clipped'] = df['salary'].clip(lower=lower, upper=upper)

🏷️ 第2部分:类别特征

2、1 独热编码

将每个类别展开为独立的0/1列,适用于没有内在顺序的名义类别。

 df_encoded = pd.get_dummies(df, columns=['city'], drop_first=True)

⚠️ 如果某列包含500个唯一类别,独热编码会产生500列。这种情况应改用目标编码。

2、2 标签编码

为每个类别赋一个整数,仅限数据确实存在顺序关系的场景。

 df['education'] = df['education'].map({  
     'High School': 0, 'Bachelor': 1, 'Master': 2, 'PhD': 3  
 })

不要对城市名一类的名义数据做标签编码——模型会错误地推断 London > Mumbai。

2、3 目标编码

用对应分组的目标变量均值替换每个类别值,处理高基数列时收效明显。

 from category_encoders import TargetEncoder  
 df['city_encoded'] = TargetEncoder().fit_transform(df['city'], df['churn'])

⚠️ 风险在于数据泄漏。生产环境中应采用交叉折叠目标编码。

2、4 频率编码

用每个类别的出现频率替换原始值。做法简单,但在树模型中的效果常常出人意料。

 freq_map = df['city'].value_counts(normalize=True)  
 df['city_freq'] = df['city'].map(freq_map)

2、5 二进制编码

介于标签编码与独热编码之间的折中方案,在保持较少列数的前提下处理高基数特征。

 from category_encoders import BinaryEncoder  
 df_encoded = BinaryEncoder().fit_transform(df[['city']])  
 # 100 categories → only 7 binary columns

📅 第3部分:日期时间特征

原始日期对多数模型没有意义,需要把其中蕴含的时间信息提取出来。

3、1 标准提取

 df['order_date'] = pd.to_datetime(df['order_date'])  

 df['month']        = df['order_date'].dt.month  
 df['day_of_week']  = df['order_date'].dt.dayofweek  
 df['is_weekend']   = df['day_of_week'].isin([5, 6]).astype(int)  
 df['quarter']      = df['order_date'].dt.quarter  
 df['days_since']   = (df['order_date'] - pd.Timestamp('2024-01-01')).dt.days

3、2 周期编码 🔄

月份如果作为普通数字输入,模型会认为十二月(12)和一月(1)距离很远——但它们只隔一个月。用正弦和余弦变换可以保留周期结构:

 import numpy as np  
 df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12)  
 df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12)

同样的思路适用于一天中的小时(除以24)。

3、3 工作日历特征

 import holidays  
 indian_holidays = holidays.India(years=2025)  

 df['is_holiday']     = df['order_date'].apply(lambda d: d in indian_holidays).astype(int)  
 df['is_month_end']   = df['order_date'].dt.is_month_end.astype(int)

📝 第4部分:文本特征

4、1 基础统计特征

在引入任何NLP手段之前,先提取简单的统计量。实际效果往往超出预期。

 df['word_count']      = df['review'].str.split().str.len()  
 df['avg_word_len']    = df['review'].str.len() / df['word_count']  
 df['has_question']    = df['review'].str.contains(r'\\\\?').astype(int)  
 df['uppercase_ratio'] = df['review'].apply(  
     lambda x: sum(c.isupper() for c in str(x)) / max(len(str(x)), 1)  
 )

4、2 TF-IDF

TF-IDF将文本转换为按词项重要性加权的数值表示。

 from sklearn.feature_extraction.text import TfidfVectorizer  
 tfidf = TfidfVectorizer(max_features=100, ngram_range=(1, 2), stop_words='english')  
 X_tfidf = tfidf.fit_transform(df['review'])

4、3 情感得分

 from textblob import TextBlob  
 df['sentiment'] = df['review'].apply(lambda x: TextBlob(str(x)).sentiment.polarity)  
 # 范围从-1(非常消极)到1(非常积极)

4、4 句子嵌入

更现代的做法是用预训练模型将文本压缩为稠密向量,从而捕获语义信息。在深度学习场景下,这比TF-IDF的表达能力高出一个量级。

 from sentence_transformers import SentenceTransformer  
 model = SentenceTransformer('all-MiniLM-L6-v2')  
 embeddings = model.encode(df['review'].tolist())  
 # Shape: (n_rows, 384) — each row becomes 384 numerical features

📍 第5部分:地理空间特征

5、1 距离特征

一个数据点与关键地标之间的距离,本身就是一个信息量很大的特征。

 from math import radians, sin, cos, sqrt, atan2  

def haversine(lat1, lon1, lat2, lon2):  
    R = 6371  
    lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])  
    a = sin((lat2-lat1)/2)**2 + cos(lat1)*cos(lat2)*sin((lon2-lon1)/2)**2  
    return R * 2 * atan2(sqrt(a), sqrt(1-a))  
city_centre = (28.6139, 77.2090)  

df['dist_to_centre_km'] = df.apply(  
    lambda r: haversine(r['lat'], r['lon'], *city_centre), axis=1  
 )

5、2 Geohash

Geohash将经纬度编码为短字符串,每个前缀对应一个地理区域,天然适合做位置聚合。

 import pygeohash as pgh  
 df['geohash_5'] = df.apply(lambda r: pgh.encode(r['lat'], r['lon'], precision=5), axis=1)  
 # precision 5 = roughly 5km area

📊 第6部分:聚合特征

在生产环境的机器学习系统中,聚合类特征的价值极高,尤其是在客户行为和交易数据上。

6、1 分组聚合

 stats = df.groupby('customer_id').agg(  
    total_orders=('order_id', 'count'),  
    total_spent=('amount', 'sum'),  
    avg_order_value=('amount', 'mean'),  
    max_order=('amount', 'max')  
).reset_index()  

 df = df.merge(stats, on='customer_id', how='left')

6、2 滞后和滚动特征

序列数据中,过去N个时间段内发生了什么,往往是预测能力最强的信号。

 df = df.sort_values(['customer_id', 'order_date'])  

df['prev_order_amount'] = df.groupby('customer_id')['amount'].shift(1)  
df['amount_change']     = df['amount'] - df['prev_order_amount']  

df['rolling_30d_spend'] = (  
    df.groupby('customer_id')['amount']  
    .transform(lambda x: x.rolling(3).sum())  
 )

🔍 第7部分:特征选择

构造特征只是工作的一半,另一半是筛掉无用的。

7.1 删除低方差特征

如果一列的值几乎不变化,模型从中学不到任何东西。

 from sklearn.feature_selection import VarianceThreshold  
 selector = VarianceThreshold(threshold=0.01)  
 X_reduced = selector.fit_transform(X)

7.2 删除高相关特征

高度相关的特征本质上是冗余信息。保留一个,其余丢弃。

 corr = df.corr().abs()  
 upper = corr.where(np.triu(np.ones(corr.shape), k=1).astype(bool))  
 to_drop = [col for col in upper.columns if any(upper[col] > 0.95)]  
 df.drop(columns=to_drop, inplace=True)

7.3 特征重要性

用树模型对特征排序,重要性接近零的直接去掉。

 from sklearn.ensemble import RandomForestClassifier  
 model = RandomForestClassifier(n_estimators=100, random_state=42)  
 model.fit(X_train, y_train)  

 importance = pd.Series(model.feature_importances_, index=X_train.columns)  
 print(importance.sort_values(ascending=False).head(20))

7.4 SHAP值

SHAP不仅能揭示哪些特征重要,还能解释每个特征对单条预测结果的具体影响方向和幅度。

 import shap  
 explainer = shap.TreeExplainer(model)  
 shap_values = explainer.shap_values(X_train)  
 shap.summary_plot(shap_values, X_train)

🤖 第8部分:自动化特征工程

当候选组合数量庞大时,手动构造特征不再现实。更好的做法是用程序批量生成,再交由特征选择环节做筛选。

 import featuretools as ft  

es = ft.EntitySet(id='orders')  
es = es.add_dataframe(dataframe_name='orders', dataframe=df,  
                      index='order_id', time_index='order_date')  
feature_matrix, feature_defs = ft.dfs(  
    entityset=es,  
    target_dataframe_name='orders',  
    agg_primitives=['sum', 'mean', 'count', 'max', 'std'],  
    trans_primitives=['month', 'weekday', 'is_weekend'],  
    max_depth=2  
)  
 print(f"Generated {len(feature_defs)} features automatically")

跑完之后,依次过方差过滤、相关性过滤,再看特征重要性得分,留下来的就是值得用的。

✍️ 总结

特征工程是领域知识和技术能力的交叉地带。算法再精妙,也无法弥补特征层面的粗糙。

持续产出高质量模型的工程师,往往不是掌握算法最多的人而是对数据理解最深的人。从简单的特征开始量化每一步的收益,只在简单版本不够用的时候才引入复杂度。💪

https://avoid.overfit.cn/post/18311991fa7f403c95cadf2d1352489b

by ATNO

目录
相关文章
|
2月前
|
机器学习/深度学习 搜索推荐 算法
拆解推荐系统:候选生成、过滤、排序、多样性的分层设计
推荐系统是端到端流水线,非单一算法:涵盖候选生成、过滤、特征工程、多目标排序、多样性调控与反馈闭环。强调关注点分离,以保障质量、速度与行为可控。动手前须明确定义Item、用户行为及成功指标。
389 12
拆解推荐系统:候选生成、过滤、排序、多样性的分层设计
|
2月前
|
大数据 PHP
5个提升开发效率的PHP技巧
5个提升开发效率的PHP技巧
358 143
|
2月前
|
安全 JavaScript 前端开发
5个让PHP代码更优雅的小技巧
5个让PHP代码更优雅的小技巧
225 139
|
17天前
|
机器学习/深度学习 人工智能 算法
Skill Factory:三天手搓面向Harness设计的技能工厂(附AI coding实践)
文章内容基于作者个人技术实践与独立思考,旨在分享经验,仅代表个人观点。
Skill Factory:三天手搓面向Harness设计的技能工厂(附AI coding实践)
|
2月前
|
人工智能 JavaScript API
(技术贴)别被全网爆火的OpenClaw骗了!实测2小时,真不适合普通人
别被全网爆火的OpenClaw误导!实测2小时发现:部署卡顿、API成本高(日耗几十至千元)、报错难排查,需懂命令行与调试——它本质是开发者框架,非普通人开箱即用工具。现阶段,等待成熟或选择成熟产品更明智。
409 6
|
3月前
|
缓存 调度 异构计算
TPU 架构与 Pallas Kernel 编程入门:从内存层次结构到 FlashAttention
本文详解TPU与GPU编程范式本质差异:TPU无自动缓存,需显式管理HBM→VMEM→寄存器三级数据搬运。JAX Pallas通过Grid、BlockSpec、Ref三大抽象,以tile为单位描述计算,自动生成DMA调度,大幅简化开发。文章由浅入深实现逐元素加法、分块点积、融合RMSNorm及生产级FlashAttention,揭示其底层机制与工程实践。
203 14
TPU 架构与 Pallas Kernel 编程入门:从内存层次结构到 FlashAttention
|
2月前
|
缓存 小程序 算法
外卖配送小程序开发核心难点:调度系统与订单分发机制解析
外卖配送小程序开发的核心不在前端界面,而在后端两大能力:智能调度系统(决定配送效率)与科学订单分发机制(保障稳定性和骑手体验)。多数项目“能用但跑不动”,症结恰在此——缺乏多约束实时优化、动态评分派单、多单路径规划及高并发架构设计。
|
2月前
|
弹性计算 测试技术 数据库
2026年阿里云活动中心优惠活动集锦:个人、企业和学生专属及同享活动参考
阿里云通过多元优惠活动降低用户上云门槛与成本,个人、企业、学生均可享特惠。个人用户可选38元/年轻量服务器或99元/年经济型e实例;企业用户有199元/年特惠u1实例,及迁云与出海补贴;学生与教师享“云工开物”计划,学生领300元无门槛券,教师获5折优惠。阿里云构建全应用场景普惠云生态,用户可根据需求选择最经济合适的方案。
578 5
|
2月前
|
大数据 PHP 开发者
PHP 开发中你可能忽略的 4 个实用技巧
PHP 开发中你可能忽略的 4 个实用技巧
224 139
|
2月前
|
弹性计算 人工智能 Linux
阿里云ECS/轻量服务器部署 OpenClaw 图文攻略:Slack集成+千问Qwen3.6-Plus与Coding Plan配置教程
本文完整覆盖2026年**阿里云轻量服务器/ECS云服务器部署OpenClaw、本地MacOS/Linux/Windows11全平台搭建、千问Qwen3.6-Plus付费API与免费Coding Plan双模型配置、Slack全球协作工具集成**四大核心流程,搭配全场景高频问题排查方案,所有命令均为实测可直接复制,无需复杂操作即可完成部署。
527 18