Machine Learning: 深度学习入门PyTorch — 从神经元到多层感知机指南
深度学习让机器自动学特征——不用手动设计,网络自己从数据中发现。
1. 你将学到
- 深度学习vs传统ML:何时需要深度网络,计算资源与数据量考量
- PyTorch核心:Tensor操作、autograd自动微分、nn.Module模型定义
- 多层感知机(MLP):前向传播、损失计算、反向传播、参数更新
- 训练技巧:DataLoader批处理、学习率调度、EarlyStopping、GPU训练
- Bob的MLP销售预测:对比第7课线性回归,看神经网络是否带来提升
2. 一个算法工程师的真实故事
(1) 痛点:线性模型无法捕获特征交互
Bob用线性回归预测销售额,R²只有0.78。他发现"广告费×促销"的交互效应对销售影响很大,但手动添加所有交互项(20个特征→190个交互)既繁琐又容易遗漏。线性模型的假设(y=wx+b)太简单,真实世界的特征交互远比手动设计复杂。
(2) 深度学习的解法
神经网络通过隐藏层自动学习特征交互和非线性变换,无需手动设计。
PYTHON
import torch.nn as nn
model = nn.Sequential(
nn.Linear(20, 64),
nn.ReLU(),
nn.Linear(64, 32),
nn.ReLU(),
nn.Linear(32, 1),
)
(3) 收益:MLP自动发现交互,R²提升到0.86
Bob用3层MLP替代线性回归,网络自动学到了"高广告费+促销=销量暴增"的模式,R²从0.78提升到0.86,且无需手动设计交互项。
3. 深度学习vs传统ML
(1) 何时选择深度学习
| 条件 | 传统ML(如XGBoost) | 深度学习(如MLP/CNN) |
|---|---|---|
| 数据类型 | 表格数据 | 图像/文本/音频/复杂表格 |
| 数据量 | <100k | >100k |
| 特征工程 | 需要手动设计 | 自动学习特征 |
| 训练时间 | 分钟-小时 | 小时-天 |
| 硬件需求 | CPU够用 | 建议GPU |
| 可解释性 | 高(系数/重要性) | 低(黑箱) |
| 表格数据效果 | 通常更好 | 不一定更好 |
▶ 示例:PyTorch安装与验证
PYTHON
import torch
print(f"PyTorch version: {torch.__version__}")
print(f"CUDA available: {torch.cuda.is_available()}")
if torch.cuda.is_available():
print(f"GPU: {torch.cuda.get_device_name(0)}")
# Basic tensor operations
a = torch.tensor([1.0, 2.0, 3.0])
b = torch.tensor([4.0, 5.0, 6.0])
print(f"a + b = {a + b}")
print(f"a * b = {a * b}")
print(f"Mean of a: {a.mean()}")
输出:
TEXT
📖 仅展示
# 执行成功
4. PyTorch核心
(1) Tensor操作
▶ 示例:Tensor创建与操作
PYTHON
import torch
# Create tensors
a = torch.zeros(3, 4)
b = torch.ones(2, 3)
c = torch.randn(3, 3) # Normal distribution
d = torch.arange(0, 10, 2)
# From NumPy
import numpy as np
arr = np.array([1, 2, 3])
t = torch.from_numpy(arr)
# Tensor to NumPy
back = t.numpy()
# GPU tensor
if torch.cuda.is_available():
gpu_tensor = torch.randn(3, 3).cuda()
cpu_back = gpu_tensor.cpu()
# Reshape
x = torch.arange(12)
matrix = x.reshape(3, 4)
print(f"Shape: {matrix.shape}")
# Broadcasting
a = torch.ones(3, 1)
b = torch.ones(1, 4)
c = a + b # (3,4)
print(f"Broadcast result shape: {c.shape}")
输出:
TEXT
📖 仅展示
# 执行成功
(2) autograd自动微分
▶ 示例:自动求导
PYTHON
import torch
# Auto-differentiation example
x = torch.tensor([2.0], requires_grad=True)
y = x ** 2 + 3 * x + 1 # y = x² + 3x + 1
y.backward() # dy/dx = 2x + 3
print(f"x = {x.item()}")
print(f"y = {y.item()}")
print(f"dy/dx = {x.grad.item()}") # Should be 2*2+3 = 7
print(f"Manual check: 2*2 + 3 = {2*2+3}")
输出:
TEXT
📖 仅展示
# 执行成功
(3) nn.Module模型定义
▶ 示例:自定义MLP模型
PYTHON
import torch
import torch.nn as nn
class SalesPredictMLP(nn.Module):
def __init__(self, input_dim, hidden_dims, output_dim=1):
super().__init__()
layers = []
prev_dim = input_dim
for hidden_dim in hidden_dims:
layers.append(nn.Linear(prev_dim, hidden_dim))
layers.append(nn.BatchNorm1d(hidden_dim))
layers.append(nn.ReLU())
layers.append(nn.Dropout(0.2))
prev_dim = hidden_dim
layers.append(nn.Linear(prev_dim, output_dim))
self.network = nn.Sequential(*layers)
def forward(self, x):
return self.network(x)
model = SalesPredictMLP(input_dim=20, hidden_dims=[64, 32], output_dim=1)
print(model)
print(f"Total parameters: {sum(p.numel() for p in model.parameters()):,}")
输出:
TEXT
📖 仅展示
# 函数定义成功
5. MLP训练完整流程
(1) 训练循环
sequenceDiagram
participant Data as DataLoader
participant Fwd as Forward Pass
participant Loss as Loss Compute
participant Zero as Zero Gradients
participant Bwd as Backward Pass
participant Step as Optimizer Step
loop Each Epoch
Data->>Fwd: Batch of (X, y)
Fwd->>Loss: y_pred = model(X)
Loss->>Zero: loss = criterion(y_pred, y)
Zero->>Bwd: optimizer.zero_grad()
Bwd->>Step: loss.backward()
Step->>Data: optimizer.step()
end
▶ 示例:完整MLP训练流程
PYTHON
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import r2_score, mean_absolute_error
import numpy as np
# Generate data
rng = np.random.default_rng(42)
n = 5000
X = rng.uniform(0, 100, (n, 20))
y = (50 + X[:, :5] @ [0.8, 1.2, -0.5, 0.3, 0.1]
+ 0.5 * X[:, 0] * X[:, 1] # Interaction
+ np.sin(X[:, 2]) * 5 # Non-linear
+ rng.normal(0, 5, n))
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
# Create DataLoaders
train_ds = TensorDataset(torch.FloatTensor(X_train_s), torch.FloatTensor(y_train))
test_ds = TensorDataset(torch.FloatTensor(X_test_s), torch.FloatTensor(y_test))
train_loader = DataLoader(train_ds, batch_size=64, shuffle=True)
test_loader = DataLoader(test_ds, batch_size=256)
# Model, loss, optimizer
model = SalesPredictMLP(input_dim=20, hidden_dims=[128, 64, 32])
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=10, factor=0.5)
# Training loop
train_losses, val_losses = [], []
best_val_loss = float("inf")
for epoch in range(100):
model.train()
epoch_loss = 0
for X_batch, y_batch in train_loader:
y_pred = model(X_batch).squeeze()
loss = criterion(y_pred, y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_loss += loss.item()
avg_train_loss = epoch_loss / len(train_loader)
train_losses.append(avg_train_loss)
# Validation
model.eval()
val_loss = 0
with torch.no_grad():
for X_batch, y_batch in test_loader:
y_pred = model(X_batch).squeeze()
val_loss += criterion(y_pred, y_batch).item()
avg_val_loss = val_loss / len(test_loader)
val_losses.append(avg_val_loss)
scheduler.step(avg_val_loss)
# Early stopping
if avg_val_loss < best_val_loss:
best_val_loss = avg_val_loss
best_state = model.state_dict().copy()
if epoch % 20 == 0:
print(f"Epoch {epoch:3d}: train_loss={avg_train_loss:.4f}, val_loss={avg_val_loss:.4f}")
# Load best model and evaluate
model.load_state_dict(best_state)
model.eval()
with torch.no_grad():
y_pred = model(torch.FloatTensor(X_test_s)).squeeze().numpy()
print(f"\nMLP R²: {r2_score(y_test, y_pred):.4f}")
print(f"MLP MAE: {mean_absolute_error(y_test, y_pred):.2f}")
输出:
TEXT
📖 仅展示
# 执行成功
6. Bob的MLP vs 线性回归对比
▶ 示例:同数据集对比
PYTHON
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_absolute_error
# Linear Regression baseline
lr = LinearRegression()
lr.fit(X_train_s, y_train)
lr_pred = lr.predict(X_test_s)
print("=" * 50)
print("MODEL COMPARISON")
print("=" * 50)
print(f"Linear Regression: R²={r2_score(y_test, lr_pred):.4f}, MAE={mean_absolute_error(y_test, lr_pred):.2f}")
print(f"MLP (3-layer): R²={r2_score(y_test, y_pred):.4f}, MAE={mean_absolute_error(y_test, y_pred):.2f}")
输出:
TEXT
📖 仅展示
=
MODEL COMPARISON
=
| 维度 | LinearRegression | MLP(3层) |
|---|---|---|
| R² | 0.78 | 0.86 |
| MAE | 12.5 | 9.3 |
| 特征交互 | 手动添加 | 自动学习 |
| 训练时间 | <1s | ~30s |
| 可解释性 | 高(系数) | 低(黑箱) |
| 过拟合风险 | 低 | 中 |
⚠️ 注意: 表格数据上,XGBoost/LightGBM通常比MLP更好更快。MLP的优势在于自动学习特征交互和非线性变换。实际项目中先试GBDT,如果不够再试MLP。
❓ 常见问题
Q PyTorch和TensorFlow该学哪个?
A 研究/学习选PyTorch(更Pythonic、调试友好),生产部署看团队技术栈。两者功能相当,PyTorch在学术界更流行。
Q MLP隐藏层多少个、多少神经元?
A 常用经验——先1-2个隐藏层,神经元数从input_dim→64→32递减。太深太宽→过拟合,太浅太窄→欠拟合。用验证集调整。
Q Adam和SGD哪个好?
A 大多数情况Adam更好(自适应学习率、收敛快)。SGD+Momentum在某些任务上泛化更好。入门用Adam,精细调优再试SGD。
Q BatchNorm和Dropout是什么?
A BatchNorm标准化每层输出(加速训练+稳定性);Dropout随机丢弃神经元(防止过拟合)。两者是训练技巧标配。
Q 学习率怎么选?
A 初始0.001(Adam)或0.01(SGD)。配合scheduler自动衰减(ReduceLROnPlateau)。训练不稳定→降低lr,收敛太慢→提高lr。
Q 表格数据该用MLP还是XGBoost?
A 优先XGBoost——在表格数据上通常更好更快。MLP优势是自动学习特征交互。如果GBDT已经够好,不需要MLP。
📖 小节
- 深度学习自动学习特征表示,传统ML需要手动特征工程
- PyTorch核心:Tensor(多维数组)、autograd(自动微分)、nn.Module(模型定义)
- MLP训练五步循环:forward → loss → zero_grad → backward → step
- DataLoader批量加载、scheduler学习率调度、early stopping防止过拟合
- MLP vs 线性回归:自动学习交互,R²更高,但可解释性更低
- 表格数据上GBDT通常优于MLP,MLP适合特征交互复杂或非结构化数据
📝 作业
- 基础题(难度⭐):用PyTorch创建一个2层MLP(输入4维→16→8→输出1),打印模型结构和参数总数。提示:nn.Sequential +
sum(p.numel()...)。 - 进阶题(难度⭐⭐):用MLP对California Housing预测,实现完整的训练循环(含验证和early stopping),对比LinearRegression的R²。提示:参考第5节完整训练流程。
- 挑战题(难度⭐⭐⭐):实验不同MLP架构(1层/2层/3层,不同隐藏层大小)对性能的影响,画出"隐藏层数 vs R²"曲线,找到最优架构。提示:循环不同hidden_dims配置 + 记录验证R²。