前言:

本次实战项目是新冠病毒感染人数预测,使用到的数据集来自于kaggle的经典项目,本项目使用将训练集拆分为部分训练集和部分验证集,以防止在测试集部分失去预测性。

网址:

https://www.kaggle.com/

主要思想是:

  • 从训练数据中提取除第一行和第一列以外的全部数据并存入ori_data
  • 然后根据索引每五个划分一个为验证集,这样就可以成比例的设置出80%训练数据和20%的验证数据,此处优化了全部训练数据为影响预测结果关系最大的六组数据
  • 需要注意的是这里需要设置两个模型,一个是Dateset,它内部需要实现__init__()、__getitem__()、__len__()三个函数,另一个是Model,内部需要实现__init__()、forward(),一旦模型缺少必要的函数就会实现失败
  • 接着需要调用函数DataLoader()从我们已经存入并得到的数据集中随机取出行数据经行训练和验证
  • 设置损失函数,并通过正则化损失函数进行避免过拟合优化
  • 最后就开始训练模型,通过损失函数进行优化调整模型
  • 最后是调用测试集并输出规定形式的预测结果

 代码解析:

导包:
import matplotlib.pyplot as plt
import pandas as pd
import torch
import numpy as np
import csv#读excel文件
import pandas
import time
import torch.nn as nn
from torch.utils.data import DataLoader,Dataset
from sklearn.feature_selection import SelectKBest,chi2
from torch import optim
 covidDateset类(数据集):
class CovidDataset(Dataset):
    def __init__(self,file_path,mode="train",all_feature=False,feature_dim=6):#后面两项决定是使用全部列还是几项列
        with open(file_path,"r")as f:
            ori_data=list(csv.reader(f))#初始数据
            column=ori_data[0]#打印列名字
            csv_data=np.array(ori_data[1:])[:,1:].astype(float)#去除第一行和第一列
        feature=np.array(ori_data[1:])[:,1:-1]#第一列开始到最后一列结束
        label_data=np.array(ori_data[1:])[:,-1]
        if all_feature:
            col = np.array([i for i in range(0, 93)])
        else:
            _,col=get_feature_importance(feature,label_data, feature_dim, column)#下划线也是变量,但后续不打算用了
        col=col.tolist()#列
        # self.mode=mode
        if mode=="train":#逢五取一,选取作为测试集
            indices = [i for i in range(len(csv_data)) if i % 5 != 0]
            data = torch.tensor(
                csv_data[indices, :-1])  # 为-1是去掉最后一列,为x,从csv_data中选择indices指定的行(以及这些行的所有列),然后将这部分数据转换成PyTorch张量
            self.y=torch.tensor(csv_data[indices,-1])#z指定索引的最后一列也就是训练集的最后一列相当于y值
        elif mode=="val":
            indices = [i for i in range(len(csv_data)) if i%5==0]
            data = torch.tensor(
                csv_data[indices, :-1])  # 为-1是去掉最后一列,为x,从csv_data中选择indices指定的行(以及这些行的所有列),然后将这部分数据转换成PyTorch张量
            self.y = torch.tensor(csv_data[indices, -1])
        else:
            indices = [i for i in range(len(csv_data))]
            data = torch.tensor(
                csv_data[indices])  #为x,从csv_data中选择indices指定的行(以及这些行的所有列),然后将这部分数据转换成PyTorch张量
        data=data[:,col]
        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()#加上.float()是为了减少模型消耗
        else:
            return self.data[idx].float()

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

如代码,这个模型实现了三个函数,在__init__里面ori_data=list(csv.reader(f))用于从文件file_path读取训练数据,其中ori_data1是全部数据,包含第一列和第一行,column_data只有测试集第一行的地区名,col是对预测结果影响最大的六组测试集,后面用于优化模型使用。

接下来的判断语句是识别是测试、验证、还是训练,分别生成需要的x和y,也就是模型的输入值和输出值,用于验证模型是否输出正确。self.data那行是使用均值将数据分布在0的左右,便于后续操作。

