☰
DNN二分类示例
2026/10/14 15:58:46 网站建设 项目流程
# ============================================================ # 第一部分:导入依赖 # ============================================================ 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}")

逐部分关键解释

第一部分:导入依赖

导入作用
torchPyTorch 核心
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_transformvstransform:

    • 训练集: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 步:

步骤代码作用
1optimizer.zero_grad()清空梯度
2logits = model(X_batch)前向传播
3loss = criterion(logits, y_batch)计算损失
4loss.backward()反向传播,算梯度
5optimizer.step()更新参数
6total_loss += loss.item()累计损失
7scheduler.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。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询