第三章 回归训练实战(以预测新冠感染人数为例)

完整项目代码(预测第三天的新冠感染人数)

from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
import csv #读 CSV
import numpy as np
import time
import matplotlib.pyplot as plt
import pandas as pd
from torch import optim
import torch.nn as nn
import torch
from torch.utils.data import Dataset,DataLoader


def get_feature_importance(feature_data, label_data, k=4,column = None):
    """
    此处省略 feature_data, label_data 的生成代码。
    如果是 CSV 文件,可通过 read_csv() 函数获得特征和标签。
    这个函数的目的是, 找到所有的特征ZHONG, 比较有用的k个特征, 并打印这些列的名字。
    """
    model = SelectKBest(chi2, k=k)      #定义一个选择k个最佳特征的函数
    X_new = model.fit_transform(feature_data, label_data)   #用这个函数选择k个最佳特征
    #feature_data是特征数据,label_data是标签数据,该函数可以选择出k个特征
    print('x_new', X_new)
    scores = model.scores_                # scores即每一列与结果的相关性
    # 按重要性排序,选出最重要的 k 个
    indices = np.argsort(scores)[::-1]        #[::-1]表示反转一个列表或者矩阵。
    # argsort这个函数, 可以矩阵排序后的下标。 比如 indices[0]表示的是,scores中最小值的下标。

    if column:                            # 如果需要打印选中的列
        k_best_features = [column[i+1] for i in indices[0:k].tolist()]         # 选中这些列 打印
        print('k best features are: ',k_best_features)
    return X_new, indices[0:k]                  # 返回选中列的特征和他们的下标。


class covidDataset(Dataset):
    def __init__(self, path, mode="train", feature_dim=5, all_feature=False):
        with open(path, 'r') as f:
            csv_data = list(csv.reader(f))
            column = csv_data[0]
            x = np.array(csv_data)[1:,1:-1]     # 1: 第一行后面的,   1:-1
            y = np.array(csv_data)[1:,-1]
            if all_feature:
                col_indices = np.array([i for i in range(0,93)])                  # 若全选,则选中所有列。
            else:
                _, col_indices = get_feature_importance(x, y, feature_dim, column)      # 选重要的dim列。
            col_indices = col_indices.tolist()             # col_indices 从array 转为列表。
            csv_data = np.array(csv_data[1:])[:,1:].astype(float)       #取csvdata从第二行开始, 第二列开始的数据,并转为float

            if mode == 'train':                                # 训练数据逢5选4, 记录他们的所在行
                indices = [i for i in range(len(csv_data)) if i % 5 != 0]       #1,2,3,4, 6,7,8,9
                self.y = torch.tensor(csv_data[indices,-1])      # 训练标签是csvdata的最后一列。 要转化为tensor型
            elif mode == 'val':               # 验证数据逢5选1, 记录他们的所在列
                indices = [i for i in range(len(csv_data)) if i % 5 == 0]
                # data = torch.tensor(csv_data[indices,col_indices])
                self.y = torch.tensor(csv_data[indices,-1])        # 验证标签是csvdata的最后一列。 要转化为tensor型
            else:
                indices = [i for i in range(len(csv_data))]     # 测试机只有数据
                # data = torch.tensor(csv_data[indices,col_indices])
            data = torch.tensor(csv_data[indices, :])           # 根据选中行取 X , 即模型的输入特征
            self.data = data[:, col_indices]                   #  col_indices 表示了重要的K列, 根据重要性, 选中k列。
            self.mode = mode                                   # 表示当前数据集的模式

            self.data = (self.data - self.data.mean(dim=0,keepdim=True)) / self.data.std(dim=0,keepdim=True)  # 对数据进行列归一化 0正太分布
            assert feature_dim == self.data.shape[1]                   # 判断数据的列数是否为规定的dim列, 要不然就报错。

            print('Finished reading the {} set of COVID19 Dataset ({} samples found, each dim = {})'
                  .format(mode, len(self.data), feature_dim))             # 打印读了多少数据

    def __getitem__(self, item):               # getitem 需要完成读下标为item的数据
        if self.mode == 'test':                  # 测试集没标签。   注意data要转为模型需要的float32型
            return self.data[item].float()
        else :                                  # 否则要返回带标签数据
            return self.data[item].float(), self.y[item].float()
    def __len__(self):
        return len(self.data)                 # 返回数据长度。


class myNet(nn.Module):
    def __init__(self, inDim):
        super(myNet,self).__init__()
        self.fc1 = nn.Linear(inDim, 128)              # 全连接
        self.relu = nn.ReLU()                        # 激活函数 ,添加非线性
        # self.fc3 = nn.Linear(128, 128)
        self.fc2 = nn.Linear(128,1)                     # 全连接             设计模型架构。 他没有数据

    def forward(self, x):                     #forward, 即模型前向过程
        x = self.fc1(x)
        x = self.relu(x)
        # x = self.fc3(x)
        x = self.fc2(x)
        if len(x.size()) > 1:
            return x.squeeze(1)
        else:
            return x




def train_val(model, trainloader, valloader,optimizer, loss, epoch, device, save_):

    # trainloader = DataLoader(trainset,batch_size=batch,shuffle=True)
    # valloader = DataLoader(valset,batch_size=batch,shuffle=True)
    model = model.to(device)                # 模型和数据 ,要在一个设备上。  cpu - gpu
    plt_train_loss = []
    plt_val_loss = []
    val_rel = []
    min_val_loss = 100000                 # 记录训练验证loss 以及验证loss和结果

    for i in range(epoch):                 # 训练epoch 轮
        start_time = time.time()             # 记录开始时间
        model.train()                         # 模型设置为训练状态      结构
        train_loss = 0.0
        val_loss = 0.0
        for data in trainloader:                     # 从训练集取一个batch的数据
            optimizer.zero_grad()                   # 梯度清0
            x, target = data[0].to(device), data[1].to(device)       # 将数据放到设备上
            pred = model(x)                          # 用模型预测数据
            bat_loss = loss(pred, target)       # 计算loss
            bat_loss.backward()                        # 梯度回传, 反向传播。
            optimizer.step()                            #用优化器更新模型。  轮到SGD出手了
            train_loss += bat_loss.detach().cpu().item()             #记录loss和

        plt_train_loss. append(train_loss/trainloader.dataset.__len__())   #记录loss到列表。注意是平均的loss ,因此要除以数据集长度。

        model.eval()                 # 模型设置为验证状态
        with torch.no_grad():                    # 模型不再计算梯度
            for data in valloader:                      # 从验证集取一个batch的数据
                val_x , val_target = data[0].to(device), data[1].to(device)          # 将数据放到设备上
                val_pred = model(val_x)                 # 用模型预测数据
                val_bat_loss = loss(val_pred, val_target)          # 计算loss
                val_loss += val_bat_loss.detach().cpu().item()                  # 计算loss
                val_rel.append(val_pred)                 #记录预测结果
        if val_loss < min_val_loss:
            torch.save(model, save_)               #如果loss比之前的最小值小, 说明模型更优, 保存这个模型

        plt_val_loss.append(val_loss/valloader.dataset.__len__())  #记录loss到列表。注意是平均的loss ,因此要除以数据集长度。
        #
        print('[%03d/%03d] %2.2f sec(s) TrainLoss : %.6f | valLoss: %.6f' % \
              (i, epoch, time.time()-start_time, plt_train_loss[-1], plt_val_loss[-1])
              )              #打印训练结果。 注意python语法, %2.2f 表示小数位为2的浮点数, 后面可以对应。


        # print('[%03d/%03d] %2.2f sec(s) TrainLoss : %3.6f | valLoss: %.6f' % \
        #       (i, epoch, time.time()-start_time, 2210.2255411, plt_val_loss[-1])
        #       )              #打印训练结果。 注意python语法, %2.2f 表示小数位为2的浮点数, 后面可以对应。
    plt.plot(plt_train_loss)              # 画图, 向图中放入训练loss数据
    plt.plot(plt_val_loss)                # 画图, 向图中放入训练loss数据
    plt.title('loss')                      # 画图, 标题
    plt.legend(['train', 'val'])             # 画图, 图例
    plt.show()                                 # 画图, 展示





