[深度学习]神经网络-回归项目

简单神经网络项目-回归项目

文章目录

深度学习的基本流程

所谓深度学习,用我们熟悉的方式来看,就是寻找函数关系。

在进行深度学习时,我们已知实际输入数据X,以及实际输出数据Y,通过一系列函数的组合,逐步优化出一个合适的映射函数 f(x),使得输入数据通过这个复合函数得到与目标输出尽可能接近的结果。

  1. 处理数据

    关于数据数据过程,一般输入的文件地址,或者数据内容,输出是一个存储了数据X,Y的数据结构。

    • 数据处理往往是深度学习项目最麻烦,也是最重要的部分,好的数据处理能够提升性能。
  2. 模型定义

    根据数据事先设定函数模型,然后将X,通过设定的函数模型,得到输出预测值。

  3. 设定超参

    • 深度学习中的参数包含超参 ,也就是事先指定的,在训练过程中不可改变的参数,比如事先定义的模型就是一种超参,此外还有学习率、优化器、损失函数LOSS等;其中一个简单的LOSS函数如下:

    • 另一种参数是在训练过程中可以改变的参数,比如我们将模型定义为线性函数模型:

    y = w x + b y=wx+b y=wx+b

    模型本身是线性关系不可改变,但是其中的参数w,b在训练过程中是可以调整,以此得到满足要求的拟合数据的模型,所谓调整模型就是调整可该改变的参数

  4. 训练过程

  • 使用设定的生成的数据和定义的模型以及超参,计算输出数据Y的预测值,这个过程就是所谓的前向过程
  • 前向过程中,得到了预测值,通过预测值和实际数据Y的LOSS值,回传梯度,进行参数的调整也就是模型更新,这个过程就是所谓的回传过程

通过不断的前向和回传过程,获得我们需要的预测模型。

训练集,测试集和验证集

  • 在实际的项目或者比赛中,深度学习给出的数据分为两部分,一部分是用作训练模型的训练集 ,训练集中的数据既有输入数据X,也有输出数据Y,一部分是测试集,只给出输入数据X,通过训练得到的模型和测试集中的X,输出预测值Y。

但是仅仅使用训练集去得出模型是不够的 ,因为模型可以通过不断调整参数得到高度拟合训练集的模型,但是这样的模型往往只能对训练集有很好的拟合,一旦使用没有训练集中见过的数据,往往不会有很好的拟合性,也就是说模型往往具有局限性,因此为了得到更好的模型,还要对训练集进行划分。

  • 训练集 划分为更细致的训练集和验证集 ,使用训练集进行模型训练然后得到模型,再使用验证集测试得到模型对验证集的拟合性,选取和验证集拟合性更好的模型,使得模型具有更好的普适性。
  • **每次训练后都用验证集去验证模型,并记录每次训练后得到的模型,选取验证效果最好的模型作为训练结果。**验证集的数据只用于验证模型,模型不会根据验证集的数据改变。
  • 训练集的选取要随机,能够更加覆盖的更加均匀。

因为模型的训练往往不是和训练次数完全正相关或负相关的,因此要在得到的众多模型中,选取拟合性最好的模型。

对训练集所进行的操作:

使用训练集中X通过模型得到预测值,然后根据预测值和Y的LOSS值以及学习率等调整模型。

对验证集进行的操作:

使用验证集中X通过模型得到预测值,然后将预测值和Y进行效果对比。不进行模型训练和调整。

  • 对于回归项目,所谓的效果好坏就是预测值和实际Y的差值的大小。
  • 对于分类项目,所谓的效果好坏就是分类结果是否正确。

新冠病毒感染人数预测项目

项目来源

ML2021Spring-hw1 | Kaggle

项目介绍

说的是啊,这个美国,有40个州, 这四十个州呢 ,统计了连续三天的新冠阳性人数,和每天的一些社会特征,比如带口罩情况, 居家办公情况等等。现在有一群人比较坏,把第三天的数据遮住了,我们就要用前两天的情况以及第三 天的特征,来预测第三天的阳性人数。但幸好的是,我们还是有一些数据可以作为参考的,就是我们的训练集。

处理数据

要进行数据处理,首先要看看数据的形式,首先是训练集的数据:

第一行为州的名字,数据采用的是one_hot编码,即是哪个州的数据,就让那个州对应的数据为1。第一列为数据序号。

每一列表示一个属性,也就是训练时所用的数据X中的内容,由于是训练集,因此最后一列表示第三天阳性人数的数据是给出的,也即是训练时所用的标签Y。

在看看测试集的数据:

测试集的数据没有给出第三天的阳性人数,也就是只给出的训练时所用的数据X。

