百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

深度学习-基于Pytorch 的cifar-10_AlexNet 实践

ztj100 2024-10-31 16:14 24 浏览 0 评论

我是一个初学者,将学习《动手学习深度学习》课程的内容记录如下:

主要内容参考原文,也加了一些查阅资料。

cifar-10 是一个典型的图像分类问题,

首先进入数据训练模型阶段,我学习的时候将相关学习都写着注释里面,方便看,具体代码如下:

# -*- coding: utf-8 -*-
# @Time    : 2021/12/7 9:43
# @Author  : 
# @Email   : 
# @File    : cifar-10_alexNet.py
import numpy as np
import torch
import torchvision
import torchvision.transforms as transforms
# torchvision,实现了常用的一些深度学习的相关的图像数据的加载功能,
# 比如cifar10、Imagenet、Mnist等等的,保存在torchvision.datasets模块中
import torch.utils.data as Data
from torch import nn
from torch.nn import functional as F
import torch.optim as optim
from torch.autograd import Variable

from torchsummary import summary
import os
import time
import matplotlib.pyplot as plt


# 定义函数用于查看部分数据
def imshow(img,title = None):
    #normalized x = (x - mean) / std
    img = img * 0.225 + 0.45   #unnormalized  dataloader 时做了 normalized
    npimg = img.numpy() # 将 torch.FloatTensor 转换为 numpy的格式 *255
    #npimg = npimg.astype(np.uint8)
    
    # plt.imshow(np.transpose(npimg, (1, 2,0)))。
    # 因为在plt.imshow在现实的时候输入的是(imagesize,imagesize,channels),
    # 而def imshow(img,text,should_save=False)中,参数img的格式为(channels,imagesize,imagesize),
    # 这两者的格式不一致,我们需要调用一次np.transpose函数,
    plt.imshow(np.transpose(img,(1,2,0)))
    plt.axis('off')
    #plt.imshow(img) #此处有可能有警告,或者报错,原因就是归一化的时候和显示还原不一致。
    if title is not None:
        plt.title(title)


# 构建网络,继承nn.Module
class CNNNet(nn.Module):
    def __init__(self):
        super(CNNNet, self).__init__()
        # 添加第一个卷积层
        # Conv2d(1,20,5)表示,输入是1通道的图像,输出是20通道,也就是20个卷积核,卷积核尺寸是5*5,其余参数默认值
        self.conv1 = nn.Conv2d(in_channels=3,out_channels=16,kernel_size=5,stride=1)
        # MaxPool2d(2,2)
        self.pool1 = nn.MaxPool2d(kernel_size=2,stride=2)
        self.conv2 = nn.Conv2d(in_channels=16,out_channels=36,kernel_size=3,stride=1)
        self.pool2 = nn.MaxPool2d(kernel_size=2,stride=2)
        # 接着定义全连接层
        self.fc1 = nn.Linear(1296,128)
        self.fc2 = nn.Linear(128,10)
    #     定义前向传播函数
    def forward(self,x):
        x = self.pool1(F.relu(self.conv1(x)))
        x = self.pool2(F.relu(self.conv2(x)))
        # print(x.shape)  应该是:torch.Size([4, 36, 6, 6])
        # .view() 改变tensor size,但是元素总数不变
        # -1 表示:这个参数由另一个元素确定,比如矩阵元素总数确定的情况下,列数确定了之后,行数也可以确定
        # 全连接层 就是矩阵的乘法,保证2个矩阵可以相乘,所以把X 调整到正确的size
        x = x.view(-1,36*6*6)
        x = F.relu(self.fc2(F.relu(self.fc1(x))))
        return x
# 定义AlexNet
class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__()
        self.Conv = nn.Sequential(
            # IN : 3*32*32
            nn.Conv2d(in_channels=3, out_channels=96, kernel_size=5, stride=2, padding=2),
            # 论文中kernel_size = 11,stride = 4,padding = 2
            nn.ReLU(),
            # IN : 96*16*16
            nn.MaxPool2d(kernel_size=2, stride=2),  # 论文中为kernel_size = 3,stride = 2
            # IN : 96*8*8
            nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=1, padding=2),
            nn.ReLU(),
            # IN :256*8*8
            nn.MaxPool2d(kernel_size=2, stride=2),  # 论文中为kernel_size = 3,stride = 2
            # IN : 256*4*4
            nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            # IN : 384*4*4
            nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            # IN : 384*4*4
            nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            # IN : 384*4*4
            nn.MaxPool2d(kernel_size=2, stride=2),  # 论文中为kernel_size = 3,stride = 2
            # OUT : 384*2*2
        )
        self.linear = nn.Sequential(
            nn.Linear(in_features=384 * 2 * 2, out_features=4096),
            nn.ReLU(),
            nn.Linear(in_features=4096, out_features=4096),
            nn.ReLU(),
            nn.Linear(in_features=4096, out_features=10),
        )

    def forward(self, x):
        x = self.Conv(x)
        x = x.view(-1, 384 * 2 * 2)
        x = self.linear(x)
        return x
# 显示网络参数量
def Init_net():
    model = AlexNet()
    model.to(device)
    data_input = Variable(torch.randn(8,3,32,32))
    print(data_input.size())
    data_input.to(device)
    model(data_input)
    print(summary(model,(3,32,32)))

# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    # 有GPU,优先用GPU
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    #  **由于torchvision的datasets的输出是[0,1]的PILImage,所以我们先先归一化为[-1,1]的Tensor**
    #  首先定义了一个变换transform,利用的是上面提到的transforms模块中的Compose( )
    #  把多个变换组合在一起,可以看到这里面组合了ToTensor和Normalize这两个变换
    transform = transforms.Compose(
        [transforms.ToTensor(), #range [0, 255] -> [0.0,1.0]
         transforms.Normalize((0.45, 0.45, 0.45), (0.225, 0.225, 0.225))] # Normalize()函数去计算 x = (x - mean)/std
    )
    # 前面的(0.5,0.5,0.5)是RGB三个通道上的均值,后面(0.5, 0.5, 0.5)是三个通道的标准差,
    # 注意通道顺序是RGB ,用过opencv的同学应该知道openCV读出来的图像是BRG顺序。
    # 这两个tuple数据是用来对RGB图像做归一化的.这里均值和标准差都是近似的值,可以在数据集中计算。

    # 利用torchvision数据收集,train=True表示拉取训练集,FALSE表示测试集,num_workers:用多少个子进程加载数据。0表示数据将在主进程中加载(默认: 0)
    # DataLoader:pin_memory=True,意味着生成的Tensor属于内存的锁业内存,这样转GPU的时候快一些
    trainset = torchvision.datasets.CIFAR10('./data', train=True, download=True, transform=transform)
    trainloader = Data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2,pin_memory=True)
    testset = torchvision.datasets.CIFAR10('./data', train=False, download=True, transform=transform)
    testloader = Data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2,pin_memory=True)
    print("训练数据数量:", len(trainset))
    print("训练数据分为4份后数量:", len(trainloader))
    print("测试数据数量:", len(testset))
    print("测试数据分为4份后数量:", len(testloader))

    classes = ('airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

    # 随机获取部分训练数据,创建一个迭代器,可以调用 next 下一个对象
    dataiter = iter(trainloader)
    imges,labels = dataiter.next()   # 包大小为4,所以一份有 4 个值,len(x) = 4

    #print(len(imges[0]))
    print(device)

    # 显示个batch 图像
    for num in range(imges.shape[0]):
        plt.subplot(1,4,num+1)
        imshow(imges[num],classes[labels[num].item()]) 
    plt.show() #此处有可能有警告,或者报错,原因就是归一化的时候和显示还原不一致。

    # imshow(torchvision.utils.make_grid(imges))  #直接以格子的形式展现
    # print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
    # 打印初始化网络,查看网络情况
    # Init_net()

    # net = CNNNet()
    net = AlexNet()
    net = net.to(device=device)
    # 可以打印网络查看一下
    print(net)

    # 定义优化器和损失函数
    criterion = nn.CrossEntropyLoss() #nn工具箱中的交叉熵损失函数
    # 使用nn工具箱中的 SGD 梯度优化算法(随机梯度下降)
    optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
    # 优化器:pytorch将深度学习中常用的优化方法全部封装在torch.optim之中,所有的优化方法都是继承基类optim.Optimizier
    # 如果要使用GPU,在定义优化器之前,就要完成 .cuda()操作
    print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
    # 下面进入训练操作
    start_time = time.time()
    for epoch in range(6): # loop over the dataset multiple times 指定训练一共要循环几个epoch
        running_loss = 0.0 #定义一个临时变量,用于记录和输出loss
        for i,data in enumerate(trainloader,0):
            #传入训练数据,enumerate
            # data 是从enumerate 返回的,包含数据和标签信息,分别赋值inputs和labels
            inputs,labels = data
            # copy数据到设备,如果使用CPU,这一步是多余的
            inputs,labels = inputs.to(device),labels.to(device)
            # 将数据转换成Variable,
            # 注意:虽然官网给的程序有这么一句 from torch.autograd import Variable,
            # 但是此步中确实没有显式地用到variable,也可以不用转,下一步
            # 只能说网络里运行的数据确实要以variable的形式存在
            #inputs, labels = Variable(inputs), Variable(labels)
            optimizer.zero_grad()#要把梯度重新归零,因为反向传播过程中梯度会累加上一次循环的梯度
            # forward + backward + optimize
            outputs = net(inputs) #把数据输入网络中
            loss = criterion(outputs,labels) #计算损失值
            # 想要计算各个variable的梯度,只需调用根节点的backward方法,Autograd就会自动沿着整个计算图进行反向计算
            # 而在此例子中,根节点就是我们的loss,所以:
            # 程序中的loss.backward()代码就是在实现反向传播,自动计算所有的梯度。
            loss.backward() #反向传递损失函数
            optimizer.step() #反向传播之后,把优化器的参数进行更新,方便下一轮运算

            # 下面打印loss结果,方便查看损失值,但与训练关系不大
            running_loss += loss.item()  #此处选择每2000 累计一次损失值
            # 从下面一行代码可以看出它是每循环0-1999共两千次才打印一次
            if i % 2000 == 1999:
                print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
                '[%d, %5d] loss: %.3f' %(epoch + 1, i + 1, running_loss / 2000))
                running_loss = 0.0  # 重新初始化,下次再使用
    endtime = time.time()
    print("训练完成,训练用时:%f S"%(endtime - start_time))

    # 测试训练情况
    # 查看测试集数据
    dataiter = iter(testloader) #创建迭代器
    images,labels = dataiter.next() #返回一个batch_size的图片
    # imshow(torchvision.utils.make_grid(images))  # 展示这四张图片
    # 显示个batch 图像
    for num in range(imges.shape[0]):
        plt.subplot(1, 4, num + 1)
        imshow(imges[num], classes[labels[num].item()])
    # plt.show()

    # print('GroundTruth: ', ' '.join(
    #     '%5s' % classes[labels[j]] for j in range(4)))  # python字符串格式化 ' '.join表示用空格来连接后面的字符串,参考python的join()方法
    start_time = time.time()
    # images, labels = images.to(device), labels.to(device)
    # outputs = net(Variable(images))  # 注意这里的images是我们从上面获得的那四张图片,所以首先要转化成variable
    # 返回返回输入Tensor中每行的最大值,并转换成指定的dim(维度);
    # 参数dim=1,相当于调用了squeeze(1),如果dim=0,它其实是在返回每列的最大值
    # 这里很明显,这个返回的元组的第一个元素是image data,即是最大的值,第二个元素是label, 即是最大的值的索引
    # 我们只需要label(最大值的索引),所以就会有 _ , predicted这样的赋值语句
    # _, predicted = torch.max(outputs.data, 1)
    # 这个 _ , predicted是python的一种常用的写法,表示后面的函数其实会返回两个值
    # 但是我们对第一个值不感兴趣,就写个_在那里,把它赋值给_就好,我们只关心第二个值predicted
    # 比如 _ ,a = 1,2 这中赋值语句在python中是可以通过的,你只关心后面的等式中的第二个位置的值是多少
    # print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))  # python的字符串格式化


    correct = 0  # 定义预测正确的图片数,初始化为0
    total = 0  # 总共参与测试的图片数,也初始化为0
    # 在使用pytorch时,并不是所有的操作都需要进行计算图的生成(计算过程的构建,以便梯度反向传播等操作)。
    # 而对于tensor的计算操作,默认是要进行计算图的构建的.
    # 在这种情况下,可以使用 with torch.no_grad():强制之后的内容不进行计算图构建。
    with torch.no_grad():
        for data in testloader:  # 循环每一个batch
            images, labels = data
            # copy数据到设备,如果使用CPU,这一步是多余的
            images, labels = images.to(device), labels.to(device)
            outputs = net(images)  # 输入网络进行测试
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)  # 更新测试图片的数量
            correct += (predicted == labels).sum().item() # 更新正确分类的图片的数量

    print('Accuracy of the network on the 10000 test images: %d %%' % (
            100 * correct / total))  # 最后打印结果
    endtime = time.time()
    print("测试完成,测试用时:%f S"%(endtime - start_time))

    # 打印各个类别的识别准确率,这看起来比随机预测要好,
    # 随机预测的准确率为10%(随机预测出为10类中的哪一类)。
    # 看来网络学到了东西。
    class_correct = list(0. for i in range(10))
    class_total = list(0. for i in range(10))
    t = 0
    with torch.no_grad():
        for data in testloader:
            images, labels = data
            images, labels = images.to(device), labels.to(device)
            outputs = net(images)
            _, predicted = torch.max(outputs, 1)
            c = (predicted == labels).squeeze()

            for i in range(4):
                label = labels[i]
                class_correct[label] += c[i].item()
                class_total[label] += 1
    for i in range(10):
        print('Accuracy of %5s : %2d %%' % (
            classes[i], 100 * class_correct[i] / class_total[i]))


    # 只保存网络中训练好的权重文件,使用的时候会快一些
    print('===> Saving models...')
    path_model = './model'
    state = {
        'state': net.state_dict(),
        'epoch': epoch  # 将epoch一并保存
    }
    if not os.path.isdir('model'):  # 如果没有这个目录就创建一个
        os.mkdir('./model')
    # 保存整个网络模型,也可以保存整个模型
    torch.save(net,"./model/cifar-10_alxNet_model.pkl")
    # 保存模型的参数,参数后续使用快于整个模型
    torch.save(net.state_dict(), './model/cifar-10_alxNet_model_params.pkl')


接下来是训练好模型后具体的使用环节,下面还是继续用Python来体验:

# -*- coding: utf-8 -*-
# @Time    : 2021/12/7 9:43
# @Author  : 
# @Email   : 
# @File    : use_AlexNet.py
import os

os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'
import time
import json
import torch
from torch import nn
import torchvision.transforms as transforms
from PIL import Image,ImageFont,ImageDraw
import torch.optim as optim
from matplotlib import pyplot as plt
import torchvision.models as models

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

classes = ('airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

#图像预处理,必须转换成和模型相同的尺寸,否则将无法识别
image_transforms = {
    'test': transforms.Compose([
        transforms.Resize(size=32),
        transforms.CenterCrop(size=32),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406],
                             [0.229, 0.224, 0.225])
    ])
}

# 定义AlexNet
class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__()
        self.Conv = nn.Sequential(
            # IN : 3*32*32
            nn.Conv2d(in_channels=3, out_channels=96, kernel_size=5, stride=2, padding=2),
            # 论文中kernel_size = 11,stride = 4,padding = 2
            nn.ReLU(),
            # IN : 96*16*16
            nn.MaxPool2d(kernel_size=2, stride=2),  # 论文中为kernel_size = 3,stride = 2
            # IN : 96*8*8
            nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=1, padding=2),
            nn.ReLU(),
            # IN :256*8*8
            nn.MaxPool2d(kernel_size=2, stride=2),  # 论文中为kernel_size = 3,stride = 2
            # IN : 256*4*4
            nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            # IN : 384*4*4
            nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            # IN : 384*4*4
            nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            # IN : 384*4*4
            nn.MaxPool2d(kernel_size=2, stride=2),  # 论文中为kernel_size = 3,stride = 2
            # OUT : 384*2*2
        )
        self.linear = nn.Sequential(
            nn.Linear(in_features=384 * 2 * 2, out_features=4096),
            nn.ReLU(),
            nn.Linear(in_features=4096, out_features=4096),
            nn.ReLU(),
            nn.Linear(in_features=4096, out_features=10),
        )

    def forward(self, x):
        x = self.Conv(x)
        x = x.view(-1, 384 * 2 * 2)
        x = self.linear(x)
        return x


