一、介绍一个最简单的神经网络深度学习项目(回归)

所有深度学习项目的框架几乎是互通的,主要分为4个部分:数据处理(给一个数据的地址,最麻烦的地方、也是最能提升模型性能的部分)、建立模型、超参(模型里面的参数中不可以改变和学习的部分,人为指定的如:学习率、优化器、损失函数)、训练流程(取数据得到预测值,和真实值比较得到LOSS,数据通过模型并更新模型)

tips:根据源代码一步一步理解代码含义,不懂的看#注释,写在博客里,一句写一个注释,写清楚在干嘛,以后看到也能马上回想起来;一个项目可能数据集、数据处理有所区别,明白基本的框架和理念以后就可以举一反三。

二、训练流程

分为训练数据和验证数据两部分

见过的数据是训练数据,要验证数据的好坏靠测试数据,测试数据中只有x没有y。对于见过的数据,无论准确率再怎么高,也没有实际意义

那么一般应该怎么做呢?

把训练数据二八分,其中的8让模型看得到,剩下的2用来作验证集来做验证和效果的判断()比如用验证集的x测,算出来一个y,再和真的y作比较看情况),并在验证集中挑选一个最好的模型,save下来,拿去参加官方的测试。

1、训练集

训练集分布要随机、均匀,保证覆盖范围足够的广。其中的数据是含x、含y的,一组一组对应

eg:恋爱次数与其他权重的关系,如果取500条学校的数据,但是验证的数据在社会取,会发现结果完全不准

2、验证集

从先前的训练集中抽留出来的部分。数据是含x、含y的,一组一组对应

3、测试集

只含x,不含y

三、项目实战

这次要实际训练的项目内容

来源于李宏毅老师在 kaggle上传的一个经典问题,网址如下

kaggle.com/competitions/ml2021spring-hw1/code

这个网站有个好处,可以在评论区看大佬们的讨论和心得,data、model部分还可以下载

1、数据

打开文件包中的这个文件,这里是原始数据,有2000多组

里面涵盖了90多维的特征,性别、感染程度、所属州、阳性人数等。其中测试集最后一列是空的,需要预测

训练模型时的第1列(纯标号)和第1行(项目内容)都是不要的

这里数据采用的是one-hot独热编码,如用1的位置来表示不同的类别、猫(1,0,0),狗(0,1,0),猪(0,0,1)

2、模型

文件包里的main文件是已经写好的所有的代码,test是自己重新写的代码

以后写代码之前需固定以下代码作为开头,有额外需要的引用的包再另外增加

import torch
import numpy as np #矩阵处理的包
import csv    #读excel文件的
import pandas as pd  #和上面的功能类似
from torch.utils.data import DataLoader,Dataset

以下是正式代码

在切换时需要把注释及时去掉

import matplotlib.pyplot as plt
import torch
import numpy as np #矩阵处理包
import csv #读excel文件
import pandas as pd
from torch.utils.data import DataLoader,Dataset #数据处理有关的包
import torch.nn as nn
from torch import optim  #引用优化器
import time  #后面要用来记录时间
from sklearn.feature_selection import SelectKBest,chi2  #机器学习的包,里面有很多好用的函数
#from Dataset import Covid_dataset

import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"

#Dataset是一个类
   #init 初始化
   #getitem 取数据
   #len提供数据长度
def get_feature_importance(feature_data, label_data, k = 4, column = None):
    """
    feature_data, label_data 要求字符串形式
    k为选择的特征数量
    如果需要打印column,需要传入行名
    此处省略 feature_data, label_data 的生成代码。
    如果是 CSV 文件,可通过 read_csv() 函数获得特征和标签。
    这个函数的目的是, 找到所有的特征种, 比较有用的k个特征, 并打印这些列的名字。
    """
    model = SelectKBest(chi2, k=k)      #定义一个选择k个最佳特征的函数
    feature_data = np.array(feature_data, dtype=np.float64)
    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中最小值的下标。sort排序的是矩阵中的元素
    if column:                            # 如果需要打印选中的列名字
        k_best_features = [column[i] for i in indices[0:k].tolist()]         # 选中这些列 打印
        print('k best features are: ',k_best_features)
    return X_new, indices[0:k]                  # 返回选中列的特征和他们的下标。

class CovidDataset(Dataset):  #dataset通过init(初始化、给一个文件地址,数据放在x、y列表)、getitem(取数据,【idx】得到x【idx】)、len提供数据长来实现
    #继承上面Dataset的类
   def __init__(self, file_path, mode, dim=4, all_feature=False):  #还可以写成mode=“train”默认值是train,如果不传入数据就默认是训练模式 是否使用全部列进行计算,feature_dim=6可以加多一个条件写想要的列
       with open(file_path,"r")as f:
           ori_data = list(csv.reader(f))
           #红点23行,运行光标到16行,复制这句话到控制台绿色光标处,可以在调试器的变量中看到ori_data文件夹下的95列原始数据
           #csv_data = np.array(ori_data[1:])[:, 1:]  去掉第一行、去掉第一列的表达式
           #可以先在评估表达式中输入 ori_data[1:] ,再输入 np.array(ori_data[1:]) ,再输入csv_data = np.array(ori_data[1:])[:, 1:].astype(float),可以看到是如何一步一步变化的-并转为浮点数
           #点击调试器下面csv_data中的作为array查看,可以看到作为表格形式的数据出现在右边
           #点红点然后调试,在调试中输入“ori_data = list(csv.reader(f))",可以看到读取的每一行数据
           feature = np.array(ori_data[1:])[:,1:-1]
           label_data = np.array(ori_data[1:])[:, 1:-1]
           _, col = get_feature_importance(feature,label_data)
           #下划线的意思是承接一下,但是不准备用
           col = col.tolist()  #转化为列表

           data = np.array(csv_data[1:])

           if mode == "train":  #如果是训练集,需要取大部分,方法是逢5取1,每过4行取一个数据
               indices = [i for i in range(len(data)) if i % 5 !=0]  #循环0-2700  excel里面的那个数据 对5取余≠0就把它加入训练集中
           elif mode == "val":  #验证集,取了0 5 10 15……
               indices = [i for i in range(len(data)) if i % 5 ==0]
           if all_feature:      #测试集
               col_idx = [i for i in range(0, 93)]
           else:
               col_idx = get_feature_importance(data[:, 1:-1], data[:, -1], k=dim, column=csv_data[0][1:-1])

           if mode == "test":
               x = data[:, 1:].astype(float)
               x = torch.tensor(x[:, col_idx])
           else:
               x = data[indices, 1:-1].astype(float)
               x = torch.tensor(x[:, col_idx])
               y = data[indices, -1].astype(float)
               self.y = torch.tensor(y)
           self.x = (x - x.mean(dim=0, keepdim=True)) / x.std(dim=0, keepdim=True)
           self.mode = mode
       #else:
       #    indices = [i for i in range(len(csv_data))]  #测试集是放所有的下标,不分家,测试集没有y
       #    data = torch.tensor(csv_data[indices])  #神经网络模型必须是张量进入   #测试集可以取最后一行
       #self.data = (data- data.mean(dim=0, keepdim=True))/data.std(dim=0, keepdim=True)   #归一:减去均值,除以标准差
       #self.mode = mode
       #一些数据组成batch,一个batch更新一次

   def __getitem__(self, item):
       if self.mode == "test":
           return self.x[item].float()  #给一个下标,给一个数据
       else:
           return self.x[item].float(), self.y[item].float()  #测试集没有y

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

class myModel(nn.Module):
   def __init__(self, dim):
       super(myModel, self).__init__()
       self.fc1 = nn.Linear(dim, 100)  #(输入维度,输出维度),上一层的输出是下一层的输入    93→64
       self.relu1 = nn.ReLU()     #激活函数,不改变维度
       self.fc2 = nn.Linear(100,1)     #100→1

   def forward(self, x):   #模型前向过程
       x = self.fc1(x)
       x = self.relu(x)
       x = self.fc2(x)      #一般的神经网络都会用这3行

       if len(x.size()) > 1:
           x = x.squeeze(dim=1)   #意思是去掉一维,去掉的是第1维
       return x
       #print(1)

#以下是训练流程
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 = 9999999999     #记录最小的loss值--即记录最好的模型,先放一个比较大的数,后面小于这个值再替换成小的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、y
            x, target = batch_x.to(device), batch_Y.to (device) #先吧X,Y放在设备上
            pred = model(x)  #得到预测值
            train_bat_loss = loss(pred, target)   #用pred计算loss
            train_bat_loss.backward()            #让loss梯度回传
            optimizer.step()         #更新模型的作用
            optimizer.zero_grad()    #让模型的梯度清零
            train_loss += train_bat_loss.cpu().item()   #记录一下这一轮的loss
            #= val_bat_loss.cpu().item()
        plt_train_loss.append(train_loss/train_loader.dataset.__len__())    #全部的训练过程。要把所有的loss加在这个列表里
        #plt_val_loss.append(val_loss / val_loader.__len__())

        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__())  #把每一轮的val_loss记录下来
        if val_loss < min_val_loss:
            torch.save(model, save_path)
            min_val_loss = val_loss
        print("[%03d/%03d]  %2.2f secs Trainloss: %.6f Valloss: %.6f"%\
              # 训练到第几轮   个十位,保留2位小数                      # 一个百分号表示一个格式化的输出,这%\ 表示换行
             (epoch, epochs, time.time()-start_time,plt_train_loss[-1],plt_val_loss[-1])) #有几个百分号%就要有几个输出
           #当前轮次  占总轮次的多少  当前时间-开始时间=这轮用了多久时间
    plt.plot(plt_train_loss)  #plt表示画图
    plt.plot(plt_val_loss)
    plt.title("loss图")
    plt.legend(["train", "val"])    #图例
    plt.show()

###evaluate(config["save_path"], test_loader, device, config["rel_path"])
def evaluate(save_path, device, test_loader, rel_path): #得出测试结果文件
    model = torch.load(save_path).to(device) #把模型加载出来
    rel = []                #定义一个结果的列表
    model.eval()
    with torch.no_grad():   #模型在计算的时候是不能计算出梯度并更新模型的
        for x in test_loader:
            pred = model(x.to(device))
            rel.append(pred.cpu().item())    #把测试值放在结果列表中,放在CPU上再取出来
    print(rel)
    with open(rel_path, "w", newline='') as f:  #不要换行,newline默认是\n,改成‘’
        csv_writer = csv.writer(f)
        csv_writer.writerow(["id","tested_positive"])  #写进去都要字符串
        #for i in rang(len(rel)):   也可以这样写
        for i, value in enumerate(rel):         #同时取到下标和下标里的值
            csv_writer.writerow([str(i), str(rel[i])])
    #print("文件已经保存到{}".format(rel_path))    也可以这样写
    print("文件已保存到"+rel_path)

        #     optimizer.step()#更新模型的作用
        #     optimizer.zero_grad()
        #     train_loss += train_bat_loss.cpu().item()
        # plt_train_loss.append(train_loss / train_loader.__len__())

        #         val_bat_loss.backward()
        #         optimizer.step()#更新模型的作用
        #         optimizer.zero_grad()
        #         train_loss += train_bat_loss.cpu().item()
all_feature = False
if all_feature:
    dim = 93
else:
    dim = 6
device = "cuda" if torch.cuda.is_available() else "cpu"
 # print(device)
train_file="covid.train.csv"
test_file="covid.test.csv"

train_data = Covid_dataset(train_file, "train", dim=dim, all_feature=all_feature)  #训练集
val_data = Covid_dataset(train_file, "val", dim=dim, all_feature=all_feature)      #验证集
test_data = Covid_dataset(test_file, "test", dim=dim, all_feature=all_feature)     #测试集

train_loader = DataLoader(train_data, batch_size=16, shuffle=True)
val_loader = DataLoader(val_data, batch_size=16, shuffle=True)
test_loader = DataLoader(test_data, batch_size=1, shuffle=False)

#    for batch_x, batch_y in train_loader:
#        print(batch_x, batch_y)

#model = MyModel(inDim=93)

# predy = model(batch_x)

#for data in train_dataset:
#    print(data)

# file=pd.read_csv(train_file)  #pd用来读文件
# print(file.head())     #到这里可以试运行一下,可以看到数据已经读出来了

#以上是模型部分
#以下是超参部分
#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)
# 返回损失。

# 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()
optimizer = optim.SGD(model.parameters(), lr=config["lr"], momentum=config["momentum"])    #优化器optim; momentum是动量,loss不断变小的过程

train_val(model, train_loader, val_loader, device, config["epochs"], optimizer, loss, config["save_path"])#传入所需的设备、优化器、loss函数,最后一个config是保存路径

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

#train_dataset = CovidDataset(train_file, "train")  #训练集
#val_dataset = CovidDataset(train_file, "val")  #验证集
  #测试集

(1)注意dataloader

取数据是否需要所有的数据算一次loss?

可能很准,但是一步要走多长是不知道的,需要用所有数据算一次、很慢。每一步走多远就很关键 。不太推荐

一个样本算一次、一个数据更新一次是可以的吗

不可以,对于每一个单独的样本来说可能是乱跑的

那应该多少更新一次?

一个batch!即一个批次更新一次,这样又快又好

(2)Model

主要结构是:

①init初始化

②forward模型前项代码

需要关注模型维度变化,防止出现维度变换不对应的情况,Linear(输入维度,输出维度),上一层Linear的输出维度是下一层的输入维度

3、训练过程的开始


for epoch in range(epochs): #训练开始,冲锋的号角

(1)改造成正则项

正则化可以缓解过拟合,让预测的函数曲线更加平滑

mseloss是一个普通的loss,修改如下

修改前:
def mseLoss(pred, target, model):
loss =  mseLoss 
修改后:
def mseLoss_with_reg(pred, target, model):
loss =  mseLoss_with_reg

(2)相关系数:线性相关SelectKBest

不一定每个列都是有用的,只是起到噪声干扰的作用,可以挑选出哪些列对我们是有用的,通过相计算每一列和最后一列标签列的相关系数,找到最高的一列,剩下的全都不要

(3)主成分分析PCA

主要作用是降维,所有列都改造成一个维度(很多考研的老师很喜欢问这个算法)

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