DNN二分类示例

复制代码
# ============================================================
# 第一部分:导入依赖
# ============================================================
import torch                          # PyTorch 核心库
import torch.nn as nn                 # 神经网络层、损失函数
import torch.optim as optim           # 优化器(Adam、SGD 等)
from torch.utils.data import DataLoader, TensorDataset  # 数据加载工具
from sklearn.datasets import make_classification        # 生成模拟二分类数据
from sklearn.model_selection import train_test_split    # 划分训练/测试集
from sklearn.preprocessing import StandardScaler        # 标准化
from sklearn.metrics import accuracy_score, roc_auc_score  # 评估指标
import numpy as np                    # 数值计算


# ============================================================
# 第二部分:数据准备
# ============================================================

# 2.1 生成模拟二分类数据
# n_samples=5000:5000 个样本
# n_features=20:20 个特征
# n_informative=10:其中 10 个是真正有信息量的
# n_redundant=5:5 个是冗余特征(由信息特征线性组合而成)
# weights=[0.9, 0.1]:类别比例 9:1,故意制造类别不平衡
# random_state=42:随机种子,保证每次运行结果一致
X, y = make_classification(
    n_samples=5000,
    n_features=20,
    n_informative=10,
    n_redundant=5,
    weights=[0.9, 0.1],
    random_state=42
)
print(f"数据形状: X={X.shape}, y={y.shape}")
print(f"类别分布: {np.bincount(y)}")  # 输出如 [4500 500]

# 2.2 划分训练集和测试集
# test_size=0.2:测试集占 20%,训练集占 80%
# stratify=y:分层采样,保证训练/测试集的类别比例一致
# 不平衡数据必须用 stratify,否则测试集可能几乎没有正样本
X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.2,
    random_state=42,
    stratify=y
)
print(f"训练集: {X_train.shape}, 测试集: {X_test.shape}")

# 2.3 标准化
# fit_transform 在训练集上:计算均值和标准差,并转换
# transform 在测试集上:用训练集的均值和标准差转换,不重新计算
# 为什么要分开?防止数据泄露(测试集统计信息不能参与训练)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

# 2.4 转成 PyTorch 张量
# FloatTensor:PyTorch 默认用 float32,不能用 float64
# reshape(-1, 1):把 y 从 (4000,) 变成 (4000, 1)
# 为什么?模型输出是 (batch_size, 1),标签也必须形状一致
X_train_t = torch.FloatTensor(X_train)
y_train_t = torch.FloatTensor(y_train).reshape(-1, 1)
X_test_t = torch.FloatTensor(X_test)
y_test_t = torch.FloatTensor(y_test).reshape(-1, 1)

# 2.5 构造 DataLoader
# TensorDataset:把 X 和 y 打包,每个样本是 (x_i, y_i)
# DataLoader:
#   batch_size=64:每批 64 个样本
#   shuffle=True:每个 epoch 打乱顺序,防止模型学到样本顺序
train_dataset = TensorDataset(X_train_t, y_train_t)
train_loader = DataLoader(
    train_dataset,
    batch_size=64,
    shuffle=True
)


# ============================================================
# 第三部分:定义模型
# ============================================================

class DNNBinaryClassifier(nn.Module):
    """
    DNN 二分类模型
    结构:输入层 → 隐藏层1 → 隐藏层2 → 隐藏层3 → 输出层
    输出层不加 Sigmoid,配合 BCEWithLogitsLoss 使用
    """
    def __init__(self, input_dim):
        super().__init__()  # 调用父类 nn.Module 的初始化,必须写
        self.net = nn.Sequential(
            # 第一层:input_dim → 128
            nn.Linear(input_dim, 128),   # 全连接层,权重矩阵 (128, input_dim)
            nn.BatchNorm1d(128),         # 批归一化,对每个 batch 的 128 维做归一化
            nn.ReLU(),                   # 激活函数 max(0, x),引入非线性
            nn.Dropout(0.3),             # 随机丢弃 30% 神经元,防止过拟合

            # 第二层:128 → 64
            nn.Linear(128, 64),
            nn.BatchNorm1d(64),
            nn.ReLU(),
            nn.Dropout(0.3),

            # 第三层:64 → 32
            nn.Linear(64, 32),
            nn.ReLU(),                   # 接近输出层,不再加 BatchNorm 和 Dropout

            # 输出层:32 → 1
            nn.Linear(32, 1)             # 输出 1 个值(logit),不加 Sigmoid
        )

    def forward(self, x):
        # 前向传播:数据依次经过每一层
        return self.net(x)


# ============================================================
# 第四部分:训练配置
# ============================================================

# 4.1 选择设备
# 有 GPU 用 CUDA,没有就用 CPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")

# 4.2 初始化模型,并移到设备
model = DNNBinaryClassifier(input_dim=X_train.shape[1]).to(device)

# 4.3 计算 pos_weight,处理类别不平衡
# pos_weight = 负样本数 / 正样本数
# 本例中约等于 9,表示正样本的损失权重是负样本的 9 倍
# 作用:让模型更关注少数类(正样本)
pos_weight = torch.tensor(
    [(y_train == 0).sum() / (y_train == 1).sum()]
).to(device)
print(f"pos_weight: {pos_weight.item():.4f}")

