一.概述

        本项目为基于深度学习的新冠病毒人数预测的回归项目,数据有两千多份,有93个特征值,用来自学回顾,项目来源来自kaggle

链接地址:https://www.kaggle.com/competitions/ml2021spring-hw1

二.主要代码

1.获取数据集

        分别获取训练集、验证集和测试集的数据,需要注意训练集和测试集,训练集的x和y分别对应1到92列和最后一列。(第0列为id)。这里把训练集分成训练集和验证集,用来判断模型的好坏,找到最好的模型。

        获取数据集的函数必须严格按照父类的模式

class CovidDataset(Dataset):
    def __init__(self, file_path, mode="train"):  # 默认是获取训练集
        self.mode = mode
        with open(file_path, "r") as f:  # 以只读的方式打开
            ori_data = list(csv.reader(f))  # ori_data是一个迭代器,可以用来逐行读取或逐列读取,这里将其转为列表
            csv_data = np.array(ori_data[1:])[:, 1:].astype(float)  # 列表转为二维numpy数组
            indices = []  # 用来存储和将要返回的数据下标

        if mode == "train":  # 训练集
            # indices = [i for i in range(csv_data)]  # 这行代码和下面的代码是一个意思
            for i in range(len(csv_data)):
                if i % 5 != 0:
                    indices.append(i)
            x = torch.tensor(csv_data[indices, :-1])  # 获取indices这些行,再除去最后一列的要预测的值
            self.y = torch.tensor(csv_data[indices, -1])  # y只有最后一列,这里的y是一维的
        elif mode == "val":  # 验证集
            for i in range(len(csv_data)):
                if i % 5 == 0:
                    indices.append(i)
            x = torch.tensor(csv_data[indices, :-1])  # 获取indices这些行,再除去最后一列的要预测的值
            self.y = torch.tensor(csv_data[indices, -1])
        else:  # 测试集
            for i in range(len(csv_data)):
                indices.append(i)
            x = torch.tensor(csv_data[indices])  # 获取indices这些行,测试集取全部的列
        # 对每一列的数据进行标准化,消除同一行的不同特征之间的权重影响问题
        # dim=0为按列,就是对每个特征值进行操作
        self.x = (x - x.mean(dim=0, keepdim=True)) / x.std(dim=0, keepdim=True)


    def __getitem__(self, idx):  # 给一个idx下标,返回这一行的数据
        if self.mode != "test":
            return self.x[idx].float(), self.y[idx].float()
        else:
            return self.x[idx].float()

    def __len__(self):  # 返回获取到的数据集的长度
        return len(self.x)
train_dataset = CovidDataset(train_file, "train")  # 训练集
val_dataset = CovidDataset(train_file, "val")  # 验证集
test_dataset = CovidDataset(test_file, "test")  # 测试集

train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=16, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False)  # 测试集的batch——size一般为1 且不可以打乱,不然答案顺序就乱了

2.拿数据

train_file = "covid.train.csv"  # 训练文件
test_file = "covid.test.csv"  # 测试文件

3.定义自己的模型

        这里要特别注意linner的维度问题,全连接,前一层的输出维度为后一层的输入维度。由于最后得到的维度为二维,所以要用x.squeeze(1)将其转变为一维,用来和y进行计算

class Mymodel(nn.Module):
    def __init__(self, inDim):  # inDim为输入维度
        super(Mymodel, self).__init__()  # 重新定义父类的初始化函数
        self.fc1 = nn.Linear(inDim, 100)
        self.relu1 = nn.ReLU()
        self.fc2 = nn.Linear(100, 1)

    def forward(self, x):  # 模型前向传递过程
        x = self.fc1(x)  # 通过第一层全连接
        x = self.relu1(x)  # 通过第二层全连接
        x = self.fc2(x)  # 通过第三层全连接
        return x.squeeze(1)  # 返回一维的数据,用来和y做计算

4训练模型

        开始训练模型,分别传入模型 ,训练集,验证集,设备(我这里是cpu),训练次数,优化器,损失函数,用来保存最好的模型的路径。模型的好坏不是呈直线上升和直线下降,是不规则的曲线,所以要保存预测值最好的模型。还要注意模型的模式,分为测试和验证

        梯度回传:

train_batch_loss = loss(pred, target)  # 计算损失
train_batch_loss.backward()  # 梯度回传、        

        优化器:

# para -= para.grad * lr  # 不能写成   para = para - para.grad*lr
# para.grad.zero_()
optimizer.step()  # 相当于上一个的更新模型的作用,自动更新模型
optimizer.zero_grad()  # 梯度清零

        具体来说就是梯度回传是计算导数值,优化器是要知道要更新那个参数,然后去执行,至于到底更新多少数值,不是优化器的工作

def train_val(model, train_loader, val_loader, device, epochs, optimizer, loss, save_path):
    model = model.to(device)  # 把模型放在cpu上

    plt_train_loss = []
    plt_val_loss = []
    min_val_loss = 9999999999  # 记录最小的loss值
    for epoch in range(epochs):
        train_loss = 0.0  # 记录每一轮的训练loss
        val_loss = 0.0  # 记录每一轮的验证loss

        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_batch_loss = loss(pred, target)  # 计算损失
            train_batch_loss.backward()  # 梯度回传

            # 下面两行优化器相当于:
            # para -= para.grad * lr  # 不能写成   para = para - para.grad*lr
            # para.grad.zero_()
            optimizer.step()  # 相当于上一个的更新模型的作用,自动更新模型
            optimizer.zero_grad()  # 梯度清零
            # train_loss += train_batch_loss.cpu().item()  # 把数据放在cpu上把损失的数值取出来
            train_loss += train_batch_loss.cpu().item()  # 测试原本就在cpu上运行
        plt_train_loss.append(train_loss / train_loader.dataset.__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_batch_loss = loss(pred, target)
                val_loss += val_batch_loss.cpu().item()
        plt_val_loss.append(val_loss / val_loader.dataset.__len__())

        if val_loss < min_val_loss:
            # torch.save(model, save_path)  # 把好的模型保存起来
            torch.save(model.state_dict(), save_path)
            min_val_loss = val_loss

        # 打印训练结果。 注意python语法, %2.2f 表示小数位为2的浮点数, 后面可以对应。
        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]))

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

5.验证测试集,生成数据

        这里我的模型只能以这种方式加载到设备上

def evaluate(save_path, device, test_loader, ans_path):
    # model = torch.load(save_path).to(device)

    # 不知道为什么上面那样不可以?????????????????????????????????
    # 总之这里的意思是把最好的模型、最好的参数从文件中取出来,放到设备上
    state_dict = torch.load(save_path, map_location=device)
    model.load_state_dict(state_dict)
    model.to(device)

    ans = []  # 用来存储模型预测出来的值
    with torch.no_grad():
        for x in test_loader:
            pred = model.forward(x.to(device))
            ans.append(pred.cpu().item())
    # print(ans)

    # 打开文件,以写的方式,不进行换行(答案格式要求)
    with open(ans_path, "w", newline="") as f:
        csv_writer = csv.writer((f))
        csv_writer.writerow(["id", "tested_positive"])
        for i in range(len(ans)):
            csv_writer.writerow([str(i), str(ans[i])])
        print("文件被保存到" + ans_path)

6.程序开始

1.设置超参数

# 设置超参数
config = {
    "lr": 0.001,
    "momentum": 0.9,  # 动量越大表示向前缓冲的越多
    "epochs": 20,
    "save_path": "model_save/model.pth",
    "ans_path": "pred.csv"
}

2.开始执行

device = "cuda" if torch.cuda.is_available() else "cpu"  # 查看自己的设备,我的是cpu
# print(device)
# 维度为93(案例要求)
dim = 93
# 把模型放在cpu上
model = Mymodel(dim).to(device)
# 不用自己写loss函数,直接用官方的(参考上节课的loss)
# def maeLoss(pre_y, y):
#     return torch.sum(abs(pre_y - y)) / len(y)
loss = nn.MSELoss()  # 相当于上面的loss函数,只是函数不一样,这是是均方误差,上面是平均值误差
# 不用自己写优化器,直接用官方的(参考上节课的sgd)
# 模型的参数,学习率,动量(可以理解为惯性)
# para -= para.grad * lr  # 不能写成para = para - para.grad*lr
# para.grad.zero_()  # 这个没有动量
optimizer = optim.SGD(model.parameters(), lr=config["lr"], momentum=config["momentum"])  # 优化器,相当于上面两行代码

# 开始训练
# 模型,训练集,验证机,设备(cpu),超参数,优化器,损失函数,保存路径(用来保存最好的模型)
# 模型的好坏不是呈直线上升和直线下降,是不规则的曲线,所以要保存预测值最好的模型
train_val(model, train_loader, val_loader, device, config["epochs"], optimizer, loss, config["save_path"])

# 开始预测,生成预测值,保存
evaluate(config["save_path"], device, test_loader, config["ans_path"])

三.回顾

        这个项目总体上说就是根据给出的数据生成一个模型,用来预测最终的结果。最重要的是要学会计算模型中的参数的数量

        每个神经元有一个b,并且对应了前一层的全连接数量,如图所示,从而计算出整个模型中的参数的数量。

更多推荐