机器学习特征工程----常见的特征构建与转换方法

发布于:2025-08-02 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、特征工程的核心目标

特征工程的目标是提取、构造或转换特征,使其更好地表达与目标变量的关系,从而提升机器学习模型的预测性能。

简单来说:特征工程 = 用数学、统计和领域知识,将原始数据变成模型更易理解的信息表

达。

 二、常见特征工程方法

当你通过特征筛选(如互信息、相关性分析)找到潜在有用的特征后,下一步就是开发新特征。常见的开发方式包括:

  • 数学变换(Math Transforms)

  • 计数特征(Counts)

  • 分解和组合特征(Building-Up and Breaking-Down)

  • 分组聚合变换(Group Transforms)

1️⃣ 数学变换(Mathematical Transforms)

作用:重塑数值特征分布,提取潜在关系。

(1) 比例、乘积、加减运算

  • 常见于领域知识场景,例如:

    • 房地产:房价/面积 = 单价面积 × 质量等级 = 房屋价值因子

    • 汽车:stroke_ratio = stroke / bore 衡量效率

df["stroke_ratio"] = df.stroke / df.bore

原因:模型尤其是线性模型对比率特征较难学习,显式添加能降低学习难度。

(2) 幂次或对数变换

  • 用于偏态分布特征归一化,提升模型拟合能力。

df["LogWindSpeed"] = np.log1p(df.WindSpeed)  # log(1+x)避免log(0)

作用

  • 缩小极端值影响(重尾分布)。

  • 满足线性模型和神经网络的近似正态假设。

 相关数学:Box-Cox变换

y(\lambda) = \begin{cases} \frac{y^{\lambda} - 1}{\lambda}, & \lambda \neq 0 \\ \ln(y), & \lambda = 0 \end{cases}

(3) 标准化与归一化 

标准化 (Z-score): 使特征均值为0,标准差为1

 z = \frac{x - \mu}{\sigma}

归一化 (Min-Max): 将特征缩放到[0,1]

x' = \frac{x - \min(x)}{\max(x) - \min(x)}

适用模型

  • 线性模型(线性回归、逻辑回归)

  • 神经网络(梯度下降对尺度敏感)

树模型(XGBoost、RF)不需要标准化,因为分裂点与数值范围无关。

2️⃣ 计数特征(Counts / Aggregation)

作用:将多个布尔或稀疏特征合并为一个信息浓缩的计数型变量。

(1) 二值/布尔累加

交通事故:统计事故周边道路设施数:

roadway_features = ["Amenity","Bump","Crossing"]
df["RoadFeatures"] = df[roadway_features].sum(axis=1)

医疗领域:统计患者风险因素个数(如高血压、糖尿病)。

(2) 非零元素计数

混凝土配方:统计一个配方包含的有效材料种类:

components = ["Cement","FlyAsh","Water"]
df["ComponentsCount"] = df[components].gt(0).sum(axis=1)

意义

对树模型特别有效,能直接表示“某特征的数量多寡与目标的关系”。

3️⃣ 特征分解与组合(Decomposition & Combination)

作用:从复杂字符串/标识符中拆分信息,或组合特征以捕获交互。

(1) 字符串分解

电话号提取区号:

df["AreaCode"] = df["Phone"].str.extract(r'\((\d{3})\)')

地址拆分:街道、城市、州。

(2) 特征组合(交互特征)

拼接两列类别型特征:

df["Make_Style"] = df["make"] + "_" + df["body_style"]

 构造交互特征(乘积、比率):

df["Area_Quality"] = df["GrLivArea"] * df["OverallQual"]

作用

显式表达特征交互关系,尤其在数据量较小时帮助模型更快捕捉模式。

4️⃣ 分组聚合(Group Transforms)

作用:基于类别变量分组,对另一变量计算聚合统计量。

(1) 分组均值/中位数

按州计算平均收入:

df["AvgIncomeByState"] = df.groupby("State")["Income"].transform("mean")

2) 频率编码(Frequency Encoding)

类别特征转换为类别频率:

df["StateFreq"] = df.groupby("State")["State"].transform("count") / df.State.count()

(3) 验证集注意事项

必须仅用训练集统计,再 merge 到验证集,避免数据泄露:

df_valid = df_valid.merge(
    df_train[["Coverage","AverageClaim"]].drop_duplicates(),
    on="Coverage", how="left")

5️⃣ 日期与时间特征

时间特征是高信息量变量,可拆分为:

年、月、日、季度、是否周末。

事件间间隔:DaysSinceLastPurchase

df["Year"] = df["Date"].dt.year
df["IsWeekend"] = df["Date"].dt.weekday >= 5

6️⃣ 空间(地理)特征

经纬度转为距离特征:如距市中心、距最近医院。

使用Haversine公式计算地理距离。

三、模型与特征工程的适配性

线性模型(LR、Lasso、Ridge):

        需要显式构造交互项与非线性变换。
        对缩放敏感,需标准化。

树模型(RF、XGBoost、LightGBM):

        自动捕捉非线性和交互关系。
        不依赖特征缩放,但计数特征和类别频率编码有帮助。

神经网络(NN):

        需要归一化(梯度稳定)。
        高维组合可用嵌入(Embedding)。

四、例子详解

 这个是kaggle里面的例子,首先是载入需要进行实验的文件。

# Setup feedback system
from learntools.core import binder
binder.bind(globals())
from learntools.feature_engineering_new.ex3 import *

import numpy as np
import pandas as pd
from sklearn.model_selection import cross_val_score
from xgboost import XGBRegressor


def score_dataset(X, y, model=XGBRegressor()):
    # Label encoding for categoricals
    for colname in X.select_dtypes(["category", "object"]):
        X[colname], _ = X[colname].factorize()
    # Metric for Housing competition is RMSLE (Root Mean Squared Log Error)
    score = cross_val_score(
        model, X, y, cv=5, scoring="neg_mean_squared_log_error",
    )
    score = -1 * score.mean()
    score = np.sqrt(score)
    return score


# Prepare data
df = pd.read_csv("../input/fe-course-data/ames.csv")
X = df.copy()
y = X.pop("SalePrice")

 1) 创建数学变换

创建以下特征:
LivLotRatioGrLivArea 与 LotArea 的比率
SpaciousnessFirstFlrSF 和 SecondFlrSF 之和除以 TotRmsAbvGrd
TotalOutsideSF:WoodDeckSF、OpenPorchSF、EnclosedPorchThreeseasonporch 和 ScreenPorch 的总和

X_1 = pd.DataFrame()  # dataframe to hold new features

X_1["LivLotRatio"] = df.GrLivArea / df.LotArea
X_1["Spaciousness"] = (df.FirstFlrSF + df.SecondFlrSF) / df.TotRmsAbvGrd
X_1["TotalOutsideSF"] = df.WoodDeckSF + df.OpenPorchSF + df.EnclosedPorch + df.Threeseasonporch + df.ScreenPorch

 2) 与分类的交互

如果发现了数值特征和分类特征之间的交互效应,则可能需要使用单热编码对其进行显式建模,如下所示:

# One-hot encode Categorical feature, adding a column prefix "Cat"
X_new = pd.get_dummies(df.Categorical, prefix="Cat")

# Multiply row-by-row
X_new = X_new.mul(df.Continuous, axis=0)

# Join the new features to the feature set
X = X.join(X_new)

这段代码的作用是:
基于一个分类特征(Categorical)和一个连续特征(Continuous),生成交互特征(Interaction Features),并加入到原始特征集 X

BldgType 和 GrLivArea 之间的交互

# One-hot encode BldgType. Use `prefix="Bldg"` in `get_dummies`
X_2 = pd.get_dummies(df.BldgType, prefix="Bldg")

# Multiply
X_2 = X_2.mul(df.GrLivArea, axis=0)

3) 计数特征

让我们尝试创建一个功能来描述住宅有多少种室外区域。创建一个功能 PorchTypes,用于计算以下大于 0.0 的要素数量:WoodDeckSF OpenPorchSF EnclosedPorch Threeseasonporch ScreenPorch

X_3 = pd.DataFrame()

# YOUR CODE HERE
X_3["PorchTypes"] = df[["WoodDeckSF","OpenPorchSF","EnclosedPorch","Threeseasonporch","ScreenPorch"]].gt(0.0).sum(axis=1)

 4) 分解分类特征

MSSubClass 描述住宅的类型:

df.MSSubClass.unique()

 结果是:

array(['One_Story_1946_and_Newer_All_Styles', 'Two_Story_1946_and_Newer',
       'One_Story_PUD_1946_and_Newer',
       'One_and_Half_Story_Finished_All_Ages', 'Split_Foyer',
       'Two_Story_PUD_1946_and_Newer', 'Split_or_Multilevel',
       'One_Story_1945_and_Older', 'Duplex_All_Styles_and_Ages',
       'Two_Family_conversion_All_Styles_and_Ages',
       'One_and_Half_Story_Unfinished_All_Ages',
       'Two_Story_1945_and_Older', 'Two_and_Half_Story_All_Ages',
       'One_Story_with_Finished_Attic_All_Ages',
       'PUD_Multilevel_Split_Level_Foyer',
       'One_and_Half_Story_PUD_All_Ages'], dtype=object)

 可以看到,每个类别的第一个词(大致)描述了一个更通用的分类。通过在第一个"_"拆分 MSSubClass ,创建仅包含这些第一个单词的功能。(提示:在拆分方法中使用参数 n=1

例如:

"20_1Story" → ["20", "1Story"]
"60_2Story" → ["60", "2Story"]
 

X_4 = pd.DataFrame()

# YOUR CODE HERE
X_4["MSClass"] = df.MSSubClass.str.split("_", n=1, expand=True)[0]

'''
.str.split("_", n=1):按下划线 "_" 只分割一次(参数 n=1 限制最大分割次数)。
expand=True:将结果展开为多个列(DataFrame 形式)。
'''

5) 使用分组变换

房屋的价值通常取决于它与附近典型房屋的比较。创建一个要素 MedNhbdArea,用于描述按Neighborhood分组的 GrLivArea 的中位数 

X_5 = pd.DataFrame()

# YOUR CODE HERE
X_5["MedNhbdArea"] = df.groupby("Neighborhood").GrLivArea.transform("median")


网站公告

今日签到

点亮在社区的每一天
去签到