Tutorial 5: 机器学习基础
什么是机器学习?
机器学习 是让计算机从数据中自动学习规律的方法,而不需要显式编程。
传统编程:
┌────────────┐ ┌────────────┐ ┌────────────┐
│ 数据 │ + │ 规则 │ ──► │ 结果 │
└────────────┘ └────────────┘ └────────────┘
机器学习:
┌────────────┐ ┌────────────┐ ┌────────────┐
│ 数据 │ + │ 结果 │ ──► │ 规则 │
└────────────┘ └────────────┘ └────────────┘
通俗理解
想象你在教小孩认识猫:
传统编程: “猫有四条腿、有尾巴、会喵喵叫…”(写规则)
机器学习: 给小孩看很多猫的图片,让他自己总结特征(从数据学习)
机器学习的类型
机器学习
├── 监督学习 (Supervised Learning)
│ ├── 分类 (Classification)
│ └── 回归 (Regression)
│
├── 无监督学习 (Unsupervised Learning)
│ ├── 聚类 (Clustering)
│ └── 降维 (Dimensionality Reduction)
│
├── 强化学习 (Reinforcement Learning)
│ └── 通过奖励学习策略
│
└── 半监督学习 / 自监督学习
└── 结合标注和未标注数据
1. 监督学习
有标签的数据,学习输入到输出的映射。
import torch
import torch.nn as nn
# 分类示例:判断邮件是否为垃圾邮件
# 输入: 邮件特征向量
# 输出: 0 (正常) 或 1 (垃圾)
# 回归示例:预测房价
# 输入: 房屋特征 (面积、位置、房龄等)
# 输出: 价格 (连续值)
2. 无监督学习
没有标签,发现数据中的结构。
# 聚类示例:客户分群
# 输入: 客户行为数据
# 输出: 将客户分成若干群组
# 降维示例:数据可视化
# 输入: 高维数据
# 输出: 低维表示
3. 强化学习
通过与环境交互,学习最优策略。
# 示例:学习下棋
# 状态: 棋盘局面
# 动作: 走哪一步
# 奖励: 赢了+1, 输了-1
核心概念
1. 数据集
import torch
from torch.utils.data import Dataset, DataLoader
class SimpleDataset(Dataset):
"""简单数据集"""
def __init__(self, X, y):
self.X = torch.tensor(X, dtype=torch.float32)
self.y = torch.tensor(y, dtype=torch.float32)
def __len__(self):
return len(self.X)
def __getitem__(self, idx):
return self.X[idx], self.y[idx]
# 数据集划分
def train_test_split(X, y, test_ratio=0.2):
n = len(X)
indices = torch.randperm(n)
split = int(n * (1 - test_ratio))
train_idx = indices[:split]
test_idx = indices[split:]
return X[train_idx], X[test_idx], y[train_idx], y[test_idx]
2. 模型
# 线性模型
class LinearModel(nn.Module):
def __init__(self, input_dim, output_dim):
super().__init__()
self.linear = nn.Linear(input_dim, output_dim)
def forward(self, x):
return self.linear(x)
# 多层感知机
class MLP(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, output_dim)
)
def forward(self, x):
return self.layers(x)
3. 损失函数
衡量预测与真实值的差距。
# 回归任务:均方误差
mse_loss = nn.MSELoss()
# 分类任务:交叉熵
ce_loss = nn.CrossEntropyLoss()
# 二分类:二元交叉熵
bce_loss = nn.BCEWithLogitsLoss()
# 示例
predictions = torch.tensor([2.5, 0.0, 2.1])
targets = torch.tensor([3.0, -0.5, 2.0])
loss = mse_loss(predictions, targets)
print(f"MSE Loss: {loss.item():.4f}")
4. 优化器
更新模型参数以最小化损失。
model = LinearModel(10, 1)
# 随机梯度下降
sgd = torch.optim.SGD(model.parameters(), lr=0.01)
# Adam(常用)
adam = torch.optim.Adam(model.parameters(), lr=0.001)
# 学习率调度
scheduler = torch.optim.lr_scheduler.StepLR(adam, step_size=10, gamma=0.1)
实战:线性回归
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
# 1. 生成数据
torch.manual_seed(42)
# 真实关系: y = 3x + 2 + 噪声
X = torch.randn(100, 1)
y = 3 * X + 2 + torch.randn(100, 1) * 0.5
# 2. 定义模型
model = nn.Linear(1, 1)
# 3. 定义损失和优化器
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
# 4. 训练
losses = []
for epoch in range(100):
# 前向传播
predictions = model(X)
loss = criterion(predictions, y)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
losses.append(loss.item())
if (epoch + 1) % 20 == 0:
print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")
# 5. 查看学到的参数
print(f"\n学到的参数:")
print(f" 权重 w = {model.weight.item():.2f} (真实值: 3)")
print(f" 偏置 b = {model.bias.item():.2f} (真实值: 2)")
# 6. 可视化
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.scatter(X.numpy(), y.numpy(), alpha=0.5, label='数据点')
plt.plot(X.numpy(), model(X).detach().numpy(), 'r-', label='学到的直线')
plt.xlabel('X')
plt.ylabel('y')
plt.legend()
plt.title('线性回归结果')
plt.subplot(1, 2, 2)
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('训练损失曲线')
plt.tight_layout()
plt.savefig('linear_regression.png')
print("\n图表已保存到 linear_regression.png")
实战:分类问题
import torch
import torch.nn as nn
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
# 1. 生成分类数据
X, y = make_classification(
n_samples=1000,
n_features=20,
n_informative=10,
n_classes=2,
random_state=42
)
# 转换为 PyTorch 张量
X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32).unsqueeze(1)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 2. 定义模型
class Classifier(nn.Module):
def __init__(self, input_dim):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(input_dim, 64),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(64, 32),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(32, 1)
)
def forward(self, x):
return self.layers(x)
model = Classifier(20)
# 3. 训练
criterion = nn.BCEWithLogitsLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
for epoch in range(100):
model.train()
# 前向传播
outputs = model(X_train)
loss = criterion(outputs, y_train)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 20 == 0:
# 评估
model.eval()
with torch.no_grad():
train_pred = (torch.sigmoid(model(X_train)) > 0.5).float()
test_pred = (torch.sigmoid(model(X_test)) > 0.5).float()
train_acc = (train_pred == y_train).float().mean()
test_acc = (test_pred == y_test).float().mean()
print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}, "
f"Train Acc: {train_acc:.2%}, Test Acc: {test_acc:.2%}")
过拟合与正则化
过拟合: 模型在训练数据上表现好,但在新数据上表现差。
┌─────────────────────────────────────────────────────────┐
│ │
│ 欠拟合 恰好 过拟合 │
│ │
│ ────── ~~~~~~ ~∿~∿~∿~ │
│ · · · · · · · · · │
│ · · · · · · · · · │
│ │
│ 训练误差高 训练误差低 训练误差很低 │
│ 测试误差高 测试误差低 测试误差高 │
│ │
└─────────────────────────────────────────────────────────┘
防止过拟合的方法
# 1. L2 正则化(权重衰减)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=0.01)
# 2. Dropout
class ModelWithDropout(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(100, 50)
self.dropout = nn.Dropout(0.5) # 50% 的神经元随机失活
self.fc2 = nn.Linear(50, 10)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = self.dropout(x) # 训练时随机失活
x = self.fc2(x)
return x
# 3. 早停(Early Stopping)
best_loss = float('inf')
patience = 10
counter = 0
for epoch in range(1000):
train_loss = train_one_epoch()
val_loss = evaluate()
if val_loss < best_loss:
best_loss = val_loss
counter = 0
# 保存最佳模型
torch.save(model.state_dict(), 'best_model.pt')
else:
counter += 1
if counter >= patience:
print("Early stopping!")
break
# 4. 数据增强(后续教程详细介绍)
模型评估指标
分类任务
def evaluate_classification(y_true, y_pred):
"""分类评估指标"""
# 混淆矩阵
TP = ((y_pred == 1) & (y_true == 1)).sum().item()
TN = ((y_pred == 0) & (y_true == 0)).sum().item()
FP = ((y_pred == 1) & (y_true == 0)).sum().item()
FN = ((y_pred == 0) & (y_true == 1)).sum().item()
# 准确率
accuracy = (TP + TN) / (TP + TN + FP + FN)
# 精确率
precision = TP / (TP + FP) if (TP + FP) > 0 else 0
# 召回率
recall = TP / (TP + FN) if (TP + FN) > 0 else 0
# F1 分数
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
return {
'accuracy': accuracy,
'precision': precision,
'recall': recall,
'f1': f1
}
回归任务
def evaluate_regression(y_true, y_pred):
"""回归评估指标"""
# 均方误差
mse = ((y_pred - y_true) ** 2).mean().item()
# 均方根误差
rmse = mse ** 0.5
# 平均绝对误差
mae = (y_pred - y_true).abs().mean().item()
# R² 决定系数
ss_res = ((y_true - y_pred) ** 2).sum()
ss_tot = ((y_true - y_true.mean()) ** 2).sum()
r2 = 1 - ss_res / ss_tot
return {
'mse': mse,
'rmse': rmse,
'mae': mae,
'r2': r2.item()
}
关键概念总结
概念 |
解释 |
|---|---|
监督学习 |
从标注数据学习输入到输出的映射 |
无监督学习 |
从无标注数据发现结构 |
损失函数 |
衡量预测与真实值的差距 |
优化器 |
更新模型参数的算法 |
过拟合 |
模型过度拟合训练数据 |
正则化 |
防止过拟合的技术 |
验证集 |
用于调参和早停的数据 |
下一步
在下一个教程中,我们将深入学习神经网络的原理。