def evaluate(model_path, testset, rel_path ,device):
    model = torch.load(model_path).to(device)                     # 模型放到设备上。  加载模型
    testloader = DataLoader(testset, batch_size=1, shuffle=False)         # 将验证数据放入loader 验证时, 一般batch为1
    val_rel = []
    model.eval()               # 模型设置为验证状态
    with torch.no_grad():               # 模型不再计算梯度
        for data in testloader:                 # 从测试集取一个batch的数据
            x = data.to(device)                # 将数据放到设备上
            pred = model(x)                        # 用模型预测数据
            val_rel.append(pred.item())                #记录预测结果
    print(val_rel)                                     #打印预测结果
    with open(rel_path, 'w') as f:                        #打开保存的文件
        csv_writer = csv.writer(f)                           #初始化一个写文件器 writer
        csv_writer.writerow(['id','tested_positive'])         #在第一行写上 “id” 和 “tested_positive”
        for i in range(len(testset)):                           # 把测试结果的每一行放入输出的excel表中。
            csv_writer.writerow([str(i),str(val_rel[i])])
    print("rel已经保存到"+ rel_path)





all_col = False            #是否使用所有的列
device = 'cuda' if torch.cuda.is_available() else 'cpu'       #选择使用cpu还是gpu计算。
print(device)
train_path = 'covid.train.csv'                     # 训练数据路径
test_path = 'covid.test.csv'              # 测试数据路径
file = pd.read_csv(train_path)
file.head()                    # 用pandas 看看数据长啥样

if all_col == True:
    feature_dim = 93
else:
    feature_dim = 6              #是否使用所有的列

trainset = covidDataset(train_path,'train', feature_dim=feature_dim, all_feature=all_col)
valset = covidDataset(train_path,'val', feature_dim=feature_dim, all_feature=all_col)
testset = covidDataset(test_path,'test', feature_dim=feature_dim, all_feature=all_col)   #读取训练, 验证,测试数据

         # 返回损失。
#
# def mseLoss(pred, target, model):
#     loss = nn.MSELoss(reduction='mean')
#     ''' 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))
#         regularization_loss += torch.sum(param ** 2)                  # 计算所有参数平方
#     return loss(pred, target) + 0.00075 * regularization_loss             # 返回损失。
#
# loss =  mseLoss           # 定义mseloss 即 平方差损失,


loss =  nn.MSELoss()          # 定义mseloss 即 平方差损失,

config = {
    'n_epochs': 50,                # maximum number of epochs
    'batch_size': 32,               # mini-batch size for dataloader
    'optimizer': 'SGD',              # optimization algorithm (optimizer in torch.optim)
    'optim_hparas': {                # hyper-parameters for the optimizer (depends on which optimizer you are using)
        'lr': 0.0001,                 # learning rate of SGD
        'momentum': 0.9              # momentum for SGD
    },
    'early_stop': 200,               # early stopping epochs (the number epochs since your model's last improvement)
    'save_path': 'model_save/model.pth',  # your model will be saved here
}

model = myNet(feature_dim).to(device)                      # 实例化模型

optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)             # 定义优化器  动量
trainloader = DataLoader(trainset, batch_size=config['batch_size'], shuffle=True)
valloader = DataLoader(valset, batch_size=config['batch_size'], shuffle=True)  # 将数据装入loader 方便取一个batch的数据

train_val(model, trainloader, valloader, optimizer, loss, config['n_epochs'], device,save_=config['save_path'])  # 训练


evaluate(config['save_path'], testset, 'pred.csv', device)           # 验证

分析

1. 导入必要的库

from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
import csv  # 读 CSV
import numpy as np
import time
import matplotlib.pyplot as plt
import pandas as pd
from torch import optim
import torch.nn as nn
import torch
from torch.utils.data import Dataset, DataLoader

解析

  • sklearn.feature_selection: 用于特征选择,SelectKBestchi2 是常用的方法。
  • csv: 处理 CSV 文件读写。
  • numpy: 数值计算和数组操作。
  • time: 记录训练时间。
  • matplotlib.pyplot: 绘制训练过程中的损失曲线。
  • pandas: 数据处理和分析,尤其适合处理表格数据。
  • torch 相关库: 深度学习框架 PyTorch 的核心模块,用于构建模型、优化、数据加载等。

2. 特征选择函数 get_feature_importance

def get_feature_importance(feature_data, label_data, k=4, column=None):
    """
    此处省略 feature_data, label_data 的生成代码。
    如果是 CSV 文件,可通过 read_csv() 函数获得特征和标签。
    这个函数的目的是,找到所有的特征中,比较有用的 k 个特征,并打印这些列的名字。
    """
    model = SelectKBest(chi2, k=k)  # 定义一个选择 k 个最佳特征的函数
    X_new = model.fit_transform(feature_data, label_data)  # 用这个函数选择 k 个最佳特征
    print('x_new', X_new)
    scores = model.scores_  # scores 即每一列与结果的相关性

    # 按重要性排序,选出最重要的 k 个
    indices = np.argsort(scores)[::-1]  # [::-1] 表示反转一个列表或者矩阵

    if column:  # 如果需要打印选中的列
        k_best_features = [column[i + 1] for i in indices[0:k].tolist()]  # 选中这些列并打印
        print('k best features are: ', k_best_features)
    return X_new, indices[0:k]  # 返回选中列的特征和它们的下标

