一个简易神经网络项目的核心要素解析

1. 数据准备(Data)
  • 核心目标:构建一个能够存储输入特征(x)和对应标签(y)的数据结构。
  • 核心目标:构建存储输入特征(x)与标签(y)的数据结构
  • 实践方式:在PyTorch框架中,通常利用DataLoader工具来高效装载和管理数据,它支持批量处理、打乱数据顺序等功能,为后续模型训练提供便利。
  • 实践方式:PyTorch中使用DataLoader实现批量加载与数据打乱
2. 模型构建(Model)
  • 核心目标:定义一个能够接收输入数据(x)并输出预测结果的数学模型。
  • 核心目标:定义输入到预测输出的映射模型
  • 模型本质:模型由一系列可学习的参数和特定的计算流程组成,其功能是通过学习数据中的模式,将输入映射到期望的输出空间。例如,Linear(in_features, out_features)层是一种基础模型组件,它通过执行线性变换实现维度转换,其参数数量计算公式为in_features * out_features + out_features(其中+ out_features为偏置项参数),如Linear(4, 100)将包含4*100 + 100 = 500个参数。
3. 超参数设置(Hyperparameters)
  • 核心模型本质:通过可学习参数实现特征映射,如Linear(4,100)含4×100+100=500个参数(含偏置)
  • 目标:配置除模型结构参数之外,用于控制训练过程的关键参数。
  • 主要类别
    • 学习率(Learning Rate):决定了核心目标:配置控制训练过程的关键参数
    • 模型参数在每次更新时的调整步长,对模型收敛速度和最终性能有重要影响。
  • 优化器(Optimizer):负责根据损失函数计算得到的学习率:控制参数更新步长,影响收敛速度与性能
    • 梯度信息,对模型参数进行更新,常见的有SGD、Adam等。
  • 损失函数(Loss Function):用于量化模型预测优化器:基于梯度更新参数(如SGD、Adam)
    • 值与真实标签(y)之间的差异。例如,此处提及的绝对误差损失函数Loss = abs(真实y - 预测y)。
4. 损失函数(Loss)
  • 核心定义:Loss = ab损失函数:量化预测误差(如绝对误差Loss=abs(真实y-预测y))
  • s(真实y - 预测y),即计算预测值与真实值之间绝对差值的平均值(通常指平均绝对误差MAE的简化形式,具体取决于是否取平均)。
  • 核心作用:损失函数是模型性能的直接量化指标,其值的大小反核心定义:Loss=abs(真实y-预测y),衡量预测与真实值差异
  • 映了当前模型预测的准确程度。在训练过程中,模型参数的更新方向和幅度均由损失函数通过反向传播计算的梯度来指导。一旦输入数据(x)通过模型进行前向传播计算,计算图便会自动记录梯度信息,为后续的参数更新做准备。
5. 数据集划分与模型评估策略
  • 训练集与测试集:训练集是核心作用:指导参数更新,输入通过模型后自动记录梯度信息
  • 模型可见并用于参数学习的数据;测试集是模型在训练过程中不可见,仅用于在训练结束后评估模型泛化能力的独立数据。
    • 验证集的引入:为了在训练过程中监控模型状态并及时调整策略数据集划分:训练集(模型学习)、测试集(泛化评估,训练不可见)
  • ,通常从训练集中划分出一部分数据作为验证集。
    • 划分原则:验证集的划分可以采用随机划分、确保类别分布均匀验证集:从训练集划分,用于监控训练过程与超参数调优
    • 的分层划分等方法。
    • 核心作用:验证集不参与模型参数的训练过程,而是用于在训练的不同阶段评估模型性能,帮助判断模型是否出现过拟合或欠拟合,以及指导超参数调整。
6. 数据加载策略(取数据)
  • 背景:在模型训练时,如何选取数据样本来计算损失并更新参数是一个关键问题。
  • 常见策略分析
    • 使用全部数据计算损失(全批量梯度下降):理论上,使用全部数据计算的损失能更准确地反映整体数据分布,梯度估计更稳定。然而,当数据集规模非常大时,这会导致计算资源消耗巨大,训练效率低下,且在某些情况下可能陷入局部最优。
  • 单个数据更新一次(随机梯度下降):每次仅使用一个样本计算全批量梯度下降:用全部数据计算损失,梯度稳定但计算成本高
    • 损失并更新参数,计算量小,更新频繁。但缺点是单个样本的损失值具有较大随机性,导致梯度估计方差大,训练过程可能震荡剧烈,难以稳定收敛到最优解。
  • 折衷方案:实际中广泛采用小批量梯度下降(Mini-Bat随机梯度下降:单样本更新,计算量小但梯度波动大
  • ch Gradient Descent),即每次选取一小批(Batch Size)样本进行计算和更新。这种方法平衡了计算效率和梯度估计的稳定性,是目前主流的训练模式。
