- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
- 🍨 本文记录基于 TensorFlow 与 PyTorch 完成心脏病二分类任务的实践过程
- 🍖 数据集:
Data/heart.csv(303 条样本,13 个输入特征)
文章目录
- [1. 简介](#1. 简介)
- [2. 环境](#2. 环境)
- [3. 代码实现](#3. 代码实现)
-
- [3.1 前期准备](#3.1 前期准备)
-
- [3.1.1 导入库与设置随机种子](#3.1.1 导入库与设置随机种子)
- [3.1.2 数据加载与预处理](#3.1.2 数据加载与预处理)
- [3.2 模型建立与训练](#3.2 模型建立与训练)
-
- [3.2.1 TensorFlow 全连接网络](#3.2.1 TensorFlow 全连接网络)
- [3.2.2 PyTorch RNN 分类器](#3.2.2 PyTorch RNN 分类器)
- [4. 模型评估](#4. 模型评估)
-
- [4.1 TensorFlow 训练过程与最终评估](#4.1 TensorFlow 训练过程与最终评估)
- [4.2 PyTorch 训练过程与预测结果](#4.2 PyTorch 训练过程与预测结果)
- [4.3 TensorFlow 与 PyTorch 结果对比](#4.3 TensorFlow 与 PyTorch 结果对比)
- [5. 总结](#5. 总结)
1. 简介
| 项目 | 内容 |
|---|---|
| 任务 | 心脏病二分类:0 表示不会患心脏病,1 表示可能患心脏病 |
| 数据集 | 303 条样本、13 个特征、1 个标签列;正类 165 条,负类 138 条 |
| TensorFlow 模型 | 全连接神经网络:Dense(24) → Dropout(0.2) → Dense(1) |
| PyTorch 模型 | RNN 分类器:RNN(13, 200) → Linear(200, 50) → Linear(50, 2) |
| TensorFlow 测试结果 | 测试准确率 96.77%(30/31) |
| PyTorch 测试结果 | 最高测试准确率 90.3% ;第 50 轮最终准确率 87.1%(27/31) |
本实验分别使用 TensorFlow 与 PyTorch 完成同一份心脏病数据的二分类训练,并记录数据处理、模型构建、训练过程和评估结果。两套实现的训练策略不同,因此文末会单独说明比较时需要注意的条件。
2. 环境
- 语言环境:Python 3.12.13
- 编译器:Jupyter Notebook
- TensorFlow:TensorFlow 2.21.0,运行设备为 CPU(未检测到 GPU)
- PyTorch:运行设备为 CPU
- 数据处理:pandas、NumPy、scikit-learn
- 可视化:Matplotlib、seaborn
3. 代码实现
3.1 前期准备
3.1.1 导入库与设置随机种子
TensorFlow 实现固定随机种子,保证数据划分和模型初始化尽量可复现;同时检测可用 GPU,若存在则启用按需分配显存。
python
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
tf.keras.utils.set_random_seed(42)
gpus = tf.config.list_physical_devices("GPU")
if gpus:
gpu0 = gpus[0]
tf.config.experimental.set_memory_growth(gpu0, True)
tf.config.set_visible_devices([gpu0], "GPU")
PyTorch 实现使用 torch.cuda.is_available() 自动选择运行设备。本次运行结果为 device(type='cpu')。
python
import numpy as np
import pandas as pd
import torch
from torch import nn
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from torch.utils.data import TensorDataset, DataLoader
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
3.1.2 数据加载与预处理
数据包含年龄、性别、胸痛类型、静息血压、胆固醇、最大心率等 13 个输入特征,target 为二分类标签。原始数据没有缺失值。
TensorFlow 实现先按类别分层划分数据,再只以训练集拟合标准化器,避免测试集信息进入训练过程。最终训练集为 272 条、测试集为 31 条。
python
df = pd.read_csv("./Data/heart.csv")
X = df.iloc[:, :-1]
y = df.iloc[:, -1]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.1, random_state=1, stratify=y
)
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)
PyTorch 实现将标准化后的特征转为 float32 张量、标签转为 int64 张量,并扩展为 (样本数, 1, 13),因此 RNN 接收到的是一个时间步、13 个特征的输入。该实现得到的训练集形状为 torch.Size([272, 1, 13]),标签形状为 torch.Size([272])。
python
X = df.iloc[:, :-1]
y = df.iloc[:, -1]
sc = StandardScaler()
X = sc.fit_transform(X)
X = torch.tensor(np.array(X), dtype=torch.float32)
y = torch.tensor(np.array(y), dtype=torch.int64)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.1, random_state=1
)
X_train = X_train.unsqueeze(1)
X_test = X_test.unsqueeze(1)
train_dl = DataLoader(TensorDataset(X_train, y_train), batch_size=64, shuffle=False)
test_dl = DataLoader(TensorDataset(X_test, y_test), batch_size=64, shuffle=False)
注意:PyTorch 的原始运行过程在划分前对完整特征矩阵做了标准化,且未使用分层划分;这一点会影响其与 TensorFlow 结果的严格可比性,详见第 5 节。
3.2 模型建立与训练
3.2.1 TensorFlow 全连接网络
心脏病数据属于结构化表格数据,TensorFlow 使用轻量级全连接网络建模,而不是将独立特征当作时间序列。网络只有 361 个可训练参数:隐藏层使用 ReLU 激活与 L2 正则化,Dropout(0.2) 随机失活部分隐藏单元,以缓解小样本场景下的过拟合;输出层使用 Sigmoid 给出患病概率。
| 组件 | 配置 | 作用 |
|---|---|---|
| 输入层 | 13 个标准化特征 | 接收每位受试者的表格特征 |
| 隐藏层 | Dense(24, relu) + L2(5e-3) |
学习非线性特征组合,并限制权重过大 |
| 正则化层 | Dropout(0.2) |
降低特征共适应,缓解过拟合 |
| 输出层 | Dense(1, sigmoid) |
输出二分类概率 |
python
model = Sequential([
tf.keras.layers.Input(shape=(X_train.shape[1],)),
Dense(24, activation='relu',
kernel_regularizer=tf.keras.regularizers.l2(5e-3)),
Dropout(0.2),
Dense(1, activation='sigmoid'),
])
model.summary()
训练使用 Adam 优化器和二元交叉熵损失。训练轮数上限为 400,但通过 EarlyStopping 监控验证损失:若连续 10 轮改善不足 1e-3,则停止训练并恢复验证损失最低时的权重。
python
opt = tf.keras.optimizers.Adam(learning_rate=2e-3)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
early_stopping = tf.keras.callbacks.EarlyStopping(
monitor='val_loss', patience=10, min_delta=1e-3,
restore_best_weights=True
)
history = model.fit(
X_train, y_train, validation_split=0.2, epochs=400,
batch_size=32, callbacks=[early_stopping], verbose=1
)
3.2.2 PyTorch RNN 分类器
PyTorch 模型以单步 RNN 编码 13 个特征,随后使用两个全连接层完成二分类。RNN 的隐藏状态维度为 200,输出层为两个 logits,故训练时搭配 CrossEntropyLoss,无需额外添加 Softmax 层。
python
class ModelRNN(nn.Module):
def __init__(self):
super().__init__()
self.rnn0 = nn.RNN(input_size=13, hidden_size=200,
num_layers=1, batch_first=True)
self.fc0 = nn.Linear(200, 50)
self.fc1 = nn.Linear(50, 2)
def forward(self, x):
out, _ = self.rnn0(x)
out = out[:, -1, :]
out = self.fc0(out)
return self.fc1(out)
model = ModelRNN().to(device)
训练阶段对每个 mini-batch 完成前向传播、损失计算、反向传播和参数更新;测试阶段使用 torch.no_grad(),仅计算准确率与损失。
python
loss_fn = nn.CrossEntropyLoss()
opt = torch.optim.Adam(model.parameters(), lr=1e-4)
epochs = 50
for epoch in range(epochs):
model.train()
epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)
model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
4. 模型评估
4.1 TensorFlow 训练过程与最终评估
EarlyStopping 在第 26 轮停止训练,并恢复第 17 轮附近的最佳验证权重。训练准确率与验证准确率在最佳轮次分别约为 87.1% 和 85.5%,验证损失最低约为 0.445;训练曲线没有出现持续扩大的训练/验证差距。
最终在从未参与训练或验证的 31 条测试数据上评估:
python
test_loss, test_accuracy = model.evaluate(X_test, y_test, verbose=0)
print(f"Test accuracy: {test_accuracy:.2%}")
bash
Test accuracy: 96.77%

4.2 PyTorch 训练过程与预测结果
PyTorch 共训练 50 轮。测试准确率在第 12 至 15 轮达到最高的 90.3%,随后维持在 87.1%;与此同时,测试损失在后期继续下降,说明准确率这一离散指标在仅 31 条测试样本上会产生波动。
| 轮次 | 训练准确率 | 训练损失 | 测试准确率 | 测试损失 |
|---|---|---|---|---|
| 1 | 46.0% | 0.692 | 64.5% | 0.670 |
| 12 | 80.1% | 0.575 | 90.3% | 0.505 |
| 15 | 82.4% | 0.542 | 90.3% | 0.460 |
| 50 | 84.9% | 0.358 | 87.1% | 0.284 |

第 50 轮输出的混淆矩阵为 [[13, 3], [1, 14]]:16 个真实负类中有 13 个识别正确,15 个真实正类中有 14 个识别正确。最终准确率为 (13 + 14) / 31 = 87.1%。

对单条测试样本的预测结果为类别 0,其含义为"不会患心脏病"。

4.3 TensorFlow 与 PyTorch 结果对比
| 维度 | TensorFlow | PyTorch |
|---|---|---|
| 模型 | 正则化全连接网络 | 单步 RNN + 两层全连接网络 |
| 训练集 / 测试集 | 272 / 31,分层划分 | 272 / 31,随机划分 |
| 标准化 | 仅由训练集拟合 | 划分前由完整特征矩阵拟合 |
| 过拟合控制 | L2、Dropout、EarlyStopping | 未使用 EarlyStopping 或最佳权重保存 |
| 最佳测试准确率 | 96.77% | 90.3% |
| 最终轮测试准确率 | 96.77%(恢复最佳权重) | 87.1%(第 50 轮) |
5. 总结
-
TensorFlow 的正则化全连接网络表现最好 :在 31 条独立测试样本上得到 96.77% 准确率。该模型参数量小,L2 正则化、Dropout 和 EarlyStopping 有助于控制小数据集上的过拟合。
-
PyTorch RNN 已具备有效的分类能力 :训练中最高测试准确率达到 90.3%,最终混淆矩阵显示其对正类的识别较好(14/15),但第 50 轮未保留最高准确率对应的模型权重,因此最终准确率回落到 87.1%。
-
两组结果不宜直接作严格的横向优劣判断:两者的划分方式和标准化时机不同。若要公平比较,应统一为"先分层划分、只在训练集拟合标准化器、固定随机种子",并让两种实现使用相同的训练/验证/测试索引。
-
测试集规模较小:测试集仅有 31 条样本,单个样本就会带来约 3.23 个百分点的准确率变化。后续建议使用交叉验证,并报告准确率的均值与标准差。
-
后续优化方向:对于 PyTorch,可在验证集指标最佳时保存模型、加入 EarlyStopping 与权重衰减;对于两种实现,都可以尝试特征选择、学习率调度和交叉验证,以获得更稳定的泛化评估。