解析

  • 功能:
    1. 从给定的特征数据中选择与标签数据相关性最高的 k 个特征。
    2. 这是优化模型训练的一种方法,让模型只关注最重要的一些数据。
  • 步骤:
    1. 使用 SelectKBestchi2 统计方法来选择最佳特征。
    2. fit_transform 方法选择并转换特征数据,这是进行特征提取与数据转换的关键函数。
    3. 获取每个特征的相关性评分 (scores_),model.scores_SelectKBest对象的一个属性,包含了每个特征的得分,得分越高,表示该特征与目标变量的相关性越强。
    4. 使用 argsort 对评分进行排序,选出得分最高的 k 个特征,注意argsort排序得到的是相关性评分从小到大的行的下标的队列。
    5. 如果提供了列名 (column),则打印选中的特征名称。
    6. 返回选中的特征数据和它们的索引。
  • 注意:
    • chi2 适用于非负特征。
    • k 的默认值为 4,可以根据需要调整。

3. 自定义数据集类 covidDataset

class covidDataset(Dataset):
    def __init__(self, path, mode="train", feature_dim=5, all_feature=False):
        with open(path, 'r') as f:
            csv_data = list(csv.reader(f))
            column = csv_data[0]
            x = np.array(csv_data)[1:, 1:-1]  # 特征数据
            y = np.array(csv_data)[1:, -1]    # 标签数据

            if all_feature:
                col_indices = np.array([i for i in range(0, 93)])  # 选中所有特征列
            else:
                _, col_indices = get_feature_importance(x, y, feature_dim, column)  # 选择重要的特征列
            col_indices = col_indices.tolist()

            csv_data = np.array(csv_data[1:])[:, 1:].astype(float)  # 转换为浮点数

            if mode == 'train':
                indices = [i for i in range(len(csv_data)) if i % 5 != 0]  # 训练集: 选取 4/5 的数据
                self.y = torch.tensor(csv_data[indices, -1])
            elif mode == 'val':
                indices = [i for i in range(len(csv_data)) if i % 5 == 0]  # 验证集: 选取 1/5 的数据
                self.y = torch.tensor(csv_data[indices, -1])
            else:
                indices = [i for i in range(len(csv_data))]  # 测试集: 选取所有数据

            data = torch.tensor(csv_data[indices, :])
            self.data = data[:, col_indices]  # 选取重要的特征列
            self.mode = mode

            # 数据归一化(标准化)
            self.data = (self.data - self.data.mean(dim=0, keepdim=True)) / self.data.std(dim=0, keepdim=True)
            assert feature_dim == self.data.shape[1], "特征维度不匹配"

            print('Finished reading the {} set of COVID19 Dataset ({} samples found, each dim = {})'
                  .format(mode, len(self.data), feature_dim))

    def __getitem__(self, item):
        if self.mode == 'test':
            return self.data[item].float()
        else:
            return self.data[item].float(), self.y[item].float()

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

解析

  • 作用:对数据进行预处理,在模型训练中是相当重要的一步,很多时候不同任务的神经网络结构可能相似,而对于数据的处理则大有不同。
  • 继承: 继承自 torch.utils.data.Dataset,用于创建自定义数据集。
  • 构造函数 __init__:
    1. 读取数据:
      • 使用 csv.reader 读取 CSV 文件。
      • column 存储列名。
      • x 为特征数据,去除第一列(假设是 ID)和最后一列(标签)。
      • y 为标签数据。
    2. 特征选择:
      • 如果 all_featureTrue,则选取所有 93 个特征。
      • 否则,调用 get_feature_importance 函数选择最重要的 feature_dim 个特征。
    3. 数据预处理:
      • 将选定的特征列转换为浮点数。
      • 根据 mode 划分训练集、验证集和测试集:
        • 训练集: 选择非 5 的倍数索引的数据(即 4/5 的数据)。
        • 验证集: 选择 5 的倍数索引的数据(即 1/5 的数据)。
        • 测试集: 选择所有数据。
      • 将特征和标签转换为 PyTorch 的 tensor 类型。
    4. 数据归一化:
      • 对每一列特征进行标准化,使其均值为 0,标准差为 1。
      • 如果不进行归一化,则数据的大小参差不齐,影响神经网络的判断。
      • self.data = (self.data - self.data.mean(dim=0, keepdim=True)) / self.data.std(dim=0, keepdim=True) 这个运算能够运行,原因是self.data是张量,而PyTorch 张量支持广播机制,即使 self.data 的形状是 (n_samples, n_features),而 meanstd 的形状是 (1, n_features),PyTorch 会自动将 meanstd 广播到与 self.data 相同的形状,然后进行逐元素运算。
    5. 验证特征维度
      • 使用 assert 确保选择的特征维度与 feature_dim 一致。
    6. 打印信息:
      • 输出数据集类型、样本数量和特征维度。
  • 方法 __getitem__:
    • 作用__getitem__ 是 PyTorch Dataset 类的必需方法。它定义了如何通过索引 item 从数据集中获取一个样本。每次调用 DataLoader 时,__getitem__ 会被自动调用,用于加载一个批次的数据。
    • 测试集: 仅返回特征数据。
    • 训练/验证集: 返回特征数据和对应的标签。
  • 方法 __len__:
    • 返回数据集的样本数量。
  • 注意:
    • 数据划分采用的是简单的按索引划分,确保训练集和验证集的比例约为 4:1。
    • 特征选择和归一化在数据加载时完成,保证一致性。

4. 神经网络模型类 myNet

class myNet(nn.Module):
    def __init__(self, inDim):
        super(myNet, self).__init__()
        self.fc1 = nn.Linear(inDim, 128)  # 全连接层,将输入维度映射到 128 维
        self.relu = nn.ReLU()             # 激活函数,增加非线性
        self.fc2 = nn.Linear(128, 1)      # 输出层,将 128 维映射到 1 维(回归任务)

    def forward(self, x):
        x = self.fc1(x)        # 输入通过第一层全连接
        x = self.relu(x)       # 激活函数
        x = self.fc2(x)        # 输出层
        if len(x.size()) > 1:
            return x.squeeze(1)  # 如果输出有多个维度,压缩第1维
        else:
            return x

解析

  • 继承: 继承自 torch.nn.Module,用于定义神经网络模型。
  • 构造函数 __init__:
    • fc1: 第一个全连接层,将输入特征维度 (inDim) 映射到 128 维。
    • relu: ReLU 激活函数,增加网络的非线性能力。
    • fc2: 输出层,将 128 维映射到 1 维,适用于回归任务(预测连续值)。
  • 方法 forward:
    1. 输入数据通过第一个全连接层 fc1
    2. 通过 ReLU 激活函数。
    3. 通过输出层 fc2
    4. 检查输出的维度:
      • 如果输出有多个维度(例如 batch_size x 1),则使用 squeeze 压缩第 1 维,变为 (batch_size,)。
      • 否则,直接返回输出。
  • 注意:
    • 此模型结构简单,适用于初学者理解。可以根据需要增加更多层或调整神经元数量以提升模型性能。

