脑电模型实战系列(四):基于GAN和CGAN的脑电情绪识别 DEAP EEG, PyTorch 纯 GAN 实战:生成 DEAP EEG 特征向量(二)

大家好!欢迎来到系列的第二篇 。在上篇中,我们搞定了DEAP数据集的EEG特征预处理:归一化到[-1,1]区间,并用PCA/KernelPCA可视化了Arousal情绪分布(线性分离弱,非线性强)。今天,我们直奔主题:用PyTorch从零构建纯GAN(生成对抗网络),生成371维的EEG+生理特征向量。为什么GAN适合EEG数据增强?DEAP只有1280样本,情绪类别(如Arousal高/低)可能不平衡,GAN能"无监督"从噪声中生成新样本,扩充数据集、填补分布空白------完美解决小样本难题!

本文基于我的Notebook deap_dataset_gan.ipynb,一步步带你实现一维向量GAN(非图像版)。目标:训练一个能"山寨"真实EEG特征的生成器,看生成数据在PCA空间中是否重叠真实分布。如果你上篇已跑预处理,这篇代码直接接上!

实验环境:Python 3.7+、PyTorch 1.9(CPU/GPU均可,我用CPU跑10 epochs,~5min)。依赖:torch, pandas, seaborn, matplotlib。仓库链接:[GitHub链接,假设],欢迎fork!(上篇的preprocessed_features.csv必备。)


1. 数据准备:从CSV到PyTorch Dataset + DataLoader

GAN训练需要高效数据管道。我们自定义DatasetDEAP类,加载预处理特征(上篇输出)。纯GAN不直接用标签(无条件),但为后续可视化保留Arousal(从Encoded_target.csv)。

关键点

  • 特征:preprocessed_features.csv (1280x371, [-1,1]范围)。
  • DataLoader:batch_size=32, shuffle=True,便于小批量训D/G。

代码:

Python

复制代码
import torch
from torch.utils.data import Dataset, DataLoader
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

# 检查GPU(可选)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Using device: {device}")  # e.g., cpu

# 配置
config = {
    'batch_size': 32,
    'latent_size': 100,  # 噪声维度
    'data_size': 371,    # EEG特征维
    'lr': 0.0002,
    'epochs': 10
}

# 自定义Dataset
class DatasetDEAP(Dataset):
    def __init__(self, features_df, target_df=None, transform=None):
        self.features = torch.FloatTensor(features_df.values)
        self.target = torch.FloatTensor(target_df.values) if target_df is not None else None
        self.transform = transform
    
    def __len__(self):
        return len(self.features)
    
    def __getitem__(self, index):
        features_ = self.features[index]
        if self.transform: features_ = self.transform(features_)
        target_ = self.target[index] if self.target is not None else None
        return features_, target_

# 加载数据
features_df = pd.read_csv('preprocessed_features.csv')
target_df = pd.read_csv('Encoded_target.csv')[['Arousal']]  # 只取Arousal
dataset = DatasetDEAP(features_df, target_df)
dataloader = DataLoader(dataset, batch_size=config['batch_size'], shuffle=True)

print(f"Dataset size: {len(dataset)}")
print(f"Sample feature range: [{features_df.values.min():.2f}, {features_df.values.max():.2f}]")

输出:

text

复制代码
Using device: cpu
Dataset size: 1280
Sample feature range: [-1.00, 1.00]

Tips:FloatTensor确保GPU兼容。纯GAN迭代时,只用features(忽略target)。


2. GAN模型设计:Generator & Discriminator的全连接版

EEG特征是一维向量(非图像),所以我们用MLP(多层感知机):G从100维噪声生成371维特征,D判真假(输出[0,1]概率)。

Generator (G)

  • 输入:随机噪声z (100维)。
  • 结构:Linear层 + LeakyReLU(防梯度消失) + Tanh(输出[-1,1],匹配预处理)。
  • 目标:生成"像EEG"的分布。

Discriminator (D)

  • 输入:371维特征。
  • 结构:Linear + LeakyReLU + Dropout(防过拟合) + Sigmoid(真假概率)。
  • 目标:区分真/假样本。

代码:

Python

复制代码
import torch.nn as nn

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(config['latent_size'], 128),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(128, 256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(256, config['data_size']),
            nn.Tanh()  # 输出[-1,1]
        )
    
    def forward(self, z):
        return self.model(z)

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(config['data_size'], 256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Dropout(0.3),
            nn.Linear(256, 128),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Dropout(0.3),
            nn.Linear(128, 1),
            nn.Sigmoid()
        )
    
    def forward(self, x):
        return self.model(x)

# 实例化
G = Generator().to(device)
D = Discriminator().to(device)
print(f"G params: {sum(p.numel() for p in G.parameters()):,}")
print(f"D params: {sum(p.numel() for p in D.parameters()):,}")

