一位深度学习小萌新的学渣笔记(二)LeNet+AlexNet网络介绍及代码详解

LeNet网络结构详解与模型的搭建

简单介绍LeNet

  • LeNet分为卷积层块和全连接层块两个部分。
  • 卷积层块⾥的基本单位是卷积层后接最⼤池化层
  • 卷积层⽤来识别图像⾥的空间模式,如线条和物体局部,
  • 最⼤池化层则⽤来降低卷积层对位置的敏感性。
  • LeNet组成 卷积层、 下采样层、 卷积层、 下采样层、 三个全连接层

model.py中代码解读

import torch.nn as nn
import torch.nn.functional as F

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__() 
        #super函数解决在多种继承中更好的调用父类super在多继承中经常使用 
        self.conv1 = nn.Conv2d(3, 16, 5)#可以在pytorch官网Docs中查找Conv2d可以找到函数详细的定义#
        #可以显示计算公式 3代表输入特征层的深度,用了16个卷积核、卷积核大小是5*5 
        self.pool1 = nn.MaxPool2d(2, 2)#点击查看MaxPool2d的函数定义,发现没有初始化函数、
        #跳转看其父类MaxPoolNd才能看到初始化参数 (self,kernel_size,,stride=None,padding=0,dilation=1,return_indices=False,ceil_mode=False)
        #如果没有传入stride参数、那stride=kernal_size卷积核大小   
        #这里的2代表卷积核大小,2代表步长 
        self.conv2 = nn.Conv2d(16, 32, 5)#16深度 32卷积核个数 5*%卷积核大小
        self.pool2 = nn.MaxPool2d(2, 2) #这里的2代表卷积核大小,2代表步长 
        self.fc1 = nn.Linear(32*5*5, 120) #全连接层的输入是一个一维的向量,这里需要展平所以是32*5*5 深度*高度*宽度 节点个数为120
        self.fc2 = nn.Linear(120, 84) #上一层的输出120 第二层设计输出84
        self.fc3 = nn.Linear(84, 10)  #上一层的输出84 第二层设计输出需要根据我们的训练集合进行修改,用来实验的具有10个分类任务,所以设为10
 def forward(self, x):
        x = F.relu(self.conv1(x))    # input(3, 32, 32) output(16, 28, 28)
        # (深度,高度、宽度) N = 28 = ( 32 - 5 + 0 )/1 + 1     16 = 卷积核的个数 
        x = self.pool1(x)            # output(16, 14, 14) 池化层只会影响特征矩阵的高和宽不会影响深度,高度和宽度缩小为原来的一半
        x = F.relu(self.conv2(x))    # output(32, 10, 10)  
        # N = 10 = ( 14 - 5 + 0 )/1 + 1 32卷积核个数
        x = self.pool2(x)            # output(32, 5, 5) #高度和宽度缩小为原来的一半
        x = x.view(-1, 32*5*5)       # output(32*5*5) #view函数可以用于自动展平数据 -1代表第一个维度batch 32*5*5是展平后的数据个数
        x = F.relu(self.fc1(x))      # output(120)
        x = F.relu(self.fc2(x))      # output(84)
        x = self.fc3(x)              # output(10)
        return x #为什么这个分类问题没有使用softmax函数,但是在训练网络过程中计算卷积交叉熵时已经内部实现了一个搞笑的softmax方法
调试信息
import torch
input1 = torch.rand([32, 3, 32, 32])
model = LeNet()
print(model)
output = model(input1)
# 可以  x = F.relu(self.conv1(x)) 设置断点进行调试

train.py代码解读

使用到的数据集


先导入所需要的包

import torch
import torchvision
import torch.nn as nn
from model import LeNet
import torch.optim as optim
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np

下载训练集and初始化

# 50000张训练图片
#用来下载训练集的函数
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
#用到两个函数对图片进行预处理
#ToTensor是将PIL Image 或者是numpy.ndarra (H x W x C(深度)) 每一维度的像素值都是0-255  转化成tensor (C x H x W)每 一维度的像素值都是改成0.0-1.0 
#Normalize 标准化的一个过程 使用均值(mean)和标准差(std) 来标准化我们的tensor
#标准化过程output[channel]=(input[channel]-mean[channel])/std[channel]  


#下载我们的训练集
# 50000张训练图片
train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
#参数解析:   
#root参数是代表下载到何处,这里./data不行的话可以用data,train参数如果为true会导入CIFAR10的训练集的样本,download为true就开始自动下载,transform是对图片进行预处理 把download的参数False改成True开始下载
#torchvision.datasets.参看pytorch官方提供的数据集

训练集导入

train_loader = torch.utils.data.DataLoader(train_set, batch_size=36,
                                          shuffle=True, num_workers=0)
#这个函数将训练集导入,然后随机分成一个批次36张,将shuffle的参数由False改成True,表示随机batch    #num_worker在window下设置为0

测试集下载and导入 与训练集类似的的方法

# 10000张验证图片
val_set = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=False, transform=transform)
                                       #注意这时的train参数是False
val_loader = torch.utils.data.DataLoader(val_set, batch_size=5000,
                                         shuffle=False, num_workers=0)
                                         #注意这里的batch_size直接设为了5000,很大,不像前面的36
val_data_iter = iter(val_loader) #将val_loader转化为一个可迭代的迭代器
val_image, val_label = val_data_iter.next() #用next获得一批数据 数据中包含了图像以及图像对应的标签值
classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

:point_down:

导入官方数据集中imshow的函数
imshow包在numpy中
plt是在matplotlib.pyplot中

def imshow(img):
    img = img / 2 + 0.5     # unnormalize  #对图像进行反标准化处理
    #标准化是output=(input-0.5)/0.5  反标准化是input=output*0.5+0.5=output82+0.5
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0))) #将图像转化为numpy格式 将channel x height x width 变成高 宽 深度 0是深度 1是高度 2是宽度
    plt.show()
    plt.show()
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
#使用时要将val_loader...batch_size=5000,batch_size改成4
# show images
imshow(torchvision.utils.make_grid(images))

:point_up_2: 这一段测试完就注释掉
测试效果如下

net = LeNet()
loss_function = nn.CrossEntropyLoss()#损失函数 点进去CrossEntropyLoss可以看到 这个函数内部就已经有softmax这个激活函数
optimizer = optim.Adam(net.parameters(), lr=0.001)#优化器 选择Adam这个优化器 第一个参数就是net.parameters()将net中可训练的参数都进行训练 lr就是学习率给的是0.001

进行训练过程

for epoch in range(5):  # loop over the dataset multiple times #将训练集迭代5轮

    running_loss = 0.0 #累加我们训练过程中的损失
    for step, data in enumerate(train_loader, start=0): #遍历我们的训练集样本 返回每一批数据中的data 还会返回step步数 
        # get the inputs; data is a list of [inputs, labels]
        inputs, labels = data #将数据分离成input也就是我们输入的图像以及它所对应的标签

        # zero the parameter gradients
        optimizer.zero_grad() #将历史损失梯度清零 为什么每计算一个batch就要调用一次optimizer.zero_grad()? 如果不清楚历史梯度,就会对计算梯度进行累加,通过这个特性你能够变相实现一个很大的batch数值的训练 
        # forward + backward + optimize
        outputs = net(inputs) #将数据输入网络进行正向传播然后得到输出
        loss = loss_function(outputs, labels) #再通过定义的loss——function 计算损失 output是网络预测的值 labels是输入图片对应的真实的标签
        loss.backward()#反向传播
        optimizer.step()#最后通过优化器进行参数的更新

        # print statistics 打印的一个过程
        running_loss += loss.item() #每计算一个就累加到running_loss这个变量中
        if step % 500 == 499:    # print every 500 mini-batches 每隔500步打印一次数据的信息
            with torch.no_grad(): #with是一个上下文管理器 不会计算每个节点的误差梯度 如果没有使用这个函数 可能会导致训练失败
                outputs = net(val_image)  # [batch, 10] 进行正向传播
                predict_y = torch.max(outputs, dim=1)[1] #在dim=1 也就是在维度1上面寻找输出的最大的index在什么位置,也可以理解成网络预测最可能为哪一个类别  torch.max()[1]代表的意思是我们只需要知道它的index值 知道他在什么地方就行了 并不需要知道max是多少
                accuracy = (predict_y == val_label).sum().item() / val_label.size(0)