5. 训练与验证函数 train_val

def train_val(model, trainloader, valloader, optimizer, loss, epoch, device, save_):
    model = model.to(device)  # 将模型移动到指定设备(CPU 或 GPU)
    plt_train_loss = []
    plt_val_loss = []
    val_rel = []
    min_val_loss = 100000  # 初始化最小验证损失

    for i in range(epoch):  # 训练多个轮次
        start_time = time.time()
        model.train()  # 设置模型为训练模式
        train_loss = 0.0
        val_loss = 0.0

        # 训练阶段
        for data in trainloader:
            optimizer.zero_grad()  # 清空梯度
            x, target = data[0].to(device), data[1].to(device)  # 将数据移动到设备
            pred = model(x)  # 前向传播
            bat_loss = loss(pred, target)  # 计算损失
            bat_loss.backward()  # 反向传播
            optimizer.step()  # 更新参数
            train_loss += bat_loss.detach().cpu().item()  # 累加训练损失

        # 记录平均训练损失
        plt_train_loss.append(train_loss / len(trainloader.dataset))

        # 验证阶段
        model.eval()  # 设置模型为评估模式
        with torch.no_grad():  # 关闭梯度计算
            for data in valloader:
                val_x, val_target = data[0].to(device), data[1].to(device)
                val_pred = model(val_x)
                val_bat_loss = loss(val_pred, val_target)
                val_loss += val_bat_loss.detach().cpu().item()
                val_rel.append(val_pred)

        # 保存最优模型
        if val_loss < min_val_loss:
            torch.save(model, save_)  # 保存模型
            min_val_loss = val_loss  # 更新最小验证损失

        # 记录平均验证损失
        plt_val_loss.append(val_loss / len(valloader.dataset))

        # 打印训练和验证损失
        print('[%03d/%03d] %2.2f sec(s) TrainLoss : %.6f | valLoss: %.6f' % (
            i, epoch, 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()

解析

  • 功能: 负责模型的训练和验证过程,并记录和可视化损失。
  • 参数:
    • model: 神经网络模型。
    • trainloader: 训练数据的 DataLoader。
    • valloader: 验证数据的 DataLoader。
    • optimizer: 优化器,用于更新模型参数。
    • loss: 损失函数。
    • epoch: 训练的总轮次。
    • device: 设备类型(CPU 或 GPU)。
    • save_: 模型保存路径。
  • 步骤:
    1. 初始化:
      • 将模型移动到指定设备。
      • 初始化用于记录训练和验证损失的列表。
      • 设置一个初始的最小验证损失值,用于保存最优模型。
    2. 训练循环(外层循环,按轮次):
      • 记录开始时间。
      • 将模型设置为训练模式 (model.train()),启用 Dropout 等层的训练行为。
      • 初始化训练和验证损失累加变量。
      1. 训练阶段(内层循环,按批次):
        • 清空优化器的梯度缓存 (optimizer.zero_grad())。
        • 获取输入数据和标签,并移动到设备。
        • 前向传播得到预测值。
        • 计算损失。
        • 反向传播计算梯度。
        • 优化器更新模型参数。
        • 累加训练损失。
      2. 记录训练损失:
        • 计算并记录平均训练损失(总损失除以训练集样本数)。
      3. 验证阶段:
        • 将模型设置为评估模式 (model.eval()),禁用 Dropout 等层的训练行为。
        • 使用 torch.no_grad() 禁用梯度计算,节省内存和计算资源。
        • 遍历验证数据集,计算验证损失并累加。
        • 累加预测结果到 val_rel(虽然在代码中未被使用)。
      4. 保存最优模型:
        • 如果当前验证损失小于之前的最小验证损失,保存模型并更新最小验证损失。
      5. 记录验证损失:
        • 计算并记录平均验证损失(总损失除以验证集样本数)。
      6. 打印训练进度:
        • 输出当前轮次、总轮次、训练时间、训练损失和验证损失。
    3. 绘制损失曲线:
      • 使用 matplotlib 绘制训练和验证损失随轮次变化的曲线,帮助可视化训练过程。
  • 注意:
    • train_lossval_loss 的计算方式需要确保正确,避免由于批次大小不同导致的损失不一致。
    • val_rel 变量在函数中被记录但未被使用,可能需要进一步处理或删除。

6. 评估函数 evaluate

def evaluate(model_path, testset, rel_path, device):
    model = torch.load(model_path).to(device)  # 加载并移动模型到指定设备
    testloader = DataLoader(testset, batch_size=1, shuffle=False)  # 测试集 DataLoader
    val_rel = []
    model.eval()  # 设置模型为评估模式

    with torch.no_grad():  # 禁用梯度计算
        for data in testloader:
            x = data.to(device)
            pred = model(x)
            val_rel.append(pred.item())  # 存储预测结果

    print(val_rel)  # 打印所有预测结果

    # 将预测结果保存到 CSV 文件
    with open(rel_path, 'w') as f:
        csv_writer = csv.writer(f)
        csv_writer.writerow(['id', 'tested_positive'])  # 写入表头
        for i in range(len(testset)):
            csv_writer.writerow([str(i), str(val_rel[i])])
    print("rel已经保存到" + rel_path)

解析

  • 功能: 使用训练好的模型对测试集进行预测,并将结果保存到 CSV 文件中。
  • 参数:
    • model_path: 已保存的模型文件路径。
    • testset: 测试数据集(covidDataset 实例)。
    • rel_path: 结果保存的 CSV 文件路径。
    • device: 设备类型(CPU 或 GPU)。
  • 步骤:
    1. 加载模型:
      • 使用 torch.load 加载保存的模型,并将其移动到指定设备。
    2. 准备测试数据加载器:
      • 创建一个 DataLoader,批次大小为 1,且不进行数据洗牌(shuffle=False),保证预测结果的顺序与数据集一致。
    3. 预测阶段:
      • 将模型设置为评估模式 (model.eval()),禁用 Dropout 等层的训练行为。
      • 使用 torch.no_grad() 禁用梯度计算,提高预测效率。
      • 遍历测试数据集,获取每个样本的预测值,并存储在 val_rel 列表中。
    4. 输出预测结果:
      • 打印所有预测结果。
    5. 保存预测结果到 CSV:
      • 创建并打开目标 CSV 文件。
      • 写入表头 ['id', 'tested_positive']
      • 遍历预测结果,将每个预测值与其对应的 ID(索引)一起写入 CSV 文件。
      • 打印保存成功的提示信息。
  • 注意:
    • 预测结果是回归任务的连续值,需确保保存格式正确。
    • id 对应于测试集中的样本索引,确保与实际数据匹配。

7. 主程序流程

all_col = False  # 是否使用所有的列
device = 'cuda' if torch.cuda.is_available() else 'cpu'  # 选择使用 CPU 还是 GPU
print(device)

train_path = 'covid.train.csv'  # 训练数据路径
test_path = 'covid.test.csv'    # 测试数据路径
file = pd.read_csv(train_path)
file.head()  # 用 pandas 查看数据结构(仅用于调试,未赋值)

if all_col == True:
    feature_dim = 93
else:
    feature_dim = 6  # 是否使用所有的列

# 创建数据集实例
trainset = covidDataset(train_path, 'train', feature_dim=feature_dim, all_feature=all_col)
valset = covidDataset(train_path, 'val', feature_dim=feature_dim, all_feature=all_col)
testset = covidDataset(test_path, 'test', feature_dim=feature_dim, all_feature=all_col)

# 定义损失函数(此处使用均方误差损失)
loss = nn.MSELoss()

# 训练配置
config = {
    'n_epochs': 50,                 # 最大轮次
    'batch_size': 32,               # 每个批次的大小
    'optimizer': 'SGD',             # 优化算法
    'optim_hparas': {               # 优化器的超参数
        'lr': 0.0001,                # 学习率
        'momentum': 0.9              # 动量
    },
    'early_stop': 200,              # 早停策略(未使用)
    'save_path': 'model_save/model.pth',  # 模型保存路径
}

# 实例化模型
model = myNet(feature_dim).to(device)

# 定义优化器
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)

# 创建数据加载器
trainloader = DataLoader(trainset, batch_size=config['batch_size'], shuffle=True)
valloader = DataLoader(valset, batch_size=config['batch_size'], shuffle=True)

# 开始训练与验证
train_val(model, trainloader, valloader, optimizer, loss, config['n_epochs'], device, save_=config['save_path'])

# 评估并保存预测结果
evaluate(config['save_path'], testset, 'pred.csv', device)

解析

  • 变量设置:
    • all_col: 是否使用所有特征列,False 表示选择部分重要特征。
    • device: 检查是否有可用的 GPU,优先使用 GPU 加速,否则使用 CPU。
  • 数据路径:
    • train_path: 训练数据文件路径。
    • test_path: 测试数据文件路径。
  • 查看数据:
    • 使用 pandas 读取训练数据并查看前几行(file.head()),帮助理解数据结构。注意:此行代码未赋值或使用,实际运行时可能不显示输出。
  • 特征维度:
    • 如果 all_colTrue,则 feature_dim 为 93。
    • 否则,设置 feature_dim 为 6(根据特征选择函数选择的特征数)。
  • 创建数据集实例:
    • trainset: 训练集。
    • valset: 验证集。
    • testset: 测试集。
    • 数据集的特征选择和预处理在 covidDataset 类中完成。
  • 损失函数:
    • 使用均方误差损失 (nn.MSELoss),适用于回归任务。
  • 训练配置 (config 字典):
    • n_epochs: 训练的总轮次,设置为 50。
    • batch_size: 每个批次的样本数量,设置为 32。
    • optimizer: 优化算法,设置为 SGD。
    • optim_hparas: 优化器的超参数,包括学习率和动量。
    • early_stop: 早停策略的参数(在当前代码中未使用)。
    • save_path: 模型保存路径。
  • 实例化模型:
    • 创建 myNet 类的实例,输入维度为 feature_dim,并将模型移动到指定设备。
  • 定义优化器:
    • 使用随机梯度下降(SGD)优化器,学习率为 0.001,动量为 0.9。
    • 注意config['optim_hparas'] 中定义的学习率 (0.0001) 未被使用,实际使用的是 0.001
  • 创建数据加载器:
    • trainloader: 训练数据的 DataLoader,启用数据洗牌 (shuffle=True)。
    • valloader: 验证数据的 DataLoader,启用数据洗牌 (shuffle=True)。
    • 注意:通常验证集不需要洗牌 (shuffle=False),以保持验证结果的一致性。
  • 训练与验证:
    • 调用 train_val 函数,传入模型、训练加载器、验证加载器、优化器、损失函数、轮次、设备和模型保存路径。
  • 评估与保存预测结果:
    • 调用 evaluate 函数,传入模型保存路径、测试数据集、结果保存路径和设备类型。
  • 注意:
    • early_stop 参数在当前代码中未被实现,可以考虑在 train_val 函数中添加早停机制,以防止过拟合。
    • 优化器的超参数定义与实际使用不一致,建议统一设置。

优化模型训练过程的常用策略

【1】 正则化

深度学习中的正则化

正则化(Regularization)是深度学习中一种防止模型过拟合的技术。过拟合是指模型在训练集上表现很好,但在验证集或测试集上的表现较差,这通常是因为模型过于复杂,过度拟合了训练数据中的噪声或细节,导致泛化能力下降。

正则化通过在模型训练过程中引入某些约束或修改优化目标,减少模型对训练数据的过度依赖,从而提高模型的泛化能力。


1. 为什么需要正则化?

在深度学习中,模型通常具有大量的参数(例如深度神经网络中的权重和偏置),这使得模型非常灵活,能够拟合复杂的数据分布。然而,这种灵活性也会导致模型容易记住训练数据中的噪声或异常值,而不是学习到数据的真实分布,最终导致模型在新数据上的表现较差。

正则化的目标是限制模型的复杂度,使模型能够更好地泛化到未见过的数据。


2. 常见的正则化方法

以下是深度学习中常见的正则化方法:

(1) L1 和 L2 正则化

L1 和 L2 正则化通过在损失函数中添加权重参数的约束,限制模型参数的大小,从而防止模型过于复杂。

  • L2 正则化(权重衰减,Weight Decay)
    在损失函数中添加权重的平方和作为惩罚项:
    L total = L original + λ ∑ i w i 2 L_{\text{total}} = L_{\text{original}} + \lambda \sum_{i} w_i^2 Ltotal=Loriginal+λiwi2
    其中, L original L_{\text{original}} Loriginal 是原始损失函数, w i w_i wi 是模型的权重, λ \lambda λ 是正则化强度的超参数。

    L2 正则化倾向于让权重变得更小、更平滑,从而减少过拟合。

  • L1 正则化
    在损失函数中添加权重的绝对值和作为惩罚项:
    L total = L original + λ ∑ i ∣ w i ∣ L_{\text{total}} = L_{\text{original}} + \lambda \sum_{i} |w_i| Ltotal=Loriginal+λiwi
    L1 正则化会导致一些权重变为零,从而实现特征选择(稀疏性)。

(2) Dropout

Dropout 是一种随机失活的方法,用于防止神经网络过拟合。在每次训练迭代中,随机将一部分神经元的输出置为零,模拟多个子网络的训练过程。

  • 在训练过程中,Dropout 会随机以一定的概率(如 p = 0.5 p=0.5 p=0.5)将一些神经元的输出置为零。
  • 在测试过程中,所有神经元都会被激活,但其输出会乘以保留概率 p p p,以保证输出的期望值一致。

这种方法可以减少神经元之间的共适应性,从而提高模型的泛化能力。

(3) 数据增强

数据增强通过对训练数据进行随机变换(如图像的旋转、翻转、裁剪等),生成更多样化的训练样本,从而降低模型对特定数据模式的依赖。

  • 例如,在图像分类任务中,可以对图像进行随机旋转、缩放、翻转等操作。
  • 数据增强可以被视为一种隐式正则化方法,因为它通过扩展数据集间接限制了模型的复杂度。
(4) 提前停止(Early Stopping)

提前停止是一种简单但有效的正则化方法。在训练过程中,监控验证集的损失或准确率,如果验证集的性能不再提升,则提前停止训练。

  • 这样可以防止模型在训练集上过度拟合,同时提高验证集和测试集上的性能。
(5) Batch Normalization

Batch Normalization(批归一化)是通过对每一层的激活值进行归一化,防止激活值过大或过小,从而使训练过程更加稳定。

  • 虽然 Batch Normalization 的主要目的是加速训练,但它也具有一定的正则化效果。
(6) 正则化激活函数

正则化激活函数(如 ReLU、Leaky ReLU 等)通过限制激活值的范围,间接降低模型的复杂度,从而减少过拟合。


3. 正则化的作用

  • 防止过拟合:通过限制模型的复杂度,减少模型对训练数据的过度依赖。
  • 提高泛化能力:使模型在未见过的数据上也能表现良好。
  • 增强模型鲁棒性:减少模型对噪声和异常值的敏感性。

4. 如何选择正则化方法?

正则化方法的选择依赖于具体任务和数据特点:

  • 如果模型参数过多且容易过拟合,L2 正则化是一个常见的选择。
  • 如果数据较少,可以使用数据增强来扩展数据集。
  • 如果训练时间较长且验证集性能开始下降,可以使用提前停止。
  • 如果神经网络较深,Dropout 是一种有效的方法。
  • 如果需要加速训练并提高稳定性,可以使用 Batch Normalization。

【2】 特征选择——SelectKBest

在深度学习或机器学习中,SelectKBest 是一种特征选择方法,通常用于从高维特征中选择最重要的特征,以提高模型的性能或减少计算成本。它属于特征选择(Feature Selection)的一部分,特征选择的目的是减少输入数据的维度,同时保留对模型预测最有用的信息。


1. 什么是 SelectKBest

SelectKBest 是 Scikit-learn 库中提供的一种特征选择工具。它按照统计指标对特征进行评分,然后选择得分最高的 k k k 个特征。其核心思想是基于某种统计方法评估每个特征与目标变量之间的相关性,从而挑选出最相关的特征。

公式化地说,给定一个数据集 X X X 和目标变量 y y y

  • X X X 是特征矩阵,形状为 ( n , m ) (n, m) (n,m),其中 n n n 是样本数, m m m 是特征数。
  • SelectKBest 会根据每个特征 x i x_i xi 与目标变量 y y y 的相关性得分,选择得分最高的 k k k 个特征。

2. SelectKBest 的工作流程

  1. 计算每个特征的得分

    • 根据用户指定的评分函数(如卡方检验、F检验、互信息等),对每个特征进行打分,衡量其与目标变量的相关性。
  2. 排序

    • 按得分从高到低对特征进行排序。
  3. 选择前 k k k 个特征

    • 根据排序结果,选择得分最高的 k k k 个特征,丢弃其他特征。

3. 常用的评分函数

SelectKBest 支持多种评分函数,不同的评分函数适用于不同类型的数据。以下是一些常用的评分函数及其适用场景:

(1) 卡方检验(Chi-Square, chi2
  • 适用场景:用于分类任务中的离散型特征。
  • 评估方式:计算每个特征与目标变量之间的卡方统计量,衡量特征与目标变量的独立性。
  • 注意:特征值必须为非负数,因此通常需要对数据进行预处理(如归一化或标准化)。
(2) F 检验(F-Test, f_classiff_regression
  • 适用场景:
    • f_classif:用于分类任务中的连续型特征。
    • f_regression:用于回归任务中的连续型特征。
  • 评估方式:计算每个特征与目标变量之间的方差分析(ANOVA)F 值,衡量特征对目标变量的区分能力。
(3) 互信息(Mutual Information, mutual_info_classifmutual_info_regression
  • 适用场景:
    • mutual_info_classif:用于分类任务。
    • mutual_info_regression:用于回归任务。
  • 评估方式:计算每个特征与目标变量之间的互信息,衡量它们之间的依赖关系(非线性相关性也能捕获)。

4. SelectKBest 的实现

在 Scikit-learn 中,SelectKBest 的使用非常简单。以下是一个具体的实现示例:

示例 1:用于分类任务(卡方检验)
from sklearn.feature_selection import SelectKBest, chi2
from sklearn.datasets import load_iris

# 加载数据集
data = load_iris()
X, y = data.data, data.target

# 使用卡方检验选择前 2 个最重要的特征
selector = SelectKBest(score_func=chi2, k=2)
X_new = selector.fit_transform(X, y)

print("原始特征维度:", X.shape)
print("选择后的特征维度:", X_new.shape)
示例 2:用于回归任务(F 检验)
from sklearn.feature_selection import SelectKBest, f_regression
from sklearn.datasets import make_regression

# 生成回归数据
X, y = make_regression(n_samples=100, n_features=10, noise=0.1)

# 使用 F 检验选择前 5 个最重要的特征
selector = SelectKBest(score_func=f_regression, k=5)
X_new = selector.fit_transform(X, y)

print("原始特征维度:", X.shape)
print("选择后的特征维度:", X_new.shape)

5. SelectKBest 的优点和局限性

优点:
  1. 简单易用:只需指定评分函数和 k k k 值,就可以快速进行特征选择。
  2. 提高模型性能:通过减少无关或冗余特征,可以提高模型的训练速度和预测性能。
  3. 减少过拟合:通过去除不重要的特征,可以降低模型的复杂度,从而减少过拟合的风险。
局限性:
  1. 单变量方法SelectKBest 是一种单变量特征选择方法,它独立地评估每个特征与目标变量的相关性,而不考虑特征之间的交互作用。
  2. 依赖评分函数:选择的特征质量高度依赖于所使用的评分函数。如果评分函数不适合数据分布,可能会导致错误的特征选择。
  3. 固定特征数量:需要手动指定 k k k 的值,这可能需要通过实验或交叉验证来确定。

6. SelectKBest 在深度学习中的应用

在深度学习中,SelectKBest 通常用于数据预处理阶段,尤其是在处理高维稀疏数据(如文本特征或基因数据)时。通过先用 SelectKBest 降维,可以减少输入特征的数量,从而加速深度学习模型的训练过程。

例如:

  • 在自然语言处理(NLP)任务中,SelectKBest 可以用于从词袋模型(Bag-of-Words)或 TF-IDF 特征中选择最重要的词语。
  • 在生物信息学中,SelectKBest 可以用于从基因表达数据中选择与疾病分类最相关的基因。

【3】 主成分分析(PCA)

主成分分析(Principal Component Analysis,简称 PCA)是一种广泛应用于数据降维和特征提取的线性技术。它在深度学习和机器学习中经常被用来减少数据的维度,同时保留尽可能多的有用信息,从而提高模型的效率。以下是关于 PCA 的详细介绍:


1. 什么是 PCA?

主成分分析是一种线性降维方法,旨在通过寻找数据的主要变化方向,将高维数据投影到一个低维子空间,同时尽可能保留原始数据的主要信息(即数据的方差)。

核心思想:
  • 数据的主要信息通常体现在数据的方差中。
  • PCA 通过找到数据的主成分(Principal Components),即数据中方差最大的方向,将数据投影到这些主成分上,从而实现降维。
数学目标:

给定一个数据集 X ∈ R n × m X \in \mathbb{R}^{n \times m} XRn×m n n n 是样本数, m m m 是特征数),PCA 的目标是找到一组正交向量(主成分),将数据从 m m m 维投影到 k k k 维( k < m k < m k<m),使得投影后的数据保留尽可能多的方差。


2. PCA 的数学原理

PCA 的数学原理可以分为以下几个步骤:

(1) 数据标准化

为了确保每个特征对结果的影响是均等的,PCA 通常要求对数据进行标准化处理,使得每个特征的均值为 0 0 0,方差为 1 1 1。公式为:
X 标准化 = X − μ σ X_{\text{标准化}} = \frac{X - \mu}{\sigma} X标准化=σXμ
其中, μ \mu μ 是特征的均值, σ \sigma σ 是特征的标准差。

(2) 计算协方差矩阵

协方差矩阵衡量了不同特征之间的线性关系。对于标准化后的数据 X ∈ R n × m X \in \mathbb{R}^{n \times m} XRn×m,协方差矩阵 C C C 的计算公式是:
C = 1 n − 1 X T X C = \frac{1}{n-1} X^T X C=n11XTX
其中, C ∈ R m × m C \in \mathbb{R}^{m \times m} CRm×m

(3) 特征值分解

对协方差矩阵 C C C 进行特征值分解,得到特征值和特征向量:
C v i = λ i v i C v_i = \lambda_i v_i Cvi=λivi

  • λ i \lambda_i λi 是协方差矩阵的特征值,表示数据在对应方向上的方差大小。
  • v i v_i vi 是协方差矩阵的特征向量,表示数据的主成分方向。
(4) 按特征值排序

将特征值从大到小排序,并选择前 k k k 个特征值对应的特征向量,构成投影矩阵 W ∈ R m × k W \in \mathbb{R}^{m \times k} WRm×k

(5) 数据投影

将原始数据 X X X 投影到低维空间:
X 降维 = X W X_{\text{降维}} = X W X降维=XW
其中, X 降维 ∈ R n × k X_{\text{降维}} \in \mathbb{R}^{n \times k} X降维Rn×k 是降维后的数据。


3. PCA 的优点和局限性

优点:
  1. 降维
    • PCA 能有效降低数据维度,减少计算开销。
  2. 去冗余
    • PCA 可以去除特征之间的多重共线性,保留独立的主成分。
  3. 可视化
    • 在高维数据中,PCA 常用于将数据降维到 2D 或 3D 空间,以便进行可视化分析。
  4. 数据压缩
    • PCA 可以用于数据压缩,同时尽量保留数据的主要信息。
局限性:
  1. 线性假设
    • PCA 假设数据的主要结构是线性的,无法处理非线性数据。
  2. 信息损失
    • 降维过程中可能会丢失一些重要信息,特别是在降维到非常低的维度时。
  3. 特征可解释性差
    • PCA 的主成分是线性组合,通常很难解释它们的具体含义。
  4. 对数据分布敏感
    • PCA 对数据的标准化和分布非常敏感,如果数据未标准化或分布不均匀,可能导致结果偏差。

4. PCA 在深度学习中的应用

在深度学习中,PCA 通常用于数据预处理和特征降维,以下是一些典型的应用场景:

(1) 数据降维
  • 在处理高维数据(如图像、文本或基因数据)时,PCA 可以减少数据的维度,从而加速模型的训练过程。
  • 例如,在处理图像数据时,可以使用 PCA 将每张图片的像素数据降维为主成分表示。
(2) 噪声过滤
  • PCA 可以用来去除数据中的噪声。例如,将数据投影到前几个主成分上,可以去除小特征值对应的高频噪声。
(3) 可视化
  • 对于高维数据,PCA 可以将数据降维到 2D 或 3D 空间,从而便于可视化分析和理解数据分布。
(4) 特征压缩
  • 在深度学习中,PCA 可以用于减少模型输入特征的数量,从而减小模型的复杂度。

5. PCA 的实现

以下是使用 Python 和 Scikit-learn 实现 PCA 的示例代码:

示例 1:基本的 PCA 实现
import numpy as np
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris

# 加载数据集
data = load_iris()
X = data.data

# 初始化 PCA,保留 2 个主成分
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)

print("原始数据维度:", X.shape)
print("降维后数据维度:", X_pca.shape)
示例 2:查看主成分的方差解释率
# 查看主成分的方差解释率
explained_variance_ratio = pca.explained_variance_ratio_
print("每个主成分的方差解释率:", explained_variance_ratio)
print("总方差解释率:", np.sum(explained_variance_ratio))
示例 3:手动实现 PCA
# 手动实现 PCA
# 数据标准化
X_mean = np.mean(X, axis=0)
X_std = X - X_mean

# 计算协方差矩阵
cov_matrix = np.cov(X_std, rowvar=False)

# 计算特征值和特征向量
eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix)

# 按特征值排序
sorted_indices = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[sorted_indices]
eigenvectors = eigenvectors[:, sorted_indices]

# 选择前 2 个主成分
W = eigenvectors[:, :2]

# 数据投影
X_pca_manual = np.dot(X_std, W)

print("手动实现的降维结果:", X_pca_manual[:5])

6. PCA 的扩展

(1) 核 PCA (Kernel PCA)
  • 核 PCA 使用核方法将数据映射到高维空间,再在高维空间中进行 PCA。
  • 它可以处理非线性数据。
(2) 稀疏 PCA (Sparse PCA)
  • 稀疏 PCA 在降维的同时,强制主成分具有稀疏性,从而提高主成分的可解释性。
(3) 增量 PCA (Incremental PCA)
  • 增量 PCA 适用于大规模数据集,通过批量处理数据来进行降维。

【4】归一化

在深度学习中,归一化(Normalization)是一种重要的预处理和优化技术,目的是对数据或中间特征进行变换,使其满足某些特定的统计特性,从而加速训练过程、提高模型的性能和稳定性。归一化可以应用于输入数据或神经网络的中间层输出,主要目的是减少梯度消失或梯度爆炸问题,增强模型的收敛性。

以下是深度学习中常见的归一化操作及其详细介绍:


1. 输入数据的归一化

输入数据的归一化是深度学习中最基本的归一化操作,目的是将数据调整到某个固定范围或分布,便于模型处理。

a. Min-Max 归一化

将数据缩放到固定范围(通常是 [ 0 , 1 ] [0, 1] [0,1] [ − 1 , 1 ] [-1, 1] [1,1])。公式如下:
x ′ = x − min ( x ) max ( x ) − min ( x ) x' = \frac{x - \text{min}(x)}{\text{max}(x) - \text{min}(x)} x=max(x)min(x)xmin(x)
其中 x x x 是原始数据, min ( x ) \text{min}(x) min(x) max ( x ) \text{max}(x) max(x) 分别是数据的最小值和最大值。

  • 优点:简单直观,适合数据分布已知的情况。
  • 缺点:对异常值(outliers)非常敏感。
b. Z-Score 归一化

将数据调整为均值为 0 0 0,标准差为 1 1 1 的正态分布。公式如下:
x ′ = x − μ σ x' = \frac{x - \mu}{\sigma} x=σxμ
其中 μ \mu μ 是数据的均值, σ \sigma σ 是数据的标准差。

  • 优点:对不同量纲的数据统一处理,适合数据分布未知的情况。
  • 缺点:如果数据分布严重偏态,可能仍会有问题。
c. 对数变换

对具有长尾分布的数据,使用对数变换压缩数据范围:
x ′ = log ⁡ ( x + 1 ) x' = \log(x + 1) x=log(x+1)

  • 优点:对数据分布不均匀(如指数分布)有较好的效果。
  • 缺点:仅适用于非负数据。

2. 神经网络中的归一化方法

在深度学习中,除了对输入数据进行归一化,还可以对神经网络的中间层输出进行归一化,以加速训练和提高模型的性能。

a. Batch Normalization(BN)

Batch Normalization 是最常用的归一化方法之一,主要用于对每一层的激活值进行归一化。其核心思想是:在每个小批量(batch)中,将每个神经元的输出调整为均值为 0 0 0,标准差为 1 1 1,然后再通过可学习的参数进行线性变换。

公式如下:

  1. 计算均值和方差:
    μ B = 1 m ∑ i = 1 m x i , σ B 2 = 1 m ∑ i = 1 m ( x i − μ B ) 2 \mu_B = \frac{1}{m} \sum_{i=1}^m x_i, \quad \sigma_B^2 = \frac{1}{m} \sum_{i=1}^m (x_i - \mu_B)^2 μB=m1i=1mxi,σB2=m1i=1m(xiμB)2
    其中 m m m 是 batch 的大小。
  2. 标准化:
    x ^ i = x i − μ B σ B 2 + ϵ \hat{x}_i = \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}} x^i=σB2+ϵ xiμB
    其中 ϵ \epsilon ϵ 是一个小值,用于避免分母为零。
  3. 缩放和平移(可学习参数):
    y i = γ x ^ i + β y_i = \gamma \hat{x}_i + \beta yi=γx^i+β
    其中 γ \gamma γ β \beta β 是可学习的参数。
  • 优点
    • 加速收敛。
    • 减少对权重初始化的敏感性。
    • 在一定程度上起到正则化作用。
  • 缺点
    • 对小批量(batch size 很小)较为敏感。
    • 在某些任务中(如序列模型)可能不适用。

