模型训练+验证(train+demo)套路

以 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的意思就是执行代码的时候需要手动输入文件目录,改为默认之后就不用再输入了

  • 16
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值