对于这个数据形式,在数据处理时需要注意的是第一行表示名称的数据和第一列数据序号,因为对训练没有用,不需要读入。

Dataset类

将数据处理的动作交给Dataset类,后续使用数据时,只需要使用Dataset对象获取即可。一般包含三个函数:init、getitem、len。

  • init函数的作用是初始化,输入文件地址等,输出数据X,和标签Y。
  • getitem函数的作用是取数据,输入下标,输出特定下标对应的值。
  • len函数的作用是得到数据长度。
随机梯度下降

在梯度下降过程中,采用全部数据的LOSS相加取平均的方法有着如下特点:

  • 使用全部数据来计算梯度,得到的梯度估计是非常精确的,因此每次更新都很稳定。
  • 每次迭代都需要遍历整个数据集,计算成本高,特别是在数据集很大的时候,训练过程可能非常缓慢,甚至无法在有限的时间内完成。
  • 由于每次更新都需要遍历整个数据集,训练过程中每次更新的步伐较慢,整体收敛速度较低,特别是在数据量较大时。

既然使用全部数据存在训练过程慢,收敛速度低的问题,那采用每次一个数据呢?

  • 每次只使用一个数据点来更新参数,因此计算量非常小,每次迭代的速度非常快。
  • 使用一个数据点来计算梯度,会导致每次更新的梯度估计非常不准确,因此参数更新会非常不稳定,收敛过程可能会震荡,难以控制。
  • 尽管每次迭代更新较快,但由于每次更新较为随机,最终的收敛可能需要更多的迭代次数才能达到全局最优。

为了避免以上出现的问题,采用随机梯度下降算法。

随机梯度下降(Stochastic Gradient Descent, SGD)与传统的梯度下降 (Gradient Descent, GD)不同,SGD在每次迭代中只使用一小部分 (mini-batch)样本来计算梯度,从而更新模型参数。因为随机梯度下降每次只使用一个样本的数据,因此称为一个batch ,一轮训练称为一个epoch ,一个epoch 包含多个batch

代码实现

由于处理数据交给了Dataset类,因此我们要在Dataset类中实现函数完成处理数据。

继承Dataset类

python 复制代码
class CovidDataset(Dataset):  #继承Dataset类
  • 继承Dataset类然后对函数进行定义。

实现init函数

python 复制代码
def __init__(self, file_path, mode="train"): #训练集,验证集,测试集的数据形式不同
	with open(file_path, "r") as f: #读方式打开文件
		ori_data = list(csv.reader(f))
        csv_data = np.array(ori_data[1:])[:, 1:].astype(float) #转为矩阵方便去掉无用部分,先去掉第一行后去掉第一列,将读入的字符型数据转为浮点型
        
    if mode == "train": #逢5取1
        indices = [i for i in range(len(csv_data)) if i % 5 != 0]
        data = torch.tensor(csv_data[indices, :-1])  # 得到X,神经网络数据要使用张量
        self.y = torch.tensor(csv_data[indices, -1]) #得到Y
    elif mode == "val":
        indices = [i for i in range(len(csv_data)) if i % 5 == 0]
        data = torch.tensor(csv_data[indices, :-1])  # 得到X,神经网络数据要使用张量
        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 - data.mean(dim=0, keepdim=True))/data.std(dim=0, keepdim=True) #归一化,消除量纲的影响,第一维为列向
		self.mode = mode#和读取X和Y的过程一样,不同模式下其他操作也有所不同
  • 逢5取1,每取5个数,四个放入训练集,一个放入验证集。
  • 训练集和验证集有数据X和标签Y,测试集只有数据X。

实现getitem函数

python 复制代码
def __getitem__(self, idx):
    if self.mode != "test":
        return self.data[idx].float(), self.y[idx].float()
    else:
        return self.data[idx].float() #转为32位减少训练消耗
  • 只有是测试模式下,也就是只有测试集获取数据时只获得数据X,训练集和验证集即获取数据X,也获得标签Y。

实现len函数

py 复制代码
def __len__(self):
    return len(self.data)
  • 返回数据X长度。

模型定义

Model类

将模型设定的交给Model类,后续让数据通过模型时,只需使用Model对象即可。一般包含两个函数:init、forward。

  • init函数的作用是初始化,设定模型,包括全连接的传递过程、激活函数等。
  • forward函数的作用是让数据通过模型得到预测值,也就是前向过程。

在init函数可以中设定如下的多层神经网络模型:

设定多层神经网络模型时,需要注意维度问题,当前层设定的输入维度一定要和上一层输出的维度相符。

代码实现