MyModel 类:
class MyModel(nn.Module):
    def __init__(self,inDim):
        super(MyModel, self).__init__()
        self.fc1=nn.Linear(inDim,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)#移除张量中第二个轴
        return x

定义了一个简单的两层全连接神经网络并返回标量,其中激活函数是ReLU,增加模型的非线性能力,需要注意的是张量维度的对应关系。

Train_val函数:
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

    for epoch in range(epochs):#开始训练
        train_loss=0.0#每一回合的loss
        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)
            pred=model(x)#让数据通过模型得到预测值
            train_bat_loss=loss(pred,target,model)
            train_bat_loss.backward()
            optimizer.step()#更新模型
            optimizer.zero_grad()
            train_loss+=train_bat_loss.cpu().item()
        plt_train_loss.append(train_loss / train_loader.__len__())
#这里的关键步骤是计算平均损失,即将累积的总损失 train_loss 除以总批次数 train_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,model)
                val_loss+=val_bat_loss.cpu().item()

        plt_val_loss.append(val_loss / val_loader.__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]))

    plt.plot(plt_train_loss)
    plt.plot(plt_val_loss)
    plt.title("loss_draw")
    plt.legend(["train","val"])#图例,就是标示何线是哪条
    plt.show()

用损失函数更新模型时,通常是四个步骤:前向传播(计算得到预测值和实际标签之间的损失)、计算梯度(即反向传播,计算损失函数关于每个参数的梯度,由loss.backward()实现)、参数更新(沿着梯度的相反方向调整参数,以减小损失值,由优化器SGD的step()来实现)、迭代训练。

相对于训练部分,验证部分不需要更新grad和模型,所以代码有所区别。

 mseLoss函数(正则化版)
