1D卷积神经网络完成二分类任务

该项目是机器学习的课程作业,代码实现了一个1D卷积神经网络(1D CNN)模型,通过三层卷积层(通道数16→32→64)和最大池化层提取一维序列特征,最终通过全局平均池化与全连接层完成二分类任务。模型使用Adam优化器和BCEWithLogitsLoss损失函数,输入数据为320维特征(经CSVDataset转换为[batch, 1, 320]张量),适用于处理如时间序列或基因序列的二分类问题。

1.导入库

代码开始导入了以下的python库:

PyTorch:用于构建和训练神经网络的核心框架。

nn:神经网络模块(层、激活函数、损失函数)。

optim:优化器(如Adam)。

Dataset 和 DataLoader:数据加载工具。

Pandas:用于读取和处理CSV文件。

NumPy:数值计算工具。  

scikit-learn:提供balanced_accuracy_score用于评估分类性能(适用于类别不平衡数据)。

2.CSVDataset类

该自定义数据集类用于加载CSV文件中的二分类数据,适配PyTorch的Dataset接口,支持数据划分、标签编码、张量转换及可选的预处理操作。

首先读取CSV文件,处理标签编码,划分特征和标签。将标签列中的"A"替换为1,"B"替换为0(适用于二分类任务)。根据split列的值筛选数据,选取第4列及之后的列作为特征,选取第3列作为标签列。

class CSVDataset(Dataset):
   def __init__(self, path, type, transform=None):

       data = pd.read_csv(path)
       data = data.replace("A", 1)
       data = data.replace("B", 0)

       self.x = data.loc[data['split'] == type].iloc[:, 3:].values
       self.y = data.loc[data['split'] == type].iloc[:, 2].values.reshape(-1, 1) 

       self.transform = transform

之后返回数据集的样本总数(即特征数据self.x的行数)。

def __len__(self):
   return len(self.x)

之后根据索引idx返回单个样本的特征和标签。将NumPy数组转为PyTorch张量。为特征添加通道维度,适配Conv1d输入格式,通过transform参数支持自定义预处理。

3.CNN1D类

这是一个用于二分类任务的1D卷积神经网络(CNN),通过多层卷积和池化提取时序特征,最终通过全连接层输出分类结果。

首先进行初始化。卷积层中,conv1:输入单通道(如时序信号),输出16通道,卷积核大小3,填充1(保持输入长度不变)。conv2 和 conv3:通道数递增(16,32,64),逐步提取深层特征。池化层中最大池化(kernel_size=2),每次将序列长度减半。

正则化:Dropout层(丢弃率0.3)减少过拟合风险。全局池化:AdaptiveAvgPool1d(1) 将每个通道的时序特征压缩为1个值,生成全局特征向量。全连接层:将64维特征映射到128维,再输出1维。

class CNN1D(nn.Module):
   def __init__(self):
       super(CNN1D, self).__init__()
       self.conv1 = nn.Conv1d(in_channels=1, out_channels=16, kernel_size=3, padding=1)
       self.relu = nn.ReLU()
       self.pool = nn.MaxPool1d(kernel_size=2)
       self.conv2 = nn.Conv1d(in_channels=16, out_channels=32, kernel_size=3, padding=1)
       self.conv3 = nn.Conv1d(in_channels=32, out_channels=64, kernel_size=3, padding=1)
       self.dropout = nn.Dropout(p=0.3)
       self.global_pool = nn.AdaptiveAvgPool1d(1)
       self.fc1 = nn.Linear(64, 128)
       self.fc2 = nn.Linear(128, 1)

输入数据依次通过三个卷积层(conv1、conv2、conv3),每层后接ReLU激活函数和最大池化(pool),逐步提取特征并压缩序列长度。使用自适应平均池化(global_pool)将特征压缩为全局向量,展平后通过Dropout层随机丢弃部分神经元以防止过拟合。经过两个全连接层(fc1、fc2)映射到最终输出,并通过squeeze(1)调整维度,得到形状为[batch]的二分类结果。

4.train函数代码

训练模式与梯度清零,设置模型为训练模式,遍历数据加载器的每个批次,将输入和标签移至指定设备(如GPU),并清空优化器的历史梯度。