由于处理数据交给了Model类,因此我们要在Model类中实现函数完成处理数据。

继承Model类

python 复制代码
class MyModel(nn.Module):
  • 继承nn.Modiule类然后对函数进行定义。

实现init函数

在这个函数中定义深度学习使用的模型。

python 复制代码
def __init__(self, dim): #给出输入维度dim,输出维度为定值1
    super(MyModel, self).__init__()#修改模型nn.Module类初始化
    self.fc1 = nn.Linear(dim, 64) #全连接
    self.relu1 = nn.ReLU() #激活函数
    self.fc2 = nn.Linear(64, 1)
#深度神经网络模型定义完成
  • 参数dim为输入X的维度。
  • nn.Linear为一层全连接,第一个参数为输入维度,第二个参数为输出维度。
  • Relu为激活函数。

由此模型定义为经过两次全连接的神经网路。

实现forward函数

在这个函数中实现深度学习过程中前向的过程。

python 复制代码
def forward(self, x): #模型前向过程
    x = self.fc1(x)
    x = self.relu1(x)
    x = self.fc2(x)

    if len(x.size()) > 1:
        return x.squeeze(1) #让数据的维度和Y保持一致,以至于能够计算

    return x
  • 调用init中定义的模型函数,实现数据X经过神经网路的过程。
  • 模型计算出的预测值要用于和标签Y进行计算,得出LOSS值,因此如果预测值要保持和标签Y相同的维度。

设定超参

动量

假设参数值与损失函数的关系如下图:

使用梯度回传算法时,如果参数落入了图中从左向后看第一个低点附近时,有可能会发生参数在这附近震荡的情况,可是从图中可以看出让参数值持续向右走得到的模型更好,因此我们引入了动量,让参数值在向右走后,不会立刻停止,而是继续向右走一段再停止,让参数 有可能突破震荡而导致的模型效果不能更好的问题。

动量在参数更新时的作用如下:

  • 在梯度方向一致的区域,动量会加速参数更新。
  • 在梯度方向变化频繁的区域,动量可以平滑更新路径,避免震荡。

**动量可以简单的理解为惯性。**给梯度回传过程加入动量后,有可能获得更好的模型。

代码实现

通过字典设定超参数,方便传入训练函数,进行训练。

python 复制代码
config = {
    "lr": 0.001,#学习率
    "epochs": 20,#训练轮次
    "momentum": 0.9,#动量越大惯性越大
    "save_path": "model_save/best_model.pth" #训练模型保存路径,在验证集上效果好要保存
}
model = MyModel(dim=93).to(device)#设定神经网络模型
loss = nn.MSELoss()#定义损失函数
optimizer = optim.SGD(model.parameters(), lr=config["lr"], momentum=config["momentum"])#定义优化器,即定义了如何根据损失函数的梯度信息调整模型参数

训练过程

进行多轮次训练,每轮次训练都将数据分为多个批次,并且记录每轮次的LOSS,然后将本轮次训练出的模型使用验证集测试效果,若效果为当前多轮次中最好则记录下来,并且记录模型和验证集效果。

代码实现
python 复制代码
def train_val(model, train_loader, val_loader, device, epochs, optimizer, loss, save_path):
model = model.to(device)

plt_train_loss = [] #记录所有轮次的LOSS
plt_val_loss = []

min_val_loss = 99999999999 #记录最小LOSS

for epoch in range(epochs): #训练epochs轮次,开始训练,阅读大佬代码的重点
    train_loss = 0.0
    val_loss = 0.0#表示模型效果

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

    model.train() #模型调为训练模式,有时训练模式和测试模式的模型不同
    for batch_x, batch_y in train_loader:
        x, target = batch_x.to(device), batch_y.to(device)
        pred = model(x)
        train_bat_loss = loss(pred, target) #获取一批数据的LOSS
        train_bat_loss.backward() #梯度回传
        optimizer.step()#更新模型
        optimizer.zero_grad()
        train_loss += train_bat_loss.cpu().item() #将gpu上的张量数据放到cpu上取出数据计算,累加记录本轮LOSS
	#完成一轮次的多批次训练
    plt_train_loss.append(train_loss / train_loader.__len__()) #除以数据个数,得到每个轮次的LOSS

    model.eval()#调为验证模式
    with torch.no_grad():#所有模型中的张量计算都积攒梯度,而验证时不需要梯度
        for batch_x, batch_y in val_loader:
            x, target = batch_x.to(device), batch_y.to(device)
            pred = model(x)
            val_bat_loss = loss(pred, target)#再次用训练集得到预测值,然后和验证集数据计算LOSS
            val_loss += val_bat_loss.cpu().item()
    plt_val_loss.append(val_loss / val_loader.dataset.__len__())
    if val_loss < min_val_loss: #如果当前模型效果更好,进行记录
        torch.save(model, save_path)
        min_val_loss = val_loss

