一、接触的名词含义and两个常用函数

Pytorch/Tensorflow就是python的库

Anaconda:自带python环境 ;带有虚拟环境的功能,可以在多个虚拟环境中实现不同版本的项目,有conda,pip创建管理功能

CUDA :下载pytorch的cuda是软件层面的cuda:利用gpu计算的工具;操控英伟达显卡是硬件层面的cuda;软件cuda版本<=硬件cuda版本

例如:dir(torch);help(torch.cuda.is_available)

二、Dataset类代码实践

1.Python相关语法

1.类的成员变量可以忽略不写,在构造方法中直接使用(即赋值又定义)

2.def就是函数,def__init__()为构造方法  :init前后两个下划线

3.self(必须有)相当于java中的this

4.class MyData(Dataset): 子类MyData继承父类Dataset

5.import导入整个模块,而 from   import导入指定成员

2.Dataset具体代码

在PyTorch中,Dataset类的主要作用是提供一个抽象的方式来表示数据集,它不能被直接实例化,而是需要用户定义自己的数据集类作为Dataset的子类来继承其属性和方法。

Dataset的主要作用包括:

  1. 数据集的抽象化:Dataset类将数据集抽象成可以进行遍历的对象,每次迭代可以从数据集中返回一组数据。这使得研究人员能够方便地处理和训练机器学习模型。
  2. 加载与预处理数据:在Dataset中,通常会进行数据的加载和预处理工作。例如,可以加载图片、文本或其他类型的数据,并进行必要的转换或增强操作,以满足模型训练的需求。
  3. 提供数据访问接口:Dataset类提供了__getitem____len__等接口,使得用户可以方便地访问数据集中的每一个样本及其标签,以及获取数据集的总长度。这对于构建数据加载器和进行批量训练等操作非常重要。

总的来说,PyTorch中的Dataset类为研究人员提供了一种方便的方式来定义、加载、预处理和访问数据集,使得机器学习模型的训练和评估过程更加高效和灵活。

作用:获取数据和数据的label,获取数据的总数量

label  就是标注:蚂蚁  返回图片和标签

三、Tensorboard(可视化工具)的使用

1.Summarywriter类

2.add_scalar( )方法

add_scalar(tag, scalar_value, global_step)

tag:描述该标量数据图的标题;scalar_value:y轴;global_step:x轴

下图y=x的标量图

range(100)->是到99结束

3.代码的可视化展示

在终端中输入tensorboard --logdir=xxx(文件名)即打开该文件,默认端口为6006

可通过tensorboard --logdir=xxx --port=6666,更改端口为6666

4.add_image( )方法

第一步:导入对应模块

第二步:得到PIL格式的图像

第三步:转成numpy格式(一般用opencv,下边例子用的numpy转化)图像(还可以tensor类型)

第四步:add_image(“标题”,图像,第几step,图片类型shape HWC or CHW....)

第五步:通过tensorboard --logdir=xxx(文件名)展示

四、Transforms

1.Transforms的使用(加工厂)

在PyTorch中,transforms是一个功能强大的工具,主要用于对图像进行一系列的预处理操作。这些操作包括但不限于数据中心化、缩放、裁剪、旋转、翻转、填充、添加噪声、灰度变换、线性变换、仿射变换,以及亮度、饱和度、对比度的调整等。这些预处理操作的主要目的是对图像数据进行增强,从而提高模型的泛化能力。

transforms是PyTorch图像处理库torchvision的一部分,提供了多种图像变换的方法。例如,ToTensor是一个类,它的功能是将其他图像数据(如PIL Image或ndarray)类型转化为tensor类型,并归一化至[0-1]。

总的来说,transforms在PyTorch中起到了对图像数据进行预处理和增强的关键作用,有助于提高深度学习模型的性能和准确性。

彩色图像一般就是三通道:rgb:红绿蓝   黑白图像一般是单通道

第二种:PIL转换为Tensor类型(先实例化,再调用),在神经网络中,要将图片转化为tensor类型再进行训练

2、常见的Transforms

1.Totensor

2.标准化normalize

在PyTorch中,transforms.Normalize是一个用于数据预处理的重要函数,特别是在图像、视频等数据的预处理中。其主要作用是对数据进行标准化处理。

具体来说,transforms.Normalize会将数据的每个通道减去均值,再除以标准差,使得数据在均值为0,标准差为1的分布中。如果希望限制数据在某个范围内,可以传入参数meanstd来自定义均值和标准差。

这种标准化处理对于模型训练的效果具有积极的影响。它有助于消除不同特征之间的量纲差异,使得模型在训练过程中更容易收敛,并且可以提高模型的性能和稳定性。此外,标准化处理还可以缩放像素值并使其在特定范围内(如0-1)平均分布,这有助于减少训练时间并进一步提高模型的准确性。

归一化和标准化的区别

标准化和归一化在数据预处理中都是常见的技术,但它们之间存在着明显的区别。

归一化是将数据的数值范围进行特定缩放,但不改变其数据分布的一种线性特征变换。它通常用于将数据缩放到一个特定的范围,如0到1或-1到1之间。这种转换是基于数据的最大值和最小值来进行的。归一化的主要目的是调整特征维度,使得不同特征对目标函数的影响权重一致,改变原始数据的分布形状,例如将扁平分布的数据伸缩变换成类圆形。归一化的优点是可以提高迭代求解的精度。然而,需要注意的是,最大值和最小值非常容易受到异常点的影响,因此归一化可能在一些特定场景下鲁棒性较差。

标准化则是一种使数据具有标准正态分布特性的过程,有助于数据的统计分析。标准化基于数据的均值和标准差进行转换,使数据具有零均值和单位方差。这种转换不会改变原始数据的分布形状或信息,只是进行了伸缩变化。标准化的好处在于它可以忽略不同特征之间的度量差异,保留样本在各个维度上的信息。在已有样本足够多的情况下,标准化处理相对稳定,适合现代嘈杂大数据场景。

总结来说,归一化和标准化的主要区别在于操作方式、数学原理以及适用场景。归一化主要关注数据的范围缩放,而标准化则关注数据的分布特性。在实际应用中,应根据数据的特性和模型的需求来选择适当的预处理技术。

3.compose

transforms.Composetorchvision.transforms模块中的一个类,其主要作用是串联多个图片变换的操作。具体来说,它接受一个变换列表作为参数,该列表包含一系列的图像处理操作,例如图像随机缩放、裁剪、旋转和翻转等。当对图像进行变换时,这些变换会按照列表中的顺序依次应用。

使用transforms.Compose,可以方便地将多个变换步骤整合到一起,形成一个完整的变换序列。例如,你可以创建一个变换序列,先对图像进行中心裁剪,然后再将其转换为张量。在这个例子中,中心裁剪的输出结果将作为转换为张量的输入。

需要注意的是,Compose中的每个变换步骤都是有顺序的,且前一个步骤的输出结果需要是后一个步骤可以接受的输入类型,以确保整个变换序列的顺利执行。

总的来说,transforms.Compose在图像处理和机器学习中是一个非常重要的工具,它可以帮助我们更方便地构建复杂的图像变换序列,以满足不同的任务需求

4.resize  调整图片大小

transforms.Resize是PyTorch中torchvision.transforms模块的一个类,主要用于调整图像的大小。它可以将输入图像(PIL Image或Tensor)调整为给定的大小。

具体来说,transforms.Resize可以接受两种类型的参数作为目标大小:

  1. 一个整数:当传入一个整数时,这个整数表示将图像的较短边缩放到指定长度,同时保持长宽比。例如,transforms.Resize(256)会将图像的较短边调整为256像素,而较长边将按比例缩放。
  2. 一个包含两个元素的列表或元组:例如[width, height],这表示将图像的宽度和高度分别调整为指定的尺寸。例如,transforms.Resize([256, 256])会将图像的宽度和高度都调整为256像素。需要注意的是,在这种情况下,图像的长宽比可能会发生改变,因此图像可能会被拉伸或压缩来适应指定的大小。

此外,transforms.Resize还有一个可选参数interpolation,用于指定插值方法,这决定了在调整图像大小时使用的算法。默认情况下,它使用双线性插值(InterpolationMode.BILINEAR)。

总的来说,transforms.Resize是一个非常实用的工具,可以帮助我们在图像处理和机器学习任务中方便地调整图像的大小。

5.randomcrop,随机裁剪,用于数据增强

transforms.RandomCroptorchvision.transforms模块中的一个类,用于在图像上执行随机裁剪操作。这个变换对于数据增强非常有用,因为它可以帮助模型更好地泛化到不同的输入尺寸和位置。

transforms.RandomCrop的主要参数包括:

  • size:期望裁剪后的输出尺寸。可以是一个整数,表示裁剪出的正方形区域的边长;也可以是一个包含两个元素的元组或列表,如(height, width),表示裁剪出的矩形区域的高和宽。
  • padding:一个可选参数,用于指定在裁剪前对图像进行填充的像素数。这有助于在原始图像尺寸小于期望的裁剪尺寸时,仍然能够执行裁剪操作。padding可以是一个整数,表示在图像的四周都填充相同的像素数;也可以是一个包含两个元素的元组或列表,如(padding_left_right, padding_top_bottom),分别指定左右和上下两个方向的填充像素数;还可以是一个包含四个元素的元组或列表,如(padding_left, padding_top, padding_right, padding_bottom),分别指定四个方向的填充像素数。
  • pad_if_needed:一个布尔值,当图像小于所需的裁剪尺寸并且提供了padding时,如果设置为True,则会对图像进行填充以执行裁剪操作。如果设置为False,则当图像小于所需尺寸时不会进行填充,这可能会导致裁剪操作失败。
  • fill:一个可选参数,当padding被设置时,这个参数用于指定填充像素的值。默认情况下,填充像素的值为0。如果padding_mode设置为'constant',则填充像素的值由fill参数决定。
  • padding_mode:一个字符串,用于指定填充模式。有以下几种选项:
    • 'constant':使用常数值进行填充,该常数值由fill参数指定。
    • 'edge':使用图像边缘的像素值进行填充。
    • 'reflect':使用反射方式进行填充,即像素值从边缘开始镜像反射。
    • 'symmetric':使用对称方式进行填充,即像素值从边缘开始对称复制。

使用transforms.RandomCrop时,会在图像的随机位置上进行裁剪,并返回裁剪后的新图像。这种随机性有助于模型在训练时看到不同的输入,从而提高其泛化能力。需要注意的是,如果原始图像的尺寸小于期望的裁剪尺寸,并且没有设置足够的填充或pad_if_neededFalse,则裁剪操作可能会失败

五、torchvision的数据集的使用

1.CIFAR10数据集

通过torchvision.datasets.CIFAR10(root=, train=, transform=, download=True)下载;root表示下载哪,train为true为训练集(大)false为测试集(小),transform为转化为何种类型,download为没有该文件时是否下载

2.数据集dataset和transforms的联用

Compose(【】,【】)多个操作联合在一起,此处只有一个:dataset_transform = torchvision.transforms.Compose([torchvision.transforms.ToTensor()]),实例化后通过CIFAR10中的transform参数使用

六、Dataloader的使用

DataLoader是PyTorch中的一个重要工具,用于数据加载和批处理。它是数据加载器,结合了数据集(Dataset)和取样器(Sampler),并可以提供多个线程处理数据集。在训练模型时,DataLoader会把训练数据分成多个小组(即批次),并在每次迭代时抛出一组数据,直至所有的数据都被抛出。这样,DataLoader就起到了数据的初始化作用。

DataLoader有很多参数,但常用的有下面几个:

  • dataset:表示Dataset类,传入需要读取的数据集。
  • batch_size:表示一次读取多少数据,即每个批次有多少个样本。
  • num_workers:表示是否多进程读取数据,这个参数决定了有几个进程来处理数据加载。如果设置为0,则所有的数据都会被加载进主进程。
  • shuffle:表示在每个epoch开始的时候,是否对数据进行重新排序。
  • drop_last:如果设置为True,当样本数不能被batch_size整除时,会舍弃最后一批数据。如果为False(默认),则会继续正常执行,只是最后的batch_size会小一点。

在实践中,数据读取经常是训练的性能瓶颈,特别是在模型较简单或者计算硬件性能较高时。而DataLoader允许使用多进程来加速数据读取,从而提高训练的效率。

需要注意的是,在使用DataLoader之前,需要先创建一个Dataset对象,定义好如何获取数据和标签。然后,通过DataLoader的循环,将数据和标签拿到模型中去训练。

总之,DataLoader是PyTorch中用于高效加载和批处理数据的重要工具,可以极大地提高模型的训练效率

为网络提供不同的数据形式

Dataloader(dataset:数据集,batch_size:每次从数据集中取图片个数,shuffle:是否随机读取,num_workers:是否多线程(0代表主进程,>0表示多线程),drop_last:是否保留余数)

七、神经网络

1.nn.module的使用

nn.Module是PyTorch深度学习框架中的一个核心概念,它是一个抽象基类,用于表示神经网络的各个部分或层。通过继承nn.Module类,用户可以自定义神经网络的层或整个网络结构,包括定义网络的前向传播行为。

在实际应用中,创建自定义的神经网络层或模型时,通常需要继承nn.Module类,并在子类中定义__init__函数来初始化网络层,以及forward函数来指定数据在网络中的前向传播方式。__init__函数中通常包含网络层的定义,而forward函数则定义了数据如何通过这些层进行传递。

nn.Module为神经网络的模块化设计提供了便利,使得构建、训练和调试复杂的神经网络模型变得更加容易。无论是创建简单的神经网络层,还是构建复杂的深度神经网络结构,都可以通过继承nn.Module类来实现。

总的来说,nn.Module是PyTorch中神经网络模型构建的基础,它为深度学习模型的自定义和扩展提供了强大的支持。

第一步:创建一个类继承nn.module

第二步:重写方法forward(直接调用),nn.Module把__call__方法实现为类对象的forward函数,所以任意继承了nn.Module的类对象都可以这样简写来调用forward函数。

forward是怎样调用的:通过call来调用

2.卷积操作(Conv2d:二维操作)

第一步:定义输入图像和卷积核(此处为2维)

第二步:卷积操作需要(minibatch,channel,H,W),所以要重塑类型,使用reshape()

第三步:卷积操作conv2d(输入图片,卷积核,步长stride,填充padding)

3.卷积层(多维信息交互)

卷积层在卷积神经网络中起着至关重要的作用,其主要功能和作用体现在以下几个方面:

  1. 特征提取:卷积层通过一组滤波器或权值矩阵与前一层的特征图上的部分神经元相连,进行卷积操作以提取输入图片中的信息,这些信息被称为图像特征。这些特征可能是图像的纹理、颜色或其他属性,由图像中的每个像素通过组合或独立的方式所体现。这种局部连接的方式有助于模型从局部到全局地理解图像内容。
  2. 减少参数和计算量:卷积层通过局部连接和权值共享的方式,大大减少了网络的自由参数,降低了模型的复杂度,从而在一定程度上避免了网络过拟合。局部连接意味着每个神经元只需要对局部区域进行感知,而权值共享则意味着在整个输入图片上,使用相同的卷积核进行计算,这大大减少了需要学习的参数数量。
  3. 提高模型的推理速度:通过减少参数和计算量,卷积层有助于加快模型的推理速度,使其在处理大规模数据集或实时应用时更加高效。
  4. 扩大感知野:随着网络层数的增加,虽然神经元的数量在减少,但每个神经元所覆盖的原始输入数据的区域(即感知野)在逐渐扩大,这有助于模型捕获更大范围的上下文信息。

总的来说,卷积层通过其独特的结构和操作方式,使得卷积神经网络能够有效地处理图像数据,提取有用的特征信息,并在各种计算机视觉任务中取得出色的性能。

inchannel:等于输入的深度(而卷积核的深度与输入的 深度一样(为了每一层对应计算),因此卷积核的输入通道往往省略不写)

卷积核的初始值是随机的    图1是6个卷积核,标注错误

4.池化层(降低数据量)

(1)池化层参数介绍

池化层(Pooling Layer)是卷积神经网络(CNN)中的一个重要组件,主要用于减少特征图(feature maps)的维度,同时保留重要的特征信息。以下是关于池化层的详细解释:

  1. 作用
  • 降维:通过减小特征图的高和宽,降低数据的空间维度,有助于减少计算量和避免过拟合。
  • 特征提取:通过聚合操作抽象出更高级别的特征,对局部变化不太敏感。
  • 平移不变性:增加了模型对特征位置的变化不敏感,使特征在空间维度上具有平移不变性。
  1. 操作方式
  • 池化操作通常涉及在特征图上滑动一个窗口,并对窗口内的值进行某种聚合操作。
  • 最常见的池化类型是最大池化(Max Pooling)平均池化(Average Pooling)。最大池化选择窗口内的最大值,而平均池化则计算窗口内值的平均值。
  1. 参数与效果
  • 池化窗口的大小决定了聚合操作覆盖的区域,而步长决定了窗口滑动的间距。
  • 池化窗口越大,降维效果越强,但也可能损失更多特征信息。因此,池化层的参数设置需要根据具体问题来确定。

在卷积神经网络中,池化层通常紧跟在卷积层之后,用于进一步处理卷积层提取的特征。通过结合最大池化和平均池化,池化层可以有效地实现降维、特征提取和变换不变性等目的,从而提高模型的性能

kernel_size:卷积核大小,stride:步长默认是卷积核大小,padding:填充

dilation:空洞卷积(中间隔一个)

ceil_mode:(true保留),(false舍弃)

(2)池化层代码复现

import torch
import torchvision
from torch import nn
from torch.nn import MaxPool2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

dataset = torchvision.datasets.CIFAR10("./data4", train=False, download=True, transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=64)
input1 = torch.tensor([[1, 2, 0, 3, 1],
                       [0, 1, 2, 3, 1],
                       [1, 2, 1, 0, 0],
                       [5, 2, 3, 1, 1],
                       [2, 1, 0, 1, 1]], dtype=torch.float32)  # 浮点型

input1 = torch.reshape(input1, (-1, 1, 5, 5))


class Ygh(nn.Module):
    def __init__(self):
        super(Ygh, self).__init__()
        self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=True)

    def forward(self, input2):
        output = self.maxpool1(input2)
        return output


yyy = Ygh()
writer = SummaryWriter("logs_maxPool")
step = 0
for data in dataloader:
    img, targets = data
    writer.add_images("input", img, step)
    step = step + 1
    output = yyy(img)
    writer.add_images("output", output, step)

writer.close()

5.非线性激活

非线性激活在神经网络中扮演着至关重要的角色,其作用主要体现在以下几个方面:

  1. 引入非线性关系:神经网络中的线性操作,如线性加权和,只能表达线性关系。然而,现实世界中的数据和问题往往具有复杂的非线性性质。通过引入非线性激活函数(如sigmoid、tanh、ReLU等),神经网络可以学习和表示这些非线性的模式,从而增强了网络的表示能力,使其能够拟合更复杂的数据。
  2. 模拟生物神经元行为:生物神经元的行为是非线性的,非线性激活函数更好地模拟了神经元的生物特性,使得神经网络更适合处理生物数据和生物启发的问题。
  3. 解决逼近任意函数的问题:使用非线性激活函数,特别是具有一定深度的神经网络,具备了逼近任意复杂函数的能力。这是神经网络的强大之处,被称为通用函数逼近定理。
  4. 解决异或问题:非线性激活函数能够解决异或(XOR)等非线性问题。例如,在单层感知机中,使用线性激活函数无法分割异或问题,但通过引入非线性激活函数,神经网络可以轻松解决这类问题。
  5. 防止过拟合:非线性激活函数有助于提供一定的模型容量,从而减少过拟合风险。线性模型的拟合能力有限,容易受到噪声的影响,而非线性激活函数可以提高模型的泛化能力。

因此,在神经网络中,对每一层线性变换后叠加一个非线性激活函数,可以避免多层网络等效于单层线性函数,从而获得更大的学习与拟合能力。

(1)非线性激活的作用:神经网络需要非线性激活函数,才能在多层网络中逐渐组合线性变换,从而表达更丰富的函数和特征。

(2)常用nn_relu:

ReLU(Rectified Linear Activation Function)是一种常用的激活函数,主要用于神经网络的非线性变换。ReLU函数的定义是:对于输入x,如果x大于等于0,则输出为x本身;如果x小于0,则输出为0。可以表示为ReLU(x) = max(0, x)。

ReLU激活函数的作用是通过将负值部分截断为0,增强神经网络的非线性特性,并且可以帮助网络更好地学习特征。在神经网络的训练过程中,ReLU激活函数可以加快训练速度并减少梯度消失问题。

       其中参数inplace的作用:inplace为True时,原地替换为0;inplace为False时,新生成一个变量来保留结果。relu=max(0,input)

(2.1)sigmoid函数

在PyTorch中,sigmoid函数的主要作用是将神经网络的输出映射到0和1之间的概率值。这种映射使得sigmoid函数在二元分类问题中特别有用,因为输出可以被解释为属于某个类别的概率。

sigmoid函数的数学表达式为f(x) = 1 / (1 + e^-x),其中e是自然对数的底数。这个函数的输出值范围在0和1之间,因此非常适合用于表示概率。

在PyTorch中,sigmoid函数通常用作神经网络的激活函数,特别是在输出层。通过将sigmoid函数应用于神经网络的输出,可以将其转换为概率分布,使得模型可以更容易地处理分类问题。

需要注意的是,尽管sigmoid函数在某些情况下很有用,但它也可能导致梯度消失问题。当神经网络的层数较多或输入值很大或很小时,sigmoid函数的导数可能变得非常小,这可能导致梯度在反向传播过程中逐渐消失,从而影响模型的训练效果。因此,在选择激活函数时,需要根据具体的应用场景和需求进行权衡。

总的来说,PyTorch中的sigmoid函数是一个强大的工具,可以帮助我们更好地处理二元分类问题,并在神经网络中引入非线性因素。然而,也需要注意其可能带来的梯度消失问题,并考虑其他可能的激活函数选择。

(3)代码复现

import torch
from torch import nn
from torch.nn import ReLU

input1 = torch.tensor([[1, 0.5], [-1, 3]])
input1 = torch.reshape(input1, (-1, 1, 2, 2))
print(input1)


class ygh(nn.Module):
    def __init__(self):
        super(ygh, self).__init__()
        self.relu1 = ReLU()

    def forward(self, input2):
        output1 = self.relu1(input2)
        return output1


yy = ygh()
output = yy(input1)
print(output)

6.损失函数和反向传播

反向传播来计算梯度,根据梯度来更新参数,实现loss最小化

八、网络模型

1.网络模型的使用和修改

VGG网络模型中的参数pretrain为false时为未训练的,true为训练好的(大)

VGG类似一个函数集合vgg16_true = torchvision.models.vgg16()

添加一层模块vgg16_true.addmodule("add_linear", nn.linear( 1000,100))

修改一层模块vgg16_true.classifier[6]=nn.linear(4096,10)

2.网络模型的保存和读取

保存方式1:保存结构和参数

保存方式2:将模型保存成字典的形式(官方推荐)

加载方式1:载入pth

加载方式2:载入字典

九、模型训练套路

Data

from torchvision import transforms
from torch.utils.data import Dataset, DataLoader
from PIL import Image
import numpy as np


def Myloader(path):
    return Image.open(path).convert('RGB')   #将打开的图像转化为rgb模式  RGB格式具有较高的兼容性和通用性


# get a list of paths and labels.
def init_process(path, lens):
    data = []
    name = find_label(path)       #根据文件路径返回文件标签
    for i in range(lens[0], lens[1]):
        data.append([path % i, name])  # %是强path中的占位符用i替换    然后将【更新的path, name】作为列表添加到append
    return data


class MyDataset(Dataset):                           #继承了Dateset
    def __init__(self, data, transform, loader):
        self.data = data
        self.transform = transform
        self.loader = loader

    def __getitem__(self, item):   #返回图像和他的标签  item就是第几个
        img, label = self.data[item]
        img = self.loader(img)
        img = self.transform(img)
        return img, label

    def __len__(self):
        return len(self.data)


def find_label(str): #查询标签  判断为1(dog)还是0
    """
    Find image tags based on file paths.

    :param str: file path
    :return: image label
    """
    first, last = 0, 0
    for i in range(len(str) - 1, -1, -1):   #从尾部开始到  步长为-1  range(开始,结束,步长)
        if str[i] == '%' and str[i - 1] == '.':
            last = i - 1
        if (str[i] == 'c' or str[i] == 'd') and str[i - 1] == '/':
            first = i
            break

    name = str[first:last]  #找到倒数第一个/  确认是dog还是cat
    if name == 'dog':
        return 1
    else:
        return 0


def load_data():     #加载和预处理图像数据
    print('data processing...')
    transform = transforms.Compose([
        transforms.RandomHorizontalFlip(p=0.3),   #随机水平翻转 概率0.3   作用是增强数据的多样性
        transforms.RandomVerticalFlip(p=0.3),      #随机垂直翻转
        transforms.Resize((256, 256)),       #图片大小调整为256*256
        transforms.ToTensor(),   #(C H W)  对于卷积操作,内部做卷积运算的加速设计在 (C, H, W) 格式上会比 (H, W, C) 处理起来更容易、更快。
        transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))  # normalization
                                                                        #标准化 使得数据的分布更适合于神经网络的训练,从而提高模型的性能和收敛速度。
    ])
    path1 = 'data/training_data/cats/cat.%d.jpg'                        #生成500张猫的训练集
    data1 = init_process(path1, [0, 500])
    path2 = 'data/training_data/dogs/dog.%d.jpg'                        #500张狗的训练集
    data2 = init_process(path2, [0, 500])
    path3 = 'data/testing_data/cats/cat.%d.jpg'
    data3 = init_process(path3, [1000, 1200])                           #200张猫的测试集
    path4 = 'data/testing_data/dogs/dog.%d.jpg'
    data4 = init_process(path4, [1000, 1200])                           #200张狗的测试集
    data = data1 + data2 + data3 + data4   # 1400
    # shuffle
    np.random.shuffle(data)       #打乱   训练集900  验证集200  测试集300
    # train, val, test = 900 + 200 + 300
    train_data, val_data, test_data = data[:900], data[900:1100], data[1100:]
    train_data = MyDataset(train_data, transform=transform, loader=Myloader)   #loader  transform转化 为pytorch可以识别的数据集
    Dtr = DataLoader(dataset=train_data, batch_size=50, shuffle=True, num_workers=0)#(数据集,,每次送入多少数据,是否打乱,是否用子进程加快速度)
    val_data = MyDataset(val_data, transform=transform, loader=Myloader)
    Val = DataLoader(dataset=val_data, batch_size=50, shuffle=True, num_workers=0)
    test_data = MyDataset(test_data, transform=transform, loader=Myloader)
    Dte = DataLoader(dataset=test_data, batch_size=50, shuffle=True, num_workers=0)

    return Dtr, Val, Dte

CNN

import copy
import os
import random

import numpy as np
import torch
import torch.nn as nn
from torch import optim
from torch.autograd import Variable
from tqdm import tqdm

from data_process import load_data
import torch.nn.functional as F

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # gpu可用就用gpu否则cpu


def setup_seed(seed):
    os.environ['PYTHONHASHSEED'] = str(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    np.random.seed(seed)
    random.seed(seed)
    torch.backends.cudnn.deterministic = True


setup_seed(20)


class cnn(nn.Module):
    def __init__(self):
        super(cnn, self).__init__()
        self.relu = nn.ReLU()
        # self.sigmoid = nn.Sigmoid()
        self.conv1 = nn.Sequential(   #一定要学会卷积层
            nn.Conv2d(
                in_channels=3,
                out_channels=16,      #output_size = (input_size - kernel_size + 2 * padding) / stride + 1
                kernel_size=3,
                stride=2,
            ),
            nn.BatchNorm2d(16),  #卷积后使用归一化是为了提高模型的稳定性和性能,加速模型的收敛速度,避免梯度问题,并提高模型的泛化能力。
            nn.ReLU(),#归一化后使用ReLU函数能够增强神经网络的非线性表达能力,缓解梯度消失问题,提高计算效率,并产生稀疏表示,从而提升网络的性能。
            nn.MaxPool2d(kernel_size=2),#简化尺寸      63
        )
        #
        self.conv2 = nn.Sequential(
            nn.Conv2d(
                in_channels=16,
                out_channels=32,
                kernel_size=3,
                stride=2,
            ),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),   #图片尺寸15
        )
        #
        self.conv3 = nn.Sequential(
            nn.Conv2d(
                in_channels=32,
                out_channels=64,
                kernel_size=3,
                stride=2,
            ),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),           #图片尺寸3
        )
        self.fc1 = nn.Linear(3 * 3 * 64, 64)   #全连接层在 CNN 中的主要作用是进一步提取和整合前面卷积层学习到的特征,以便进行最终的分类或回归任务。
        self.fc2 = nn.Linear(64, 10)
        self.out = nn.Linear(10, 2)   #两个类别

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        # print(x.size())
        x = x.view(x.shape[0], -1)  #这通常在卷积神经网络(CNN)的全连接层之前使用,因为全连接层需要一个二维输入(通常是 [batch_size, features])。假设 x 是卷积层之后的输出,它可能是一个四维张量(如 [batch_size, channels, height, width])。通过 x.view(x.shape[0], -1),我们可以将这个四维张量重新塑形为二维,其中第二个维度的大小是自动计算的,包含了所有样本的所有特征。
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        x = self.out(x)
        x = F.log_softmax(x, dim=1)  #归一化输出:softmax函数可以将神经网络的原始输出转化为概率分布。这意味着,对于每个输入样本,softmax会生成一个概率向量,其中每个元素表示该样本属于某个特定类别的概率。这些概率值的范围是[0,1],并且所有类别的概率之和为1。
        return x

#其目的是计算给定模型在验证集(Val)上的平均损失
def get_val_loss(model, Val): #模型和验证集
    model.eval()   #这将模型设置为评估模式。在评估模式下,模型的某些层(如 Dropout 和 BatchNorm)的行为会有所不同,以确保在评估时得到一致的输出
    criterion = nn.CrossEntropyLoss().to(device)  #这里定义了交叉熵损失函数
    val_loss = [] #存储每个批次的损失值
    for (data, target) in Val:
        data, target = Variable(data).to(device), Variable(target.long()).to(device)   #取数据和标签
        output = model(data)
        loss = criterion(output, target)     #计算差值
        val_loss.append(loss.cpu().item())   #GPU进行并行计算,其他数据处理要到cpu上

    return np.mean(val_loss)


def train():
    Dtr, Val, Dte = load_data()
    print('train...')
    epoch_num = 30
    best_model = None
    min_epochs = 5   #5轮更新一次
    min_val_loss = 5
    model = cnn().to(device)
    optimizer = optim.Adam(model.parameters(), lr=0.0008)
    criterion = nn.CrossEntropyLoss().to(device)
    for epoch in tqdm(range(epoch_num), ascii=True):   #tqdm可视化进度指示   ascii码显示
        train_loss = []
        for batch_idx, (data, target) in enumerate(Dtr, 0):   #获取索引batch  和数据      enumerate(从哪里取数据,开始索引)
            data, target = Variable(data).to(device), Variable(target.long()).to(device)
            # target = target.view(target.shape[0], -1)
            # print(target)
            optimizer.zero_grad()
            output = model(data)
            # print(output)
            loss = criterion(output, target)
            loss.backward()
            optimizer.step()
            train_loss.append(loss.cpu().item())      #取值放入train——loss
        # validation
        val_loss = get_val_loss(model, Val)
        model.train()        #转为训练模式
        if epoch + 1 > min_epochs and val_loss < min_val_loss:
            min_val_loss = val_loss
            best_model = copy.deepcopy(model)    #深拷贝最优模型

        tqdm.write('Epoch {:03d} train_loss {:.5f} val_loss {:.5f}'.format(epoch, np.mean(train_loss), val_loss))

    torch.save(best_model.state_dict(), "model/cnn.pkl")  #这行代码的作用是将 best_model 的参数状态保存到一个文件中,


def test():
    Dtr, Val, Dte = load_data()
  #  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model = cnn().to(device)
    model.load_state_dict(torch.load("model/cnn.pkl"), False)
    model.eval()
    total = 0
    current = 0
    for (data, target) in Dte:
        data, target = data.to(device), target.to(device)
        outputs = model(data)
        predicted = torch.max(outputs, 1)[1]   #获取outputs的第二个纬度的最大值    返回最大值索引和最大值 选【1】
        total += target.size(0)
        current += (predicted == target).sum()

    print('Accuracy:%d%%' % (100 * current / total))


if __name__ == '__main__':
    train()
    test()

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