#将预测值与标签的真实值进行对比,如果相等返回1 否则0 然后sum函数就累加出一种计算对了几个样本 如果需要获得数值那就要使用item() 函数 再处于测试样本的个数
                print('[%d, %5d] train_loss: %.3f  test_accuracy: %.3f' %
                      (epoch + 1, step + 1, running_loss / 500, accuracy))
                      #epoch + 1代表训练迭代到第几轮了 step + 1某一轮的多少步,每500步打印一次 running_loss 训练过程中累加的误差 /500 得到500步中平均的训练误差  accuracy准确率
               
                running_loss = 0.0 清零 进行下500步的训练过程

print('Finished Training')#打印训练完毕

save_path = './Lenet.pth'
torch.save(net.state_dict(), save_path) #把训练的数据进行保存

训练完后生成./Lenet.pth 模型权重文件

接下来在网上随便找一张图片

predict.py

import torch
import torchvision.transforms as transforms
from PIL import Image
from model import LeNet

transform = transforms.Compose(
    [transforms.Resize((32, 32)), #跟train.py预处理步骤不同的地方 将图片大小固定
     transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

net = LeNet() #实例化
net.load_state_dict(torch.load('Lenet.pth')) #通过net.load_state_dict函数导入Lenet.pth权重文件

#依靠的是PIL模块
im = Image.open('1.jpg')
im = transform(im)  # [C, H, W] #用transform 预处理
im = torch.unsqueeze(im, dim=0)  # [N, C, H, W] #加上一个维度

with torch.no_grad():#这里不计算损失梯度
    outputs = net(im)
    predict = torch.max(outputs, dim=1)[1].data.numpy()
    #predict = torch.softmax(outputs, dim=1) 用softmax函数得出一个tensor概率分布 说明是plane的概率呀 或者是别的东西的概率是多少
print(classes[int(predict)])



AlexNet网络结构详解与模型的搭建

简单介绍AlexNet

结构


AlexNet包含8层变换:
5层卷积和2层全连接隐藏层,以及1个全连接输出层

卷积层1
Maxpool下采样层1
卷积层2
Maxpool下采样层2
卷积层3
卷积层4
卷积层5
Maxpool下采样层3
三个全连接层

改进

特点:

  • 首次利用GPU进行网络加速训练
  • 使用ReLu激活函数,而不是传统的sigmoid激活函数以及Tanh激活函数
  • 使用了LRM局部响应归一化
  • 在全连接层的前两层中使用了Dropout随机失火神经元操作,以减少过拟合。
    随即失活神经元

    过拟合:
  • AlexNet通过丢弃法来控制全连接层的模型复杂度。而LeNet并没有使⽤丢弃法。
  • AlexNet引⼊了⼤量的图像增⼴,如翻转、裁剪和颜⾊变化,从而进⼀步扩⼤数据集来缓解过拟合。

激活函数

AlexNet将sigmoid激活函数改成了更加简单的ReLU激活函数。

  1. ReLU激活函数的计算更简单,例如它并没有sigmoid激活函数中的求幂运算
  2. ReLU激活函数在不同的参数初始化⽅法下使模型更容易训练
    这是由于当sigmoid激活函数输出极接近0或1时,这些区域的梯度⼏乎为0,从而造成反向传播⽆法继续更新部分模型参数;
    而ReLU激活函数在正区间的梯度恒为1。因此,若模型参数初始化不当,sigmoid函数可能在正区间得到⼏乎为0的梯度,从而令模型⽆法得到有效训练。

model.py中代码解读

  1. 卷积层
import torch.nn as nn
import torch


class AlexNet(nn.Module):
    def __init__(self, num_classes=1000, init_weights=False):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2),  
            # input[3, 224, 224]  output[48, 55, 55] 
            #3是指RGB三层。48是指卷积核数目,卷积个数是48,也就是48个卷积核输出的特征矩阵深度为48,在Alex的原论文中是96,但是为了提高训练速度,而且准确率不会改变,11是卷积核的大小,步长为4,上下左右都补上两行0或者是两列0,这样子算出来是一个小数,不是整数的话,会舍弃下边的一行0或者是右边的一列0,这样就和原论文padding中(1,2)的输出是一样的,
            nn.ReLU(inplace=True),#ReLu激活函数,inplace增加计算量,但是降低内存的使用容量,这样可以载入更大的模型。
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[48, 27, 27]
            #池化核的大小等于3,步长为2,池化核个数为none,没有池化核个数这个参数,padding为0
            nn.Conv2d(48, 128, kernel_size=5, padding=2),           # output[128, 27, 27] 上面输入的特征矩阵深度是48,输出为128也是在原论文数据的基础上除于2 就是256/2,卷积核大小为5,padding为2,与原论文数据相同
            nn.ReLU(inplace=True), #与上面相同
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[128, 13, 13]  #与上面相同
            nn.Conv2d(128, 192, kernel_size=3, padding=1),          # output[192, 13, 13]	#输入的特征矩阵的深度为128,输出为的特征矩阵深度为192(也是原论文中的一半),是由卷积核个数决定的。卷积核大小等于3,paddding=1
            nn.ReLU(inplace=True), #与上面相同
            nn.Conv2d(192, 192, kernel_size=3, padding=1),          # output[192, 13, 13]		#输入的特征矩阵的深度为192,输出为的特征矩阵深度为192(也是原论文中的一半),是由卷积核个数决定的。卷积核大小等于3,paddding=1
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 128, kernel_size=3, padding=1),          # output[128, 13, 13]#输入的特征矩阵的深度为192,输出为的特征矩阵深度为128(也是原论文中的一半),是由卷积核个数决定的。卷积核大小等于3,paddding=1
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[128, 6, 6]   #与上面相同
        )
  1. 全连接层

Dropout函数插入在全连接层与全连接层之间

  self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),#p表示随机失活的比例以0.5的比例随机失活神经元
            nn.Linear(128 * 6 * 6, 2048),#卷积层的输入是深度128 大小6*6 全连接层的节点个数是2048个
            nn.ReLU(inplace=True),#激活函数
            nn.Dropout(p=0.5),#p表示随机失活的比例以0.5的比例随机失活神经元
            nn.Linear(2048, 2048),#卷积层的输入是深度128 大小6*6 全连接层的节点个数是2048个
            nn.ReLU(inplace=True),#激活函数
            nn.Linear(2048, num_classes),#输入时上一层输出的值,输出时数据集类别的个数,默认为1000,我们初始时要设置
        )
  1. 正态传播过程还有初始化权重
 if init_weights:#其实可以不需要,因为pytorch中会自动使用凯明初始化权重的方法
            self._initialize_weights()
           #如果函数初始化时传入了init_weights(初始化权重)这个参数,会进入_initialize_weights()这个函数

    def forward(self, x):   #正态传播过程,x是输入进来的变量,训练样本
        x = self.features(x) 
        x = torch.flatten(x, start_dim=1) #展平处理,展平的维度是从dim1开始展平,pytorch中的Tensor通道排列顺序是:[batch, channel, height, width],batch不去改变,所以顺序是从channel、height、width开始三个维度展平程一个一维向量。
        x = self.classifier(x) #输入到全连接层
        return x

    def _initialize_weights(self):
        for m in self.modules():#可以通过ctrl+鼠标左键跳转到modules内部查看。首先遍历这个模modules块,它的父类是nn.Module,通过self.modules,返回一个迭代器,他会遍历网络中所有的模块
            if isinstance(m, nn.Conv2d):#判断类别是否是卷积层
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') #对weight进行初始化 凯明初始化的方法
                if m.bias is not None: #如果偏置不为空的话就置为0 
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):#如果是连接层的话
                nn.init.normal_(m.weight, 0, 0.01) #通过一个正态分布来给weight赋值,正态分布的均值是为0,方差是为0.01
                nn.init.constant_(m.bias, 0)#将bias初始化为0

~~

train.py代码解读

导入包

import torch
import torch.nn as nn
from torchvision import transforms, datasets, utils
import matplotlib.pyplot as plt
import numpy as np
import torch.optim as optim
from model import AlexNet
import os
import json
import time
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#如果当前可使用的GPU设备的话,就默认使用第一块GPU 如果没有就默认使用CPU
print(device)

定义数据预处理函数 这里把训练集的预处理和测试集的预处理需要的操作写到一个函数中