测试过程

测试过程就是将给出的测试集X通过训练得到的模型得到的预测值按照项目要求的形式记录下来。

代码实现
python 复制代码
def evaluate(save_path, test_loader, device, rel_path):
    model = torch.load(save_path).to(device) #加载模型
    rel = []
    with torch.no_grad():
        for x in test_loader:
            pred = model(x.to(device))#计算测试集中每个X对应的预测值
            rel.append(pred.cpu().item())
    print(rel)
    with open(rel_path, "w", newline='') as f:
        csvWriter = csv.writer(f) #文件写指针
        csvWriter.writerow(["id", "tested_positive"])
        for i, value in enumerate(rel): #同时得到下标和预测值
            csvWriter.writerow([str(i), str(value)])#文件读写都是字符形式
        print("文件已经保存到{}".format(rel_path))

测试效果:

完整代码

python 复制代码
import matplotlib.pyplot as plt
import torch
import numpy as np
import csv
import pandas as pd
from torch.utils.data import DataLoader, Dataset
import torch.nn as nn
from torch import optim
import time


class CovidDataset(Dataset):  #继承Dataset类
    def __init__(self, file_path, mode="train"): #训练集,验证集,测试集的数据形式不同
        with open(file_path, "r") as f: #读方式打开文件
            ori_data = list(csv.reader(f))
            csv_data = np.array(ori_data[1:])[:, 1:].astype(float) #转为矩阵方便去掉无用部分,先去掉第一行后去掉第一列,将读入的字符型数据转为浮点型

        if mode == "train": #逢5取1
            indices = [i for i in range(len(csv_data)) if i % 5 != 0]
            data = torch.tensor(csv_data[indices, :-1])  # 得到X,神经网络数据要使用张量
            self.y = torch.tensor(csv_data[indices, -1]) #得到Y
        elif mode == "val":
            indices = [i for i in range(len(csv_data)) if i % 5 == 0]
            data = torch.tensor(csv_data[indices, :-1])  # 得到X,神经网络数据要使用张量
            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 - data.mean(dim=0, keepdim=True))/data.std(dim=0, keepdim=True) #归一化,消除量纲的影响,第一维为列向
        self.mode = mode

    def __getitem__(self, idx):
        if self.mode != "test":
            return self.data[idx].float(), self.y[idx].float()
        else:
            return self.data[idx].float() #转为32位减少训练消耗

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


class MyModel(nn.Module):
    def __init__(self, dim): #给出输入维度dim,输出维度为定值1
        super(MyModel, self).__init__()#修改模型nn.Module类初始化
        self.fc1 = nn.Linear(dim, 64) #全连接
        self.relu1 = nn.ReLU() #激活函数
        self.fc2 = nn.Linear(64, 1)
    #深度神经网络模型定义完成

    def forward(self, x): #模型前向过程
        x = self.fc1(x)
        x = self.relu1(x)
        x = self.fc2(x)

        if len(x.size()) > 1:
            return x.squeeze(1) #让数据的维度和Y保持一致,以至于能够计算

        return x


def train_val(model, train_loader, val_loader, device, epochs, optimizer, loss, save_path):
    model = model.to(device)

    plt_train_loss = [] #记录所有轮次的LOSS
    plt_val_loss = []

    min_val_loss = 99999999999 #记录最小LOSS

    for epoch in range(epochs): #开始训练,阅读大佬代码的重点
        train_loss = 0.0
        val_loss = 0.0#表示模型效果

        start_time = time.time()

        model.train() #模型调为训练模式,有时训练模式和测试模式的模型不同
        for batch_x, batch_y in train_loader:
            x, target = batch_x.to(device), batch_y.to(device)
            pred = model(x)
            train_bat_loss = loss(pred, target) #获取一批数据的LOSS
            train_bat_loss.backward() #梯度回传
            optimizer.step()#更新模型
            optimizer.zero_grad()
            train_loss += train_bat_loss.cpu().item() #将gpu上的张量数据放到cpu上取出数据计算,累加记录本轮LOSS

        plt_train_loss.append(train_loss / train_loader.__len__()) #除以轮次数,得到每个轮次的LOSS平均值

        model.eval()#调为验证模式
        with torch.no_grad():#所有模型中的张量计算都积攒梯度,而验证时不需要梯度
            for batch_x, batch_y in val_loader:
                x, target = batch_x.to(device), batch_y.to(device)
                pred = model(x)
                val_bat_loss = loss(pred, target)
                val_loss += val_bat_loss.cpu().item()
        plt_val_loss.append(val_loss / val_loader.dataset.__len__())
        if val_loss < min_val_loss: #如果当前模型效果更好,进行记录
            torch.save(model, save_path)
            min_val_loss = val_loss

        print('[%03d/%03d] %2.2f sec(s) TrainLoss : %.6f | valLoss: %.6f' % \
              (epoch, epochs, time.time() - start_time, plt_train_loss[-1], plt_val_loss[-1])
              )  # 打印训练结果。 注意python语法, %2.2f 表示小数位为2的浮点数, 后面可以对应。

        plt.plot(plt_train_loss)
        plt.plot(plt_val_loss)
        plt.title("loss")
        plt.legend(["train", "val"])
        plt.show()


