特征工程是机器学习项目中最关键的环节之一,它直接决定了模型性能的上限。在实际项目中,我们经常会遇到这样的情况:明明使用了最先进的算法,但模型效果就是上不去。这时候问题往往出在特征处理环节。就拿房价预测这个经典场景来说,原始数据可能包含建筑年份、面积、周边人口密度等数十个特征,但如何让这些特征发挥最大价值,就需要特征工程的魔法了。
我处理过一个真实的房产数据集,最初直接用原始特征训练模型,R²只有0.6左右。经过系统的特征工程处理后,同样的模型R²提升到了0.89。这个提升不是靠换更复杂的模型,而是通过正确处理特征间的共线性、合理降维和创造性特征构建实现的。特征工程就像给模型配了一副合适的眼镜,让原本模糊的世界变得清晰起来。
共线性就像团队中的"复读机"成员,他们总在重复别人说过的话。在特征工程中,当多个特征高度相关时,就会出现这种情况。比如在房价数据中,"白天人口密度"和"夜间人口密度"这两个特征往往高度相关,因为它们反映的是同一区域的活跃程度。
判断共线性的黄金标准是相关系数矩阵。我习惯用热力图直观展示:
python复制import seaborn as sns
corr_matrix = df.corr()
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm')
当看到相关系数超过0.8的特征对时,就要警惕了。共线性会导致线性模型系数不稳定,增加方差,降低可解释性。在最近一个项目中,我发现三个高度相关的特征导致线性回归模型的系数符号与业务常识相反,这就是典型的共线性问题。
处理共线性我常用三种方法,各有适用场景:
python复制# 计算各特征与目标变量的相关性
target_corr = df.corr()['price'].abs().sort_values(ascending=False)
# 选择相关性最高的特征保留
selected_feature = target_corr.index[1] # 假设索引0是目标变量本身
python复制df['day_night_ratio'] = df['daypop'] / (df['nightpop'] + 1e-6) # 避免除零
降维不是每个项目都必须的,但在这些情况下特别有用:特征数接近样本量、特征间相关性高、需要可视化高维数据。我常用的降维方法主要有PCA和模型筛选两种。
PCA就像把一组相关的描述转换成几个综合评分。比如描述一个人可以用"身高""体重""鞋码"等多个指标,但本质上都在反映"体型"这一个维度。在房价预测中,PCA可以帮助我们发现隐藏在多个特征背后的潜在因素。
使用PCA时最常被问的问题是:保留多少个主成分合适?我的经验是:
python复制from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
pca = PCA().fit(X_scaled)
plt.plot(np.cumsum(pca.explained_variance_ratio_))
plt.xlabel('主成分数')
plt.ylabel('累计解释方差')
业务解释性:有时保留解释95%方差的主成分可能仍有10个,但前3个就能很好解释业务含义,这时我会选择后者。
后续模型需求:如果后面要用线性模型,可以多保留些主成分;如果用树模型,可以减少些。
基于模型的筛选特别适合有监督场景。Lasso回归是我的首选,它通过L1正则化自动进行特征选择。
python复制from sklearn.linear_model import LassoCV
lasso = LassoCV(cv=5).fit(X_train, y_train)
selected_features = X.columns[lasso.coef_ != 0]
在最近一个项目中,原始23个特征经Lasso筛选后剩下7个,模型性能反而提升了15%。关键是要通过交叉验证选择合适的正则化强度alpha。
当模型出现欠拟合时,特征扩展是解决方案。多项式特征是常用方法,但要避免"维度灾难"。我的经验是:
python复制from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2, interaction_only=True)
X_poly = poly.fit_transform(X[['area', 'year']]) # 只扩展面积和年份的交互项
最好的特征往往来自业务洞察。在房价预测中,我创造过这些成功特征:
python复制df['age'] = 2023 - df['build_year']
df['price_per_sqm'] = df['last_price'] / df['area']
这类特征通常比原始特征预测力强得多,因为它们编码了领域知识。
时间特征需要特殊处理。对于建筑年份这类特征,我常用的处理方法包括:
python复制# 年代分箱
df['decade'] = pd.cut(df['build_year'],
bins=[1980,1990,2000,2010,2020],
labels=['80s','90s','00s','10s'])
# 周期编码
df['month_sin'] = np.sin(2*np.pi*df['month']/12)
df['month_cos'] = np.cos(2*np.pi*df['month']/12)
让我们用一个完整的房价预测案例串联所有技术点。假设数据集包含以下特征:
python复制import pandas as pd
from sklearn.model_selection import train_test_split
df = pd.read_csv('house_data.csv')
X = df.drop('price', axis=1)
y = df['price']
X_train, X_test = train_test_split(X, test_size=0.2, random_state=42)
第一步:共线性处理
第二步:降维处理
第三步:特征扩展
python复制from sklearn.cluster import KMeans
coords = X[['longitude','latitude']]
kmeans = KMeans(n_clusters=5).fit(coords)
X['location_cluster'] = kmeans.labels_
完成特征工程后,用简单模型(如线性回归)做快速验证:
python复制from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
model = LinearRegression().fit(X_train, y_train)
pred = model.predict(X_test)
print(f'RMSE: {mean_squared_error(y_test, pred, squared=False)}')
根据模型表现,可能需要返回调整:
在指导新人做特征工程时,我发现这些高频错误:
经过多个项目的锤炼,我总结出这些最佳实践:
python复制# 特征版本控制示例
import hashlib
def get_feature_hash(df):
return hashlib.md5(pd.util.hash_pandas_object(df).values).hexdigest()
print(f'特征版本: {get_feature_hash(X_train)}')
当处理大规模数据时,这些技巧能显著提升效率:
python复制# 并行特征生成示例
from joblib import Parallel, delayed
def create_feature(col):
return df[col] * df['area']
features = Parallel(n_jobs=-1)(
delayed(create_feature)(col) for col in ['room_num', 'floor']
)