一、核心类
1、Coivid_dataset类,用于处理数据,内部有三个方法:
__init__,用于将数据从csv文件中读入,并设定train(训练),val(局部测试),test(测试)三个模式,并分割样本数据为训练集和局部测试集,方便后续验证模型准确性。
__getitem__,该方法根据模式判别和传入的item,模式为test返回训练集中某项的X[item],模式为val或train,返回X[item]与Y[item],返回时转化为浮点数类型。 __len__,返回数据长度,用于后续计算loss的均值
2、myModel类,用于设定训练过程中的维度变化
3、train_val类,用于具体训练
S1:每一轮训练,取“训练集”中的X和Y,将X依次送入模型算出预测值pred,用pred和Y计算出train_loss,梯度回传,取“局部测试集”的X和Y,将X依次送入模型算出预测值pred,用pred和Y计算出val_loss,保存最优模型
S2:画图
4、evaluate类,用test数据集,用训练出的最佳模型计算预测值
5、get_feature_importance类,选出相关系数最高的前k个列
二、原代码
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
import csv #读 CSV
import numpy as np
import time
import matplotlib.pyplot as plt
import pandas as pd
from torch import optim
import torch.nn as nn
import torch
from torch.utils.data import Dataset,DataLoader
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个最佳特征的函数
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] 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, dim=4, all_feature=False):
with open(file_path, "r") as f:
csv_data = list(csv.reader(f))
data = np.array(csv_data[1:]) # 1: 第一行后面的
if mode == "train": # 训练数据逢5选4, 记录他们的所在行
indices = [i for i in range(len(data)) if i % 5 !=0] #1,2,3,4, 6,7,8,9
elif mode == "val": # 验证数据逢5选1, 记录他们的所在列
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]) # 选重要的dim列。
if mode == "test":
x = data[:, 1:].astype(float) #测试集没标签,取第二列开始的数据,并转为float
x = torch.tensor(x[:, col_idx]) # col_idx表示了选取的列,转为张量
else:
x = data[indices, 1:-1].astype(float)
x = torch.tensor(x[:, col_idx])
y = data[indices, -1].astype(float) #训练接和验证集有标签,取最后一列的数据,并转为float
self.y = torch.tensor(y) #转为张量
self.x = (x-x.mean(dim=0,keepdim=True))/x.std(dim=0,keepdim=True) # 对数据进行列归一化 0正太分布
self.mode = mode # 表示当前数据集的模式
def __getitem__(self, item):
if self.mode == "test":
return self.x[item].float() # 测试集没标签。 注意data要转为模型需要的float32型
else: # 否则要返回带标签数据
return self.x[item].float(), self.y[item].float()
def __len__(self):
return len(self.x) # 返回数据长度。
class myNet(nn.Module):
def __init__(self, inDim):
super(myNet,self).__init__()
self.fc1 = nn.Linear(inDim, 128) # 全连接
self.relu = nn.ReLU() # 激活函数 ,添加非线性
# self.fc3 = nn.Linear(128, 128)
self.fc2 = nn.Linear(128,1) # 全连接 设计模型架构。 他没有数据
def forward(self, x): #forward, 即模型前向过程
x = self.fc1(x)
x = self.relu(x)
# x = self.fc3(x)
x = self.fc2(x)
if len(x.size()) > 1:
return x.squeeze(1)
else:
return x
def train_val(model, trainloader, valloader,optimizer, loss, epoch, device, save_):
# trainloader = DataLoader(trainset,batch_size=batch,shuffle=True)
# valloader = DataLoader(valset,batch_size=batch,shuffle=True)
model = model.to(device) # 模型和数据 ,要在一个设备上。 cpu - gpu
plt_train_loss = []
plt_val_loss = []
val_rel = []
min_val_loss = 100000 # 记录训练验证loss 以及验证loss和结果
for i in range(epoch): # 训练epoch 轮
start_time = time.time() # 记录开始时间
model.train() # 模型设置为训练状态 结构
train_loss = 0.0
val_loss = 0.0
for data in trainloader: # 从训练集取一个batch的数据
optimizer.zero_grad() # 梯度清0
x, target = data[0].to(device), data[1].to(device) # 将数据放到设备上
pred = model(x) # 用模型预测数据
bat_loss = loss(pred, target) # 计算loss
bat_loss.backward() # 梯度回传, 反向传播。
optimizer.step() #用优化器更新模型。 轮到SGD出手了
train_loss += bat_loss.detach().cpu().item() #记录loss和
plt_train_loss. append(train_loss/trainloader.dataset.__len__()) #记录loss到列表。注意是平均的loss ,因此要除以数据集长度。
model.eval() # 模型设置为验证状态
with torch.no_grad(): # 模型不再计算梯度
for data in valloader: # 从验证集取一个batch的数据
val_x , val_target = data[0].to(device), data[1].to(device) # 将数据放到设备上
val_pred = model(val_x) # 用模型预测数据
val_bat_loss = loss(val_pred, val_target) # 计算单轮loss
val_loss += val_bat_loss.detach().cpu().item() # 计算loss和
val_rel.append(val_pred) #记录预测结果
if val_loss < min_val_loss:
torch.save(model, save_) #如果loss比之前的最小值小, 说明模型更优, 保存这个模型
plt_val_loss.append(val_loss/valloader.dataset.__len__()) #记录loss到列表。注意是平均的loss ,因此要除以数据集长度。
#
print('[%03d/%03d] %2.2f sec(s) TrainLoss : %.6f | valLoss: %.6f' % \
(i, epoch, time.time()-start_time, plt_train_loss[-1], plt_val_loss[-1])
) #打印训练结果。 注意python语法, %2.2f 表示小数位为2的浮点数, 后面可以对应。
# print('[%03d/%03d] %2.2f sec(s) TrainLoss : %3.6f | valLoss: %.6f' % \
# (i, epoch, time.time()-start_time, 2210.2255411, plt_val_loss[-1])
# ) #打印训练结果。 注意python语法, %2.2f 表示小数位为2的浮点数, 后面可以对应。
plt.plot(plt_train_loss) # 画图, 向图中放入训练loss数据
plt.plot(plt_val_loss) # 画图, 向图中放入训练loss数据
plt.title('loss') # 画图, 标题
plt.legend(['train', 'val']) # 画图, 图例
plt.show() # 画图, 展示
def evaluate(model_path, testset, rel_path ,device):
model = torch.load(model_path).to(device) # 模型放到设备上。 加载模型
testloader = DataLoader(testset, batch_size=1, shuffle=False) # 将验证数据放入loader 验证时, 一般batch为1
val_rel = []
model.eval() # 模型设置为验证状态
with torch.no_grad(): # 模型不再计算梯度
for data in testloader: # 从测试集取一个batch的数据
x = data.to(device) # 将数据放到设备上
pred = model(x) # 用模型预测数据
val_rel.append(pred.item()) #记录预测结果
print(val_rel) #打印预测结果
with open(rel_path, 'w') as f: #打开保存的文件
csv_writer = csv.writer(f) #初始化一个写文件器 writer
csv_writer.writerow(['id','tested_positive']) #在第一行写上 “id” 和 “tested_positive”
for i in range(len(testset)): # 把测试结果的每一行放入输出的excel表中。
csv_writer.writerow([str(i),str(val_rel[i])])
print("rel已经保存到"+ rel_path)
all_col = False #是否使用所有的列
device = 'cuda' if torch.cuda.is_available() else 'cpu' #选择使用cpu还是gpu计算。
print(device)
train_path = 'covid.train.csv' # 训练数据路径
test_path = 'covid.test.csv' # 测试数据路径
file = pd.read_csv(train_path)
file.head() # 用pandas 看看数据长啥样
if all_col == True:
feature_dim = 93
else:
feature_dim = 6 #是否使用所有的列
trainset = covidDataset(train_path,'train', feature_dim, all_feature=all_col)
valset = covidDataset(train_path,'val', feature_dim, all_feature=all_col)
testset = covidDataset(test_path,'test', feature_dim, all_feature=all_col) #读取训练, 验证,测试数据
# 返回损失。
#
# 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() # 定义mseloss 即 平方差损失,
config = {
'n_epochs': 50, # maximum number of epochs
'batch_size': 32, # mini-batch size for dataloader
'optimizer': 'SGD', # optimization algorithm (optimizer in torch.optim)
# hyper-parameters for the optimizer (depends on which optimizer you are using)
'lr': 0.0001, # learning rate of SGD
'momentum': 0.9, # momentum for SGD
'early_stop': 200, # early stopping epochs (the number epochs since your model's last improvement)
'save_path': 'model_save/model.pth', # your model will be saved here
}
model = myNet(feature_dim).to(device) # 实例化模型
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9) # 定义优化器 动量
trainloader = DataLoader(trainset, batch_size=config['batch_size'], shuffle=True)
valloader = DataLoader(valset, batch_size=config['batch_size'], shuffle=True) # 将数据装入loader 方便取一个batch的数据
train_val(model, trainloader, valloader, optimizer, loss, config['n_epochs'], device,save_=config['save_path']) # 训练
evaluate(config['save_path'], testset, 'pred.csv', device) # 验证
三、遇到的问题
1、运行的loss结果出现 “nan”
可能的原因:
- 脏数据:训练数据(包括label)中有无异常值(nan, inf等);
- 除0问题。这里实际上有两种可能,一种是被除数的值是无穷大,即 Nan,另一种就是0作为了除数(分母可以加一个eps=1e-8)。之前产生的 Nan 或者0,有可能会被传递下去,造成后面都是 Nan。请先检查一下神经网络中有可能会有除法的地方,例 softmax 层,再认真的检查一下数据。可以尝试加一些日志,把神经网络的中间结果输出出来,看看哪一步开始出现 Nan 。
- 可能0或者负数作为自然对数,或者 网络中有无开根号(torch.sqrt), 保证根号下>=0
- 初始参数值过大:也有可能出现 Nan 问题。输入和输出的值,最好也做一下归一化。
- 学习率设置过大:初始学习率过大,也有可能造成这个问题。如果在迭代的100轮以内,出现NaN,一般情况下的原因是因为你的学习率过高,需要降低学习率。可以不断降低学习率直至不出现NaN为止,一般来说低于现有学习率1-10倍即可。如果为了排除是不是学习率的原因,可以直接把学习率设置为0,然后观察loss是否出现Nan,如果还是出现就不是学习率的原因。需要注意的是,即使使用 adam 之类的自适应学习率算法进行训练,也有可能遇到学习率过大问题,而这类算法,一般也有一个学习率的超参,可以把这个参数改的小一些。
- 梯度过大,造成更新后的值为 Nan 。如果当前的网络是类似于RNN的循环神经网络的话,在序列比较长的时候,很容易出现梯度爆炸的问题,进而导致出现NaN,一个有效的方式是增加”gradient clipping”(梯度截断来解决):对梯度做梯度裁剪,限制最大梯度,
- 需要计算loss的数组越界(尤其是自定义了一个新的网络,可能出现这种情况)
- 在某些涉及指数计算,可能最后算得值为 INF(无穷)(比如不做其他处理的softmax中分子分母需要计算exp(x),值过大,最后可能为INF/INF,得到NaN,此时你要确认你使用的softmax中在计算exp(x)做了相关处理(比如减去最大值等等)
答案源地址:pytorch训练模型时出现nan原因整合_Johngo学长 (johngo689.com)
最后,原因是上文的第五条,由于学习率过大导致。