def mseLoss_with_reg(pred, target, model):
    loss = nn.MSELoss(reduction='mean')#计算所有loss的平均数
    ''' 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             # 返回损失。

正则化就是避免过拟合,使曲线平滑,需要在得到的损失值再加上一个学习率×参数平方,学习率一般很小。

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个最佳特征的函数
    feature_data = np.array(feature_data, dtype=np.float64)
    # label_data = np.array(label_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中最小值的下标。

    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]                  # 返回选中列的特征和他们的下标。

 重点解析indices = np.argsort(scores)[::-1]        #[::-1]表示反转一个列表或者矩阵

argsort()是返回数值排序好后的索引,有点像键值对,有了[::-1]表示是从大到小排序。

完整代码 :
import matplotlib.pyplot as plt
import pandas as pd
import torch
import numpy as np
import csv#读excel文件
import pandas
import time
import torch.nn as nn
from torch.utils.data import DataLoader,Dataset
from sklearn.feature_selection import SelectKBest,chi2
from torch import optim

#下面这个函数会得到最重要的四列数据
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个最佳特征的函数
    feature_data = np.array(feature_data, dtype=np.float64)
    # label_data = np.array(label_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中最小值的下标。

    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,file_path,mode="train",all_feature=False,feature_dim=6):#后面两项决定是使用全部列还是几项列
        with open(file_path,"r")as f:
            ori_data=list(csv.reader(f))#初始数据
            column=ori_data[0]#打印列名字
            csv_data=np.array(ori_data[1:])[:,1:].astype(float)#去除第一行和第一列
        feature=np.array(ori_data[1:])[:,1:-1]#第一列开始到最后一列结束
        label_data=np.array(ori_data[1:])[:,-1]
        if all_feature:
            col = np.array([i for i in range(0, 93)])
        else:
            _,col=get_feature_importance(feature,label_data, feature_dim, column)#下划线也是变量,但后续不打算用了
        col=col.tolist()#列
        # self.mode=mode
        if mode=="train":#逢五取一,选取作为测试集
            indices = [i for i in range(len(csv_data)) if i % 5 != 0]
            data = torch.tensor(
                csv_data[indices, :-1])  # 为-1是去掉最后一列,为x,从csv_data中选择indices指定的行(以及这些行的所有列),然后将这部分数据转换成PyTorch张量
            self.y=torch.tensor(csv_data[indices,-1])#z指定索引的最后一列也就是训练集的最后一列相当于y值
        elif mode=="val":
            indices = [i for i in range(len(csv_data)) if i%5==0]
            data = torch.tensor(
                csv_data[indices, :-1])  # 为-1是去掉最后一列,为x,从csv_data中选择indices指定的行(以及这些行的所有列),然后将这部分数据转换成PyTorch张量
            self.y = torch.tensor(csv_data[indices, -1])
        else:
            indices = [i for i in range(len(csv_data))]
            data = torch.tensor(
                csv_data[indices])  #为x,从csv_data中选择indices指定的行(以及这些行的所有列),然后将这部分数据转换成PyTorch张量
        data=data[:,col]
        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()#加上.float()是为了减少模型消耗
        else:
            return self.data[idx].float()

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

class MyModel(nn.Module):
    def __init__(self,inDim):
        super(MyModel, self).__init__()
        self.fc1=nn.Linear(inDim,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)#移除张量中第二个轴
        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

    for epoch in range(epochs):#开始训练
        train_loss=0.0#每一回合的loss
        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)
            pred=model(x)#让数据通过模型得到预测值
            train_bat_loss=loss(pred,target,model)
            train_bat_loss.backward()
            optimizer.step()#更新模型
            optimizer.zero_grad()
            train_loss+=train_bat_loss.cpu().item()
        plt_train_loss.append(train_loss / train_loader.__len__())
#这里的关键步骤是计算平均损失,即将累积的总损失 train_loss 除以总批次数 train_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,model)
                val_loss+=val_bat_loss.cpu().item()

        plt_val_loss.append(val_loss / val_loader.__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]))

    plt.plot(plt_train_loss)
    plt.plot(plt_val_loss)
    plt.title("loss_draw")
    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:#newline=''意味不换行
        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))

all_feature=False
if all_feature:
    feature_dim=93
else:
    feature_dim=6
train_file="covid.train.csv"
test_file="covid.test.csv"

train_dataset=CovidDataset(train_file,"train",all_feature=all_feature, feature_dim=feature_dim)
val_dataset=CovidDataset(train_file,"val",all_feature=all_feature, feature_dim=feature_dim)#验证集
test_dataset=CovidDataset(test_file,"test",all_feature=all_feature, feature_dim=feature_dim)

# for data in train_dataset:
#     print(data)

batch_size=16#开始取数据部分
train_loader=DataLoader(train_dataset,batch_size=batch_size,shuffle=True)#以指定的批次大小和随机顺序加载训练数据集
val_loader=DataLoader(val_dataset,batch_size=batch_size,shuffle=True)
test_loader=DataLoader(test_dataset,batch_size=1,shuffle=False)
# for batch_x,batch_y in train_loader:
#     print(batch_x,batch_y)

# predy=model(batch_x)
# file=pd.read_csv(train_file)
# print(file.head())
device="cuda" if torch.cuda.is_available() else "cpu"#显卡可用用显卡,否则cpu
print(device)
#config 字典在深度学习模型训练过程中起到了参数配置
config={
    "lr":0.001,
    "epochs":20,
    "momentum":0.9,
    "save_path":"model_save/best_model.pth",#保持当前已有最佳的路径
    "rel_path":"pred.csv"
}

def mseLoss_with_reg(pred, target, model):
    loss = nn.MSELoss(reduction='mean')#计算所有loss的平均数
    ''' 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             # 返回损失。

model =MyModel(inDim=feature_dim).to(device)#把模型放在设备上
loss=mseLoss_with_reg#正则化损失函数
optimizer=optim.SGD(model.parameters(),lr=config["lr"],momentum=config["momentum"])#momentum是动量,创建初始化为一个 SGD 优化器对象
#通过迭代地调整模型参数,以最小化某个损失函数,从而训练出性能良好的模型
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"])
运行结果:

PS:有理解不到位的地方,欢迎大佬给予建议,后续时间充裕可能会更新另一个房价预测的回归代码。

Logo

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

更多推荐