神经网络原理:
进一步实战:
神经网络在乳腺癌数据集上的小型二分类示例 --- 逐行详解
概览(一句话)
用 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)        # 输入 -> 全连接层1
        x = self.relu(x)       # ReLU激活
        x = self.fc2(x)        # 全连接层2
        x = self.sigmoid(x)    # Sigmoid输出(二分类概率)
        return x
model = 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再转numpy
    y_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。 
 - 权重通常使用 Kaiming/Xavier 类初始化(PyTorch 默认对 
 
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再转numpy
    y_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。