深度学习——神经网络简单实践(在乳腺癌数据集上的小型二分类示例)
神经网络在乳腺癌数据集上的小型二分类示例 — 逐行详解
概览(一句话)
用 PyTorch 实现一个非常基础的二层全连接神经网络(SimpleNN
)对 sklearn 的 load_breast_cancer
数据做二分类:先用 StandardScaler
标准化数据,然后把 NumPy 数据转成 torch.Tensor
,用 BCELoss
+ Adam
在全部训练数据上训练若干 epoch,最终在测试集上预测并计算准确率。
流程:
开始程序
↓
加载数据集 (sklearn.datasets.load_breast_cancer)
↓
划分训练集 / 测试集 (train_test_split)
↓
数据标准化 (StandardScaler.fit_transform / transform)
↓
转为 torch.tensor (float32) → 并调整 y 的形状为 (N,1)
↓
选择运行设备 (CPU / GPU: torch.device)
↓
构建神经网络模型 (继承 nn.Module)
┌───────────────────────────────────────┐
│ 输入层 (nn.Linear(in_dim, 16)) │
│ → ReLU 激活 (nn.ReLU()) │
│ → 输出层 (nn.Linear(16, 1)) │
│ → Sigmoid 激活 (nn.Sigmoid()) │
└───────────────────────────────────────┘
↓
定义损失函数 (nn.BCELoss)
↓
定义优化器 (optim.Adam(model.parameters(), lr=0.01))
↓
================= 训练循环 (epoch) =================
│
├─ model.train() → 进入训练模式
│
├─ 前向传播:y_pred = model(X_train_t)
│
├─ 计算损失:loss = criterion(y_pred, y_train_t)
│
├─ 反向传播:loss.backward()
│
├─ 更新参数:optimizer.step()
│
└─ 清空梯度:optimizer.zero_grad()
↓
================= 训练结束 =================
↓
进入测试阶段:model.eval()
↓
前向传播得到预测概率:y_prob = model(X_test_t)
↓
将概率转为类别 (≥0.5 → 1, <0.5 → 0)
↓
计算准确率 (accuracy_score)
↓
输出结果 (测试集准确率)
↓
结束程序
0)先看完整代码
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score# ========== 0. 判断设备 ==========
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("当前设备:", device)# ========== 1. 数据 ==========
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)X_train_t = torch.tensor(X_train, dtype=torch.float32).to(device)
y_train_t = torch.tensor(y_train, dtype=torch.float32).view(-1,1).to(device)
X_test_t = torch.tensor(X_test, dtype=torch.float32).to(device)
y_test_t = torch.tensor(y_test, dtype=torch.float32).view(-1,1).to(device)# ========== 2. 模型 ==========
class SimpleNN(nn.Module):def __init__(self, in_dim):super().__init__()self.fc1 = nn.Linear(in_dim, 16) # 第一层self.relu = nn.ReLU() # 激活函数self.fc2 = nn.Linear(16, 1) # 第二层self.sigmoid = nn.Sigmoid() # 输出层激活def forward(self, x):x = self.fc1(x) # 输入 -> 全连接层1x = self.relu(x) # ReLU激活x = self.fc2(x) # 全连接层2x = self.sigmoid(x) # Sigmoid输出(二分类概率)return xmodel = SimpleNN(in_dim=X_train.shape[1]).to(device)
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)# ========== 3. 训练 ==========
for epoch in range(10):model.train()optimizer.zero_grad()y_pred = model(X_train_t)loss = criterion(y_pred, y_train_t)loss.backward()optimizer.step()print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")# ========== 4. 测试 ==========
model.eval()
with torch.no_grad():y_prob = model(X_test_t).cpu().numpy().ravel() # 转回CPU再转numpyy_pred = (y_prob >= 0.5).astype(int)print("测试集准确率:", accuracy_score(y_test, y_pred))
1)数据部分详解(sklearn → StandardScaler → 转为 Tensor)
torch.device("cuda" if torch.cuda.is_available() else "cpu")
判断 GPU 是否可用。
# ========== 0. 判断设备 ==========
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("当前设备:", device)
from sklearn.datasets import load_breast_cancer
功能:加载一个经典的乳腺癌数据集(已内置于 scikit-learn)。返回
Bunch
或者当return_X_y=True
时返回(X, y)
。常用参数:
return_X_y=False
(默认)返回一个包含 data、target、feature_names 等字段的 Bunch 对象;return_X_y=True
返回(X, y)
两个 ndarray。
数据形状(典型):
X
通常是(569, 30)
,y
是(569,)
,这是该内置数据集的常见规模。
StandardScaler()
/ scaler.fit_transform(X_train)
/ scaler.transform(X_test)
功能:逐特征去均值并按标准差缩放(Z-score 标准化)。
类参数:
with_mean=True
:是否减去均值(默认 True)。with_std=True
:是否除以标准差(默认 True)。copy=True
:是否复制数据。
方法:
fit(X)
:计算每个特征的均值和方差(或标准差)。transform(X)
:用已保存的均值/方差转换新数据。fit_transform(X)
:等同于fit
+transform
一步完成(对训练集使用)。
注意:对训练集
fit
,对测试集仅transform
。避免信息泄漏。
torch.tensor(...)
vs torch.from_numpy(...)
在代码中你用:
X_train_t = torch.tensor(X_train, dtype=torch.float32).to(device)
y_train_t = torch.tensor(y_train, dtype=torch.float32).view(-1,1).to(device)
(.to(device)
把模型和数据都放到对应CPU或GPU上。)
torch.tensor(data, dtype=..., device=..., requires_grad=...)
功能:从 Python 数组/NumPy 等创建一个新的 Tensor(通常会复制数据,默认
requires_grad=False
)。参数:
data
:可以是 list、ndarray、其他 Tensor 等。dtype
:指定 dtype,如torch.float32
、torch.int64
等。神经网络常用float32
。device
:'cpu'
或'cuda:0'
等。requires_grad
(布尔):是否需要追踪梯度(默认 False)。
注意:
torch.tensor
通常会复制数据(会分配新内存)。
torch.from_numpy(np_array)
功能:从 NumPy 数组创建 Tensor,但不会复制内存(共享内存)。对大数据更高效。
限制:只有当 NumPy 数组是 C-contiguous 且 dtype 可对映才可以直接共享(例如
np.float32
)。
建议:若数据已是
np.ndarray
并且希望避免额外拷贝,用:X_train_t = torch.from_numpy(X_train.astype(np.float32))
.view(-1, 1)
:等价于
reshape
,把一维向量shape (N,)
改为(N,1)
。-1
表示自动计算该维度大小。注意:
.view()
需要 contiguous 的内存,可用.reshape()
或.unsqueeze(1)
更稳妥(.unsqueeze(1)
在这里语义更明确:在第 1 个维度插入一个维度)。
2)模型定义详解(nn.Module
、nn.Linear
、激活函数等)
class SimpleNN(nn.Module):
PyTorch 模型的标准写法:继承自
torch.nn.Module
并实现__init__
与forward(self, x)
。super().__init__()
:调用父类构造器,必需以便注册子模块参数(self.fc1
等)到父类中,这样model.parameters()
才能正确返回所有参数。
nn.Linear(in_features, out_features, bias=True)
功能:全连接层(仿射变换),实现 y=xWT+by = xW^T + b(PyTorch 中权重形状为
(out_features, in_features)
)。参数:
in_features
:输入特征维数(本例为in_dim
)。out_features
:输出特征维数(本例 16 或 1)。bias=True
:是否包含偏置b
。
默认初始化:
权重通常使用 Kaiming/Xavier 类初始化(PyTorch 默认对
nn.Linear.weight
使用kaiming_uniform_
),偏置初始为 0。
nn.ReLU(inplace=False)
功能:ReLU 激活函数,max(0,x)\max(0, x)。
参数:
inplace=False
:是否就地修改输入(节省内存,但可能会与反向传播/计算图某些操作冲突)。建议默认False
,除非确定可用。
nn.Sigmoid()
功能:对标量输出做 S 型映射,把任意实数映射到 (0,1),适合二分类概率输出(sigmoid 将 logits 转为概率)。
参数:无。
forward 里数据流(形状追踪)
假设 batch_size = N
,in_dim = D
:
输入
x
:(N, D)
self.fc1(x)
→(N, 16)
(线性变换)self.relu(...)
→(N, 16)
(非线性,不改变形状)self.fc2(...)
→(N, 1)
(线性)self.sigmoid(...)
→(N, 1)
(概率)
3)损失函数与优化器(nn.BCELoss
、optim.Adam
)
criterion = nn.BCELoss()
功能:二元交叉熵损失(Binary Cross Entropy),当预测是概率(0~1)并且标签是 0 或 1 时使用。
数学形式(单样本):
BCE(p,y)=−[ylog(p)+(1−y)log(1−p)]\text{BCE}(p, y) = -\big[ y \log(p) + (1-y)\log(1-p) \big]当
reduction='mean'
(默认)时对 batch 平均。参数:
weight
:可选的每个样本的权重张量(与输入同 shape),用于不平衡样本加权;reduction
:'none'|'mean'|'sum'
。
重要注意:
nn.BCELoss
假设模型输出已经经过sigmoid
得到概率。如果你把网络的最后一层直接输出 logits(未做 sigmoid),更稳定的做法是使用nn.BCEWithLogitsLoss()
(它在数值上更稳定,内部实现合并了 sigmoid 与 BCE 的数值稳定性处理)。
optimizer = optim.Adam(model.parameters(), lr=0.01)
功能:Adam 优化器(自适应学习率)。
参数详解:
params
:模型参数迭代器(model.parameters()
返回的生成器)。lr=0.01
:初始学习率(Adam 常用 1e-3,但 0.01 也可——通常需要调参)。betas=(0.9, 0.999)
:一阶、二阶动量衰减系数(用于计算移动平均)。eps=1e-8
:数值稳定项(避免除零)。weight_decay=0
:L2 权重衰减(等价于正则化)。amsgrad=False
:是否使用 AMSGrad 变体。
注:学习率对训练稳定性影响非常大。对于小网络和这类数据,
lr=1e-3
是常见起点。
4)训练循环详解(逐行)
训练代码(简化形式):
# ========== 3. 训练 ==========
for epoch in range(10):model.train()optimizer.zero_grad()y_pred = model(X_train_t)loss = criterion(y_pred, y_train_t)loss.backward()optimizer.step()print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")
逐行讲解及每个函数/方法的参数与行为:
for epoch in range(10):
执行 10 个训练轮(epoch)。通常 epoch 数需根据验证集性能调整。
model.train()
功能:把模型设为“训练模式”。
train()
接收一个可选参数mode=True
(默认)。影响:对某些模块(如
Dropout
,BatchNorm
等)启用训练行为(比如 Dropout 会丢弃一部分神经元)。对nn.Linear
和nn.ReLU
没直接影响,但仍应每次训练开始前调用以保证模式正确。
optimizer.zero_grad()
功能:把模型参数上的梯度清零。PyTorch 在每次
backward()
时会累积梯度(默认),因此需在新一轮梯度计算前先zero_grad()
。注意点:PyTorch 1.7+ 提供
optimizer.zero_grad(set_to_none=True)
可降低内存和稍微提高性能(梯度被设为None
而非 0)。
y_pred = model(X_train_t)
功能:前向传播。调用
SimpleNN.forward(X_train_t)
。返回Tensor
,形状(N,1)
。注意:如果数据量大,建议使用 DataLoader 批次(mini-batch)训练而不是一次性把全体训练数据传入模型(这会影响训练动态并且占用大量内存)。
loss = criterion(y_pred, y_train_t)
功能:计算当前 batch 的损失值(
y_pred
为概率时使用BCELoss
)。输入条件:
y_pred
和y_train_t
形状必须可广播一致(如(N,1)
与(N,1)
或(N,)
等)。标签
y_train_t
对于 BCELoss 应该是float
(例如0.0
或1.0
),不是int
。你的代码将标签转换为float32
并.view(-1,1)
,是正确的。
loss.backward()
功能:反向传播,计算损失对模型所有可学习参数的梯度,并把这些梯度累加到每个参数的
.grad
属性中。参数:
loss.backward(gradient=None, retain_graph=False, create_graph=False)
,通常不传参数。retain_graph=True
在需要对同一计算图多次反向时使用(在常规训练中不常用)。create_graph=True
若需要二阶导数,用于更高级场景。
注意:在调用
backward()
前必须确保optimizer.zero_grad()
已清空上一次的梯度(否则会累加)。
optimizer.step()
功能:根据参数的
.grad
值,用所选优化算法(这里是 Adam)更新模型参数。参数:
optimizer.step(closure=None)
:对于某些需要重算 loss 的优化器(LBFGS),可以传入closure
,但是 Adam 不需要。
loss.item()
功能:把标量 Tensor(包含在计算图里)转换成 Python 浮点数,便于打印/记录。
注意:不要用
.numpy()
直接转换有requires_grad=True
的 tensor(会报错),loss.item()
是安全做法。
5)测试 / 评估详解
代码片段:
# ========== 4. 测试 ==========
model.eval()
with torch.no_grad():y_prob = model(X_test_t).cpu().numpy().ravel() # 转回CPU再转numpyy_pred = (y_prob >= 0.5).astype(int)print("测试集准确率:", accuracy_score(y_test, y_pred))
解释与注意:
model.eval()
:功能:把模型设为“评估模式”(
eval()
相当于train(mode=False)
)。影响:关闭 Dropout(不再随机丢弃神经元),BatchNorm 使用训练时记录的 running mean/var 而不是 batch 统计量。
with torch.no_grad():
功能:上下文管理器,禁用梯度计算与计算图构建,节省内存与计算。
用途:在验证或测试阶段应包装前向过程。也可以对单次推断使用
torch.inference_mode()
(PyTorch 新增,更快更节省内存)。
model(X_test_t).numpy()
:注意:要把 Tensor 转为 NumPy,Tensor 必须在 CPU 且不需要梯度(
requires_grad=False
)并且不是在 GPU。更稳妥:
model(X_test_t).detach().cpu().numpy()
或在 no_grad 环境下model(...).cpu().numpy()
。.ravel()
:把(N,1)
flatten 为(N,)
方便后续与y_test
对比。阈值
0.5
:基于sigmoid
的概率输出,通常以 0.5 作为二分类阈值,但在不平衡数据上你可能希望调整该阈值或使用 ROC/PR 曲线寻找最佳阈值。
accuracy_score(y_test, y_pred)
:来自
sklearn.metrics
,计算分类准确率(正确预测数量 / 总数)。注意:
y_test
应与y_pred
形状一致;通常y_test
为(N,)
,而y_pred
也应为(N,)
。
6)每个重要方法/类的“速查表”(signature + 一句话说明)
torch.tensor(data, dtype=None, device=None, requires_grad=False)
:从数据创建 Tensor(通常会复制)。torch.from_numpy(ndarray)
:从 NumPy 创建 Tensor 共享内存(高效)。ndarray.astype(np.float32)
:转换 NumPy dtype,常在转换前做以确保和 PyTorch dtype 对应。Tensor.view(shape)
/Tensor.reshape(shape)
/Tensor.unsqueeze(dim)
:修改形状(view
需要 contiguous)。nn.Module
:模型基类,子类化并实现forward()
。nn.Linear(in_features, out_features, bias=True)
:全连接层。nn.ReLU(inplace=False)
:ReLU 激活。nn.Sigmoid()
:Sigmoid 激活(将 logit 转为概率)。nn.BCELoss(weight=None, reduction='mean')
:二元交叉熵(输入必须是概率)。nn.BCEWithLogitsLoss()
:把 sigmoid 与 BCE 合并,数值更稳定(输入是 logits)。optim.Adam(params, lr=1e-3, betas=(0.9,0.999), eps=1e-8, weight_decay=0)
:Adam 优化器。model.train(mode=True)
/model.eval()
:切换训练/评估模式,影响 Dropout/BatchNorm。optimizer.zero_grad()
:把参数梯度清零。loss.backward()
:反向传播计算梯度。optimizer.step()
:根据梯度更新参数。with torch.no_grad():
:禁用梯度计算(推理/验证时使用)。tensor.detach().cpu().numpy()
:安全地将 tensor 转为 NumPy(脱离计算图并确保在 CPU)。sklearn.preprocessing.StandardScaler()
:标准化。sklearn.model_selection.train_test_split(...)
:分割数据集。sklearn.metrics.accuracy_score(y_true, y_pred)
:计算准确率。
7)总结(key takeaways)
你的代码已经覆盖了模型训练的基本流程:准备数据 → 定义模型 → 损失 & 优化器 → 训练循环 → 测试评估。
实务改进建议:
用
BCEWithLogitsLoss
代替sigmoid + BCELoss
(更稳定)。使用
DataLoader
做 mini-batch 与shuffle=True
。支持 GPU(通过
device = torch.device(...)
与.to(device)
)。加入验证集、早停、学习率调度器与更丰富的评估指标(ROC-AUC 等)。
使用
torch.from_numpy(...).float()
来避免不必要的内存复制与确保 dtype 一致。
常见坑:标签 dtype/shape 不匹配、在仍有 grad 的 Tensor 上使用
.numpy()
、误用BCELoss
与 logits。