第3节.回归实战
一、介绍一个最简单的神经网络深度学习项目(回归)
所有深度学习项目的框架几乎是互通的,主要分为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
主要作用是降维,所有列都改造成一个维度(很多考研的老师很喜欢问这个算法)
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)