# 4.4 损失函数
# BCEWithLogitsLoss:二元交叉熵 + Sigmoid,数值稳定
# 输入是 logits(未经过 Sigmoid 的输出),内部会自动做 Sigmoid
# pos_weight 用于类别不平衡
criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)

# 4.5 优化器
# Adam:自适应学习率,收敛快
# lr=1e-3:学习率 0.001
# weight_decay=1e-5:L2 正则化,防止过拟合
optimizer = optim.Adam(
    model.parameters(),
    lr=1e-3,
    weight_decay=1e-5
)

# 4.6 学习率调度器(可选)
# 每 10 个 epoch,学习率乘 0.5
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)


# ============================================================
# 第五部分:训练循环
# ============================================================

n_epochs = 20  # 训练 20 轮

for epoch in range(n_epochs):
    # 5.1 设为训练模式
    # 启用 Dropout,BatchNorm 用当前 batch 统计量
    model.train()

    total_loss = 0  # 累计一个 epoch 的损失

    # 5.2 遍历 DataLoader
    for X_batch, y_batch in train_loader:
        # X_batch.shape = (64, 20)
        # y_batch.shape = (64, 1)

        # 5.3 把数据移到设备(GPU/CPU)
        X_batch = X_batch.to(device)
        y_batch = y_batch.to(device)

        # 5.4 清空梯度
        # PyTorch 默认累加梯度,不清空会导致更新错误
        optimizer.zero_grad()

        # 5.5 前向传播
        # 输出 logits,形状 (64, 1)
        logits = model(X_batch)

        # 5.6 计算损失
        loss = criterion(logits, y_batch)

        # 5.7 反向传播
        # 计算所有参数的梯度
        loss.backward()

        # 5.8 更新参数
        # w = w - lr * grad
        optimizer.step()

        # 5.9 累计损失
        total_loss += loss.item()

    # 5.10 更新学习率
    scheduler.step()

    # 5.11 打印每个 epoch 的平均损失
    avg_loss = total_loss / len(train_loader)
    current_lr = optimizer.param_groups[0]['lr']
    print(f"Epoch {epoch+1}/{n_epochs}, Loss: {avg_loss:.4f}, LR: {current_lr:.6f}")


# ============================================================
# 第六部分:评估
# ============================================================

# 6.1 设为评估模式
# 关闭 Dropout,BatchNorm 用训练时学到的全局统计量
model.eval()

# 6.2 关闭梯度计算
# 评估不需要反向传播,省内存、加速
with torch.no_grad():
    # 6.3 前向传播,得到 logits
    logits = model(X_test_t.to(device))

    # 6.4 手动 Sigmoid,得到概率
    y_pred_prob = torch.sigmoid(logits).cpu().numpy().ravel()

    # 6.5 按 0.5 阈值转成硬分类
    y_pred = (y_pred_prob > 0.5).astype(int)

# 6.6 计算评估指标
acc = accuracy_score(y_test, y_pred)
auc = roc_auc_score(y_test, y_pred_prob)

print(f"\n测试集评估:")
print(f"准确率: {acc:.4f}")
print(f"AUC: {auc:.4f}")

逐部分关键解释

第一部分:导入依赖

导入 作用
torch PyTorch 核心
torch.nn 网络层、损失函数
torch.optim 优化器
DataLoader, TensorDataset 数据加载
make_classification 生成模拟数据
train_test_split 划分数据集
StandardScaler 标准化
accuracy_score, roc_auc_score 评估指标

第二部分:数据准备

关键点:

  1. weights=[0.9, 0.1]:故意制造不平衡,模拟真实场景。

  2. stratify=y:保证训练/测试集类别比例一致,否则测试集可能没有正样本。

  3. fit_transform vs transform:

    • 训练集:fit_transform,计算统计量 + 转换。

    • 测试集:transform,只用训练集的统计量转换,防止数据泄露。

  4. reshape(-1, 1) :把 y 从 (4000,) 变成 (4000, 1),和模型输出形状一致。

  5. shuffle=True:每个 epoch 打乱数据,防止模型学到顺序规律。


第三部分:模型定义

关键点:

  1. 继承 nn.Module:PyTorch 所有模型的基类。

  2. super().__init__():必须写,否则参数无法正确注册。

  3. nn.Sequential:把层按顺序串起来。

  4. 每层结构:

    • Linear:全连接,做线性变换。

    • BatchNorm1d:批归一化,加速训练、提升稳定性。

    • ReLU:激活函数,引入非线性。

    • Dropout(0.3):随机丢弃 30% 神经元,防止过拟合。

  5. 输出层不加 Sigmoid :配合 BCEWithLogitsLoss,数值更稳定。


第四部分:训练配置

关键点:

  1. device:自动检测 GPU,没有就用 CPU。

  2. pos_weight:处理类别不平衡,给正样本加权。

    • 公式:负样本数 / 正样本数。

    • 本例约 9,正样本损失权重是负样本的 9 倍。

  3. BCEWithLogitsLoss:

    • 输入是 logits(未 Sigmoid)。

    • 内部自动做 Sigmoid + 交叉熵,数值稳定。

    • pos_weight 用于不平衡。

  4. Adam:

    • 自适应学习率,收敛快。

    • lr=1e-3:学习率。

    • weight_decay=1e-5:L2 正则。

  5. StepLR:每 10 个 epoch 学习率乘 0.5,帮助收敛。


第五部分:训练循环

每个 batch 的 7 步:

步骤 代码 作用
1 optimizer.zero_grad() 清空梯度
2 logits = model(X_batch) 前向传播
3 loss = criterion(logits, y_batch) 计算损失
4 loss.backward() 反向传播,算梯度
5 optimizer.step() 更新参数
6 total_loss += loss.item() 累计损失
7 scheduler.step() 更新学习率(每 epoch)

为什么 zero_grad() 必须写?

PyTorch 默认累加梯度,不清空会导致更新错误。

为什么 model.train() 必须写?

启用 Dropout 和 BatchNorm 的训练行为。


第六部分:评估

关键点:

  1. model.eval():关闭 Dropout,BatchNorm 用全局统计量。

  2. torch.no_grad():关闭梯度计算,省内存、加速。

  3. torch.sigmoid(logits):手动 Sigmoid,把 logits 转成概率。

  4. (y_pred_prob > 0.5):按 0.5 阈值硬分类。

  5. accuracy_score + roc_auc_score:

    • 不平衡时,准确率会虚高。

    • AUC 更能反映真实效果。


运行结果示例

text

复制代码
数据形状: X=(5000, 20), y=(5000,)
类别分布: [4502  498]
训练集: (4000, 20), 测试集: (1000, 20)
使用设备: cpu
pos_weight: 9.0361

Epoch 1/20, Loss: 0.5234, LR: 0.001000
Epoch 2/20, Loss: 0.3121, LR: 0.001000
...
Epoch 20/20, Loss: 0.1523, LR: 0.000500

测试集评估:
准确率: 0.9230
AUC: 0.9521

核心记忆点

要点 说明
标准化 fit_transform 训练集,transform 测试集
张量形状 y 必须 reshape 成 (N, 1)
模型结构 Linear + BatchNorm + ReLU + Dropout
输出层 不加 Sigmoid,配 BCEWithLogitsLoss
不平衡 pos_weight 处理
训练 7 步 zero_grad → forward → loss → backward → step
train/eval 训练用 train(),评估用 eval()
no_grad 评估时关闭梯度
评估指标 不平衡看 AUC,不只看准确率

常见的 7 个坑

  1. 忘了 optimizer.zero_grad() → 梯度累加,训练发散。

  2. 忘了 model.train() / model.eval() → Dropout 和 BatchNorm 行为不对。

  3. 数据没移到 GPU → 报错 "Expected all tensors on same device"。

  4. y 形状不对 → BCELoss 报错,必须 (N, 1)。

  5. BCEWithLogitsLoss 配 Sigmoid → 重复计算,数值不稳定。

  6. 测试集 fit 标准化 → 数据泄露,评估虚高。

  7. 只看准确率 → 不平衡时虚高,要看 AUC、F1、Recall。


一句话总结

PyTorch DNN 二分类流程:

  1. 数据:生成 → 划分 → 标准化 → 转张量 → DataLoader。

  2. 模型:继承 nn.Module,堆叠 Linear + BN + ReLU + Dropout。

  3. 训练:train() → 7 步循环 → scheduler.step()。

  4. 评估:eval() → no_grad() → Sigmoid → AUC。

相关推荐
墨林陌2 小时前
AI 热点日报(2026-10-15):谷歌Gemini 4 Argon全球上线,STEPX首款智能体手机发布,无问芯穹C轮23亿刷新国内AI Infra纪录
人工智能
shujudang2 小时前
AI会员运营的数据实践:人群预测、等级策略与效果分析
大数据·人工智能·数据挖掘·数据分析
Maynor9962 小时前
一张照片出演吵架舞:Bad Times AI 人物/宠物短视频工具体验与实现简析
人工智能·短视频·独立开发·ai视频·badtimes·照片生成视频
zoutao982 小时前
DeepSeek 智能效果实测与能力边界解析
大数据·运维·人工智能
易知微EasyV数据可视化2 小时前
会插卡片的编辑器:在 Agent 输入框里混排文字与结构化内容
人工智能·经验分享·编辑器·agent·设计
skywalk81632 小时前
介绍一下BEAM 数据和 LoCoMo 数据:评估大模型记忆能力的数据集
人工智能·记忆
MerryLuna2 小时前
专业的EUDR机构
大数据·人工智能·python
wespaul2 小时前
VeapAI 自然语言操作实战|第 04 篇:系统数据加工——把系统自身的元数据吃成知识
java·大数据·运维·数据库·人工智能
墨云安全2 小时前
自动化安全测试工具的技术架构、集成方式与任务编排(2026 年实践)
人工智能·网络安全·渗透测试