b. Layer Normalization(LN)

Layer Normalization 是一种不依赖 batch 的归一化方法,主要用于序列模型(如 RNN、Transformer)。它对每个样本的所有神经元的激活值进行归一化。

公式如下:

  1. 计算均值和方差:
    μ = 1 H ∑ i = 1 H x i , σ 2 = 1 H ∑ i = 1 H ( x i − μ ) 2 \mu = \frac{1}{H} \sum_{i=1}^H x_i, \quad \sigma^2 = \frac{1}{H} \sum_{i=1}^H (x_i - \mu)^2 μ=H1i=1Hxi,σ2=H1i=1H(xiμ)2
    其中 H H H 是当前层的神经元数。
  2. 标准化:
    x ^ i = x i − μ σ 2 + ϵ \hat{x}_i = \frac{x_i - \mu}{\sqrt{\sigma^2 + \epsilon}} x^i=σ2+ϵ xiμ
  3. 缩放和平移(可学习参数):
    y i = γ x ^ i + β y_i = \gamma \hat{x}_i + \beta yi=γx^i+β
  • 优点
    • 不依赖 batch size,适合小批量或序列模型。
  • 缺点
    • 在某些任务中可能不如 Batch Normalization 效果好。

c. Instance Normalization(IN)

Instance Normalization 是一种专门用于图像生成任务(如风格迁移)的归一化方法。它对每个样本的每个通道单独归一化。

公式与 Layer Normalization 类似,但归一化范围是每个样本的每个通道。

  • 优点:适合风格迁移等任务。
  • 缺点:对某些任务的泛化能力较差。

d. Group Normalization(GN)

Group Normalization 是一种结合了 Batch Normalization 和 Layer Normalization 的方法。它将特征划分为多个组,然后对每一组进行归一化。

公式类似于 Layer Normalization,但归一化范围是每个组。

  • 优点:适合小批量训练。
  • 缺点:需要选择合适的组数。

e. Weight Normalization

Weight Normalization 是一种对网络权重进行归一化的方法,目的是使权重的范数固定,以便更稳定地训练。

公式如下:
w = g ∥ v ∥ v w = \frac{g}{\|v\|} v w=vgv
其中 g g g 是可学习的标量, v v v 是原始权重向量。

  • 优点:提高训练稳定性。
  • 缺点:不如 Batch Normalization 常用。

3. 归一化的作用

  • 加速训练:归一化可以使梯度下降更快、更稳定。
  • 防止梯度爆炸或消失:归一化可以避免激活值过大或过小,导致梯度异常。
  • 减少对初始化的敏感性:归一化使得模型对权重初始化的依赖性降低。
  • 正则化效果:某些归一化方法(如 Batch Normalization)可以在一定程度上防止过拟合。

4. 归一化的选择

  • 对于大多数任务,优先尝试 Batch Normalization
  • 对于序列任务或小批量任务,使用 Layer NormalizationGroup Normalization
  • 对于特定任务(如风格迁移),可以尝试 Instance Normalization

归一化方法的选择取决于任务的特性和模型的架构。通过实验和调参,可以找到最适合的归一化策略。

你可能感兴趣的:(深度学习入门笔记,深度学习,人工智能)