吴恩达机器学习课程(PyTorch适配)学习笔记:1.3 特征工程与模型优化
在实际机器学习任务中,原始数据往往无法直接用于模型训练,需要通过特征工程提取有效信息;同时,模型的计算效率也需要优化以应对大规模数据。本节将围绕多特征处理、特征缩放、特征工程、多项式回归和向量化展开,介绍实用的工程技巧与实现方法。
1.3.1 多特征处理基础
当模型输入包含多个特征时(如房价预测中的面积、房间数、楼层等),需要特殊的处理方法来保证模型效果和训练效率。
1. 多特征线性回归模型
模型定义:
对于包含nnn个特征的样本x=[x1,x2,...,xn]x = [x_1, x_2, ..., x_n]x=[x1,x2,...,xn],线性回归模型的假设函数为:
hθ(x)=θ0+θ1x1+θ2x2+...+θnxnh_\theta(x) = \theta_0 + \theta_1 x_1 + \theta_2 x_2 + ... + \theta_n x_nhθ(x)=θ0+θ1x1+θ2x2+...+θnxn
向量化表示(推荐):
引入x0=1x_0 = 1x0=1(偏置项对应的虚拟特征),则模型可简化为:
hθ(x)=θTXh_\theta(x) = \theta^T Xhθ(x)=θTX
其中:
- XXX为特征矩阵,形状为(m,n+1)(m, n+1)(m,n+1)(mmm个样本,n+1n+1n+1个特征)
- θ\thetaθ为参数向量,形状为(n+1,1)(n+1, 1)(n+1,1)
2. 多特征场景的挑战
- 量纲差异:不同特征的数值范围可能差异极大(如面积单位为平方米,房间数为整数),导致梯度下降收敛缓慢。
- 特征冗余:多个特征可能高度相关(如"房屋面积"和"使用面积"),增加模型复杂度但不提升性能。
- 特征筛选:并非所有特征都对预测有益,需要筛选出最具预测力的特征。
3. 多特征数据的组织与加载(PyTorch示例)
import torch
import pandas as pd# 1. 构造多特征数据集(房价预测:面积、房间数、年限 → 房价)
data = {'面积': [100, 150, 120, 180, 90],'房间数': [2, 3, 2, 4, 1],'房龄': [5, 3, 10, 2, 15],'房价': [500, 800, 600, 1000, 400]
}
df = pd.DataFrame(data)# 2. 转换为PyTorch张量
X = torch.tensor(df[['面积', '房间数', '房龄']].values, dtype=torch.float32) # 特征矩阵
y = torch.tensor(df['房价'].values, dtype=torch.float32).view(-1, 1) # 标签# 3. 添加偏置项x0=1
X_bias = torch.cat([torch.ones(X.shape[0], 1), X], dim=1)print(f"特征矩阵形状(含偏置项): {X_bias.shape}") # 输出: torch.Size([5, 4])
print(f"前2行数据:\n{X_bias[:2]}")
1.3.2 特征缩放(原理 + PyTorch 适配)
特征缩放是多特征场景下的关键预处理步骤,通过将不同量纲的特征转换到相同尺度,加速梯度下降收敛。
1. 为什么需要特征缩放?
考虑两个特征:
- 面积:范围[50,200][50, 200][50,200](平方米)
- 房间数:范围[1,5][1, 5][1,5](个)
在梯度下降中,参数更新幅度与特征值大小相关,会导致:
- 面积对应的参数θ1\theta_1θ1更新幅度大
- 房间数对应的参数θ2\theta_2θ2更新幅度小
成本函数的等高线会呈现"狭长椭圆"形状,梯度下降需要更多迭代才能收敛。特征缩放后,等高线更接近圆形,收敛更快。
2. 常用缩放方法
(1)标准化(Standardization)
将特征转换为均值为0、标准差为1的分布:
x′=x−μσx' = \frac{x - \mu}{\sigma}x′=σx−μ
其中μ\muμ是特征均值,σ\sigmaσ是特征标准差。
适用场景:特征近似正态分布时效果好,受异常值影响较小。
(2)归一化(Normalization)
将特征缩放到[0,1][0, 1][0,1]区间:
x′=x−xminxmax−xminx' = \frac{x - x_{\text{min}}}{x_{\text{max}} - x_{\text{min}}}x′=xmax−xminx−xmin
适用场景:特征分布未知或有边界时(如像素值[0,255][0, 255][0,255]),但受异常值影响较大。
3. PyTorch实现特征缩放
import torch
import matplotlib.pyplot as plt
import numpy as np# 1. 使用前面的多特征数据
X = torch.tensor([[100.0, 2.0, 5.0],[150.0, 3.0, 3.0],[120.0, 2.0, 10.0],[180.0, 4.0, 2.0],[90.0, 1.0, 15.0]
], dtype=torch.float32)# 2. 定义标准化和归一化函数
def standardize(x):"""标准化: (x - 均值) / 标准差"""mean = torch.mean(x, dim=0)std = torch.std(x, dim=0)return (x - mean) / std, mean, stddef normalize(x):"""归一化: (x - 最小值) / (最大值 - 最小值)"""min_val = torch.min(x, dim=0).valuesmax_val = torch.max(x, dim=0).valuesreturn (x - min_val) / (max_val - min_val + 1e-8), min_val, max_val # 加1e-8避免除零# 3. 执行缩放
X_standardized, mean, std = standardize(X)
X_normalized, min_val, max_val = normalize(X)# 4. 可视化缩放前后的特征分布
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
features = ['面积', '房间数', '房龄']for i in range(3):axes[i].hist(X[:, i].numpy(), bins=5, alpha=0.5, label='原始')axes[i].hist(X_standardized[:, i].numpy(), bins=5, alpha=0.5, label='标准化')axes[i].hist(X_normalized[:, i].numpy(), bins=5, alpha=0.5, label='归一化')axes[i].set_title(f'特征: {features[i]}')axes[i].legend()plt.tight_layout()
plt.savefig('feature_scaling.png', dpi=300)
plt.show()
4. 特征缩放的工程实践
- 缩放时机:在划分训练集和测试集后,仅使用训练集的均值/标准差进行缩放(避免数据泄露)。
- PyTorch实用工具:可使用
torchvision.transforms
中的Normalize
类:from torchvision import transforms transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize(mean=mean, std=std)])
- 无需缩放的场景:
- 决策树、随机森林等基于树的模型(不依赖梯度下降)
- 特征本身已在相同尺度(如归一化后的图像像素)
1.3.3 特征工程(构建 + 筛选思路)
特征工程是"从原始数据中提取有效特征"的过程,良好的特征往往比复杂模型更重要(“Garbage in, garbage out”)。
1. 特征构建方法
(1)组合特征
将现有特征进行数学组合,生成新特征:
- 乘法:面积 × 单价 → 总价
- 除法:总价 ÷ 面积 → 单价
- 多项式:面积²、面积×房间数等(见1.3.4多项式回归)
(2)离散化/分箱
将连续特征转换为离散类别:
- 年龄 → 少年/青年/中年/老年
- 收入 → 低收入/中等收入/高收入
PyTorch实现示例:
import torch# 连续特征:年龄
ages = torch.tensor([22, 35, 58, 19, 65], dtype=torch.float32)# 分箱:<30 → 0, 30-50 → 1, >50 → 2
bins = torch.tensor([30, 50])
age_bins = torch.bucketize(ages, bins)print(f"原始年龄: {ages}")
print(f"分箱结果: {age_bins}") # 输出: tensor([0, 1, 2, 0, 2])
(3)编码分类特征
将文本/类别特征转换为数值:
- 独热编码(One-Hot Encoding):适用于无序类别(如颜色:红/蓝/绿)
- 标签编码(Label Encoding):适用于有序类别(如学历:高中/本科/硕士)
PyTorch独热编码示例:
# 分类特征:房间朝向(0:东, 1:南, 2:西, 3:北)
directions = torch.tensor([0, 1, 1, 3], dtype=torch.long)# 独热编码
one_hot = torch.nn.functional.one_hot(directions, num_classes=4)print(f"原始类别: {directions}")
print(f"独热编码:\n{one_hot}")
2. 特征筛选思路
(1)基于统计的筛选
- 移除方差接近0的特征(几乎不变,无预测价值)
- 移除高度相关的特征(如皮尔逊系数>0.9的特征对,保留一个即可)
# 计算特征相关性矩阵
corr_matrix = torch.corrcoef(X.T) # X为特征矩阵
print(f"特征相关性矩阵:\n{corr_matrix}")
(2)基于模型的筛选
- 使用线性回归的参数绝对值衡量特征重要性(值越大越重要)
- 使用树模型的特征重要性评分(如随机森林的
feature_importances_
)
(3)特征筛选原则
- 优先保留与目标强相关的特征
- 控制特征数量(避免"维度灾难")
- 保留可解释性强的特征(便于模型调试)
1.3.4 多项式回归(非线性场景适配)
线性回归只能拟合线性关系,当特征与目标存在非线性关系时(如二次关系、指数关系),需要使用多项式回归。
1. 多项式回归模型
核心思想:通过添加特征的多项式项(如x2,x3,x1x2x^2, x^3, x_1x_2x2,x3,x1x2),将非线性关系转换为线性模型可拟合的形式。
示例:
对于单特征xxx,二次多项式回归的假设函数为:
hθ(x)=θ0+θ1x+θ2x2h_\theta(x) = \theta_0 + \theta_1 x + \theta_2 x^2hθ(x)=θ0+θ1x+θ2x2
令x1=x,x2=x2x_1 = x, x_2 = x^2x1=x,x2=x2,则可转化为线性回归形式:
hθ(x)=θ0+θ1x1+θ2x2h_\theta(x) = \theta_0 + \theta_1 x_1 + \theta_2 x_2hθ(x)=θ0+θ1x1+θ2x2
2. 多项式回归的PyTorch实现
以"二次曲线拟合"为例:
import torch
import torch.nn as nn
import matplotlib.pyplot as plt# 1. 生成非线性数据(y = 3x² + 2x + 5 + 噪声)
x = torch.linspace(-3, 3, 100).view(-1, 1)
y = 3 * x**2 + 2 * x + 5 + torch.randn_like(x) * 2 # 添加噪声# 2. 构建多项式特征(x, x²)
x_poly = torch.cat([x, x**2], dim=1) # 形状: (100, 2)
x_poly_bias = torch.cat([torch.ones(x.shape[0], 1), x_poly], dim=1) # 添加偏置项# 3. 定义模型和训练
model = nn.Linear(3, 1) # 输入3维(1, x, x²),输出1维
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)# 训练模型
for epoch in range(1000):outputs = model(x_poly_bias)loss = criterion(outputs, y)optimizer.zero_grad()loss.backward()optimizer.step()# 4. 预测与可视化
with torch.no_grad():y_pred = model(x_poly_bias)plt.figure(figsize=(8, 5))
plt.scatter(x.numpy(), y.numpy(), alpha=0.5, label='原始数据')
plt.plot(x.numpy(), y_pred.numpy(), 'r-', linewidth=2, label='多项式拟合')
plt.xlabel('x')
plt.ylabel('y')
plt.title('二次多项式回归拟合')
plt.legend()
plt.grid(alpha=0.3)
plt.savefig('polynomial_regression.png', dpi=300)
plt.show()
3. 多项式阶数的选择
- 阶数过低:欠拟合(无法捕捉数据模式)
- 阶数过高:过拟合(过度拟合噪声,泛化能力差)
选择方法:
- 绘制学习曲线,观察训练误差与验证误差
- 使用交叉验证选择最优阶数
- 结合领域知识(如物理规律已知为二次关系)
4. 多项式回归的局限性
- 特征数量多时,多项式项会急剧增加(如10个特征的二次项有55个)
- 高次多项式可能在边界处产生极端值
- 建议配合正则化使用(见后续章节)
1.3.5 向量化(计算效率优化 + PyTorch 实现框架)
向量化是将循环操作转换为矩阵/张量运算的技术,能显著提升计算效率(尤其在GPU上)。
1. 为什么向量化更高效?
- 硬件层面:现代CPU/GPU支持SIMD(单指令多数据)操作,可并行处理矩阵元素
- 软件层面:PyTorch的底层使用C++/CUDA优化,比Python循环快10~1000倍
- 代码层面:向量化代码更简洁,可读性更高
2. 向量化 vs 循环:效率对比
import torch
import time# 1. 创建大矩阵
m, n = 100, 100
A = torch.randn(m, n)
B = torch.randn(n, m)# 2. 循环实现矩阵乘法(慢)
start = time.time()
C_loop = torch.zeros(m, m)
for i in range(m):for j in range(m):for k in range(n):C_loop[i, j] += A[i, k] * B[k, j]
loop_time = time.time() - start
print(f"循环实现时间: {loop_time:.4f}秒")# 3. 向量化实现矩阵乘法(快)
start = time.time()
C_vec = torch.matmul(A, B) # 或 A @ B
vec_time = time.time() - start
print(f"向量化实现时间: {vec_time:.4f}秒")
print(f"向量化加速比: {loop_time / vec_time:.0f}x")
典型输出:
循环实现时间: 8.7625秒
向量化实现时间: 0.0030秒
向量化加速比: 2926x
3. 常用向量化操作(PyTorch)
操作 | 循环实现 | 向量化实现 |
---|---|---|
元素乘法 | for i in range(n): c[i] = a[i] * b[i] | c = a * b |
求和 | s = 0; for x in a: s += x | s = torch.sum(a) |
矩阵-向量乘法 | for i in range(m): for j in range(n): c[i] += A[i,j] * b[j] | c = A @ b |
批量计算 | for x in X: y.append(f(x)) | y = f(X) (广播机制) |
4. 向量化实现框架(线性回归示例)
import torchclass VectorizedLinearRegression:def __init__(self, input_dim):self.theta = torch.randn(input_dim + 1, 1, requires_grad=True) # 含偏置项def forward(self, X):"""向量化前向传播:X为(m, input_dim),返回预测值(m, 1)"""X_bias = torch.cat([torch.ones(X.shape[0], 1), X], dim=1) # 添加偏置项return torch.matmul(X_bias, self.theta) # 向量化计算def train(self, X, y, epochs=1000, lr=0.01):"""向量化训练流程"""optimizer = torch.optim.Adam([self.theta], lr=lr)criterion = torch.nn.MSELoss()for epoch in range(epochs):y_pred = self.forward(X)loss = criterion(y_pred, y)optimizer.zero_grad()loss.backward()optimizer.step()if (epoch + 1) % 100 == 0:print(f"Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}")# 使用框架
X = torch.randn(1000, 5) # 1000样本,5特征
y = torch.randn(1000, 1) # 标签
model = VectorizedLinearRegression(input_dim=5)
model.train(X, y)
5. 向量化的工程建议
- 避免显式循环:尽量使用PyTorch内置函数(如
torch.sum
、torch.mean
) - 利用广播机制:PyTorch会自动扩展维度不同的张量进行运算
a = torch.tensor([1, 2, 3]) b = torch.tensor([[4], [5], [6]]) print(a + b) # 广播后相加:tensor([[5, 6, 7], [6, 7, 8], [7, 8, 9]])
- 注意内存使用:过大的张量可能导致OOM(内存不足),可分批次处理
- 优先使用GPU:向量化操作在GPU上的加速效果远好于CPU
小结
- 多特征处理:需注意量纲差异,通常需配合特征缩放;采用向量化表示提升效率。
- 特征缩放:标准化(均值0方差1)和归一化([0,1])是两种常用方法,可加速梯度下降收敛。
- 特征工程:通过组合、分箱、编码等方式构建有效特征;基于统计和模型筛选冗余特征。
- 多项式回归:通过添加多项式项拟合非线性关系,需合理选择阶数以避免欠拟合/过拟合。
- 向量化:用矩阵/张量运算替代循环,可大幅提升计算效率,是处理大规模数据的核心技术。