[零基础][最简单的教程]图像多分类问题的解决——基于resnet50的pytorch的实现


前言

文章的宗旨是使小白不懂原理也可以快速入手,傻瓜式操作。

整个文章的逻辑从所有深度学习的模型的通用框架开始介绍,再推广到用pytorch进行图像识别问题的模型基本框架。由于笔者也是初学深度学习与pytorch,因此仅以此篇,分享给同样初学pytorch,且对深度学习的一些原理不是很了解的朋友,帮助大家更快的上手一个图像分类项目
PS:如果读者觉得啰嗦,直接跳到代码部分进行复制就好了,那部分教你如何正确的复制与微调。


提示:以下是本篇文章正文内容,下面案例可供参考

一、开始任务的前提条件

1.装了anoconda与pytorch

具体教程可以搜B站UP主“深度之眼官方账号”的视频“pytorch安装指南!”
在这里插入图片描述
建议在安装前把原来机器上的python给卸载了,编译器直接用pycharm,这样就可以避免出现一堆莫名其妙的BUG,导致安装一天都失败
注1:如果浏览器下载慢的话,可以把链接复制到迅雷下载,迅雷会员便宜。
注2:用pycharm创建项目后,要给它的虚拟环境配置解释器,得用anoconda里的python.exe,所以你得记住anoconda的安装路径是在哪里,不然后期慢慢搜索会很折磨的
注3:如果你没有GUP,跟着教程安装的pytorch时,得下载cpu版本,而不是cuda版本

二、深度学习通用框架

本部分转载至知乎的一篇文章,强烈建议大家先看一眼,保证心中有数。
链接: 点击进入知乎原文.

所有的深度学习模型过程都可以形式化如下图:
在这里插入图片描述
1、输入处理模块 (X 输入数据,变成网络能够处理的Tensor类型)

2、模型构建模块 (主要负责从输入的数据,得到预测的y^, 这就是我们经常说的前向过程)

3、定义代价函数和优化器模块 (注意,前向过程只会得到模型预测的结果,并不会自动求导和更新,是由这个模块进行处理)

4、构建训练过程 (迭代训练过程,就是上图表情包的训练迭代过程)

这几个模块分别与上图的数字标号1,2,3,4进行一一对应!

三、修改代码前,使用者需要明确的问题

本框架无须过多的理解深度学习的很多概念,只要你完成三个事情即可复制本代码,改一下直接使用。

1.图片分成三个集,并放在正确的位置

①图片分集
假设你有5个类,每个类有10张图片,你可以把每个类的图片都按照8:1:1的比例分成训练集,验证集和测试集,每个集中都装着5个文件夹,这5个文件夹名字为类名,装着这个类的图片。
训练集的作用是训练数据,验证集的作用是在每轮模型训练后,时时测试模型的性能
测试集是,当所有轮数都跑完之后,再测试,看看模型性能究竟如何
那么我分完之后,这三份图片应该放哪里呢?

②正确的位置
假设你的.py文件放在文件夹pythonProject里,那么你需要在文件夹A里面再创建一个文件夹,名为dataset 。然后你进入dataset,在dataset里面创建三个文件夹,分别为train,valid,test

(这步一定要做对,除了文件夹pythonProject的名字可以任意取,其余文件夹的名字必须和我一样)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.明确你是几分类问题

如果你有5类,你就是5分类问题,到时候代码里只用改几处就可以了

3.明确你是使用的模型网络

本文使用的模型是resnet50,你可以根据你需要的网络自行修改,我会在代码里指出哪里需要修改

点击链接,里面提供了pytorch支持的模型网络
链接: 点击查看pytorch支持的网络.

四、图像分类通用pytorch框架——具体代码实现

跟深度学习的通用框架一样,分四步
一、输入处理模块
0.引入库
1.数据增强
2.加载数据集
二、模型构建模块
三、定义代价函数和优化器模块
四、构建训练过程
3.训练与验证
4.训练与验证结果的可视化展示
5.测试

一、输入处理模块

0.引入库

代码如下:

这个不要更改,直接复制就行

#0导入库
import torch
from torchvision import datasets, models, transforms
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
import time

import numpy as np
import matplotlib.pyplot as plt
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
print("0导入库完成")

1.数据增强

代码如下(示例):
这个也直接复制就行

image_transforms = {
    'train': transforms.Compose([
        transforms.RandomResizedCrop(size=256, scale=(0.8, 1.0)), #随机裁剪到256*256
        transforms.RandomRotation(degrees=15),#随机旋转
        transforms.RandomHorizontalFlip(p=0.5), #依概率水平旋转
        transforms.CenterCrop(size=224),#中心裁剪到224*224符合resnet的输入要求
        transforms.ToTensor(),#填充
        transforms.Normalize([0.485, 0.456, 0.406],#转化为tensor,并归一化至[0,-1]
                             [0.229, 0.224, 0.225])
    ]),
    'valid': transforms.Compose([
        transforms.Resize(size=256),#图像变换至256
        transforms.CenterCrop(size=224),
        transforms.ToTensor(),#填充
        transforms.Normalize([0.485, 0.456, 0.406],
                             [0.229, 0.224, 0.225])
    ])
}
print("1.数据增强完成")

2.加载数据集

代码如下(示例):
这里只有一个参数需要改,就是num_classes=5这一行
需要把5改成你自己的类数

#2加载数据集
dataset = 'dataset'
train_directory = os.path.join(dataset, 'train') #训练集的路径,os.path.join()函数是路径拼接函数
valid_directory = os.path.join(dataset, 'valid') #验证集的路径
test_directory = os.path.join(dataset , 'test')  #测试集路径
batch_size = 32 #分成32组
num_classes = 5 #图像的类,这里需要改,其余不变!!!

data = {
    'train': datasets.ImageFolder(root=train_directory, transform=image_transforms['train']),
     #imagefolder(root, transform),前者是图片路径,后者是对图片的变换,生成的数据类型是dataset
    'valid': datasets.ImageFolder(root=valid_directory, transform=image_transforms['valid']),
    'test': datasets.ImageFolder(root=test_directory, transform=image_transforms['valid'])
}       #把dataset类型的数据放在数组里,便于通过键值调用

train_data_size = len(data['train'])#训练集的大小
valid_data_size = len(data['valid'])#验证集的大小
test_data_size = len(data['test'])#验证集的大小


train_data = DataLoader(data['train'], batch_size=batch_size, shuffle=True)
  #DataLoader(dataset, batch_size, shuffle) dataset数据类型;分组数;是否打乱
valid_data = DataLoader(data['valid'], batch_size=batch_size, shuffle=True)
test_data = DataLoader(data['test'], batch_size=batch_size, shuffle=True)

print("训练集数据量为:{},验证集数据量为:{},验证集数据量为{}".format(train_data_size, valid_data_size,test_data_size))
  #分别打印出训练集和验证集的样本数量
print("2.加载数据完毕")

二、加载模型

代码如下(示例):
这里有两处需要修改
1.nn.Linear(256,5)
其中这个5需要改成你的所需分类数
2.resnet50 = models.resnet50(pretrained=True)
可以把resnet50换成你想要的网络模型,如你要用alexnet,则改成:
alexnet = models.alexnet(pretrained=True)


#3加载模型,迁移学习

resnet50 = models.resnet50(pretrained=True) #开启预训练

for param in resnet50.parameters():#由于预训练的模型中的大多数参数已经训练好了,因此将requires_grad字段重置为false。
    param.requires_grad = False
   #为了适应自己的数据集,将ResNet-50的最后一层替换为,
    #将原来最后一个全连接层的输入喂给一个有256个输出单元的线性层,
   # 接着再连接ReLU层和Dropout层,然后是256 x 5的线性层,输出为5通道的softmax层。
fc_inputs = resnet50.fc.in_features
resnet50.fc = nn.Sequential(
    nn.Linear(fc_inputs, 256),
    nn.ReLU(),
    nn.Dropout(0.4),
    nn.Linear(256, 5),
    nn.LogSoftmax(dim=1)
)


三、定义损失函数、优化器

代码如下(示例):
复制即可

#定义损失函数和优化器。
loss_func = nn.NLLLoss()
optimizer = optim.Adam(resnet50.parameters())
print("3.模型载入完毕")

四、构建训练过程(训练、验证、测试)

3.训练与验证

代码如下(示例):
这个直接复制,这个代码段定义了一个训练与验证函数(把这个过程封装起来了)

#3训练模型 与 验证模型
def train_and_valid(model, loss_function, optimizer, epochs=25):
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")#设备自行判断
    history = []
    best_acc = 0.0
    best_epoch = 0

    for epoch in range(epochs):
        epoch_start = time.time() #每轮开始时间记录
        print("Epoch: {}/{}".format(epoch + 1, epochs)) #显示这是第多少轮

        model.train()  #启用 Batch Normalization 和 Dropout。(随机去除神经元)

        train_loss = 0.0
        train_acc = 0.0
        valid_loss = 0.0
        valid_acc = 0.0

        for i, (inputs, labels) in enumerate(train_data):#训练数据
            inputs = inputs.to(device)
            labels = labels.to(device)

            # 因为这里梯度是累加的,所以每次记得清零
            optimizer.zero_grad()

            outputs = model(inputs)

            loss = loss_function(outputs, labels)

            loss.backward()

            optimizer.step()

            train_loss += loss.item() * inputs.size(0)

            ret, predictions = torch.max(outputs.data, 1)
            correct_counts = predictions.eq(labels.data.view_as(predictions))

            acc = torch.mean(correct_counts.type(torch.FloatTensor))

            train_acc += acc.item() * inputs.size(0)

        with torch.no_grad():#用于通知dropout层和batchnorm层在train和val模式间切换。
            model.eval()#model.eval()中的数据不会进行反向传播,但是仍然需要计算梯度;

            for j, (inputs, labels) in enumerate(valid_data):#验证数据
                inputs = inputs.to(device)   #从valid_data里获得输入和标签
                labels = labels.to(device)

                outputs = model(inputs)  #模型的输出

                loss = loss_function(outputs, labels) #损失计算

                valid_loss += loss.item() * inputs.size(0)

                ret, predictions = torch.max(outputs.data, 1) #在分类问题中,通常需要使用max()函数对tensor进行操作,求出预测值索引。
                #dim是max函数索引的维度0 / 1,0是每列的最大值,1是每行的最大值
                #在多分类任务中我们并不需要知道各类别的预测概率,所以第一个tensor对分类任务没有帮助,而第二个tensor包含了最大概率的索引,所以在实际使用中我们仅获取第二个tensor即可。
                correct_counts = predictions.eq(labels.data.view_as(predictions))

                acc = torch.mean(correct_counts.type(torch.FloatTensor))

                valid_acc += acc.item() * inputs.size(0)

        avg_train_loss = train_loss / train_data_size
        avg_train_acc = train_acc / train_data_size

        avg_valid_loss = valid_loss / valid_data_size
        avg_valid_acc = valid_acc / valid_data_size

        history.append([avg_train_loss, avg_valid_loss, avg_train_acc, avg_valid_acc])

        if best_acc < avg_valid_acc:
            best_acc = avg_valid_acc
            best_epoch = epoch + 1

        epoch_end = time.time()

        print(
            "Epoch: {:03d}, Training: Loss: {:.4f}, Accuracy: {:.4f}%, \n\t\tValidation: Loss: {:.4f}, Accuracy: {:.4f}%, Time: {:.4f}s".format(
                epoch + 1, avg_valid_loss, avg_train_acc * 100, avg_valid_loss, avg_valid_acc * 100,
                epoch_end - epoch_start
            ))
        print("Best Accuracy for validation : {:.4f} at epoch {:03d}".format(best_acc, best_epoch))
        torch.save(model.state_dict(), '.\dataset'+'_model_' + str(epoch + 1) + '.pt')
        
    return model, history

注1:这里是一个开关作用,当你想训练的时候,就把istrain的值设置为1。当整个代码全部执行完之后,如果你还想测试,但不想再训练,就把istrain的值设置为0
注2:nun_epochs=30的意思是我要训练30轮。这个值可以由你自己定。当你调试代码的时候,这个值可以设置为1或2,帮助你快速判断代码是否出错。

istrain=1
if istrain:
    num_epochs = 30
    trained_model, history = train_and_valid(resnet50, loss_func, optimizer, num_epochs)

这一步运行完之后,正确的话会在窗口看到以下的文字:
在这里插入图片描述
需要你记住这里的一个数字,最佳轮数。
对于我而言,这次准确率最佳的轮数是第18轮。所以我需要记住18这个数。对于你而言,这个数可能不是18,可能是1-30里的任何一个数,记住它,最佳轮数。(PS:大家应该看的懂英语吧)

4.训练与可视化展示

代码如下(示例):
可以直接复制
ispicshow=1时会展示,等于0时不展示

#4可视化展示
ispicshow=0
if ispicshow:
    history = np.array(history)
    plt.plot(history[:, 0:2])
    plt.legend(['Tr Loss', 'Val Loss'])
    plt.xlabel('Epoch Number')
    plt.ylabel('Loss')
    plt.ylim(0, 1)
    plt.savefig(dataset + '_loss_curve.png')
    plt.show()

    plt.plot(history[:, 2:4])
    plt.legend(['Tr Accuracy', 'Val Accuracy'])
    plt.xlabel('Epoch Number')
    plt.ylabel('Accuracy')
    plt.ylim(0, 1)
    plt.savefig(dataset + '_accuracy_curve.png')
    plt.show()

5.测试

代码如下(示例):

首先定义了一个测试函数(封装了测试过程)
在这个测试函数的第一行,即
resnet50.load_state_dict(torch.load(’.\dataset’ + ‘model’ + ‘18’ + ‘.pt’))这行
这里面有个数字,18.他就是刚才要你记住的最佳轮数。把它换成你自己机器运行后显示的最佳轮数即可

