Python 训练营打卡 Day 53-对抗生成网络
一.GAN对抗生成网络
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore")
# 设置中文字体支持
plt.rcParams["font.family"] = ["SimHei"]
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题# 检查GPU是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")LATENT_DIM = 10 # 潜在空间的维度,这里根据任务复杂程度任选
EPOCHS = 10000 # 训练的回合数,一般需要比较长的时间
BATCH_SIZE = 32 # 每批次训练的样本数
LR = 0.0002 # 学习率
BETA1 = 0.5 # Adam优化器的参数# 检查是否有可用的GPU,否则使用CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")# --- 2. 加载并预处理数据 ---iris = load_iris()
X = iris.data
y = iris.target# 只选择 'Setosa' (类别 0)
X_class0 = X[y == 0] # 一种简便写法# 数据缩放到 [-1, 1]
scaler = MinMaxScaler(feature_range=(-1, 1))
X_scaled = scaler.fit_transform(X_class0) # 转换为 PyTorch Tensor 并创建 DataLoader
# 注意需要将数据类型转为 float
real_data_tensor = torch.from_numpy(X_scaled).float()
dataset = TensorDataset(real_data_tensor)
dataloader = DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True)print(f"成功加载并预处理数据。用于训练的样本数量: {len(X_scaled)}")
print(f"数据特征维度: {X_scaled.shape[1]}")
二.生成器
# --- 3. 构建模型 ---# (A) 生成器 (Generator)
class Generator(nn.Module):def __init__(self):super(Generator, self).__init__()self.model = nn.Sequential(nn.Linear(LATENT_DIM, 16),nn.ReLU(),nn.Linear(16, 32),nn.ReLU(),nn.Linear(32, 4),# 最后的维度只要和目标数据对齐即可nn.Tanh() # 输出范围是 [-1, 1])def forward(self, x):return self.model(x) # 因为没有像之前一样做定义x=某些东西,所以现在可以直接输出模型
2.1 nn.Sequential容器
这里我们用了torch中的容器这个概念,sequential是一个按顺序存放神经网络的容器,这样写神经网路的属性的时候,把这些内容都写在一个容器中,然后作为属性,后面定义前向传播的视时候会方便很多,除了nn.sequential这种按顺序运算的容器,还有一些其他的容器,比如nn.ModuleList对于消融实验会起到简化代码的作用
- 对于网络层按线性顺序堆叠(如 MLP、CNN 的主体部分),可以使用 Sequential 模型
- 对于网络包含复杂分支结构(如 ResNet 的残差连接、Inception 的多分支),建议手动编写forward()方法
这两种方式本质上是等价的,但前者更符合模块化编程的思想,能让你的代码更易读、易维护
三.判别器
# (B) 判别器 (Discriminator)
class Discriminator(nn.Module):def __init__(self):super(Discriminator, self).__init__()self.model = nn.Sequential(nn.Linear(4, 32),nn.LeakyReLU(0.2), # LeakyReLU 是 GAN 中的常用选择nn.Linear(32, 16),nn.LeakyReLU(0.2), # 负斜率参数为0.2nn.Linear(16, 1), # 这里最后输出1个神经元,所以用sigmoid激活函数nn.Sigmoid() # 输出 0 到 1 的概率)def forward(self, x):return self.model(x)
3.1 分类问题激活函数和损失函数的对应
二分类问题,常见的输出有2种
- 单输出 + Sigmoid
- 双输出 + Softmax
二者完全等价,一般推荐第一个,sigmoid一般对应BCE损失,softmax一般对应 CrossEntropy 损失
3.2 LeakyReLU
标准relu: f(x) = max(0, x) 小于0的输入直接置为0,计算简单,能缓解梯度消失问题,引入非线性。存在 “神经元死亡” 问题(当输入为负时,梯度为 0,参数无法更新)
LeakyReLU: f(x) = max(0.01x, x) 对负数输入保留一个小的梯度(如0.01倍),在输入为负时,仍有非零梯度(如 0.01),避免神经元永久死亡
许多 GAN 变体(如 DCGAN、WGAN)都默认使用 LeakyReLU,实践证明它能显著提高模型收敛速度和生成质量
作业:对于心脏病数据集,对于病人这个不平衡的样本用GAN来学习并生成病人样本,观察不用GAN和用GAN的F1分数差异
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import f1_score, classification_report, confusion_matrix, roc_curve, auc
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings("ignore")# 设置中文字体(解决中文显示问题)
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统常用黑体字体
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号# 设置随机种子以确保结果可复现
torch.manual_seed(42)
np.random.seed(42)# 检查GPU是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")# 1. 加载并预处理心脏病数据集
def load_heart_disease_data():try:# 读取CSV文件df = pd.read_csv(r"C:\Users\ggggg\Desktop\python\python60-days-challenge-master (5)\python60-days-challenge-master\heart.csv")print(f"成功加载heart.csv, 数据形状: {df.shape}")# 显示数据基本信息print("\n数据基本信息: ")df.info()# 显示数据集行数和列数rows, columns = df.shape# 数据可视化 - 目标变量分布plt.figure(figsize=(6, 4))sns.countplot(x='target', data=df)plt.title('心脏病患者分布')plt.xlabel('是否患病')plt.ylabel('样本数')plt.xticks([0, 1], ['健康', '患病'])plt.show()# 重命名列以便更好理解df.columns = ['age', 'sex', 'chest_pain_type', 'resting_blood_pressure', 'cholesterol', 'fasting_blood_sugar', 'rest_ecg', 'max_heart_rate_achieved', 'exercise_induced_angina', 'st_depression', 'st_slope', 'num_major_vessels', 'thalassemia', 'target']# 转换分类特征categorical_features = ['sex', 'chest_pain_type', 'fasting_blood_sugar', 'rest_ecg', 'exercise_induced_angina', 'st_slope', 'num_major_vessels', 'thalassemia']for feature in categorical_features:df[feature] = df[feature].astype('object')# 独热编码df = pd.get_dummies(df, drop_first=True)# 划分特征和目标变量X = df.drop(columns='target').valuesy = df['target'].values# 打印类别分布class_counts = pd.Series(y).value_counts()print(f"\n类别分布: \n{class_counts}")print(f"不平衡比例: {class_counts[0]/class_counts[1]:.2f}:1")return X, yexcept FileNotFoundError:print("错误: 未找到heart.csv文件,请检查文件路径")return None, Noneexcept Exception as e:print(f"数据加载错误: {str(e)}")return None, None# 加载数据
X, y = load_heart_disease_data()
if X is None or y is None:exit()# 数据缩放(直接处理多维特征矩阵)
scaler = MinMaxScaler(feature_range=(-1, 1))
X_scaled = scaler.fit_transform(X)# 分割数据
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)# 提取少数类样本 (心脏病患者)
X_minority = X_train[y_train == 1]
y_minority = y_train[y_train == 1]print(f"训练集中少数类样本数量: {len(X_minority)}")# ---------------------------
# 2. 定义条件GAN模型
# ---------------------------class ConditionalGenerator(nn.Module):def __init__(self, input_dim, output_dim, label_dim):super(ConditionalGenerator, self).__init__()self.model = nn.Sequential(nn.Linear(input_dim + label_dim, 64),nn.LeakyReLU(0.2),nn.BatchNorm1d(64),nn.Linear(64, 128),nn.LeakyReLU(0.2),nn.BatchNorm1d(128),nn.Linear(128, output_dim),nn.Tanh() # 输出范围为[-1, 1],与数据缩放范围一致)def forward(self, z, labels):# 合并噪声和标签input_tensor = torch.cat([z, labels], dim=1)return self.model(input_tensor)class ConditionalDiscriminator(nn.Module):def __init__(self, input_dim, label_dim):super(ConditionalDiscriminator, self).__init__()self.model = nn.Sequential(nn.Linear(input_dim + label_dim, 128),nn.LeakyReLU(0.2),nn.Dropout(0.3),nn.Linear(128, 64),nn.LeakyReLU(0.2),nn.Dropout(0.3),nn.Linear(64, 1),nn.Sigmoid() # 输出概率值)def forward(self, X, labels):# 合并输入特征和标签input_tensor = torch.cat([X, labels], dim=1)return self.model(input_tensor)# ---------------------------
# 3. 训练CGAN模型
# ---------------------------# 模型参数
LATENT_DIM = 10
INPUT_DIM = X_train.shape[1]
LABEL_DIM = 1 # 二分类问题
EPOCHS = 1000
BATCH_SIZE = 32
LR = 0.0002
BETA1 = 0.5# 创建数据加载器(修正DataLoader拼写)
minority_dataset = TensorDataset(torch.FloatTensor(X_minority),torch.FloatTensor(y_minority).view(-1, 1)
)
minority_dataloader = DataLoader(minority_dataset, batch_size=BATCH_SIZE, shuffle=True)# 实例化模型
generator = ConditionalGenerator(LATENT_DIM, INPUT_DIM, LABEL_DIM).to(device)
discriminator = ConditionalDiscriminator(INPUT_DIM, LABEL_DIM).to(device)# 定义损失函数和优化器
criterion = nn.BCELoss()
g_optimizer = optim.Adam(generator.parameters(), lr=LR, betas=(BETA1, 0.999))
d_optimizer = optim.Adam(discriminator.parameters(), lr=LR, betas=(BETA1, 0.999))# 训练循环
g_losses, d_losses = [], []for epoch in range(EPOCHS):epoch_g_loss, epoch_d_loss = 0, 0batches_per_epoch = 0for i, (real_data, real_labels) in enumerate(minority_dataloader):real_data = real_data.to(device)real_labels = real_labels.to(device)batch_size = real_data.size(0)batches_per_epoch += 1# 创建真实和虚假标签real_targets = torch.ones(batch_size, 1).to(device)fake_targets = torch.zeros(batch_size, 1).to(device)# ---------------------# 训练判别器# ---------------------d_optimizer.zero_grad()# 用真实数据训练real_validity = discriminator(real_data, real_labels)d_real_loss = criterion(real_validity, real_targets)# 生成假数据z = torch.randn(batch_size, LATENT_DIM).to(device)fake_labels = real_labels # 生成与真实样本相同类别的数据fake_data = generator(z, fake_labels)# 用假数据训练fake_validity = discriminator(fake_data.detach(), fake_labels)d_fake_loss = criterion(fake_validity, fake_targets)# 总判别器损失(修正变量名)d_loss = (d_real_loss + d_fake_loss) / 2d_loss.backward()d_optimizer.step()epoch_d_loss += d_loss.item()# ---------------------# 训练生成器# ---------------------g_optimizer.zero_grad()# 生成假数据fake_data = generator(z, fake_labels)fake_validity = discriminator(fake_data, fake_labels)# 生成器损失g_loss = criterion(fake_validity, real_targets)g_loss.backward()g_optimizer.step()epoch_g_loss += g_loss.item()# 计算平均损失avg_g_loss = epoch_g_loss / batches_per_epochavg_d_loss = epoch_d_loss / batches_per_epochg_losses.append(avg_g_loss)d_losses.append(avg_d_loss)# 每100个epoch打印一次损失if (epoch + 1) % 100 == 0:print(f"Epoch [{epoch+1}/{EPOCHS}], D_loss: {avg_d_loss:.4f}, G_loss: {avg_g_loss:.4f}")print("CGAN训练完成!")# 绘制训练损失曲线
plt.figure(figsize=(10, 5))
plt.plot(g_losses, label='生成器损失')
plt.plot(d_losses, label='判别器损失')
plt.title('训练损失曲线')
plt.xlabel('Epoch')
plt.ylabel('损失')
plt.legend()
plt.show()# ---------------------------
# 4. 生成合成样本
# ---------------------------# 设置为评估模式
generator.eval()# 生成与少数类样本数量相同的合成数据
num_samples_to_generate = len(X_minority)
z = torch.randn(num_samples_to_generate, LATENT_DIM).to(device)
labels = torch.ones(num_samples_to_generate, 1).to(device) # 标签为1,表示心脏病患者with torch.no_grad():synthetic_data = generator(z, labels).cpu().numpy()# 逆缩放合成数据
synthetic_data = scaler.inverse_transform(synthetic_data)# 为合成数据创建标签(补充完整代码)
synthetic_labels = np.ones(num_samples_to_generate) # 标签全为1(心脏病患者)# 逆缩放原始训练数据用于可视化
X_train_original = scaler.inverse_transform(X_train)# ---------------------------
# 5. 可视化原始数据和合成数据
# ---------------------------# 可视化特征分布对比
plt.figure(figsize=(14, 10))
feature_names = ['年龄', '血压', '胆固醇', '最大心率']
feature_indices = [0, 3, 4, 7] # 对应数据集中的特征索引for i, idx in enumerate(feature_indices):plt.subplot(2, 2, i+1)# 绘制原始少数类样本的特征分布sns.kdeplot(X_train_original[y_train == 1, idx], label='原始数据', color='blue')# 绘制合成样本的特征分布sns.kdeplot(synthetic_data[:, idx], label='合成数据', color='orange')plt.title(f'{feature_names[i]}分布对比')plt.xlabel('特征值')plt.ylabel('密度')plt.legend()plt.tight_layout()
plt.show() # 修正plt.show()缩进# ---------------------------
# 6. 比较模型性能
# ---------------------------# 6.1 使用原始数据训练的模型
model_original = RandomForestClassifier(random_state=42)
model_original.fit(X_train, y_train)
y_pred_original = model_original.predict(X_test)
y_pred_prob_original = model_original.predict_proba(X_test)[:, 1]# 6.2 使用增强数据训练的模型
# 将合成数据添加到训练集中
X_train_augmented = np.vstack([X_train, scaler.transform(synthetic_data)])
y_train_augmented = np.hstack([y_train, synthetic_labels])model_augmented = RandomForestClassifier(random_state=42)
model_augmented.fit(X_train_augmented, y_train_augmented)
y_pred_augmented = model_augmented.predict(X_test)
y_pred_prob_augmented = model_augmented.predict_proba(X_test)[:, 1]# 6.3 比较F1分数
f1_original = f1_score(y_test, y_pred_original)
f1_augmented = f1_score(y_test, y_pred_augmented)
print("\n模型性能比较:")
print(f"原始数据训练的模型 F1 分数: {f1_original:.4f}")
print(f"增强数据训练的模型 F1 分数: {f1_augmented:.4f}")# 打印详细分类报告
print("\n原始数据训练的模型分类报告:")
print(classification_report(y_test, y_pred_original))print("\n增强数据训练的模型分类报告:")
print(classification_report(y_test, y_pred_augmented))# 计算混淆矩阵
cm_original = confusion_matrix(y_test, y_pred_original)
cm_augmented = confusion_matrix(y_test, y_pred_augmented)# 计算ROC曲线
fpr_original, tpr_original, _ = roc_curve(y_test, y_pred_prob_original)
fpr_augmented, tpr_augmented, _ = roc_curve(y_test, y_pred_prob_augmented)
roc_auc_original = auc(fpr_original, tpr_original)
roc_auc_augmented = auc(fpr_augmented, tpr_augmented)# ---------------------------
# 7. 可视化评估结果
# ---------------------------# 7.1 混淆矩阵对比
plt.figure(figsize=(12, 5))plt.subplot(1, 2, 1)
sns.heatmap(cm_original, annot=True, fmt='d', cmap='Blues')
plt.title('原始数据模型混淆矩阵')
plt.xlabel('预测类别')
plt.ylabel('真实类别')plt.subplot(1, 2, 2)
sns.heatmap(cm_augmented, annot=True, fmt='d', cmap='Greens')
plt.title('增强数据模型混淆矩阵')
plt.xlabel('预测类别')
plt.ylabel('真实类别')plt.tight_layout()
plt.show()# 7.2 ROC曲线对比
plt.figure(figsize=(8, 6))
plt.plot(fpr_original, tpr_original, color='blue', lw=2, label=f'原始数据 (AUC = {roc_auc_original:.2f})')
plt.plot(fpr_augmented, tpr_augmented, color='green', lw=2, label=f'增强数据 (AUC = {roc_auc_augmented:.2f})')
plt.plot([0, 1], [0, 1], color='gray', lw=1, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('假阳性率 (1-特异性)')
plt.ylabel('真阳性率 (敏感性)')
plt.title('ROC曲线比较')
plt.legend(loc="lower right")
plt.show()# 7.3 F1分数对比
plt.figure(figsize=(8, 5))
plt.bar(['原始数据', '增强数据'], [f1_original, f1_augmented], color=['#636EFA', '#EF553B'])
plt.ylim(0, 1)
plt.title('使用GAN增强前后的模型F1分数对比')
plt.ylabel('F1分数')
for i, v in enumerate([f1_original, f1_augmented]):plt.text(i, v + 0.02, f'{v:.4f}', ha='center')
plt.show()
使用设备: cuda
成功加载heart.csv, 数据形状: (303, 14)数据基本信息:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 303 entries, 0 to 302
Data columns (total 14 columns):# Column Non-Null Count Dtype
--- ------ -------------- ----- 0 age 303 non-null int64 1 sex 303 non-null int64 2 cp 303 non-null int64 3 trestbps 303 non-null int64 4 chol 303 non-null int64 5 fbs 303 non-null int64 6 restecg 303 non-null int64 7 thalach 303 non-null int64 8 exang 303 non-null int64 9 oldpeak 303 non-null float6410 slope 303 non-null int64 11 ca 303 non-null int64 12 thal 303 non-null int64 13 target 303 non-null int64
dtypes: float64(1), int64(13)
memory usage: 33.3 KB
类别分布:
1 165
0 138
Name: count, dtype: int64
不平衡比例: 0.84:1
训练集中少数类样本数量: 133
Epoch [100/1000], D_loss: 0.5933, G_loss: 0.9950
Epoch [200/1000], D_loss: 0.6231, G_loss: 0.9423
Epoch [300/1000], D_loss: 0.5518, G_loss: 1.0263
Epoch [400/1000], D_loss: 0.5745, G_loss: 0.9765
Epoch [500/1000], D_loss: 0.5725, G_loss: 0.8739
Epoch [600/1000], D_loss: 0.6261, G_loss: 0.8975
Epoch [700/1000], D_loss: 0.5993, G_loss: 0.9735
Epoch [800/1000], D_loss: 0.6198, G_loss: 0.9446
Epoch [900/1000], D_loss: 0.5936, G_loss: 0.9563
Epoch [1000/1000], D_loss: 0.6497, G_loss: 0.9020
CGAN训练完成!
模型性能比较:
原始数据训练的模型 F1 分数: 0.8333
增强数据训练的模型 F1 分数: 0.8333原始数据训练的模型分类报告:precision recall f1-score support0 0.79 0.90 0.84 291 0.89 0.78 0.83 32accuracy 0.84 61macro avg 0.84 0.84 0.84 61
weighted avg 0.84 0.84 0.84 61增强数据训练的模型分类报告:precision recall f1-score support0 0.79 0.90 0.84 291 0.89 0.78 0.83 32accuracy 0.84 61macro avg 0.84 0.84 0.84 61
weighted avg 0.84 0.84 0.84 61
总结
从代码执行结果和可视化图表来看,整体是利用 条件 GAN(CGAN)对心脏病数据集(少数类样本)进行数据增强,并对比增强前后训练的随机森林模型性能 ,以下从数据分布、模型训练、性能对比三方面分析:
一、数据分布与增强效果
- 原始数据分布
- 类别分布显示,患病(标签
1
)样本有165
条,健康(标签0
)样本138
条 ,不平衡比例约0.84:1
,属于轻度不平衡。 - 患者分布可视化(柱状图)直观呈现了健康、患病样本的数量差异,患病样本稍多。
- 类别分布显示,患病(标签
- 合成数据效果
- 特征分布对比图(如年龄、血压等)中,合成数据(橙色曲线)与原始少数类数据(蓝色曲线)的核密度分布 整体趋势较为接近,说明 CGAN 生成的合成数据在特征层面较好地模拟了原始少数类样本的分布规律,数据增强具备一定有效性。
二、CGAN 模型训练
- 损失曲线解读
- 生成器(蓝色)和判别器(橙色)损失在训练初期(前
200
轮左右)波动较大,随后逐渐趋于稳定。 - 判别器损失(橙色)整体在
0.5~0.7
区间震荡,生成器损失(蓝色)在0.8~1.1
区间震荡 ,符合 GAN 训练特点(判别器和生成器相互博弈、逐渐平衡),说明模型训练过程基本正常。
- 生成器(蓝色)和判别器(橙色)损失在训练初期(前
- 训练稳定性
- 损失曲线虽有波动,但未出现持续发散或异常突变,表明 CGAN 在该数据集上训练未出现严重模式崩溃(Mode Collapse)等问题,生成能力相对稳定。
三、模型性能对比(随机森林)
- 指标解读
- F1 分数:原始数据和增强数据训练的模型,F1 分数均为
0.8333
,看似无提升。但需结合分类报告、混淆矩阵等综合判断 - 分类报告:两类模型在
precision
(精确率)、recall
(召回率)上表现接近,原始数据模型对类别0
(健康)召回率0.90
、类别1
(患病)召回率0.78
;增强数据模型指标几乎一致 - 混淆矩阵:二者的预测结果完全相同(矩阵数值无差异 ),说明在当前实验设置下,数据增强未改变模型最终预测表现
- ROC 曲线与 AUC:两条曲线几乎重合,AUC 均为
0.93
,反映模型对正负样本的区分能力未因数据增强而变化
- F1 分数:原始数据和增强数据训练的模型,F1 分数均为
@浙大疏锦行