提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
本文将逐句注释代码,详细介绍如何使用PyTorch构建一个简单的神经网络模型,来对COVID-19相关的数据进行特征选择和预测。我们将使用SelectKBest方法来进行特征选择,并利用自定义的数据加载器和优化策略来训练我们的模型。最后,我们会评估模型的表现,并将预测结果保存到CSV文件中。
一、环境配置与导入库
首先,我们需要导入必要的Python库。
import os #操作系统接口模块,用于环境变量设置等。
from contourpy.util import data
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" #设置环境变量以解决某些情况下可能出现的重复库加载问题。
from sklearn.feature_selection import SelectKBest, chi2#scikit-learn库中的特征选择工具。
import time #时间模块,用于计时
import torch #PyTorch库,用于深度学习模型构建。
import numpy as np #NumPy库,用于数值计算
import csv #CSV模块,用于读取和写入CSV文件
import pandas as pd #Pandas库,用于数据分析
import torch.nn as nn #PyTorch的神经网络模块
from matplotlib import pyplot as plt #Matplotlib库,用于绘图。
from torch import optim #PyTorch的优化器模块
from torch.utils.data import Dataset, DataLoader #PyTorch的数据集和数据加载器模块
Pandas库,是python+data+analysis的组合缩写,是python中基于numpy和matplotlib的第三方数据分析库,与后两者共同构成了python数据分析的基础工具包,享有数分三剑客之名。
二、主体工程
1.特征选择函数 get_feature_importance
此函数用于从大量特征中选择最具有区分能力的k个特征。
SelectKBest函数,通过相关系数,计算每一列和标签列的相关系数,挑选出相关系数最高的列,减少噪声数据。读数据时,也只需要读重点数据列即可。
SelectKBest函数内置**argsort(scores)[::-1]**函数,可以返回矩阵根据相关系数scores排序后的下标。
#这个函数的目的是, 找到所有的特征种, 比较有用的k个特征, 并打印这些列的名字。
def get_feature_importance(feature_data, label_data, k=4, column=None):
#如果是 CSV 文件,可通过 read_csv() 函数获得特征和标签。
model = SelectKBest(chi2, k=k) #定义一个选择k个最佳特征的函数
feature_data = np.array(feature_data, dtype=np.float64)
X_new = model.fit_transform(feature_data, label_data)#feature_data是特征数据,label_data是标签数据,该函数可以选择出k个特征
scores = model.scores_# 获取每个特征的得分。
indices = np.argsort(scores)[::-1]#[::-1]表示反转一个列表或者矩阵。
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]# 返回选中列的特征和他们的下标。
- def get_feature_importance(feature_data, label_data, k=4, column=None): 定义一个名为get_feature_importance的函数,接收特征矩阵feature_data、标签向量label_data、选择的特征数量k以及原始列名column作为参数。
- model = SelectKBest(chi2, k=k): 创建一个SelectKBest对象,使用卡方检验来选择前k个最佳特征。
- feature_data = np.array(feature_data, dtype=np.float64): 将特征数据转换为NumPy数组,并指定数据类型为浮点数。
- X_new = model.fit_transform(feature_data, label_data): 使用SelectKBest对象拟合特征数据并选择最佳特征。
- k_best_features = [column[i] for i in indices[0:k].tolist()]:根据排序后的索引选择前k个特征的名称
2.自定义数据集类 CovidDataset
这个类继承自torch.utils.data.Dataset,允许我们自定义数据集的获取方式。
- column = ori_data[0]:提取第一行作为列名。
- def init(self, file_path, mode=“train”, all_feature=True, feature_dim=6): 初始化方法,接收文件路径file_path、模式mode、是否使用所有特征all_feature以及特征维度feature_dim作为参数。
- csv_data = np.array(ori_data[1:])[:, 1:].astype(float): 提取第二行及以后的数据,并去掉第一列,将其转换为浮点型NumPy数组。
- self.data = data - data.mean(dim=0, keepdim=True) / data.std(dim=0, keepdim=True): 对特征数据进行标准化处理。
class CovidDataset(Dataset):#定义一个名为CovidDataset的类,继承自Dataset。
def __init__(self, file_path, mode="train", all_feature=True, feature_dim=6):#默认为train模式
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]#1:-1 ———不包含第一列和最后一列
label_data = np.array(ori_data[1:])[:, -1]#提取标签数据,即最后一列。
if all_feature:
col = np.array([i for i in range(0, 93)])
else:#否则,调用get_feature_importance函数选择前feature_dim个特征。
_, col = get_feature_importance(feature, label_data, feature_dim, column)
#调用get_feature_importance函数选择前feature_dim个特征,并忽略返回的第一个值。
col = col.tolist()#将特征索引数组转换为列表。
if mode == "train":
indices = [i for i in range(len(csv_data)) if i % 5 != 0]
data = torch.tensor(csv_data[indices, :-1])#data赋予self之后,getitem函数中也能使用data
self.y = torch.tensor(csv_data[indices, -1])
elif mode == "val":
indices = [i for i in range(len(csv_data)) if i % 5 == 0]#逢5取1
data = torch.tensor(csv_data[indices, :-1])
self.y = torch.tensor(csv_data[indices, -1])
else:
indices = [i for i in range(len(csv_data))]
data = torch.tensor(csv_data[indices])
data = data[:, col]#col为通过get_feature_importance取到的比较重要的4列
self.data = data - data.mean(dim=0, keepdim=True) / data.std(dim=0, keepdim=True)
self.mode = mode #存储当前模式。
def __getitem__(self, idx):#idx为下标,返回一个数据
if self.mode != "test":
return self.data[idx].float(), self.y[idx].float()#float用于将data从64位变为32位
else:
return self.data[idx].float()#测试集没有y。返回第idx个样本的特征,并转换为32位浮点数。
def __len__(self):
return len(self.data)
每个Dataset类均包含三个函数:
- init:初始化方法,以接收文件路径、模式、是否使用所有特征以及特征维度feature_dim作为参数。
- getitem:定义获取单个样本的方法。
- len:定义获取数据集长度的方法。
3.模型定义 MyModel
这是一个简单的全连接神经网络,包含两个线性层和一个ReLU激活函数。
class MyModel(nn.Module):#定义一个名为MyModel的类,继承自nn.Module。
def __init__(self, inDim):#初始化方法,接收输入维度inDim作为参数。
super(MyModel, self).__init__()#调用父类的初始化方法。
self.fc1 = nn.Linear(inDim, 64)#第一个全连接层,输入维度为inDim,输出维度为64。
self.relu = nn.ReLU()#ReLU激活函数
self.fc2 = nn.Linear(64, 1)#第二个全连接层,输入维度为64,输出维度为1。
def forward(self, x):#前向传播方法
x = self.fc1(x)# 输入数据通过第一个全连接层。
x = self.relu(x)# 经过ReLU激活函数。
x = self.fc2(x)# 通过第二个全连接层。
if len(x.size()) > 1: #如果输出张量的维度大于1,则进行squeeze操作。
return x.squeeze(dim=1) #压缩最后一个维度。
return x
3.训练与验证函数 train_val
实现模型的训练和验证过程。
对于所有数据,每次取一批数据(for batch_x, batch_y in train_loader)让每一批数据经过 f(x) 得到y’,然后与真实值y求loss,然后去更新模型参数。一个批次更新一次模型。直到所有数据都取完一轮,便完成了一个epoch。
一般模型会训练多轮(for epoch in range(epochs)),训练多轮后得到一个不错的模型,可以将这个模型保存下来。
下方代码实现:如果当前验证损失小于最小验证损失,则更新最小验证损失并保存模型。
if val_loss < min_val_loss:
torch.save(model, save_path)
min_val_loss = val_loss#更新最小验证损失
对于验证过程,不会进行梯度回传,因为验证过程不会更新模型,也不积攒梯度。
def train_val(model, train_loader, val_loader, device, epochs, optimizer, loss_fn, save_path):
model = model.to(device)#将模型移动到指定设备(CPU或GPU)
plt_train_loss = [] #初始化训练损失列表,记录所有轮次的loss值
plt_val_loss = []#初始化验证损失列表。
min_val_loss = float("inf")# 初始化最小验证损失为无穷大。
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, target = batch_x.to(device), batch_y.to(device)#将输入数据和目标数据移动到指定设备。
pred = model(x)#进行前向传播,得到预测结果。
train_bat_loss = loss_fn(pred, target)#计算当前批次的损失
train_bat_loss.backward()#反向传播,计算梯度。
optimizer.step()#更新模型参数。
optimizer.zero_grad()#梯度清零
train_loss += train_bat_loss.cpu().item()#计算所有批次的总loss
plt_train_loss.append(train_loss / train_loader.__len__())
#所有批次的loss除以批次数,等于平均每个样本的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_bat_loss = loss_fn(pred, target)#无梯度回传
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 secs 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()
4.测试与结果输出 evaluate
该函数用于在测试集上评估模型性能,并将结果写入CSV文件。
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:#打开结果文件进行写入。
csv_writer = csv.writer(f)#定义一个写指针
csv_writer.writerow(["id", "tested_positive"])#写入表头。
for i, value in enumerate(rel):#遍历结果列表。
csv_writer.writerow([str(i), str(value)])#将结果写入CSV文件。
print("文件已经保存到" + rel_path)
- def evaluate(save_path, test_loader, device, rel_path): 定义一个名为evaluate的函数,接收保存路径save_path、测试数据加载器test_loader、设备device以及结果文件路径rel_path作为参数。
- model = torch.load(save_path).to(device): 加载保存的模型并移动到指定设备。
4.主程序执行部分
设置超参数、实例化数据集、模型以及优化器,并开始训练和评估。
all_feature = True #是否使用所有特征。
if all_feature: #如果使用所有特征,则特征维度为93。
feature_dim = 93 #特征维度为93。
else: #否则,特征维度为6。
feature_dim = 6 #特征维度为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)
test_dataset = CovidDataset(test_file, "test", all_feature=all_feature, feature_dim=feature_dim)
val_dataset = CovidDataset(train_file, "val", all_feature=all_feature, feature_dim=feature_dim)
#验证集也是从训练集中分出来的
batch_size = 16#设置批量大小为16。
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)#shuffle打乱,每个batch随机取16个
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False)#一定不能打乱
model = MyModel(inDim=feature_dim)#实例化模型。inDim特征维度数
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"
}
loss = nn.MSELoss() #定义均方误差损失函数。
#定义随机梯度下降优化器。
optimizer = optim.SGD(model.parameters(), lr=config["lr"], momentum=config["momentum"])
#开始训练和验证。
train_val(model, train_loader, val_loader, device, config["epochs"], optimizer, loss, config["save_path"])
#在测试集上评估模型性能,并将结果保存到CSV文件中。
evaluate(config["save_path"], test_loader, device, config["rel_path"])#验证
总结
通过上述步骤,我们可以看到整个流程包含了数据预处理、特征选择、模型定义、训练和验证,以及最终的测试和结果输出。这种端到端的方法能够帮助我们更好地理解和掌握深度学习模型的应用。