我们先以典型的二分类问题,过一遍pytorch的全部过程:
这个是在代码编写之前,我们先做到心中有数的一个比较重要的过程
二分类,标签信息存储于.txt文件中
数据准备
- 数据读取,转化为模型能读取的格式,用到dataset,dataloader等。Dataset类必须重写三个函数,再用dataloader进行封装。
- 数据预处理,可能使用
resize()
和totensor
等方法
模型
数据模块构建完毕,需要输入到模型中,所以我们需要构建神经网络模型,模型接收数据并前向传播处理,输出二分类概率向量。
- 模型搭建:可能用到
nn.Module
等nn系列
优化
损失函数和优化器的搭配。这二者的关系很紧密:
损失函数用于衡量模型输出与标签之间的差异,并通过反向传播获得每个参数的梯度,有了梯度,就可以用优化器对权重进行更新。 这一步涉及LossFunction
,optim
以及学习率调整
迭代
有了模型参数更新的必备组件,接下来需要一遍又一遍地给模型喂数据,监控模型训练状态,这时候就需要for循环,不断地从dataloader里取出数据进行前向传播,反向传播,参数更新,观察loss、acc,周而复始。当达到条件,如最大迭代次数、某指标达到某个值时,进行模型保存,并break循环,停止训练。
有了上面在代码写之前的思考和每部分的宏观把握,开始写代码:
import os
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
import torchvision.transforms as transforms
from PIL import Image
def main():
# 思考:如何实现你的模型训练?第一步干什么?第二步干什么?...第n步...
# step 1/4 : 数据模块:构建dataset, dataloader,实现对硬盘中数据的读取及设定预处理方法
# step 2/4 : 模型模块:构建神经网络,用于后续训练
# step 3/4 : 优化模块:设定损失函数与优化器,用于在训练过程中对网络参数进行更新
# step 4/4 : 迭代模块: 循环迭代地进行模型训练,数据一轮又一轮的喂给模型,不断优化模型,直到我们让它停止训练
# step 1/4 : 数据模块
class COVID19Dataset(Dataset):
def __init__(self, root_dir, txt_path, transform=None):
"""
获取数据集的路径、预处理的方法
"""
self.root_dir = root_dir
self.txt_path = txt_path
self.transform = transform
self.img_info = [] # [(path, label), ... , ]
self.label_array = None
self._get_img_info()
# 重写这个函数
def __getitem__(self, index):
"""
输入标量index, 从硬盘中读取数据,并预处理,to Tensor
:param index:
:return:
"""
path_img, label = self.img_info[index]
img = Image.open(path_img).convert('L')
if self.transform is not None:
img = self.transform(img)
return img, label
# 重写这个函数
def __len__(self):
if len(self.img_info) == 0:
raise Exception("\ndata_dir:{} is a empty dir! Please checkout your path to images!".format(
self.root_dir)) # 代码具有友好的提示功能,便于debug
return len(self.img_info)
def _get_img_info(self):
"""
实现数据集的读取,将硬盘中的数据路径,标签读取进来,存在一个list中
path, label
:return:
"""
# 读取txt,解析txt
with open(self.txt_path, "r") as f:
txt_data = f.read().strip()
txt_data = txt_data.split("\n")
self.img_info = [(os.path.join(self.root_dir, i.split()[0]), int(i.split()[2]))
for i in txt_data]
root_dir = r"D:\pytorch-tutorial-2nd\data\datasets\covid-19"
img_dir = os.path.join(root_dir, "imgs")
path_txt_train = os.path.join(root_dir, "labels", "train.txt")
path_txt_valid = os.path.join(root_dir, "labels", "valid.txt")
transforms_func = transforms.Compose([
transforms.Resize((8, 8)),
transforms.ToTensor(),
])
train_data = COVID19Dataset(root_dir=img_dir, txt_path=path_txt_train, transform=transforms_func)
valid_data = COVID19Dataset(root_dir=img_dir, txt_path=path_txt_valid, transform=transforms_func)
train_loader = DataLoader(dataset=train_data, batch_size=2)
valid_loader = DataLoader(dataset=valid_data, batch_size=2)
# step 2/4 : 模型模块
class TinnyCNN(nn.Module):
def __init__(self, cls_num=2):
super(TinnyCNN, self).__init__()
# 用一个简单的卷积
self.convolution_layer = nn.Conv2d(1, 1, kernel_size=(3, 3))
# 接一个全连接
self.fc = nn.Linear(36, cls_num) # 36x2的
def forward(self, x):
x = self.convolution_layer(x)
x = x.view(x.size(0), -1) # 转置
out = self.fc(x)
return out
model = TinnyCNN(2)
# step 3/4 : 优化模块
loss_f = nn.CrossEntropyLoss() # 交叉熵
# SGD
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4)
scheduler = optim.lr_scheduler.StepLR(optimizer, gamma=0.1, step_size=50)
# step 4/4 : 迭代模块
for epoch in range(100):
# 训练集训练
model.train()
for data, labels in train_loader:
# forward & backward
outputs = model(data)
optimizer.zero_grad() # 关键的清零
# loss 计算
loss = loss_f(outputs, labels)
loss.backward() # 反向传播
optimizer.step() # 优化迭代一步
# 计算分类准确率
_, predicted = torch.max(outputs.data, 1)
correct_num = (predicted == labels).sum()
acc = correct_num / labels.shape[0]
print("Epoch:{} Train Loss:{:.2f} Acc:{:.0%}".format(epoch, loss, acc))
# print(predicted, labels)
# 验证集验证
model.eval()
for data, label in valid_loader:
# forward
outputs = model(data)
# loss 计算
loss = loss_f(outputs, labels)
# 计算分类准确率
_, predicted = torch.max(outputs.data, 1)
correct_num = (predicted == labels).sum()
acc_valid = correct_num / labels.shape[0]
print("Epoch:{} Valid Loss:{:.2f} Acc:{:.0%}".format(epoch, loss, acc_valid))
# 添加停止条件
if acc_valid == 1:
break
# 学习率调整
scheduler.step()
if __name__ == "__main__":
main()
写完上述代码,基本对pytorch应用dl有了一个流程上比较清晰的认识,但我还有以下疑问:
- ToTensor有哪些操作?
- 自己如何编写Dataset
- DataLoader有什么功能?如何使用?有什么需要注意的?
- 模型如何按自己的数据流程搭建
- nn有哪些网络层可以调用
- 损失函数有哪些?
- 优化器是如何更新model参数的?
- 学习率调整有哪些方法?如何设置它们的参数
- model.train()与model.eval()作用是什么?
- optimizer.zero_grad()是做什么?为什么要梯度清零?
- scheduler.step() 作用是什么?
未完待续…