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()
    }

关键概念总结

概念

解释

监督学习

从标注数据学习输入到输出的映射

无监督学习

从无标注数据发现结构

损失函数

衡量预测与真实值的差距

优化器

更新模型参数的算法

过拟合

模型过度拟合训练数据

正则化

防止过拟合的技术

验证集

用于调参和早停的数据

下一步

在下一个教程中,我们将深入学习神经网络的原理。

Tutorial 6: 神经网络