社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  机器学习算法

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

新机器视觉 • 2 月前 • 103 次点击  
前言

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


图片
Part 01

数值 特征


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)
Part 02

类别特征

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
Part 03

日期时间特征

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

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)
Part 04

文本特征

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=(12), 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
Part 05

地理空间特征

5.1 距离特征


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

from math import radians, sincossqrtatan2  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.613977.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
Part 06

聚合特征

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

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())   )
Part 07

特征选择

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

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)
Part 08

自动化特征工程

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

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")

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

Part 09

✍️ 总结

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

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



END







版权声明


转自ATNO,版权属于原作者,仅用于学术分享



Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/195617