def predict(model, test_image_name):
    transform = image_transforms['test']

    test_image = Image.open(test_image_name).convert('RGB')
    draw = ImageDraw.Draw(test_image)

    test_image_tensor = transform(test_image)

    if torch.cuda.is_available():
        test_image_tensor = test_image_tensor.view(1, 3, 32, 32).cuda()
    else:
        test_image_tensor = test_image_tensor.view(1, 3, 32, 32)

    with torch.no_grad():
        model.eval()

        out = model(test_image_tensor)
        # print("out=",out)
        ps = torch.exp(out)
        topk, topclass = ps.topk(1, dim=1)
        print("Prediction : ", classes[topclass.cpu().numpy()[0][0]], ", Score(exp): ", topk.cpu().numpy()[0][0])
        text = classes[topclass.cpu().numpy()[0][0]] + "\nScore(exp):" + str(topk.cpu().numpy()[0][0])
        font = ImageFont.truetype('arial.ttf', 36)
        draw.text((0, 0), text, (255, 0, 0), font=font)
        test_image.show()


if __name__ == "__main__":

    # config
    path_state_dict = os.path.join(BASE_DIR, "cifar-10_alxNet_model.pkl") #字符串组合,上文中保存的模型路径
    path_img = os.path.join(BASE_DIR, "..", "data", "111.png") #填写图片地址和名称

    # load model
    # 由于训练模型是在GPU上训练的,如果在没有GPU的设备上运行时,需要加map_location
    model = torch.load(path_state_dict, map_location=torch.device(device))
    predict(model,path_img) #预测图像中的物体,为提高识别率,可采用集成的方式

识别效果:

可寻找对应图片数据传入模型预测:



相关推荐

再说圆的面积-蒙特卡洛(蒙特卡洛方法求圆周率的matlab程序)

在微积分-圆的面积和周长(1)介绍微积分方法求解圆的面积,本文使用蒙特卡洛方法求解圆面积。...

python编程:如何使用python代码绘制出哪些常见的机器学习图像?

专栏推荐...

python创建分类器小结(pytorch分类数据集创建)

简介:分类是指利用数据的特性将其分成若干类型的过程。监督学习分类器就是用带标记的训练数据建立一个模型,然后对未知数据进行分类。...

matplotlib——绘制散点图(matplotlib散点图颜色和图例)

绘制散点图不同条件(维度)之间的内在关联关系观察数据的离散聚合程度...

python实现实时绘制数据(python如何绘制)

方法一importmatplotlib.pyplotaspltimportnumpyasnpimporttimefrommathimport*plt.ion()#...

简单学Python——matplotlib库3——绘制散点图

前面我们学习了用matplotlib绘制折线图,今天我们学习绘制散点图。其实简单的散点图与折线图的语法基本相同,只是作图函数由plot()变成了scatter()。下面就绘制一个散点图:import...

数据分析-相关性分析可视化(相关性分析数据处理)

前面介绍了相关性分析的原理、流程和常用的皮尔逊相关系数和斯皮尔曼相关系数,具体可以参考...

免费Python机器学习课程一:线性回归算法

学习线性回归的概念并从头开始在python中开发完整的线性回归算法最基本的机器学习算法必须是具有单个变量的线性回归算法。如今,可用的高级机器学习算法,库和技术如此之多,以至于线性回归似乎并不重要。但是...

用Python进行机器学习(2)之逻辑回归

前面介绍了线性回归,本次介绍的是逻辑回归。逻辑回归虽然名字里面带有“回归”两个字,但是它是一种分类算法,通常用于解决二分类问题,比如某个邮件是否是广告邮件,比如某个评价是否为正向的评价。逻辑回归也可以...

【Python机器学习系列】拟合和回归傻傻分不清?一文带你彻底搞懂

一、拟合和回归的区别拟合...

推荐2个十分好用的pandas数据探索分析神器

作者:俊欣来源:关于数据分析与可视化...

向量数据库:解锁大模型记忆的关键!选型指南+实战案例全解析

本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在...

用Python进行机器学习(11)-主成分分析PCA

我们在机器学习中有时候需要处理很多个参数,但是这些参数有时候彼此之间是有着各种关系的,这个时候我们就会想:是否可以找到一种方式来降低参数的个数呢?这就是今天我们要介绍的主成分分析,英文是Princip...

神经网络基础深度解析:从感知机到反向传播

本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在...

Python实现基于机器学习的RFM模型

CDA数据分析师出品作者:CDALevelⅠ持证人岗位:数据分析师行业:大数据...

取消回复欢迎 发表评论: