回归实战--新冠预测:用前两天的人数与相关数据预测第三天的人数。
本次复盘包括基础部分和优化部分。
基础部分:代码实现需要pytorch,numpy,matplotlib,time和csv几个库,其中pytorch用于构建,训练和部署深度学习模型(张量,梯度计算等),numpy用于数据预处理,matplotlib用于可视化,time用于记录时间,csv用于读写csv文件以加载或保存数据。
1.数据处理
class CovidDataset(Dataset):
def __init__(self, file_path, mode="train"):
#该函数接收文件路径与模式两个参数,若mode无指定值,则默认以训练模式进行
with open(file_path, "r") as f:
#以只读模式打开file_path路径下的文件,并将其赋值给变量f
ori_data = list(csv.reader(f))
#从文件对象f中读取csv格式的数据,并将其转换为列表
csv_data = np.array(ori_data[1:])[:, 1:].astype(float)
#去掉数据第一列和第一行,并将数据类型转换为浮点型
if mode == "train": #训练集
indices = [i for i in range(len(csv_data)) if i % 5 != 0]
#将csv_data中非5倍数的索引加入索引列表
data = torch.tensor(csv_data[indices, :-1])
#选择索引列表指定的所有行,以及每行除最后一列以外的所有列,转换为张量
self.y = torch.tensor(csv_data[indices, -1])
#提取最后一列转换为张量(最后一列为结果)
elif mode == "val": #验证集
indices = [i for i in range(len(csv_data)) if i % 5 == 0]
#将csv_data中为5倍数的索引加入索引列表
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]) #将数据转换为张量
self.data = (data - data.mean(dim=0, keepdim=True))/data.std(dim=0, keepdim=True)
#标准化处理,mean计算平均值,std计算标准差,dim=0代表沿样本维度,keepdim=True保证输出保持原有维度
self.mode = mode #将传入的mode参数存储为实例属性,便于传递运用
def __getitem__(self, idx): #该函数接收索引参数
if self.mode != "test": #判断是否为测试集,否返回数据和y,是则返回数据
return self.data[idx].float(), self.y[idx].float()
else:
return self.data[idx].float()
def __len__(self): #该函数返回样本数量
return len(self.data)
train_file = "covid.train.csv" #训练数据集的csv文件路径
test_file = "covid.test.csv" #测试数据集的csv文件路径
train_dataset = CovidDataset(train_file, "train") #创建训练数据集实例(使用train_file路径下的文件作为输入,并设置模式为train,以下类似)
val_dataset = CovidDataset(train_file, "val") #创建验证数据集实例
test_dataset = CovidDataset(test_file, "test") #创建测试数据集实例
上段代码定义了一个数据集类,并在类中定义了初始化、获取项目和长度三个函数用于处理数据。初始化函数读取csv文件,根据传入的mode参数决定加载相应数据集并进行标准化;获取项目函数在判断是否为测试集后决定返回特征和标签或仅特征;长度函数则返回数据集中样本数量。
2.构建模型
class MyModel(nn.Module):
def __init__(self, inDim): #初始化函数,接收输入特征维度
super(MyModel, self).__init__() #调用父类nn.Module的构造函数
#定义模型结构
self.fc1 = nn.Linear(inDim, 64) #定义一个全连接层,将输入的特征维度inDim映射到64维输出
self.relu1 = nn.ReLU() #定义激活函数层
self.fc2 = nn.Linear(64, 1) #定义一个全连接层,将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
device = "cuda" if torch.cuda.is_available() else "cpu"
#用于检查是否有可用的NVIDIA GPU,并根据结果选择将张量和模型分配到GPU还是CPU上运行
model = MyModel(inDim=93).to(device) #创建模型实例,并将其分配到相应设备
上段代码定义了一个继承自nn.Module的模型类,该模型由两层全连接层和一层ReLU激活函数构成。同时定义了初始化函数和前向传播函数。
3.训练和验证
batchsize = 16 #定义批次大小
train_loader = DataLoader(train_dataset, batch_size=batchsize, shuffle=True)
#创建用于训练的数据加载器,以下类似
val_loader = DataLoader(val_dataset, batch_size=batchsize, shuffle=True)
def train_val(model, train_loader, val_loader, device, epochs, optimizer, loss, save_path):
model = model.to(device) #将模型分配到相应设备上
plt_train_loss = [] #记录所有轮次的训练损失
plt_val_loss = [] #记录所有伦茨的验证损失
min_val_loss = 999999999999 #初始化最小验证损失
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) #前向传播(和model.forward(x)效果一致)
train_bat_loss = loss(pred, target) #利用预测值和目标值计算损失
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__())
#将当前轮次的平均训练损失添加到列表中
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.__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 graph") #折线图标题
plt.legend(["train", "val"]) #折线图图例
plt.show() #显示绘制的折线图
该函数接收模型,训练数据加载器,验证数据加载器,设备,轮次,优化器,损失计算函数等参数,利用数据对构建的模型进行训练和验证。
4.得出测试结果并保存
test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False) #测试集不能打乱
def evaluate(sava_path, test_loader, device, rel_path):
model = torch.load(sava_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:
#以写模式打开rel_path路径下的文件,并将其赋值给f,newline=''用于避免出现新的空行,可视情况进行修改
csvWriter = csv.writer(f) #创建csv写入对象
csvWriter.writerow(["id", "tested_positive"]) #写入首行信息
for i, value in enumerate(rel): #遍历rel列表,逐行写入
csvWriter.writerow([str(i), str(value)])
print("文件已经保存到"+rel_path) #或 "文件已经保存到{}".format(rel_path)
该函数接收模型路径,测试数据加载器,设备与结果文件路径四个参数,用于加载已经训练好的模型对测试数据集进行预测,并保存预测结果。
5.相应配置以及调用
config = {
"lr": 0.001, #学习率
"epochs": 20, #轮次数
"momentum": 0.9, #动量
"save_path": "covidpre/best_model.pth", #模型保存路径
"rel_path": "pred.csv" #预测结果保存路径
}
loss = nn.MSELoss() #均方误差损失函数
optimizer = optim.SGD(model.parameters(), lr=config["lr"], momentum=config["momentum"])
#优化器,optim.SGD是pytorch中实现随机梯度下降优化算法的类
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"]) #调用预测函数
以上部分为一个完整的模型训练预测过程,其所得结果如下: