第十九周:文献阅读笔记

摘要

本周阅读了AlexNet 经典论文,AlexNet是一种深度卷积神经网络,由Alex Krizhevsky、Ilya Sutskever和Geoffrey Hinton在2012年ImageNet图像分类竞赛中提出。其基本结构包括输入层、多个卷积层和池化层、全连接层以及输出层。其中,卷积层和池化层是交替进行的,卷积层用于提取图像特征,池化层则用于下采样,减少特征图的大小并增加模型的稳定性。全连接层用于将提取的特征与先验知识相结合,输出层则将网络输出转化为概率值,用于预测图像的类别。

Abstract

This week read the classic paper on AlexNet, a deep convolutional neural network proposed by Alex Krizhevsky, Ilya Sutskever, and Geoffrey Hinton in the 2012 ImageNet image classification competition. Its basic structure includes an input layer, multiple convolutional and pooling layers, a fully connected layer, and an output layer. The convolutional and pooling layers alternate, with the convolutional layer being used to extract image features and the pooling layer being used for downsampling to reduce the size of the feature map and increase the stability of the model. The fully connected layer is used to combine the extracted features with prior knowledge and the output layer transforms the network output into probability values for predicting the class of the image.

文献阅读

文献链接:ImageNet Classification with Deep Convolutional Neural Networks

1.文献摘要

作者训练了一个大型深度卷积神经网络,该神经网络拥有 6000 万个参数和 650,000 个神经元,由五个卷积层(其中一些后面是最大池层)和三个全连接层(最后一个 1000 路 softmax)组成。为了减少全连接层的过度拟合,作者采用了称为“dropout”的正则化方法,并且该大型卷积神经网络在ImageNet LSVRC-2010和ILSVRC-2012图像分类竞赛中取得了远低于第二名错误率的成绩。

2. 引言

当前的物体识别方法充分利用了机器学习方法。为了提高它们的性能,我们可以收集更大的数据集,学习更强大的模型,并使用更好的技术来防止过拟合。过去,标记图像的数据集相对较小,但现在可以收集数百万张图像的标记数据集。这些新的更大数据集能够解决物体识别中的可变性问题,并在实际环境中更好地学习和识别对象。事实证明,大图像数据集比小图像数据集能够训练更优的模型,但同时对于ImageNet这样的超大型数据集也需要与之对应的具有强大学习能力的模型,CNN对图像的性质(局部平稳和参数共享)使其比相同性能的标准前馈神经网络参数量要少得多。
本文的具体贡献如下:

  1. 在 ImageNet 大规模视觉识别挑战赛 (ILSVRC)-2010 和 ILSVRC-2012 竞赛2中使用的 ImageNet 子集上训练了迄今为止最大的 CNN 之一,并取得了十分好的成绩。
  2. 编写了高度优化的 2D 卷积 GPU 实现以及训练 CNN 中固有的所有其他操作,包括如何提高性能、减少训练时间等;
  3. 由于该网络的太大,会有过拟合的问题,所以采用了几种有效的技术来防止过拟合。

3. 数据集介绍

ImageNet 是一个包含超过 1500 万张带标签的高分辨率图像的数据集,属于大约 22,000 个类别。这些图像是从网络上收集的,并由人工贴标员使用亚马逊的 Mechanical Turk 众包工具进行标记。

ILSVRC-2010是唯一一个测试集标签可用的ILSVRC版本,因此这是作者进行大部分实验的版本。由于作者也在ILSVRC-2012竞赛中输入了他们的模型,在第6节中我们也报告了我们在这个版本的数据集上的结果,对于这个版本的数据集,测试集标签是不可用的。在ImageNet上,通常报告两个错误率:top-1和top-5,其中top-5错误率是模型认为最可能的5个标签中没有正确标签的测试图像的分数。

ImageNet 由可变分辨率图像组成,而作者的网络架构需要恒定的输入维度。因此,需要将图像统一分辨力为 256 x 256,首先给定一个矩形图像,我们首先重新缩放图像,使短边的长度为 256,然后从生成的图像中裁剪出中央 256 × 256 的块。

4. 架构

作者提出的这个网络架构,它包含八层神经网络-------五个卷积层和三个全连接层。
在这里插入图片描述

在AlexNet出现之前,sigmoid是最为常用的非线性激活函数。sigmoid函数能够把输入的连续实值压缩到0和1之间。但是,它的缺点也非常明显:当神经网络层数过多或输入值非常大或者非常小的时候会出现饱和现象,即这些神经元的梯度接近0,因此存在梯度消失问题。

sigmod激活函数: f ( x ) = 1 1 + e − x f(x)=\frac{1}{1+e^{-x}} f(x)=1+ex1
代码实现:

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1/(1+np.exp(-x))

# 使用NumPy库创建了一个数组x,以步长为0.1从-5.0到5.0的范围内取值。数组x中的元素依次为-5.0,-4.9,-4.8,…,4.8,4.9。
x=np.arange(-5.0,5.0,0.1)
y=sigmoid(x)
# plt.plot(x, y) 是一个常用的 Matplotlib 函数,用于绘制二维图形。
# 在这个函数中,x 和 y 都是一维数组。x 表示横坐标,y 表示纵坐标。这个函数会将 x 和 y 中的对应元素进行连接,形成一条曲线或折线图。
plt.plot(x,y)
# 将 y 轴的范围设置为 [-0.1, 1.1],即使得 y 轴从 -0.1 开始,到 1.1 结束。
plt.ylim(-0.1,1.1)
plt.show()

运行结果:
在这里插入图片描述
tanh函数(双曲正切激活函数)很像是sigmoid函数的放大版。在实际使用中要略微优于sigmoid函数,因为它解决的中心对称问题。指数的计算复杂,计算成本高。梯度消失(梯度弥散)的特点依旧保留,因为两边的饱和性使得梯度消失,进而难以训练。

tanh激活函数: f ( x ) = e x − e − x e x + e − x f(x)=\frac{e^{x}-e^{-x}}{e^{x}+e^{-x}} f(x)=ex+exexex
代码实现:

import numpy as np
import matplotlib.pyplot as plt

def tanh(x):
    return (np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x))

x=np.arange(-5.0,5.0,0.1)
y=tanh(x)
plt.plot(x,y)
plt.show()

运行结果:
在这里插入图片描述
ReLU激活函数: f ( x ) = m a x ( 0 , x ) f(x)=max(0,x) f(x)=max(0,x)
代码实现:

import numpy as np
import matplotlib.pyplot as plt

def relu(x):
    return np.maximum(0,x)

x=np.arange(-5.0,5.0,0.1)
y=relu(x)
plt.plot(x,y)
plt.show()

运行结果:
在这里插入图片描述
使用ReLU函数可以使神经网络训练更快、增加神经网络的非线性,并且防止梯度消失的问题出现。

在本文中,作者给出通过大量实验证明使用ReLU作为激活函数比使用tanh作激活函数训练速度要快好几倍。下图 对此进行了演示,该图显示了特定四层卷积网络在 CIFAR-10 数据集上达到 25% 训练误差所需的迭代次数。具有 ReLU(实线)的四层卷积神经网络在 CIFAR-10 上的训练错误率达到 25%,比具有 tanh 神经元(虚线)的等效网络快六倍。每个网络的学习率都是独立选择的,以使训练尽可能快。没有采用任何形式的正则化。这里展示的效果大小因网络架构而异,但使用 ReLU 的网络的学习速度始终比使用饱和神经元的网络快几倍。
在这里插入图片描述
该图表明,如果我们使用传统的饱和神经元模型,我们将无法在这项工作中试验如此大型的神经网络。

5. LRN

LRN(Local Response Normalization)是一种局部响应归一化的技术,在深度学习中常用于增强模型的泛化能力。LRN主要用于激活函数后的归一化过程,它对局部神经元响应进行归一化,使得响应较大的神经元抑制响应较小的神经元,从而增强模型的泛化性能。

LRN公式:在这里插入图片描述
LRN的作用是增强模型的鲁棒性和泛化能力,它可以在一定程度上抑制较大的激活值。LRN通常应用在激活函数后,用于对特征图进行归一化处理。主要是在深度学习的卷积神经网络中,特别是一些经典的网络结构中,如AlexNet和GoogLeNet等。

但LRN现在已不常用,被批归一化Batch Normalization代替。

通过pytorch实现LRN:

import torch
import torch.nn as nn
class LRN(nn.Module):
    def __init__(self, local_size=1, alpha=1.0, beta=0.75, k=1.0):
        super(LRN, self).__init__()
        self.local_size = local_size
        self.alpha = alpha
        self.beta = beta
        self.k = k

    def forward(self, x):
        squared_sum = torch.pow(x, 2).unsqueeze(1)
        pad = (self.local_size - 1) // 2
        squared_sum = F.pad(squared_sum, (pad, pad, pad, pad))
        squared_sum = F.avg_pool2d(squared_sum, kernel_size=self.local_size, stride=1)
        squared_sum = squared_sum.squeeze(1)
        x = x * torch.pow(self.k + self.alpha * squared_sum, -self.beta)
        return x

6. 重叠池化

重叠池化(overlapping pooling)是一种池化操作,在该操作中,池化窗口(pooling window)和步长(stride)会有重叠。与传统的池化操作不同,重叠池化在经过池化操作后仍然保留了原始特征图(feature map)中的一部分信息,从而有助于提高模型性能和减少信息损失。

一般来说,重叠池化与普通池化操作的主要区别在于,重叠池化每次移动的步长小于池化窗口的大小。例如,在一个尺寸为 7×7 的特征图上,若池化窗口大小为 3×3,步长为 2,则基于重叠池化的操作会从左上角开始遍历,每次向右移动 2 个像素和向下移动 2 个像素。

相对于传统的非重叠池化,作者采用重叠池化不仅可以提升预测精度,同时一定程度上可以减缓过拟合。相比于正常池化(步长s=2,窗口z=2) 重叠池化(步长s=2,窗口z=3) 可以减少top-1, top-5分别为0.4% 和0.3%;重叠池化可以避免过拟合。

7. 减少过拟合

7.1 数据增强

本文中作者采用减少过拟合的第一种方式是数据增强。

数据增强的第一种形式包括生成图像平移和水平反射。作者通过从 256 × 256 图像中提取随机 224 × 224 块(及其水平反射)并在这些提取的块上训练该网络来实现这一点。相当于增加了 2 × ( 256 − 224 ) 2 = 2048 2×(256-224)^{2}=2048 2×(256224)2=2048倍的数据量,大大减轻过拟合,提升泛化能力。在进行测试的时候,取图片的四个角加中间一共5个位置,并进行左右翻转,一共获得10张图片,对他们在softmax层进行10次预测结果并求均值。

第二种方式是使用Dropout方法,即在每轮前向传播和反向传播中,每层的神经元将会有一定的比例“失活”,即在本轮置为0,不参与前向传播和反向传播的过程。

7.2 Dropout

Dropout说的简单一点就是:我们在前向传播的时候,让某个神经元的激活值以一定的概率p停止工作,此时的权重值不会更新,但会保存下来,因为这个过程只是对于本次训练。这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征。

Dropout工作流程:

假设我们要训练这样一个神经网络,如图所示:
在这里插入图片描述
输入是x输出是y,正常的流程是:我们首先把x通过网络前向传播,然后把误差反向传播以决定如何更新参数让网络进行学习。使用Dropout之后,过程变成如下:

  1. 首先随机(临时)删掉网络中一半的隐藏神经元,输入输出神经元保持不变(下图中虚线为部分临时被删除的神经元)
    在这里插入图片描述
  2. 然后把输入x通过修改后的网络前向传播,然后把得到的损失结果通过修改的网络反向传播。一小批训练样本执行完这个过程后,在没有被删除的神经元上按照随机梯度下降法更新对应的参数(w,b)。
  3. 继续重复这一过程:
    1. 首先恢复删除的神经元(此时被删除的神经元保持原样,而没有被删除的神经元已经有所更新)
    2. 从隐藏层神经元中随机选择一个一半大小的子集临时删除掉(备份被删除神经元的参数)。
    3. 对一小批训练样本,先前向传播然后反向传播损失并根据随机梯度下降法更新参数(w,b) (没有被删除的那一部分参数得到更新,删除的神经元参数保持被删除前的结果)
  4. 继续重复这一操作

为什么Dropout可以解决过拟合?

Dropout的工作原理是在训练过程中随机地将神经网络中的一些神经元暂时丢弃(即将其输出设为零),并在每个训练步骤中随机选择不同的神经元丢弃。这样做的效果是,每个神经元都不能依赖于特定的其他神经元,因为在每次训练迭代中都有可能被丢弃,从而减少了神经元之间的复杂依赖关系。

Dropout的主要作用有以下几点,这些作用有助于缓解过拟合问题:

  • 减少神经元间的协同适应性: 在训练过程中,每个神经元都有可能被临时丢弃,因此网络无法过分依赖某些特定的神经元,减少了神经元之间的协同适应性。
  • 增加网络的泛化能力: 通过随机丢弃神经元,模型在训练时变得更加健壮,能够更好地适应不同的输入数据,从而提高了在未见过的数据上的泛化能力。
  • 模拟集成学习: 可以将Dropout看作是在训练过程中对多个子网络进行训练,最终将它们集成在一起。这相当于对不同子网络的平均或投票,有助于提高整体性能。
  • 降低参数之间的依赖性: Dropout减少了神经元之间的依赖性,从而减小了模型对特定权重的过度依赖,有助于更平均地利用网络中的所有参数。

8. 整体架构

在这里插入图片描述
作者提出的这个网络包含八个带有权重的层;前五个是卷积的,其余三个是全连接的,最后一个全连接层的输出被馈送到 1000 路 softmax,生成 1000 个类标签的分布。第一个卷积层使用 96 个大小为 11 × 11 × 3 的核,步长为 4 个像素(这是核图中相邻神经元的感受野中心之间的距离)对 227x227x3 输入图像进行过滤。第二个卷积层将第一个卷积层的(响应归一化和池化)输出作为输入,并使用 256 个大小为 5 × 5 × 48 的内核对其进行过滤。第三、第四和第五卷积层相互连接,无需任何中间的池化或标准化层。第三个卷积层有 384 个大小为 3 × 3 × 256 的内核,连接到第二个卷积层的(归一化、池化)输出。第四个卷积层有 384 个大小为 3 × 3 × 192 的内核,第五个卷积层有 256 个大小为 3 × 3 × 192 的内核。每个全连接层有 4096 个神经元。

【注】:卷积计算公式: H 2 = H 1 − F H + 2 P S + 1 H_{2}=\frac{H_{1}-F_{H}+2P}{S}+1 H2=SH1FH+2P+1

其中 H 1 H_{1} H1表示输入的长度, H 2 H_{2} H2表示输出特征图的长度, F F F表示卷积核长和宽的大小, S S S表示感受野的大小(滑动窗口), P P P表示边界填充

第一个卷积层为11x11x3,即卷积核尺寸为11x11,有96个卷积核,步长为4,卷积层后紧跟ReLU,因此输出的尺寸为 (227-11)/4+1=55,因此其输出的每个特征图为 55x55x96,同时后面经过LRN层处理,尺寸不变。最大池化层,池化核大小为3x3,步长为2,输出的尺寸为 (55-3)/2+1=27,因此特征图的大小为:27x27x96。由于双gpu处理,故每组数据有27x27x48个特征图,共两组数据,分别在两个gpu中进行运算。

第二层卷积的输入的数据为27x27x48,共两组数据,在两个GPU上训练
每组数据都被128个卷积核大小为: 5x5x48进行卷积运算,步长为1,尺寸不会改变,同样紧跟ReLU,和LRN层进行处理。最大池化层,池化核大小为3x3,步长为2,因此输出两组特征图:13x13x128。

第三层卷积的输入数据为 3x13x128,共两组,分别在两个GPU上,每组数据都被尺寸为 3x3x192的卷积核进行卷积运算,步长为1,加上ReLU,得到两组13x13x192的像素层。

第四层经过padding=1填充后,每组数据都被尺寸大小为 3x3x192的卷积核卷积运算,步长为1,加上ReLU,输出两组13x13x192的像素层。

第五层为便于后续处理,每幅像素层的左右两边和上下两边都要填充1个像素;2组像素层数据都被送至2个不同的GPU中进行运算。每个GPU中都有128个卷积核,经过padding=1填充后,每组数据都被尺寸大小为 3x3x128的卷积核进行卷积运算,步长为1,加上ReLU,输出两组13x13x128的像素层。两组13x13x128像素层分别在2个不同GPU中进行池化(pool)运算处理。池化运算的尺度为3x3,运算的步长为2,则池化后图像的尺寸为(13-3)/2+1=6。 即池化后像素的规模为两组6x6x128的像素层数据,共6x6x256规模的像素层数据。

第六层输入数据的尺寸是6x6x256,采用6x6x256尺寸的滤波器对第六层的输入数据进行卷积运算;每个6x6x256尺寸的滤波器对第六层的输入数据进行卷积运算生成一个运算结果,通过一个神经元输出这个运算结果;共有4096个6x6x256尺寸的滤波器对输入数据进行卷积运算,通过4096个神经元输出运算结果;这4096个运算结果通过relu激活函数生成4096个值;并通过drop运算后输出4096个本层的输出结果值。

由于第六层的运算过程中,采用的滤波器的尺寸(6x6x256)与待处理的feature map的尺寸(6x6x256)相同,即滤波器中的每个系数只与feature map中的一个像素值相乘;而其它卷积层中,每个滤波器的系数都会与多个feature map中像素值相乘;因此,将第六层称为全连接层。

第六层输出的4096个数据与第七层的4096个神经元进行全连接,然后经由relu进行处理后生成4096个数据,再经过dropout处理后输出4096个数据。第七层输出的4096个数据与第八层的1000个神经元进行全连接,经过训练后输出被训练的数值。

9. AlexNet代码实现

数据集下载:http://download.tensorflow.org/example_images/flower_photos.tgz
下载完后执行下面脚本,将数据集进行分类

#spile_data.py

import os
from shutil import copy
import random


def mkfile(file):
    if not os.path.exists(file):
        os.makedirs(file)


file = 'flower_data/flower_photos'
flower_class = [cla for cla in os.listdir(file) if ".txt" not in cla]
mkfile('flower_data/train')
for cla in flower_class:
    mkfile('flower_data/train/'+cla)

mkfile('flower_data/val')
for cla in flower_class:
    mkfile('flower_data/val/'+cla)

split_rate = 0.1
for cla in flower_class:
    cla_path = file + '/' + cla + '/'
    images = os.listdir(cla_path)
    num = len(images)
    eval_index = random.sample(images, k=int(num*split_rate))
    for index, image in enumerate(images):
        if image in eval_index:
            image_path = cla_path + image
            new_path = 'flower_data/val/' + cla
            copy(image_path, new_path)
        else:
            image_path = cla_path + image
            new_path = 'flower_data/train/' + cla
            copy(image_path, new_path)
        print("\r[{}] processing [{}/{}]".format(cla, index+1, num), end="")  # processing bar
    print()

print("processing done!")

代码解释:

  • from shutil import copy:这行代码是导入 Python 的 shutil 模块中的 copy 函数。shutil 模块是 Python 标准库中的一个实用工具模块,提供了一些对文件和文件夹进行常见操作的函数。copy 函数用于复制文件和文件夹。例如,你可以使用它将一个文件从一个位置复制到另一个位置,或者将一个文件夹及其内容复制到另一个位置。
  • os.makedirs(file):os.makedirs(file) 是使用 Python 的 os 模块中的 makedirs 函数来创建目录(文件夹)的代码。这个函数的作用是递归地创建目录,如果在创建目录的过程中缺少了任何父目录,也会一并创建。
  • flower_class = [cla for cla in os.listdir(file) if ".txt" not in cla]:这行代码使用了列表推导式,目的是从给定目录中列出的文件(或目录)中筛选出不包含 “.txt” 的文件名,并将这些文件名存储在名为 flower_class 的列表中。
  • os.listdir(file):使用 os 模块的 listdir 函数,返回指定目录 file 下的所有文件和目录的列表。
  • for cla in os.listdir(file):这一部分是一个 for 循环,遍历目录下的每个文件或目录。
  • if ".txt" not in cla:这一部分是一个条件语句,检查当前文件或目录名 cla 是否包含 “.txt”。如果不包含 “.txt”,则文件名符合条件。
  • [cla for cla in os.listdir(file) if ".txt" not in cla]:这是列表推导式的语法,将满足条件的文件名(不包含 “.txt”)收集到一个新的列表中,即 flower_class。
  • os.listdir(cla_path):用于返回指定路径下的文件和目录的名称列表
  • random.sample(images, k=int(num*split_rate)):从给定的列表(在这里是images)中随机选择指定数量(由k参数指定)的元素,并将这些元素作为列表返回。

分类后的数据:

在这里插入图片描述
文件结构:
在这里插入图片描述
实现代码:

#model.py

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] 自动舍去小数点后
            nn.ReLU(inplace=True), #inplace 可以载入更大模型
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[48, 27, 27] kernel_num为原论文一半
            nn.Conv2d(48, 128, kernel_size=5, padding=2),           # output[128, 27, 27]
            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]
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 192, kernel_size=3, padding=1),          # output[192, 13, 13]
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 128, kernel_size=3, padding=1),          # output[128, 13, 13]
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[128, 6, 6]
        )
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),
            #全链接
            nn.Linear(128 * 6 * 6, 2048),
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),
            nn.Linear(2048, 2048),
            nn.ReLU(inplace=True),
            nn.Linear(2048, num_classes),
        )
        if init_weights:
            self._initialize_weights()

    def forward(self, x):
        x = self.features(x)
        x = torch.flatten(x, start_dim=1) #展平   或者view()
        x = self.classifier(x)
        return x

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') #何教授方法
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)  #正态分布赋值
                nn.init.constant_(m.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 : GPU or CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)


#数据转换
data_transform = {
    "train": transforms.Compose([transforms.RandomResizedCrop(224),
                                 transforms.RandomHorizontalFlip(),
                                 transforms.ToTensor(),
                                 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)
                               transforms.ToTensor(),
                               transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}

#data_root = os.path.abspath(os.path.join(os.getcwd(), "../.."))  # get data root path
data_root = os.getcwd()
image_path = data_root + "/flower_data/"  # flower data set path
train_dataset = datasets.ImageFolder(root=image_path + "/train",
                                     transform=data_transform["train"])
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())
# write dict into json file
json_str = json.dumps(cla_dict, indent=4)
with open('class_indices.json', 'w') as json_file:
    json_file.write(json_str)

batch_size = 32
train_loader = torch.utils.data.DataLoader(train_dataset,
                                           batch_size=batch_size, shuffle=True,
                                           num_workers=0)

validate_dataset = datasets.ImageFolder(root=image_path + "/val",
                                        transform=data_transform["val"])
val_num = len(validate_dataset)
validate_loader = torch.utils.data.DataLoader(validate_dataset,
                                              batch_size=batch_size, shuffle=True,
                                              num_workers=0)

test_data_iter = iter(validate_loader)
test_image, test_label = test_data_iter.next()
#print(test_image[0].size(),type(test_image[0]))
#print(test_label[0],test_label[0].item(),type(test_label[0]))


#显示图像,之前需把validate_loader中batch_size改为4
# 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))


net = AlexNet(num_classes=5, init_weights=True)

net.to(device)
#损失函数:这里用交叉熵
loss_function = nn.CrossEntropyLoss()
#优化器 这里用Adam
optimizer = optim.Adam(net.parameters(), lr=0.0002)
#训练参数保存路径
save_path = './AlexNet.pth'
#训练过程中最高准确率
best_acc = 0.0

#开始进行训练和测试,训练一轮,测试一轮
for epoch in range(10):
    # train
    net.train()    #训练过程中,使用之前定义网络中的dropout
    running_loss = 0.0
    t1 = time.perf_counter()
    for step, data in enumerate(train_loader, start=0):
        images, labels = data
        optimizer.zero_grad()
        outputs = net(images.to(device))
        loss = loss_function(outputs, labels.to(device))
        loss.backward()
        optimizer.step()

        # print statistics
        running_loss += loss.item()
        # print train process
        rate = (step + 1) / len(train_loader)
        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()    #测试过程中不需要dropout,使用所有的神经元
    acc = 0.0  # accumulate accurate number / epoch
    with torch.no_grad():
        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()
        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')

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@默然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值