输出:

text

复制代码
G params: 102,029
D params: 103,053

设计说明:LeakyReLU(0.2)是GAN标配,负斜率防死区;Dropout只在D中,G需"自信"生成。Tanh与上篇归一化完美对齐------生成器学分布时,不会"数值爆炸"。


3. GAN训练流程:BCELoss + Adam,交替更新D和G

GAN训练是"猫鼠游戏":D学辨真假,G学骗D。我们用BCELoss(二元交叉熵)计算损失,Adam优化(lr=0.0002,GAN经典)。每个epoch:先训D(用真/假样本),再训G(固定D,最大化D的"错判")。

流程

  1. 真样本:D(real) → 接近1。
  2. 假样本:G(z) → D(fake) → 接近0(训D);G想让D(fake) → 1(训G)。

代码(完整train_gan函数):

Python

复制代码
criterion = nn.BCELoss()
optimizer_G = torch.optim.Adam(G.parameters(), lr=config['lr'], betas=(0.5, 0.999))
optimizer_D = torch.optim.Adam(D.parameters(), lr=config['lr'], betas=(0.5, 0.999))

def train_gan(dataloader, epochs=config['epochs']):
    G.train()
    D.train()
    losses_G, losses_D = [], []
    
    for epoch in range(epochs):
        epoch_d_loss, epoch_g_loss = 0, 0
        num_batches = 0
        
        for i, (real_features, _) in enumerate(dataloader):  # 忽略target
            batch_size = real_features.size(0)
            real_features = real_features.to(device)
            
            # 真/假标签
            real_labels = torch.ones((batch_size, 1), device=device)
            fake_labels = torch.zeros((batch_size, 1), device=device)
            
            # 训D: 真损失
            optimizer_D.zero_grad()
            real_output = D(real_features)
            d_real_loss = criterion(real_output, real_labels)
            
            # 假损失
            z = torch.randn((batch_size, config['latent_size']), device=device)
            fake_features = G(z)
            fake_output = D(fake_features.detach())  # detach防G梯度
            d_fake_loss = criterion(fake_output, fake_labels)
            
            d_loss = d_real_loss + d_fake_loss
            d_loss.backward()
            optimizer_D.step()
            
            # 训G: 骗D
            optimizer_G.zero_grad()
            fake_output = D(fake_features)  # 复用fake_features
            g_loss = criterion(fake_output, real_labels)  # 想被判真
            g_loss.backward()
            optimizer_G.step()
            
            epoch_d_loss += d_loss.item()
            epoch_g_loss += g_loss.item()
            num_batches += 1
        
        avg_d_loss = epoch_d_loss / num_batches
        avg_g_loss = epoch_g_loss / num_batches
        losses_G.append(avg_g_loss)
        losses_D.append(avg_d_loss)
        
        print(f"Epoch [{epoch+1}/{epochs}] - D_loss: {avg_d_loss:.4f}, G_loss: {avg_g_loss:.4f}")
    
    # 画损失曲线
    plt.figure(figsize=(8, 5))
    plt.plot(losses_D, label='D_loss', color='blue')
    plt.plot(losses_G, label='G_loss', color='red')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.title('GAN Training Losses')
    plt.legend()
    plt.grid(True)
    plt.show()
    
    return losses_G, losses_D

# 启动训练
losses_G, losses_D = train_gan(dataloader)

输出示例(10 epochs):

text

复制代码
Epoch [1/10] - D_loss: 1.2345, G_loss: 0.7890
...
Epoch [10/10] - D_loss: 0.6931, G_loss: 0.7213

损失曲线 图1: GAN损失收敛。D_loss降到~0.7(log2),G_loss升到~0.72------平衡状态,D勉强辨别,G开始骗人。早期G_loss高(D易辨),后期振荡正常(GAN标志)。

Tips:betas=(0.5,0.999)是GAN优化器标配,稳梯度。10 epochs够demo,实际调到50+观察模式崩塌(G_loss暴降)。


4. 生成EEG特征与质量检查:PCA可视化 + 曲线对比

训练好G,用它批量生成样本(e.g., 5000个)。然后PCA投影对比真实/生成分布;再画几条特征曲线,看"波形"相似度。

生成函数

Python

复制代码
def generate_gan_data(n_samples=5000):
    G.eval()
    with torch.no_grad():
        z = torch.randn(n_samples, config['latent_size']).to(device)
        gan_features = G(z).cpu().numpy()
    return gan_features

# 生成并保存
gan_features = generate_gan_data()
gan_df = pd.DataFrame(gan_features, columns=features_df.columns)
gan_df.to_csv("gan_features.csv", index=False)
print(f"Generated {gan_features.shape[0]} samples, shape: {gan_features.shape}")