#6测试模型
def test(model,loss_function):
    resnet50.load_state_dict(torch.load('.\dataset' + '_model_' + '18' + '.pt'))
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")  # 设备自行判断
    test_loss = 0.0
    test_acc = 0.0
    test_start = time.time()
    with torch.no_grad():  # 用于通知dropout层和batchnorm层在train和val模式间切换。
        model.eval()  # model.eval()中的数据不会进行反向传播,但是仍然需要计算梯度;
    for j, (inputs, labels) in enumerate(test_data):  # 验证数据
        inputs = inputs.to(device)  # 从test_data里获得输入和标签
        labels = labels.to(device)

        outputs = model(inputs)  # 模型的输出

        loss = loss_function(outputs, labels)  # 损失计算

        test_loss += loss.item() * inputs.size(0)

        ret, predictions = torch.max(outputs.data, 1)  # 在分类问题中,通常需要使用max()函数对tensor进行操作,求出预测值索引。

        correct_counts = predictions.eq(labels.data.view_as(predictions))

        acc = torch.mean(correct_counts.type(torch.FloatTensor))

        test_acc += acc.item() * inputs.size(0)


    avg_test_loss = test_loss / test_data_size
    avg_test_acc = test_acc / test_data_size
    test_end = time.time()

    print(
        "test: Loss: {:.4f}, Accuracy: {:.4f}%, Time: {:.4f}s".format(
            avg_test_loss, avg_test_acc * 100,
            test_end - test_start
        ))

istest=1表示可以进行训练,等于0表示不能
如果你用的模型不是resnet,则你需要把 test(resnet50,loss_func)中的resnet50改成你自己选择的模型网络。例如,如果你最开始选的是用alexnet,则需要改成:test(alexnet,loss_func)

istest=1
if istest:
    test(resnet50,loss_func)

至此,傻瓜式教学就结束了,不懂原理的同学可以根据自己的需要改改就能用了


总结

本文其实是对另一个作者的代码理解后的重新组织,帮助更多的小白快速入手。相对于原文章( 点击阅读原文.),本文封装了更多的细节,只提供了几个对开发者或使用者修改的接口,一定程度上降低了图像分类的门槛。

如果大家想继续深入学习,而不是玩玩而已的话,还是需要先慢慢打好基础再来进入实战。

  • 45
    点赞
  • 173
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ResNet 是深度学习领域中常用的卷积神经网络模型,它在训练大规模图像数据集时表现出色,特别是在解决多标签图像分类问题方面。PyTorch 是一个基于 Python 语言的机器学习库,它可以实现各种深度学习算法,包括 ResNet。 训练多标签图像数据集需要准备一个包含多个标签的数据集,并将标签分别赋予相应的图片。在 PyTorch 中,可以使用 DataLoader 来读取数据集,并使用 torchvision 库来实现 ResNet 模型。 首先,导入必要的库和模块后,可以使用 torchvision.datasets.ImageFolder() 方法来创建一个包含多标签图片的数据集。ImageFolder() 方法需要指定数据集的路径和转换图像的类型。在这里,可以将图像转换为 Tensor,并进行标准化处理以便更好地训练模型。 然后,可以创建一个 DataLoader 对象来读取数据集中的图片。DataLoader() 方法需要指定批次大小、数据集、是否随机读取数据等参数。 接下来,可以使用 torchvision.models.resnet50() 方法来创建一个 ResNet 模型。ResNet50 是一个 50 层的 ResNet 模型,可以通过修改参数来实现不同层数的模型。 在训练模型之前,需要先定义损失函数和优化器。在多标签图像分类问题中,可以使用 BCEWithLogitsLoss() 方法作为损失函数,它可以计算多个标签的损失值,并将它们加在一起作为最终的损失值。同时,可以选择 Adam() 方法作为优化器,它是一种自适应学习率的优化方法。 最后,可以开始训练模型。首先,将模型参数转移到 GPU 上以便更快地计算。然后,使用一个循环来遍历整个数据集并训练模型。在每一次循环中,将图像送入模型中计算得到预测结果,并根据预测结果和实际标签值计算损失值。之后,使用 optimizer.step() 方法来更新模型权重。最后,使用 torch.no_grad() 方法来预测测试集中的样本,并计算模型的准确率。 在训练模型过程中,可以选择保存模型的 checkpoint,以便在程序中断或重新启动时可以继续训练模型。可以使用 torch.save() 方法来保存模型的 checkpoint,并使用 torch.load() 方法来加载 checkpoint。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值