以 CIFAR10 数据集为例,分类问题(10分类)
模型训练(train)套路
model.py
import torch
from torch import nn
# 搭建神经网络
class Overwatch(nn.Module):
def __init__(self):
super(Overwatch, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3, 32, 5, 1, 2),
nn.MaxPool2d(2),
nn.Conv2d(32, 32, 5, 1, 2),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 5, 1, 2),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(64*4*4, 64),
nn.Linear(64, 10),
)
def forward(self, x):
x = self.model(x)
return x
if __name__ == '__main__': # 输入main即可跳出完整代码
OW = Overwatch()
input = torch.ones((64, 3, 32, 32))
output = OW(input)
print(output.shape)
train.py
from model import * # 引入模型
import torchvision.datasets
from torch.utils.data import DataLoader
# 准备数据集,CIFAR10数据集是PIL Image,要转换为tensor数据类型
train_data = torchvision.datasets.CIFAR10('../data', train=True, transform=torchvision.transforms.ToTensor(),download=True)
test_data = torchvision.datasets.CIFAR10('../data', train=False, transform=torchvision.transforms.ToTensor(),download=True)
# 查看训练数据集和测试数据集数量
train_data_size = len(train_data)
test_data_size = len(test_data)
print('训练数据集长度为:{}'.format(train_data_size))
print('测试数据集长度为:{}'.format(test_data_size))
# 利用DataLoader加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
# 创建网络模型
OW = Overwatch()
# 损失函数
loss_function = nn.CrossEntropyLoss()
# 优化器
learning_rate = 0.01
optimizer = torch.optim.SGD(OW.parameters(), lr=learning_rate)
# 设置训练网络的参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 10
for i in range(epoch): # 0~9
print('-----第 {} 轮训练开始-----'.format(i+1)) # 1~10
# 训练步骤开始
for data in train_dataloader:
imgs, targets = data
outputs = OW(imgs)
loss = loss_function(outputs, targets) # 输出与target计算loss
# 优化器优化模型
optimizer.zero_grad() # 梯度清零:把上一步训练的每个参数的梯度清零
loss.backward() # 反向传播:调用反向传播得到每个要更新参数的梯度
optimizer.step() # 参数优化:每个参数根据上一步得到的梯度进行优化
total_train_step = total_train_step + 1
if total_train_step % 100 == 0: # 每100次才打印
print('训练次数:{},Loss:{}'.format(total_train_step, loss.item()))
tips:
print(loss) 和 print(loss.item()) 的区别
import torch
a = torch.tensor(5)
print(a) # tensor(5)
print(a.item()) # 5
如何知道模型是否训练好,或达到需求?
每次训练完一轮就进行测试,以测试数据集上的损失或正确率来评估模型有没有训练好
测试过程中不需要对模型进行调优,利用现有模型进行测试
# 测试步骤开始(评估模型)
total_test_loss = 0
with torch.no_grad(): # 无需梯度调优
for data in test_dataloader:
imgs, targets = data
outputs = OW(imgs)
loss = loss_function(outputs, targets)
total_test_loss = total_test_loss + loss.item()
print('整体测试集上的Loss:{}'.format(total_test_loss))
与 TensorBoard 结合
正确率的实现(对分类问题)
即便得到整体测试集上的 loss,也不能很好说明在测试集上的表现效果
- 在分类问题中可以用正确率表示(下述代码改进)
- 在目标检测/语义分割中,可以把输出放在tensorboard里显示,看测试结果
outputs = torch.tensor([[0.1,0.2],
[0.3,0.4]])
print(outputs.argmax(1)) # 0或1表示方向,1为横向比较大小. 运行结果:tensor([1, 1])
argmax相当于就是返回数组中最大值的索引
preds 预测
preds = outputs.argmax(1)
targets = torch.tensor([0,1])
print(preds == targets) # tensor([False, True])
print(preds == targets.sum()) # tensor(1),对应位置相等的个数
# 测试步骤开始(评估模型)
# 初始化测试集上的总损失和总准确率
total_test_loss = 0
*total_accuracy = 0
with torch.no_grad(): # 无需梯度调优
for data in test_dataloader:
imgs, targets = data
outputs = OW(imgs)
loss = loss_function(outputs, targets)
total_test_loss = total_test_loss + loss.item()
*accuracy = (outputs.argmax(1) == targets).sum() # 得到预测正确的样本数量
*total_accuracy = total_accuracy + accuracy
print('整体测试集上的Loss:{}'.format(total_test_loss))
*print('整体测试集上的正确率:{}'.format(total_accuracy/test_data_size))
# 画test_accuracy & test_loss
*writer.add_scalar('test_accuracy', total_accuracy/test_data_size, total_test_step)
writer.add_scalar('test_loss', total_test_loss, total_test_step)
total_test_step = total_test_step + 1
完整代码
from model import * # 引入模型
import torchvision.datasets
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 准备数据集,CIFAR10数据集是PIL Image,要转换为tensor数据类型
train_data = torchvision.datasets.CIFAR10('../data', train=True, transform=torchvision.transforms.ToTensor(), download=True)
test_data = torchvision.datasets.CIFAR10('../data', train=False, transform=torchvision.transforms.ToTensor(), download=True)
# 查看训练数据集和测试数据集数量
train_data_size = len(train_data)
test_data_size = len(test_data)
print('训练数据集长度为:{}'.format(train_data_size))
print('测试数据集长度为:{}'.format(test_data_size))
# 利用DataLoader加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
# 创建网络模型
OW = Overwatch()
# 损失函数
loss_function = nn.CrossEntropyLoss()
# 优化器
learning_rate = 0.01
optimizer = torch.optim.SGD(OW.parameters(), lr=learning_rate)
# 设置训练网络的参数
total_train_step = 0 # 记录训练的次数
total_test_step = 0 # 记录测试的次数
epoch = 10 # 训练的轮数
# 添加TensorBoard
writer = SummaryWriter('../logs')
for i in range(epoch):
print('-----第 {} 轮训练开始-----'.format(i+1))
# 训练步骤开始
OW.train()
for data in train_dataloader:
imgs, targets = data
outputs = OW(imgs)
loss = loss_function(outputs, targets) # 输出与target计算loss
# 优化器优化模型
optimizer.zero_grad() # 梯度清零:把上一步训练的每个参数的梯度清零
loss.backward() # 反向传播:调用反向传播得到每个要更新参数的梯度
optimizer.step() # 参数优化:每个参数根据上一步得到的梯度进行优化
total_train_step = total_train_step + 1
if total_train_step % 100 == 0: # 每100次才打印
print('训练次数:{},Loss:{}'.format(total_train_step, loss.item()))
# 画train_loss
writer.add_scalar('train_loss', loss.item(), total_train_step)
# 测试步骤开始(评估模型)
OW.eval()
# 初始化测试集上的总损失和总准确率
total_test_loss = 0
total_accuracy = 0
with torch.no_grad(): # 无需梯度调优
for data in test_dataloader:
imgs, targets = data
outputs = OW(imgs)
loss = loss_function(outputs, targets)
total_test_loss = total_test_loss + loss.item()
accuracy = (outputs.argmax(1) == targets).sum() # 得到预测正确的样本数量
total_accuracy = total_accuracy + accuracy
print('整体测试集上的Loss:{}'.format(total_test_loss))
print('整体测试集上的正确率:{}'.format(total_accuracy/test_data_size))
# 画test_accuracy & test_loss
writer.add_scalar('test_accuracy', total_accuracy/test_data_size, total_test_step)
writer.add_scalar('test_loss', total_test_loss, total_test_step)
total_test_step = total_test_step + 1
# 保存模型
torch.save(OW, 'OW_{}.pth'.format(i)) # 每一轮保存一个结果
# torch.save(OW.state_dict,'OW_{}.pth'.format(i)) save2
print('模型已保存')
writer.close()
总结:
准备数据→加载数据→准备模型→设置损失函数→设置优化器→开始训练→最后验证→结果聚合展示
利用GPU训练
方式一
网络模型:
数据(包括输入、标注):
训练过程
损失函数:
更好的写法:这种写法在CPU和GPU上都可以跑,优先在GPU上跑
比较CPU/GPU训练时间
为了比较时间,引入 time 这个 package
方式二(更常用)
以下两种写法对于单显卡来说等价:
device = torch.device("cuda")
device = torch.device("cuda:0")
语法糖(一种语法的简写),程序在 CPU 或 GPU/cuda 环境下都能运行:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
通过这个变量可以控制是在CPU上运行还是GPU(改成 "cuda" 或 "cuda:0" )上运行
OW = OW.to(device)
loss_function.to(device)
imgs = imgs.to(device)
targets = targets.to(device)
train_gpu.py
from model import * # 引入模型
import torch
import time
import torchvision.datasets
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 定义训练设备
device = torch.device('cuda')
# 准备数据集,CIFAR10数据集是PIL Image,要转换为tensor数据类型
train_data = torchvision.datasets.CIFAR10('../data', train=True, transform=torchvision.transforms.ToTensor(), download=True)
test_data = torchvision.datasets.CIFAR10('../data', train=False, transform=torchvision.transforms.ToTensor(), download=True)
# 查看训练数据集和测试数据集数量
train_data_size = len(train_data)
test_data_size = len(test_data)
print('训练数据集长度为:{}'.format(train_data_size))
print('测试数据集长度为:{}'.format(test_data_size))
# 利用DataLoader加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
# 创建网络模型
OW = Overwatch()
# 将OW送给device
OW = OW.to(device)
# 损失函数
loss_function = nn.CrossEntropyLoss()
loss_function.to(device)
# 优化器
learning_rate = 0.01
optimizer = torch.optim.SGD(OW.parameters(), lr=learning_rate)
# 设置训练网络的参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 10
# 添加TensorBoard
writer = SummaryWriter('../logs')
start_time = time.time() # 记录下此时的时间,赋值给开始时间
for i in range(epoch):
print('-----第 {} 轮训练开始-----'.format(i+1))
# 训练步骤开始
OW.train()
for data in train_dataloader:
imgs, targets = data
imgs = imgs.to(device)
targets = targets.to(device)
outputs = OW(imgs)
loss = loss_function(outputs, targets) # 输出与target计算loss
# 优化器优化模型
optimizer.zero_grad() # 梯度清零:把上一步训练的每个参数的梯度清零
loss.backward() # 反向传播:调用反向传播得到每个要更新参数的梯度
optimizer.step() # 参数优化:每个参数根据上一步得到的梯度进行优化
total_train_step = total_train_step + 1
if total_train_step % 100 == 0: # 每100次才打印
end_time = time.time()
print(end_time - start_time) # 第一次训练100次所花费的时间
print('训练次数:{},Loss:{}'.format(total_train_step, loss.item()))
# 画train_loss
writer.add_scalar('train_loss', loss.item(), total_train_step)
# 测试步骤开始(评估模型)
# 初始化测试集上的总损失和总准确率
OW.eval()
total_test_loss = 0
total_accuracy = 0
with torch.no_grad(): # 无需梯度调优
for data in test_dataloader:
imgs, targets = data
imgs = imgs.to(device)
targets = targets.to(device)
outputs = OW(imgs)
loss = loss_function(outputs, targets)
total_test_loss = total_test_loss + loss.item()
accuracy = (outputs.argmax(1) == targets).sum() # 得到预测正确的样本数量
total_accuracy = total_accuracy + accuracy
print('整体测试集上的Loss:{}'.format(total_test_loss))
print('整体测试集上的正确率:{}'.format(total_accuracy/test_data_size))
# 画test_accuracy & test_loss
writer.add_scalar('test_accuracy', total_accuracy/test_data_size, total_test_step)
writer.add_scalar('test_loss', total_test_loss, total_test_step)
total_test_step = total_test_step + 1
# 保存模型
torch.save(OW, 'OW_{}.pth'.format(i)) # 每一轮保存一个结果
# torch.save(OW.state_dict,'OW_{}.pth'.format(i)) save2
print('模型已保存')
writer.close()
模型验证(demo)套路
核心:利用已经训练好的模型,给它提供输入进行测试
注意:load的模型当时保存的时候是用gpu训练的,因此这里需要定义device到cuda,再把image进行to(device)操作:device = torch.device('cuda')、image = image.to(device)
或者 model =torch.load("OW_30.pth",map_location=torch.device('cpu'))
采用GPU训练的模型,在CPU上加载,要从GPU上映射到CPU上
模型要求是四维的输入 [batch_size,channel,length,width],但是获得的图片是三维的 —— 图片没有指定 batch_size(网络训练过程中是需要 batch_size 的,而图片输入是三维的,需要reshape() 一下)
test.py:
import torch
import torchvision.transforms
from PIL import Image
# from model import *
# device = torch.device('cuda')
image_path = '../imgs/dog.png' # 或绝对路径
image = Image.open(image_path)
print(image)
image = image.convert('RGB')
'''因为png格式是四通道,除了RGB三通道外,还有一个透明度通道,所以要调用上述语句保留其颜色通道
如果图片本来就是三颜色通道,经过此操作,不变。加上这一步后,可以适应 png jpg 各种格式的图片'''
# 网络模型的输入只能是32x32,进行一个Resize()
transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32, 32)), # 32x32大小的PIL Image
torchvision.transforms.ToTensor()]) # 转为Tensor数据类型
image = transform(image)
print(image.shape) # torch.Size([3, 32, 32])
# model = torch.load('OW_9.pth')
model =torch.load("OW_9.pth",map_location=torch.device('cpu'))
print(model)
image = torch.reshape(image,(1, 3, 32, 32))
# image = image.to(device)
model.eval() # 模型转化为测试类型
with torch.no_grad(): # 节约内存和性能
output = model(image)
print(output)
print(output.argmax(1))
CIFAR10 对应的真实类别:
GitHub
下载代码用 PyCharm 打开后,查看代码里有没有 required=True,若有,删掉 required=True ,加一个默认值 default="./dataset/maps" ,就可以在 PyCharm 里右键运行了
即找到所有 required=True 的参数,将它删去并添加上默认值default
required的意思就是执行代码的时候需要手动输入文件目录,改为默认之后就不用再输入了