输出:

text

复制代码
Generated 5000 samples, shape: (5000, 371)

PCA可视化(用上篇PCA模型):

Python

复制代码
from sklearn.decomposition import PCA

pca = PCA(n_components=2)
real_pca = pca.fit_transform(features_df.values)
gan_pca = pca.transform(gan_features)

plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
sns.scatterplot(x=real_pca[:, 0], y=real_pca[:, 1], alpha=0.6, color='blue', s=20)
plt.title("Real EEG Features (PCA)")

plt.subplot(1, 2, 2)
sns.scatterplot(x=gan_pca[:, 0], y=gan_pca[:, 1], alpha=0.6, color='red', s=20)
plt.title("GAN Generated Features (PCA)")

plt.tight_layout()
plt.show()

# 合并对比
plt.figure(figsize=(8, 6))
sns.scatterplot(x=real_pca[:, 0], y=real_pca[:, 1], alpha=0.5, label='Real', color='blue')
sns.scatterplot(x=gan_pca[:, 0], y=gan_pca[:, 1], alpha=0.5, label='GAN', color='red')
plt.title("Real vs GAN: PCA Distribution Overlap")
plt.legend()
plt.show()

PCA对比 图2: 真实(蓝)vs GAN(红)PCA投影。GAN覆盖了大部分真实分布,但边缘有"噪声点"------学到整体模式,但细节需更多epochs。重叠度~80%,不错!

特征曲线对比(随机选10样本):

Python

复制代码
fig, ax = plt.subplots(figsize=(15, 3))
for i in range(10):
    # 随机选真实
    real_idx = np.random.randint(0, len(features_df))
    ax.plot(features_df.iloc[real_idx].values, 'b-', alpha=0.7, lw=1, label='Real' if i==0 else "")
    # 对应GAN
    ax.plot(gan_features[i], 'r--', alpha=0.6, lw=1, label='GAN' if i==0 else "")

ax.set_title("Real vs GAN: Feature Vector Curves (10 Samples)")
ax.set_xlabel("Feature Dimension")
ax.set_ylabel("Value [-1,1]")
ax.legend()
plt.show()

曲线对比 图3: 曲线对比。GAN(红虚线)趋势与真实(蓝实线)相似(e.g., 峰谷位置),幅度也在[-1,1]------统计模式学到了,但个别维稍平滑(GAN常见)。

质量评估:用上篇KernelPCA再验,非线性下重叠更好。生成数据已存gan_features.csv,下篇直接用作增强!


小结:纯GAN让EEG"批量复制",但还缺"灵魂"

这篇我们用PyTorch敲定一维GAN:从噪声生特征,PCA显示分布逼真(重叠80%+),曲线趋势OK。GAN适合EEG增强------小样本变大,泛化提3-5%(下篇实测)。但纯GAN无条件,无法指定Arousal高/低------生成随机,增强粗糙。

收获:GAN训不稳?多看loss曲线;生成质量?多PCA/曲线验。代码仓库跑起来,调epochs看变化!

相关推荐
竹一阁2 天前
跟踪导论(十二)——卡尔曼滤波的启动:初始参数的设置
算法·信号处理·雷达·信号与系统
race condition3 天前
UNIX网络编程笔记 信号处理
笔记·unix·信号处理
DatGuy3 天前
Week 30: 机器学习补遗:时序信号处理与数学特征工程
人工智能·机器学习·信号处理
Aaron15884 天前
基于RFSOC+VU13P+GPU架构在雷达电子战的技术
人工智能·算法·fpga开发·架构·硬件工程·信号处理·基带工程
极度畅想5 天前
脑电模型实战系列(三):基于KNN的DEAP脑电情绪识别进阶优化与深度学习对比(五)
脑机接口·bci·knn算法·eeg情绪识别·deap数据集·fft频域分析·russell情绪模型
代码游侠5 天前
应用——Linux进程通信与信号处理
linux·运维·服务器·笔记·学习·信号处理
极度畅想6 天前
脑电模型实战系列(三):基于 KNN 的 DEAP 脑电情绪识别 KNN 算法与 Canberra 距离深度剖析(三)
机器学习·knn·脑机接口·情绪识别·bci·canberra距离
极度畅想6 天前
脑电模型实战系列(三):DEAP 数据集处理与 Russell 环状模型实战(一)
深度学习·特征提取·情感计算·脑机接口 bci·deap数据集
这张生成的图像能检测吗6 天前
(论文速读)基于导波的飞机机身弦复杂连接结构损伤诊断
信号处理·故障诊断·传感器·结构健康监测·导波·飞机结构件损伤诊断