def evaluate(save_path, test_loader, device, rel_path):
    model = torch.load(save_path).to(device) #加载模型
    rel = []
    with torch.no_grad():
        for x in test_loader:
            pred = model(x.to(device))
            rel.append(pred.cpu().item())
    print(rel)
    with open(rel_path, "w", newline='') as f:
        csvWriter = csv.writer(f) #文件写指针
        csvWriter.writerow(["id", "tested_positive"])
        for i, value in enumerate(rel): #同时得到下标和预测值
            csvWriter.writerow([str(i), str(value)])#文件读写都是字符形式
        print("文件已经保存到{}".format(rel_path))


train_file = "covid.train.csv"
test_file = "covid.test.csv"

train_dataset = CovidDataset(train_file, "train")#使用dataset对象获取全部训练集数据
val_dataset = CovidDataset(train_file, "val")
test_dataset = CovidDataset(test_file, "test")

batch_size = 16
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) #shuffle表示打乱数据
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=True) #shuffle表示打乱数据
#取出一批次打乱的数据
test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False) #测试集的预测值必须和X正确对应


device = "cuda" if torch.cuda.is_available() else "cpu" #如果cuda可用使用显卡否则用cpu
print(device)

config = {
    "lr": 0.001,
    "epochs": 20,
    "momentum": 0.9,#动量越大惯性越大
    "save_path": "model_save/best_model.pth", #训练模型保存路径
    "rel_path": "pred.csv"
}

model = MyModel(dim=93).to(device)
loss = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=config["lr"], momentum=config["momentum"])

train_val(model, train_loader, val_loader, device, config["epochs"], optimizer, loss, config["save_path"])


evaluate(config["save_path"], test_loader, device, config["rel_path"])

项目优化思路

正则化

正则化(Regularization) 是一种防止模型过拟合的方法。采用改动loss的方式实现正则化:
l o s s = l o s s + w ∗ w loss = loss + w *w loss=loss+w∗w

由于loss要尽量的小,因此参数w也要尽量的小,避免了为了拟合一些偏离模型特别多的点而导致的参数w的剧增的现象。

相关系数

计算X中每个维度值和标签Y的相关系数,然后取出k个相关系数最大的维度,之后在训练集,验证集,测试集中只使用这些维度。

还可以考虑使用主成分分析 PCA进行优化。

相关推荐
睡不着还睡不醒11 分钟前
anaconda中可以import cv2,但是notebook中cv2 module not found
python·opencv·计算机视觉
马拉AI18 分钟前
CVPR | CNN融合注意力机制,芜湖起飞!
人工智能·神经网络·cnn
伊一大数据&人工智能学习日志21 分钟前
深度学习01 神经网络
人工智能·深度学习·神经网络·学习·机器学习
华清远见IT开放实验室31 分钟前
【每天学点AI】实战仿射变换在人工智能图像处理中的应用
图像处理·人工智能·python·opencv·仿射变换
企鹅侠客1 小时前
DeepSeek Window本地私有化部署
人工智能·深度学习·机器学习·tensorflow
码界筑梦坊1 小时前
基于Flask的历史空难数据可视化分析系统的设计与实现
python·信息可视化·flask·毕业设计
萧鼎1 小时前
使用Python的Tabulate库优雅地格式化表格数据
python
程序员小远1 小时前
接口自动化测试框架(pytest+allure+aiohttp+ 用例自动生成)
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·pytest
钮钴禄·爱因斯晨2 小时前
赛博算命之 ”梅花易数“ 的 “JAVA“ 实现 ——从玄学到科学的探索
java·开发语言·python
喵~来学编程啦2 小时前
【通俗易懂说模型】非线性回归和逻辑回归(附神经网络图详细解释)
人工智能·pytorch·深度学习·算法·回归