前向传播与损失计算,通过模型前向传播得到预测值,计算损失(criterion,如交叉熵),并调用loss.backward()进行反向传播,生成参数梯度。

参数更新与损失累计,使用优化器更新模型参数,累计批次损失,最终返回整个数据集上的平均损失(总损失除以样本总数)。

def train(model, dataloader, criterion, optimizer, device):
   model.train()
   total_loss = 0.0
   for x, y in dataloader:
       x, y = x.to(device), y.to(device)
       optimizer.zero_grad()
       outputs = model(x)
       loss = criterion(outputs, y.squeeze())
       loss.backward()
       optimizer.step()
       total_loss += loss.item() * x.size(0)
   return total_loss / len(dataloader.dataset)

    5.evaluate函数

    此函数用于评估模型性能,首先调用 model.eval() 将模型设置为评估模式,同时使用 torch.no_grad() 上下文管理器来禁止梯度计算,以减少内存消耗并加快推理速度。接着遍历数据加载器 dataloader 中的每个批次数据,将输入 x 和标签 y 移动到指定设备 device 上,通过模型 model 得到输出 outputs,再使用 torch.sigmoid 函数将输出转换为概率值,最后以 0.5 为阈值将概率值转换为预测标签 preds。最后将真实标签 y 和预测标签 preds 移回 CPU,使用 balanced_accuracy_score 函数计算平衡准确率并返回该评估指标。

    def evaluate(model, dataloader, device):
       model.eval()
       with torch.no_grad():
           for x, y in dataloader:
               x, y = x.to(device), y.to(device)
               outputs = model(x)
               preds = (torch.sigmoid(outputs) > 0.5).float()
       return balanced_accuracy_score(y.cpu(), preds.cpu())

    6.程序主入口

    主要功能即初始化训练所需的各项参数和组件,然后进行模型训练与评估,最后保存训练好的模型。

    设备选择、批次大小和训练轮数设置。device:检查系统是否支持CUDA,若支持则使用 GPU 进行计算,否则使用CPU。batch_size:每次训练时输入到模型中的样本数量,这里设置为60。epochs:模型对整个训练数据集进行训练的轮数,设置为 500 轮。

    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    batch_size = 60
    epochs = 500

    数据集和数据加载器创建。train_dataset 和 test_dataset:分别从 CSV 文件中加载训练集和测试集数据,使用自定义的 CSVDataset 类进行处理。 train_loader 和 test_loader:使用 DataLoader 类将数据集封装成可迭代的数据加载器,方便按批次加载数据。训练集数据加载器会在每个 epoch 打乱数据顺序(shuffle=True),测试集则不打乱(shuffle=False)。

    train_dataset = CSVDataset('./ACE_features_320_dimension.csv', type='train')
    test_dataset = CSVDataset('./ACE_features_320_dimension.csv', type='test')
    train_loader = DataLoader(train_dataset, batch_size=60, shuffle=True)
    test_loader = DataLoader(test_dataset, batch_size=277, shuffle=False)

    模型、损失函数和优化器初始化。model:实例化自定义的一维卷积神经网络模型 CNN1D,并将其移动到指定设备(GPU 或 CPU)上。criterion:使用二元交叉熵损失函数 nn.BCEWithLogitsLoss,适用于二分类问题。optimizer:使用 Adam 优化器来更新模型的参数,学习率设置为 0.002。

    model = CNN1D().to(device)
    criterion = nn.BCEWithLogitsLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.002)

    模型训练与评估。acc_best:用于记录模型在测试集上的最佳准确率。

    for 循环:进行 500 轮训练和评估。

    train_loss:调用 train 函数对模型进行一轮训练,并返回该轮的平均训练损失。

    acc:调用 evaluate 函数在测试集上评估模型的准确率。

    acc_best:更新最佳准确率。

    print:打印当前轮次的训练损失、测试准确率和最佳准确率。

    best_acc = 0.0
    best_epoch = 0
    best_model_path = 'cnn1d_best.pth'
    for epoch in range(1, epochs + 1):
       train_loss = train(model, train_loader, criterion, optimizer, device)
       test_acc = evaluate(model, test_loader, device)
       if test_acc > best_acc:
           best_acc = test_acc
           best_epoch = epoch
           torch.save(model.state_dict(), best_model_path)
       print(f"Epoch {epoch}/{epochs} - Loss: {train_loss:.4f} - Test Acc: {test_acc:.4f} - Best Acc: {best_acc:.4f} (Epoch {best_epoch})")

    模型保存。torch.save(model, "cnn1d_binary.pth"):将训练好的模型保存到文件 cnn1d_binary.pth 中。print:输出提示信息,表示训练完成且模型已保存。

    torch.save(model, "cnn1d_binary.pth")
    print("train finished, the model has been saved to cnn1d_binary.pth")
    1. 运行结果

    附录:整体代码:

    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torch.utils.data import Dataset, DataLoader
    import numpy as np
    import pandas as pd
    from sklearn.metrics import balanced_accuracy_score
    
    
    class CSVDataset(Dataset):
       def __init__(self, path, type, transform=None):
    
           data = pd.read_csv(path)
           data = data.replace("A", 1)
           data = data.replace("B", 0)
    
           self.x = data.loc[data['split'] == type].iloc[:, 3:].values
           self.y = data.loc[data['split'] == type].iloc[:, 2].values.reshape(-1, 1) 
    
           self.transform = transform
    
       def __len__(self):
           return len(self.x)
    
       def __getitem__(self, idx):
    
           xi = torch.from_numpy(self.x[idx]).type(torch.FloatTensor)    
           xi = xi.unsqueeze(0)
           yi = torch.tensor(self.y[idx]).type(torch.FloatTensor)
    
           if self.transform:
               xi = self.transform(xi)
           return xi, yi
    
    
    
    class CNN1D(nn.Module):
       def __init__(self):
           super(CNN1D, self).__init__()
           self.conv1 = nn.Conv1d(in_channels=1, out_channels=16, kernel_size=3, padding=1)
           self.relu = nn.ReLU()
           self.pool = nn.MaxPool1d(kernel_size=2)
           self.conv2 = nn.Conv1d(in_channels=16, out_channels=32, kernel_size=3, padding=1)
           self.conv3 = nn.Conv1d(in_channels=32, out_channels=64, kernel_size=3, padding=1)
           self.dropout = nn.Dropout(p=0.3)
           self.global_pool = nn.AdaptiveAvgPool1d(1)
           self.fc1 = nn.Linear(64, 128) 
           self.fc2 = nn.Linear(128, 1)
    
       def forward(self, x):
           x = self.conv1(x)
           x = self.relu(x)
           x = self.pool(x)
           x = self.conv2(x)
           x = self.relu(x)
           x = self.pool(x)
           x = self.conv3(x)
           x = self.relu(x)
           x = self.pool(x)
           x = self.global_pool(x)
           x = x.view(x.size(0), -1)
           x = self.dropout(x)
           x = self.fc1(x)
           x = self.relu(x)
           x = self.dropout(x)
           x = self.fc2(x)
           return x.squeeze(1)
    
    
    def train(model, dataloader, criterion, optimizer, device):
       model.train()
       total_loss = 0.0
       for x, y in dataloader:
           x, y = x.to(device), y.to(device)
           optimizer.zero_grad()
           outputs = model(x)
           loss = criterion(outputs, y.squeeze())
           loss.backward()
           optimizer.step()
           total_loss += loss.item() * x.size(0)
       return total_loss / len(dataloader.dataset)
    
    
    def evaluate(model, dataloader, device):
       model.eval()
       with torch.no_grad():
           for x, y in dataloader:
               x, y = x.to(device), y.to(device)
               outputs = model(x)
               preds = (torch.sigmoid(outputs) > 0.5).float()
       return balanced_accuracy_score(y.cpu(), preds.cpu())
    
    if __name__ == "__main__":
    
       device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
       batch_size = 60
       epochs = 500
    
       train_dataset = CSVDataset('./ACE_features_320_dimension.csv', type='train')
       test_dataset = CSVDataset('./ACE_features_320_dimension.csv', type='test')
       train_loader = DataLoader(train_dataset, batch_size=60, shuffle=True)
       test_loader = DataLoader(test_dataset, batch_size=277, shuffle=False)
       model = CNN1D().to(device)
       criterion = nn.BCEWithLogitsLoss()
       #optimizer = optim.SGD(model.parameters(), lr=0.0005, momentum=0.9)
       optimizer = optim.Adam(model.parameters(), lr=0.002)
    
       
       best_acc = 0.0
       best_epoch = 0
       best_model_path = 'cnn1d_best.pth'
       for epoch in range(1, epochs + 1):
           train_loss = train(model, train_loader, criterion, optimizer, device)
           test_acc = evaluate(model, test_loader, device)
           if test_acc > best_acc:
               best_acc = test_acc
               best_epoch = epoch
               torch.save(model.state_dict(), best_model_path)
           print(f"Epoch {epoch}/{epochs} - Loss: {train_loss:.4f} - Test Acc: {test_acc:.4f} - Best Acc: {best_acc:.4f} (Epoch {best_epoch})")
       
    
       print(f"Training complete. Best accuracy {best_acc:.4f} achieved at epoch {best_epoch}.")
       print(f"Best model weights saved to '{best_model_path}'.")

     

     

    ### 一维卷积神经网络概述 一维卷积神经网络1D Convolutional Neural Network, 1D-CNN)是一种专门用于处理序列数据的模型结构。它通过滑动窗口的方式提取局部特征,在时间序列分析、自然语言处理以及音频信号处理等领域具有广泛应用[^1]。 在一维卷积操作中,输入通常是一个二维张量,形状为 `(batch_size, sequence_length, num_features)` 或者 `(sequence_length, num_features)`。其中 `sequence_length` 表示序列长度,而 `num_features` 则表示每个时间步上的特征维度。卷积核沿着序列方向移动并计算加权和,从而捕捉到局部模式或趋势[^4]。 以下是实现一维卷积神经网络的一个简单例子: ```python import tensorflow as tf from tensorflow.keras import layers, models def create_1d_cnn(input_shape, num_classes): model = models.Sequential() # 添加第一个一维卷积层 model.add(layers.Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=input_shape)) model.add(layers.MaxPooling1D(pool_size=2)) # 添加第二个一维卷积层 model.add(layers.Conv1D(filters=128, kernel_size=5, activation='relu')) model.add(layers.GlobalAveragePooling1D()) # 输出全连接层 model.add(layers.Dense(num_classes, activation='softmax')) return model # 假设我们有如下参数 input_shape = (100, 1) # 序列长度为100,单通道特征 num_classes = 10 # 分类数为10 model = create_1d_cnn(input_shape, num_classes) model.summary() ``` 此代码片段定义了一个简单的1D-CNN架构,适用于分类任务。该模型由两个卷积层组成,并使用全局平均池化来减少空间尺寸,最后接一个全连接层完成最终预测。 #### 使用场景举例 1. **心电图数据分析** 对于医疗领域中的ECG信号,可以利用1D-CNN自动检测异常波形或者诊断疾病状态。由于这些信号本质上是一维的时间序列,因此非常适合采用此类技术进行建模[^1]。 2. **语音识别预处理** 虽然完整的语音识别系统可能涉及更复杂的RNN/LSTM组件,但在前端声学特征提取阶段,也可以借助1D-CNN高效捕获频谱帧内的短时变化规律[^4]。 3. **文本情感分类** 如果将单词嵌入向量化后的句子视为一种特殊形式的一维数组,则同样能够运用类似的思路构建基于CNN的情感极性判断器[^2]。 --- ### 训练与优化技巧 为了提高性能表现,建议遵循以下几点指导原则: - 数据增强:对于有限样本集的情况,可以通过随机裁剪、翻转等方式扩充原始资料库规模; - 正则化手段:引入Dropout机制防止过拟合现象发生;同时调整权重衰减系数控制复杂度增长速度; - 学习率调度策略:动态改变超参值有助于加速收敛进程并找到更好的解空间位置[^5]。 ---
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值