7. 模型训练的基本流程(以“新冠病毒感染人数预测”为例主流方案:小批量梯度下降(Mini-Batch),平衡效率与稳定性
  • 自定义数据集类(Dataset Class):为特定任务7. 训练流程(以新冠感染人数预测为例)
  • (如新冠病毒感染人数预测)构建数据集类是规范的数据处理方式,该类通常需要实现以下方法:
    • __init__:在类的初始化阶段完成数据的读取、预处理自定义Dataset类:需实现__init__(数据读取)、__getitem__(按索引取样本)、__len__(样本数量)
    • (如归一化、特征提取)等操作。
    • __getitem__:根据给定的索引(下标),返回对应的数据样本(通常是一个包含x和y的元组)。
    • __len__:返回数据集的总样本数量,以便DataLoader等工具了解数据规模。
  • 训练循环
    • 前向传播:将输入数据通过模型,得到预测值。
  • 计算损失:将预测值与真实标签代入损失函数(如绝对误差损失前向传播:输入数据通过模型生成预测值
    • ),计算两者之间的差异。
  • 反向传播:利用自动微分机制(如PyTorch的loss.计算损失:通过损失函数计算预测值与真实标签的差异
    • backward())计算损失函数关于模型各参数的梯度。
  • 参数更新:优化器根据计算得到的梯度,按照预设的学习率更新反向传播:自动微分计算参数梯度(如PyTorch的loss.backward())
    • 模型参数。
8. 模型训练中的关键认识与挑战
  • “损失越小模型越好”的参数更新:优化器按学习率更新模型参数
  • 辩证看待:通常情况下,较小的损失值8. 训练关键挑战
  • 意味着模型在当前数据上的预测准确度较高。然而,“并非越训练就越好”,因为过度训练可能导致模型过拟合(Overfitting)——即模型在训练数据上表现优异,但在未见过的新数据(如验证集、测试集)上性能显著下降,这表明模型学习到了数据中的噪声而非普适规律。
9. 提升模型性能的常用技巧(Tricks)
  • 正则化(R过拟合风险:过度训练导致模型在训练集表现优但泛化能力差,需通过验证集监控,是一类用于防止模型过拟合的技术。其核心思想是在原始损失函数(Loss)中加入一个惩罚项,例如L2正则化会添加w*w(权重参数的平方和,通常乘以一个正则化系数λ来控制惩罚力度),即loss = loss + λ * sum(w^2)。这样,在优化过程中,不仅要最小化预测误差,还要最小化模型参数的大小,促使模型选择更为简单、泛化能力更强的参数组合,避免对训练数据中噪声的过度拟合。
  • 相关系数(Correlation Coefficient):是一种用于衡量正则化:通过L2惩罚项(loss=loss+λ∑w²)限制参数规模,防止过拟合
  • 两个变量之间线性相关程度和方向的统计量(如皮尔逊相关系数)。在特征选择阶段,分析输入特征(x)与目标变量(y)之间的相关系数,可以帮助识别对预测任务更有价值的特征,从而简化模型或提升模型性能。
  • 主成分分析(PCA, Principal Component Analys相关系数:衡量特征与目标变量线性关系,辅助特征选择。是一种常用的降维技术。它通过线性变换将高维特征数据映射到低维空间,同时尽可能保留原始数据的方差信息。PCA可以用于去除特征间的冗余信息,降低计算复杂度,有时还能通过减少噪声干扰来间接提升模型的泛化能力。

新冠病毒感染人数预测代码示例:

import torch

import matplotlib.pyplot as plt #画图

import matplotlib

import numpy as np  #矩阵相关

import csv     #csv文件

import pandas  #csv文件

from torch.utils.data import Dataset, DataLoader

import torch.nn as nn

from torch import optim

import time

class CovidDataset(Dataset):

    """

    自定义数据集类,继承自torch.utils.data.Dataset

    用于加载COVID-19数据集并进行预处理

    """

    def __init__(self, file_path, mode):

        """

        初始化数据集

        file_path: CSV文件路径

        mode: 数据模式('train', 'val', 'test')

        """

        with open(file_path, "r") as f:

            # 读取CSV文件的所有行,形成二维列表

            ori_data = list(csv.reader(f))

            # 跳过第一行(表头)和第一列(ID),转换为numpy数组并转为浮点型

            csv_data = np.array(ori_data)[1:, 1:].astype(float)

           

            # 根据模式划分数据集

            if mode == "train":

                # 训练集:跳过每5个样本中的第1个(即每5个中取4个)

                indices = [i for i in range(len(csv_data)) if i % 5 != 0]

            elif mode == "val":

                # 验证集:只取每5个样本中的第1个(即每5个中取1个)

                indices = [i for i in range(len(csv_data)) if i % 5 == 0]

            elif mode == "test":

                # 测试集:使用全部数据

                indices = [i for i in range(len(csv_data))]

            # 提取特征数据(前93列)

            X = torch.tensor(csv_data[indices, :93])

           

            # 如果不是测试集,提取标签数据(最后一列)

            if mode != "test":

                self.Y = torch.tensor(csv_data[indices, -1])

           

            # 对特征进行标准化:(X - mean) / std

            self.X = (X - X.mean(dim=0, keepdim=True)) / X.std(dim=0, keepdim=True)

            self.mode = mode

    def __getitem__(self, item):

        """

        获取索引为item的数据样本

        item: 样本索引

        """

        if self.mode == "test":

            # 测试集只返回特征

            return self.X[item].float()

        else:

            # 训练集和验证集返回特征和标签

            return self.X[item].float(), self.Y[item].float()

    def __len__(self):

        """

        返回数据集长度

        """

        return len(self.X)

class myModel(nn.Module):

    """

    自定义神经网络模型,继承自nn.Module

    实现一个简单的两层全连接网络

    """

    def __init__(self, inDim):

        """

        初始化网络

        inDim: 输入特征维度

        """

        super(myModel, self).__init__()  # 调用父类初始化

        # 第一层全连接层:输入维度inDim -> 输出128维

        self.fc1 = nn.Linear(inDim, 128)

        # ReLU激活函数

        self.relu1 = nn.ReLU()

        # 第二层全连接层:128维 -> 输出1维(回归任务)

        self.fc2 = nn.Linear(128, 1)

    def forward(self, x):

        """

        前向传播过程

        x: 输入张量

        """

        # 经过第一层线性变换

        x = self.fc1(x)

        # 经过ReLU激活函数

        x = self.relu1(x)

        # 经过第二层线性变换

        x = self.fc2(x)

        # 如果输出维度大于1,去除多余的维度(例如从(batch_size, 1)变为(batch_size,))

        if len(x.size()) > 1:

            x = x.squeeze(1)

        return x

def train_val(model, train_loader, val_loader, lr, optimizer, device, epochs, save_path):

    """

    训练和验证模型

    model: 待训练的模型

    train_loader: 训练数据加载器

    val_loader: 验证数据加载器

    lr: 学习率

    optimizer: 优化器

    device: 设备(CPU/GPU)

    epochs: 训练轮数

    save_path: 最佳模型保存路径

    """

    model = model.to(device)    # 将模型移动到指定设备

   

    plt_train_loss = []  # 存储每个epoch的平均训练损失

    plt_val_loss = []    # 存储每个epoch的平均验证损失

    min_val_loss = 999999999999999999.9  # 记录最小验证损失

    for epoch in range(epochs):          # 开始训练循环

        model.train()  # 设置模型为训练模式(启用dropout、BN等)

        start_time = time.time()  # 记录开始时间

        train_loss = 0.0  # 初始化当前epoch的训练损失

       

        for x, y in train_loader:       # 遍历训练数据批次

            x, y = x.to(device), y.to(device)  # 将数据移到指定设备

           

            y_pred = model(x)  # 前向传播,获取预测值

            bat_loss = loss(y_pred, y, model)  # 计算损失(包含正则化项)

           

            bat_loss.backward()         # 反向传播,计算梯度

            optimizer.step()            # 更新模型参数

            optimizer.zero_grad()       # 清零梯度,避免累积

            train_loss += bat_loss.cpu().item()  # 累加损失

        # 计算平均训练损失

        plt_train_loss.append(train_loss/train_loader.__len__())

        model.eval()  # 设置模型为评估模式(禁用dropout、BN等)

        val_loss = 0.0

        with torch.no_grad():  # 不计算梯度,节省内存

            for val_x, val_y in val_loader:  # 遍历验证数据

                val_x, val_y = val_x.to(device), val_y.to(device)  # 移动数据到设备

                val_pred_y = model(val_x)  # 预测

                val_bat_loss = loss(val_pred_y, val_y, model)  # 计算验证损失

                val_loss += val_bat_loss.cpu().item()  # 累加验证损失

        # 计算平均验证损失

        plt_val_loss.append(val_loss / val_loader.__len__())

        # 保存最佳模型(验证损失最小时)

        if val_loss < min_val_loss:

            min_val_loss = val_loss

            torch.save(model, save_path)

        # 打印训练信息

        print("[%03d/%03d] %2.2f sec(s)  train_loss: %.6f val_loss:%.6f" % \

              (epoch, epochs, time.time()-start_time, plt_train_loss[-1], plt_val_loss[-1]))

    # 绘制训练和验证损失曲线

    plt.plot(plt_train_loss)

    plt.plot(plt_val_loss)

    plt.title("loss")

    plt.legend(["train", "val"])

    plt.show()

def evaluate(model_path, test_loader, rel_path, device):

    """

    在测试集上评估模型

    model_path: 模型保存路径

    test_loader: 测试数据加载器

    rel_path: 结果保存路径

    device: 设备

    """

    model = torch.load(model_path).to(device)  # 加载模型并移到指定设备

    rel = []         # 记录预测结果

    model.eval()  # 设置为评估模式

    with torch.no_grad():    # 不计算梯度

        for x in test_loader:  # 遍历测试数据

            x = x.to(device)  # 移动到设备

            pred = model(x)   # 预测

            rel.append(pred.cpu().item())  # 将预测结果移到CPU并转换为数值

   

    # 将预测结果写入CSV文件

    with open(rel_path, "w", newline="") as f:  # 打开文件

        csv_writer = csv.writer(f)  # 创建CSV写入器

        csv_writer.writerow(["id", "tested_positive"])  # 写入表头

        for i, pred in enumerate(rel):     # 同时获得索引和预测值

            csv_writer.writerow([str(i), str(pred)])  # 写入每一行

        print("结果保存到了"+rel_path)

# 定义数据文件路径

# train_file = "covid_train.csv"  # 把这行注释掉或者删掉

train_file = r"D:\深度学习\第三节,回归实战代码\regression\covid\covid.train.csv"

test_file = r"D:\深度学习\第三节,回归实战代码\regression\covid\covid.test.csv"

# 设置批量大小

batch_size = 16

# 创建训练集、验证集和测试集实例

train_set = CovidDataset(train_file, "train")

val_set = CovidDataset(train_file, "val")

test_set = CovidDataset(test_file, "test")

# 创建数据加载器

train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)  # 训练集打乱顺序

val_loader = DataLoader(val_set, batch_size=batch_size, shuffle=True)      # 验证集打乱顺序

test_loader = DataLoader(test_set, batch_size=1, shuffle=False)            # 测试集不打乱,批量为1

def mseLoss(pred, target, model):

    """

    自定义均方误差损失函数,包含L2正则化项

    pred: 预测值

    target: 真实值

    model: 模型(用于正则化)

    """

    loss = nn.MSELoss(reduction='mean')    # 创建MSE损失函数,按均值计算

    ''' Calculate loss '''

    regularization_loss = 0                    # 初始化正则化损失

    for param in model.parameters():

        # TODO: you may implement L1/L2 regularization here

        # 使用L2正则项

        # regularization_loss += torch.sum(abs(param))  # L1正则(注释掉)

        regularization_loss += torch.sum(param ** 2)    # L2正则:计算所有参数的平方和

    # 返回原始损失加上正则化损失(权重0.00075)

    return loss(pred, target) + 0.00075 * regularization_loss            

# 设置损失函数

loss = mseLoss

epochs = 20   # 训练轮次

lr = 0.001     # 学习率

# 自动检测GPU是否可用,若可用则使用CUDA,否则使用CPU

device = "cuda" if torch.cuda.is_available() else "cpu"

print(device)  # 打印使用的设备

data_dim = 93  # 特征维度(93个特征)

model = myModel(data_dim).to(device)  # 创建模型并移到指定设备

save_path = "model_save/best_model.pth"  # 模型保存路径

rel_path = "pred.csv"  # 预测结果保存路径

# 使用SGD优化器,带动量参数0.9

optimizer = optim.SGD(params=model.parameters(), lr=lr, momentum=0.9)

# 开始训练模型

train_val(model, train_loader, val_loader, lr, optimizer, device, epochs, save_path)

# 使用训练好的模型进行测试并保存结果

evaluate(save_path, test_loader, rel_path, device)     

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