data_transform = {
	#训练集
    "train": transforms.Compose([transforms.RandomResizedCrop(224),
    							#随机裁剪到224*224的像素大小
                                 transforms.RandomHorizontalFlip(),
                                 #在水平方向随机翻转
                                 transforms.ToTensor(),
                                 #转化为一个Tensor
                                 transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),#标准化处理
    "val": transforms.Compose([transforms.Resize((224, 224)),  # cannot 224, must (224, 224)#随机裁剪到224*224的像素大小
                               transforms.ToTensor(), #转化为一个Tensor
                               transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}#标准化处理

:point_down:训练集的部分

data_root = os.path.abspath(os.path.join(os.getcwd(), "../.."))  # get data root path
#返回上上层目录 .一个点是代表上层目录
image_path = data_root + "/data_set/flower_data/"  # flower data set path #意义是已经在返回上上层目录的基础上再进入/data_set/flower_data/
train_dataset = datasets.ImageFolder(root=image_path + "/train",
                                     transform=data_transform["train"])
                                     #ImageFolder函数加载我们的数据集 root表示路径 表示flower_data里的train文件 transform参数表示将train训练集传入data_transform函数中进行预处理
train_num = len(train_dataset)#打印我们训练集有多少张图片

# {'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4}
flower_list = train_dataset.class_to_idx #去获取我们分类的名称所对应的索引(返回每一个类别所对应的索引值
cla_dict = dict((val, key) for key, val in flower_list.items()) #通过这个遍历的方法去获得我们key 和value 例如{'roses':0,'sheep':1}类似 但是这个dict将key和value反过来例如{0:''roses',1:'sheep'} 这样做是为了之后预测完以后能直接得到对应的类别
# write dict into json file
json_str = json.dumps(cla_dict, indent=4)#利用json.dump将cla_dict这个字典进行编码 编码成json的格式
with open('class_indices.json', 'w') as json_file: 
    json_file.write(json_str)
#打开class_indices.json这个文件,将json_str保存到一个json文件当中,方便我们预测时方读取信息
batch_size = 32
train_loader = torch.utils.data.DataLoader(train_dataset,
                                           batch_size=batch_size, shuffle=True,
                                           num_workers=0)
                                           #将train_dataset加载进来,batch_size设置为32,shuffle代表随机从样本中获取一批批的数据 num_worker=0表示用一个主线程去加载我们的数据 windows中不允许为负值


:point_down:测试集的部分 与训练集部分原理类似

validate_dataset = datasets.ImageFolder(root=image_path + "/val",
                                        transform=data_transform["val"])
                                        #ImageFolder(载入我们的测试集
val_num = len(validate_dataset)#打印我们测试集有多少张图片
validate_loader = torch.utils.data.DataLoader(validate_dataset,
                                              batch_size=batch_size, shuffle=False,
                                              num_workers=0)
                                               #将validate_dataset加载进来,batch_size设置为4,shuffle代表随机从样本中获取一批批的数据 num_worker=0表示用一个主线程去加载我们的数据 windows中不允许为负值

:point_down:测试代码 被注释的部分

# test_data_iter = iter(validate_loader)
# test_image, test_label = test_data_iter.next()
#
# def imshow(img):
#     img = img / 2 + 0.5  # unnormalize
#     npimg = img.numpy()
#     plt.imshow(np.transpose(npimg, (1, 2, 0)))
#     plt.show()
#
# print(' '.join('%5s' % cla_dict[test_label[j].item()] for j in range(4)))
# imshow(utils.make_grid(test_image))

使用时将 validate_loader = torch.utils.data.DataLoader(validate_dataset, batch_size=batch_size, shuffle=False, num_workers=0)
batch_size=4,shuffle=True
:point_up_2:测试代码 被注释的部分

开始训练

net = AlexNet(num_classes=5, init_weights=True)#花分类设为5 这里只有5个类别初始化权重表示为true
net.to(device)#将网络指认到我们刚刚所规定的设备上 GPU/CPU
loss_function = nn.CrossEntropyLoss() #定义损失函数 使用的是一个应用在多类别的损失交叉熵函数
# pata = list(net.parameters()) #调试用的 用来参看我们模型的一个参数
optimizer = optim.Adam(net.parameters(), lr=0.0002)#优化器 Adam  优化对象是我们网络中所有可训练的参数net.parameters() 学习率设置为0.0002

save_path = './AlexNet.pth' #保存权重的一个路径
best_acc = 0.0 #定义了一个最佳准确率的参数 用来保存准确率最高的一次训练的模型
for epoch in range(10): #迭代十次 分为一个 net.train() 和一个 net.eval()管理我们的dropout失活函数 net.train()中使用dropout   net.eval()中关闭dropout
    # train
    net.train()
    running_loss = 0.0 #计算我们训练过程的平均损失
    t1 = time.perf_counter() #统计一下训练一个epoch所需要的时间
    for step, data in enumerate(train_loader, start=0):
        images, labels = data #将data分为我们的图像还有标签
        optimizer.zero_grad() #清空我们的之前的梯度信息
        outputs = net(images.to(device))#进行正向传播  images.to(device)将训练图像也指认到我们的设备中
        loss = loss_function(outputs, labels.to(device))
        #计算预测值与真实值之间的一个损失 labels.to(device)训将标签也指认到我们的设备中
        loss.backward() #将得到的计算损失进行反向传播到每一个结点当中
        optimizer.step() #更新我们每一个结点的参数

        # print statistics
        running_loss += loss.item()
        # print train process #打印训练过程中的训练进度
        rate = (step + 1) / len(train_loader) #len(train_loader)得到训练一轮所需要的步数 (step + 1)获取我们训练步数 得到我们的训练进度
        a = "*" * int(rate * 50) #不懂
        b = "." * int((1 - rate) * 50) #不懂
        print("\rtrain loss: {:^3.0f}%[{}->{}]{:.3f}".format(int(rate * 100), a, b, loss), end="") #不懂
    print()
    print(time.perf_counter()-t1)#训练开始的时间减去训练结束的时间 得到一个差值

    # validate 训练完一轮之后进行我们的验证
    net.eval() 
    acc = 0.0  # accumulate accurate number / epoch
    with torch.no_grad():  #pytorch对我们的参数进行跟踪 在我们验证过程中不进行计算损失梯度
        for val_data in validate_loader:
            val_images, val_labels = val_data #将我们的数据分为图像和标签
            outputs = net(val_images.to(device)) #进行正向传播 训练图像指认到我们的设备中
            predict_y = torch.max(outputs, dim=1)[1] #求得输出的一个最大值作为我们预测 即是输出中最有可能的那个类别
            acc += (predict_y == val_labels.to(device)).sum().item()#计算我们有几个预测正确的类别 相等为1 不相等为0 item()获得数值
        val_accurate = acc / val_num #正确的个数处于总个数 得到测试集的一个准确率
        if val_accurate > best_acc: #当前的准确率 大于我们历史最优的准确率 那么保存这个数值 
            best_acc = val_accurate
            torch.save(net.state_dict(), save_path) #保存当前权重打印相应信息
        print('[epoch %d] train_loss: %.3f  test_accuracy: %.3f' %
              (epoch + 1, running_loss / step, val_accurate))

print('Finished Training')

predict.py

导入包

import torch
from model import AlexNet
from PIL import Image
from torchvision import transforms
import matplotlib.pyplot as plt
import json


预处理

data_transform = transforms.Compose(
    [transforms.Resize((224, 224)), #图像大小
     transforms.ToTensor(),#转成tensor
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])#标准化处理

载入一张图像

# load image
img = Image.open("../tulip.jpg")
plt.imshow(img)#展示图片
# [ C, H, W]
img = data_transform(img)#预处理
# expand batch dimension [N, C, H, W]
img = torch.unsqueeze(img, dim=0) # [N, C, H, W] #加上一个维度
# read class_indict
try:
    json_file = open('./class_indices.json', 'r')#读取我们刚刚保存的channel文件
    class_indict = json.load(json_file) #解码
except Exception as e:
    print(e)
    exit(-1)
# create model #初始化网络
model = AlexNet(num_classes=5)
# load model weights
model_weight_path = "./AlexNet.pth" #载入我们的网络模型
model.load_state_dict(torch.load(model_weight_path))
model.eval()#进入我们的eval模式
with torch.no_grad(): #pytorch对我们的参数进行跟踪 在我们验证过程中不进行计算损失
    # predict class
    output = torch.squeeze(model(img))#数据通过model进行正向传播 squeeze进行压缩
    predict = torch.softmax(output, dim=0#激活函数 变成一个概率分布
    predict_cla = torch.argmax(predict).numpy()#argmax获得概率最大处所对应的索引值
print(class_indict[str(predict_cla)], predict[predict_cla].item())
#打印类别名称以及所对应的预测概率
plt.show()

如何引进自己的数据集

1.直接自己创建一个多类别数据集


前面一个目录有一个代码是将数据集与训练集分开的 所以可以直接修改这个目录下的类别数据集
把前面目录的train和val删除 运行split_data.py 生成与flower_photos 所对应的train和val
在这里插入图片描述

2.模型需要修改的参数

在predict.py和train.py 中
在这里插入图片描述

粤 ICP 备 2020080455 号