pytorch汇总

目录

操作

注意所有的操作都在这个anaconda prompt下面进行:
在这里插入图片描述

复制新的conda环境

首先我们再anaconda中集成的conda包就可以创造出两个屋子来存放不同的pytorch版本,例如一个项目此时需要用到pytorch0.4,一个需要用到pytorch1.0,假设我们此时我们都在一个conda包创建的屋子里面使用不同版本的pytorch的话,就相当于我们要卸载了又安装就非常的麻烦,此时就需要我们创建两个不同的屋子,一个屋子使用pytorch0.4,一个屋子使用pytorch1.0
指令如下:

conda create -n pytorch python=3.6

其中pytorch 就是屋子的名字,python=3.6就是这个屋子的python版本,注意python后面的等号不能有空格.
注意:在安装另一个小屋子pytorch之前,我们要确保我们已经安装了清华镜像,具体教程在这里(前提是已经装了anaconda,要在anaconda的prompt环境下运行):具体博客地址在这里:点我进入博客

创建完成后先点击y,会让我们去激活这个新的小屋子:
在这里插入图片描述
激活完成后此时我们可以看到左边括号就是我们的环境名称了
在这里插入图片描述
base是我默认的环境名称,也就是我之前那个小屋子,这个小屋子已经将我们的pytorch环境和jupyter环境安装到位了但是在新的pytorch这个环境中是没有这两个环境的,都需要我们自己手动配置.

此时使用pip list指令来看下发现我们pytorch这个小屋子里面并没有这个pytorch的环境,所以此时就需要我们安装pytorch这个环境,如下图所示:(红色报错不影响)
在这里插入图片描述

在新的环境中安装pytorch

此时可以安装pytorch了,关于在安装pytorch的时候选择什么:如果是英伟达显卡的话:选择如下配置:windows推荐conda安装,如果是集显的话选择cpu那个配置
在这里插入图片描述
复制command到我们的命令行:
此时如果出现如下错误:把我们的vpn关掉即可
在这里插入图片描述

然后再使用pip list指令即可查看我们安装成功:
在这里插入图片描述
注意:可以使用activate命令进行环境的迁移在这里插入图片描述
上面我们就从base环境迁移到了pytorch环境,如果要回到我们的根目录也就是base环境的话可以使用conda.bat deactivate命令

安装完成后,检验是否安装成功:(三步走)
在这里插入图片描述

在新的环境中安装jupyter

jupyter默认只安装在base环境中,在base环境中使用conda list
查看到时有这个包:所以此时我们需要在pytorch这个新环境中安装.
使用命令 conda install nb_conda
如下图所示:
在这里插入图片描述
安装完成后在命令框输入:jupyter notebook
跳转到页面后(如果没有跳转请手动粘贴地址到浏览器)
在这里插入图片描述
点击new---->点击python进去,到如下页面输入两行代码

import torch
torch.cuda.is_available()

如果最终输入结果为true,就说明我们的jupyter是在我们的pytorch环境下运行啦
在这里插入图片描述

python中的两大法宝函数(也可用在pytorch)

1dir()函数:打开看见
2help()函数:说明书

建立新项目

1:首先建立新项目:
在这里插入图片描述
2:选择我们的python解释器,选择Existing interpreter:
在这里插入图片描述
注意这里的选择问题,因为我们是再pytorch这个小房子内部进行开发的,所以我们就要选择pytorch环境下的python.exe文件
在这里插入图片描述
此时点击settings:可以看到我们的pytorch版本为1.10.2,python版本为3.6.13,说明我们使用了我们pytorch环境下的pytorch版本.
在这里插入图片描述

针对这个项目打开我们的jupyter notebook

首先因为我们的项目存储于d盘,所以我就需要在命令行输入的时候加上导航到d盘的指令:使用jupyter notebook d:假设是在桌面的话就直接jupyter notebook
在这里插入图片描述

注意:此时我们在创建笔记的时候需要注意的是:我们需要用管理员命令打开我们的anaconda环境,不然创建笔记的时候会出错,还有一个问题就是假如我们打开jupyter notebook无法跳转浏览器的话,也可能是没有使用管理员命令打开的原因
在这里插入图片描述
此时打开jupyter notebook后运行这两段代码:

import pytorch
torch.cuda.is_available()

在这里插入图片描述
发现为true,说明此时的jupyter notebook就是在pytorch环境中:此时就可以在里面编写代码了:
在这里区分一下python文件,jupyter notebook以及python控制台的区别在这里插入图片描述

pytorch记载数据初认识

首先需要认识两个概念,一个是dataset,一个是dataloader,我把区别放在了下面:

垃圾数据中的蓝色的就是我们的可回收垃圾,也就是我们的数据

DataSet可以为这些数据进行一个编号,
在这里插入图片描述

dataset代码示例

此时我们先来看看Dataset这个类在官网中是如何使用的:
打开jupyter notebook,输入以下代码:

from torch.utils.data import Dataset

在这里插入图片描述
或者使用Dataset??也可以,以下就是官网对于Dataset类的解释:

Init signature: Dataset()
Source:        
class Dataset(Generic[T_co]):
    r"""An abstract class representing a :class:`Dataset`.

    All datasets that represent a map from keys to data samples should subclass
    it. All subclasses should overwrite :meth:`__getitem__`, supporting fetching a
    data sample for a given key. Subclasses could also optionally overwrite
    :meth:`__len__`, which is expected to return the size of the dataset by many
    :class:`~torch.utils.data.Sampler` implementations and the default options
    of :class:`~torch.utils.data.DataLoader`.

    .. note::
      :class:`~torch.utils.data.DataLoader` by default constructs a index
      sampler that yields integral indices.  To make it work with a map-style
      dataset with non-integral indices/keys, a custom sampler must be provided.
    """

    def __getitem__(self, index) -> T_co:
        raise NotImplementedError

    def __add__(self, other: 'Dataset[T_co]') -> 'ConcatDataset[T_co]':
        return ConcatDataset([self, other])

    # No `def __len__(self)` default?
    # See NOTE [ Lack of Default `__len__` in Python Abstract Base Classes ]
    # in pytorch/torch/utils/data/sampler.py
File:           d:\anaconda3\lib\site-packages\torch\utils\data\dataset.py
Type:           type
Subclasses:     IterableDataset, TensorDataset, ConcatDataset, Subset, IterableDataset[str], IterableDataset[typing.Tuple[str, io.IOBase]], IterableDataset[typing.Tuple[str, io.BufferedIOBase]], IterableDataset[typing.Tuple[str, typing.Any]], IterableDataset[+T_co], IterableDataset[typing.Tuple[+T_co]], ...

意思就是DataSet是一个抽象类,然后我们所有子类都需要重写__getitem__方法,这个方法主要是获取每个数据及其对应的lable,同时还可以重写我们的__len__方法,定义我们的数据有多长

__getitem__就是获取样本对,模型直接通过这一函数获得一对样本对{x:y}。__len__是指数据集长度。

from torch.utils.data import Dataset

# 使用此方法来读取我们的图片
from PIL import Image
# os用来获取所有图片的地址
import os


# 创建一个MyData类继承于我们的Dataset类
class MyData(Dataset):
    """

root_dir一般情况下设置为dataset下的地址,这里的路径是dataset/train
lable_dir就是ants
    """


def __init__(self, root_dir, label_dir):
    self.roots_dir = root_dir
    self.label_dir = label_dir
    # 使用join方法可以很好的进行拼接,因为破折号/再不同系统中的含义不同,使用此方法可以忽略路径拼接中因为不同系统引发的问题
    # lable_path就等价于dataset/train\\ants
    self.label_path = os.path.join(self.root_dir, self.label_dir)
    # img_path为获取所有图片的地址的集合,lable_path就等价于dataset/train\\ants
    self.img_path = os.listdir(self.label_path)


# 此处重写我们DataSet类中的__getitem__方法
# 我们通过index来获取图片的地址
def __getitem__(self, idx):
    # img_name为获取每一个图片的名称
    img_name = self.img_path[idx]

    # 获取每个图片的相对路径,举例:dataset/train\\ants\\'0013035.jpg'
    image_item_path = os.path.join(self.root_dir, self.label_dir, img_name)

    # img为获取到某个图片
    img = Image.open(image_item_path)

    # 获取到标签
    label = self.label_dir

    # 返回数据和标签
    return img, label


# 定义数据集的长度
def __len__(self):
    # 返回的长度也只是ants这个数据集下的图片的长度
    return len(self.img_path)


root_dir = "dataset/train"
ants_label_dir = "ants"
bees_label_dir = "bees"
ants_dataset = MyData(root_dir, ants_label_dir)
bees_dataset = MyData(root_dir,bees_label_dir)
img,label = bees_dataset[1]
img.show()

安装tensorboard

我们这里说的是在每个conda环境下安装tensorboard
命令为pip
1:在命令行安装:
这里我们的conda环境为base
在这里插入图片描述
2: 也可以在项目的终端中安装:

这里的conda环境为pytorch

在这里插入图片描述

tensorboard中的add_scalar()方法

    def add_scalar(
        self,
        tag,
        scalar_value,
        global_step=None,
        walltime=None,
        new_style=False,
        double_precision=False,
    ):
        """Add scalar data to summary.

        Args:
            tag (string): Data identifier
            scalar_value (float or string/blobname): Value to save
            global_step (int): Global step value to record
            walltime (float): Optional override default walltime (time.time())
              with seconds after epoch of event
            new_style (boolean): Whether to use new style (tensor field) or old
              style (simple_value field). New style could lead to faster data loading.
        Examples::

            from torch.utils.tensorboard import SummaryWriter
            writer = SummaryWriter()
            x = range(100)
            for i in x:
                writer.add_scalar('y=2x', i * 2, i)
            writer.close()

        Expected result:

        .. image:: _static/img/tensorboard/add_scalar.png
           :scale: 50 %

        """
        torch._C._log_api_usage_once("tensorboard.logging.add_scalar")
        if self._check_caffe2_blob(scalar_value):
            from caffe2.python import workspace
            scalar_value = workspace.FetchBlob(scalar_value)

        summary = scalar(
            tag, scalar_value, new_style=new_style, double_precision=double_precision
        )
        self._get_file_writer().add_summary(summary, global_step, walltime)

这个方法有很多的内置参数,例如
tag:图表的标题
scalar_value:需要保存的数值,即为训练到多少步的时候的数值的大小即为此值
gloabal_step:步长
对应图像的话就是scalar_value表示y轴,然后global_step表示x轴
在这里插入图片描述
那么我们如何看到类似的图像呢?拿一段代码来举例把:

from torch.utils.tensorboard import SummaryWriter


writer = SummaryWriter("logs")
# tag:y = x
# scalar_value:i
# gloabal_step:i
for i in range(100):
    writer.add_scalar("y=x", i, i)

writer.close()

那么如何看到这段图像呢?在这里插入图片描述
首先打开我们的terminal终端,然后输入这段指令:tensorboard --logdir=logs
此时会默认出来一个网址:复制粘贴到浏览器就可以找到对应的模块了:
在这里插入图片描述
当然此时要注意一个问题,假设此时我们不同人在一个服务器下跑模型的时候,大家都会默认打开上面的6006端口,这样就会带来很多不便,所以此时我们需要去指定端口号,避免冲突:
使用如下指令指定6007端口号

tensorboard --logdir=logs --port=6007

在这里插入图片描述
但是需要注意的是假设我们不断向writer写入新的事件的时候,每一次的事件的图像都会进行一次拟合,就不能得到我们想要的某一次事件的图像,那么该如何解决这个问题呢
其实就可以直接将logs文件夹下的文件全部删掉,然后重新运行程序后在终端再次输入tensorboard --logdir=logs --port=6007后,便可以直接得到某次想要的结果了,并不是像之前那样拟合好几次的结果在这里插入图片描述

tensorboard中的add_image()方法

先来看此方法内部的实现:

    def add_image(self, tag, img_tensor, global_step=None, walltime=None, dataformats='CHW'):
        """Add image data to summary.

        Note that this requires the ``pillow`` package.

        Args:
            tag (string): Data identifier
            img_tensor (torch.Tensor, numpy.array, or string/blobname): Image data
            global_step (int): Global step value to record
            walltime (float): Optional override default walltime (time.time())
              seconds after epoch of event
        Shape:
            img_tensor: Default is :math:`(3, H, W)`. You can use ``torchvision.utils.make_grid()`` to
            convert a batch of tensor into 3xHxW format or call ``add_images`` and let us do the job.
            Tensor with :math:`(1, H, W)`, :math:`(H, W)`, :math:`(H, W, 3)` is also suitable as long as
            corresponding ``dataformats`` argument is passed, e.g. ``CHW``, ``HWC``, ``HW``.

        Examples::

            from torch.utils.tensorboard import SummaryWriter
            import numpy as np
            img = np.zeros((3, 100, 100))
            img[0] = np.arange(0, 10000).reshape(100, 100) / 10000
            img[1] = 1 - np.arange(0, 10000).reshape(100, 100) / 10000

            img_HWC = np.zeros((100, 100, 3))
            img_HWC[:, :, 0] = np.arange(0, 10000).reshape(100, 100) / 10000
            img_HWC[:, :, 1] = 1 - np.arange(0, 10000).reshape(100, 100) / 10000

            writer = SummaryWriter()
            writer.add_image('my_image', img, 0)

            # If you have non-default dimension setting, set the dataformats argument.
            writer.add_image('my_image_HWC', img_HWC, 0, dataformats='HWC')
            writer.close()

        Expected result:

        .. image:: _static/img/tensorboard/add_image.png
           :scale: 50 %

        """
        torch._C._log_api_usage_once("tensorboard.logging.add_image")
        if self._check_caffe2_blob(img_tensor):
            from caffe2.python import workspace
            img_tensor = workspace.FetchBlob(img_tensor)
        self._get_file_writer().add_summary(
            image(tag, img_tensor, dataformats=dataformats), global_step, walltime)

tag为图片标题

img_tensor为图像的数据类型:可以看到有很多个数据类型可以指定: 例如torch.Tensor, numpy.array, or string/blobname这几个类型都可.后面的代码要用到numpy类型作为举例,大家注意仔细观看

global_step为步长

安装Opencv

还是两种方式:
1:在anconda中使用命令安装,这里是我们的base环境:
在这里插入图片描述

2:在对应项目的terminal中使用命令安装,注意如果一个项目已经在某个conda环境下,在这个项目的终端(terminal)中安装了cv后,就不需要再命令行安装了

利用opencv读取图片,获取numpy型数据图片
import cv2

img_path = "data/train/ants_image/0013035.jpg"

cv_img = cv2.imread(img_path)

利用numpy.array(),对PIL图片进行转换

在这里插入图片描述
首先我们可以看到此时这个图片是PIL类型,我们需要将其转为numpy类型,代码如下所示:
在这里插入图片描述
在命令行调试完毕后,我们来编写代码把:

from torch.utils.tensorboard import SummaryWriter
import numpy as np
from PIL import Image

# 日志文件logs
writer = SummaryWriter("logs")
# 某个图片的相对地址
image_path = "data/train/ants_image/6240329_72c01e663e.jpg"
# 打开这个图片
img_PIL = Image.open(image_path)
# 讲这个图片从PIL类型转换为numpy类型
img_array = np.array(img_PIL)

# 这里打印我们img_array的类型结果为<class 'numpy.ndarray'>
# 说明此时图片由PIL类型转为了numpy类型
print(type(img_array))
"""
这里打印shape是因为我们这个numpy类型图片打印出来是(369, 500, 3)
这里的3是通道,369是高度,500是宽度
因为此时3这个通道在最后面,所以我们应该在add_image方法中指定dataformats
他会让你选三个``CHW``, ``HWC``, ``HW``
C代表通道,H代表高度,W代表宽度 
"""
print(img_array.shape)

"""
test等价于tag
img_array等价于img_tensor
1等价于global_step

"""
writer.add_image("train", img_array, 1, dataformats='HWC')
# y = 2x
for i in range(100):
    writer.add_scalar("y=2x", 3*i, i)

writer.close()

注意事项:

为什么我们要在add_image中指定dataformats?
答:
由print(img_array.shape)语句可知:其结果为

(369, 500, 3)

来看add_image方法内部是怎么说的:

Shape:
img_tensor: Default is :math:(3, H, W). You can use torchvision.utils.make_grid() to
convert a batch of tensor into 3xHxW format or call add_images and let us do the job.
Tensor with :math:(1, H, W), :math:(H, W), :math:(H, W, 3) is also suitable as long as
corresponding dataformats argument is passed, e.g. CHW, HWC, HW.

意思为 img_tensor中默认为(3, H, W),其中代表通道的3在最前面,
这里的3是通道,H是高度,W是宽度
但是假如我们打印shape的结果中通道3没有或者在最后面的话就需要指定我们的dataformats
他会让你选三个CHW, HWC, HW

C代表通道,H代表高度,W代表宽度 

其中(3, H, W)对应CHW
(H, W, 3)对应HWC
在这里插入图片描述

transforms的使用

简短介绍

在这里插入图片描述
先来看看其内部架构:
在这里插入图片描述
在这里插入图片描述

ToTensor类

ToTensor:意思是把一个PIL类型和numpy类型转变为tensor
在这里插入图片描述

代码练习

from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms

# python的用法:tensor数据类型
# 通过transforms.toTensor去看两个问题
# 问题1:transforms该如何使用?
# 问题2:为什么需要tensor的数据类型?


# 绝对路径     D:\python基础语法视频课程\learn_pytorch\data\train\ants_image\0013035.jpg
# 相对路径     data/train/ants_image/0013035.jpg
img_path = "data/train/ants_image/0013035.jpg"
img = Image.open(img_path)

writer = SummaryWriter("logs")

# 下面演示transforms的使用
tensor_trans = transforms.ToTensor()
# 这里其实默认调用的就是ToTensor类中的__call__方法,也可以写成tensor_ing = tensor_trans.__call__(img)
# 这里的tensor_img就是tensor类型,原因是tensor_trans调用了__call__方法
# 而__call__方法就是将PIL类型或者是numpy类型的图片转变为tensor类型
tensor_img = tensor_trans(img)

writer.add_image("tensor_img", tensor_img)

writer.close()

示意图:
在这里插入图片描述

__call__方法介绍

在这里插入图片描述
__call__方法有点像我们的__init__方法,即创建对象传参便自动调用一样

Compose类

简短介绍

可以看到Compose类就是将所有的transforms进行一个拼接
在这里插入图片描述
源码给了我们一个案例:
在这里插入图片描述
在这里插入图片描述

下面将在介绍Resize方法的时候进行Compose()的代码演示

ToPILImage类

其是将tensor类型和numpy类型转成PIL类型
在这里插入图片描述

Normalize类

在这里插入图片描述
注意我们归一化必须是一个tensor数据类型,然后归一化的计算公式如下:

output[channel] = (input[channel] - mean[channel]) / std[channel]

归一化操作代码:

from PIL import Image
from torch.utils.tensorboard import SummaryWriter

from torchvision import transforms

img_path = "image/90179376_abc234e5f4.jpg"
# 此时的img是PIL类型


writer = SummaryWriter("logs")

img = Image.open(img_path)

# ToTensor的使用
trans_tensor = transforms.ToTensor()
# 此时的img_tensor是tensor类型
img_tensor = trans_tensor(img)

# 此处打印看下img_tensor是几个信道的,结果为[3, 512, 768],是三个信道
print(img_tensor.shape)

# 下面演示Normalize的使用
# 注意Normalize方法中的参数是一个是均值,一个是标准差
# 又因为我们的图像是三个信道的,所以我们的标准差和均值都要提供三个值,此时我们都设置为0.5

# 先打印归一化前的第0层第0行第0列的像素进行输出
print(img_tensor[0][0][0])
trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])

# trans_norm中只能传入tensor类型数据
img_norm = trans_norm(img_tensor)

# 输出归一化后的第0层第0行第0列的像素
print(img_norm[0][0][0])


# 此处add_image中传入的图片类型是tensor类型
writer.add_image("Normalize",img_norm)

writer.close()


可以看到我们所给的均值为0.5,标准差为0.5,根据如下公式:

output[channel] = (input[channel] - mean[channel]) / std[channel]

我们来算下:
假设此时我们的input图片的像素值的范围在【0,1】之内,
然后此时根据公式可得:

output = (input-0.5)/0.5 = 2 * input - 1
最终的output的值范围为【-11

下面给出归一化前后图像对比:
在这里插入图片描述

Resize类

resize类的作用就是将给定的图片重新给定大小
在这里插入图片描述
这里的意思如果我们的Resize方法的参数中给定了高度和宽度的话,那么将会按照给定的参数进行输出

代码示例

Resize方法的第一种用法
from PIL import Image

from torchvision import transforms

img_path = "image/90179376_abc234e5f4.jpg"
# 此时的img是PIL类型

img = Image.open(img_path)

# ToTensor的使用
trans_tensor = transforms.ToTensor()
# 此时的img_tensor是tensor类型
img_tensor = trans_tensor(img)

# Resize的使用
# 注意Resize中输入的图片类型是PIL类型
# 此处我们先打印一下发现原来的图片大小为(500,332)
print(img.size)

# resize的第一种用法,输入的是两个值,代表高度和宽度
trans_resize = transforms.Resize((512, 512))

img_resize = trans_resize(img)

# 输出结果为<PIL.Image.Image image mode=RGB size=512x512 at 0x1E2C65073D0>
# 我们会发现经过Resize方法处理过后的图片类型仍为PIL,然后大小变为了512×512
print(img_resize)

Resize方法的第二种用法 (并附加使用Compose方法)
from PIL import Image

from torchvision import transforms

img_path = "image/90179376_abc234e5f4.jpg"
# 此时的img是PIL类型
img = Image.open(img_path)

# ToTensor的使用
trans_tensor = transforms.ToTensor()
# 此时的img_tensor是tensor类型
img_tensor = trans_tensor(img)

# Resize的使用
# 注意Resize中输入的图片类型是PIL类型
# 此处我们先打印一下发现原来的图片大小为(500,332)
print(img.size)

# resize的第二种用法,使用compose方法进行拼接
# 这里我们只给定了一个值,意为不指定高和宽,是进行等比例的缩放
trans_resize = transforms.Resize(512)

# PIL------>PIL------->tensor
# 这里其实就是trans_resize将一个PIL类型的图片调整为指定尺寸,但是最终的输出结果依然为PIL
# 然后输出的PIL格式的图片又作为trans_tensor的输入值,因为trans_tensor是将PIL格式的图片转为tensor格式
# 所以最终trans_compose就是可以将PIL类型的图片转变为tensor类型
trans_compose = transforms.Compose([trans_resize, trans_tensor])
# img_resize最终为tensor类型
img_resize = trans_compose(img)



compose这个函数的作用是可以把transforms类中的任意方法组合起来放到列表里,并且按照从前往后的顺序依次执行这些方法,前面方法的结果作为下一个方法的输入
compose组合了两个方法:Resize将输入的PIL格式的img图片转化为指定尺寸,转化后的图片类型依然为PIL类型,trans_tensor将改变了尺寸的img(还是PIL格式)的图片转换为tensor格式的图片。

总结

要多注意函数的输入输出,输入的话一般transforms的源码中会给,但是输出就未必了,如果想要知道输出的变量的类型的话可以使用type方法,即type(变量名)即可.

torchvision中数据集的使用

首先我们就拿torchvision中的CIFAR10数据集进行导入

import torchvision

"""
train_set是我们的训练数据集,所以train我们设置为true,
创建一个名为datasets的文件夹,把我们下载的数据保存进去
train后面默认的值为true
因为我们自己没有准备数据集,要从网上的torchvision中去下载我们的数据集,所以此处的download为true
"""

train_set = torchvision.datasets.CIFAR10(root="./datasets", train=True, download=True)
# test_set为测试数据集,所以train为false
test_set = torchvision.datasets.CIFAR10(root="./datasets", train=False, download=True)

导入之后先打印一下数据集的第一个看看是啥:

print(test_set[0])

结果为:

(<PIL.Image.Image image mode=RGB size=32x32 at 0x1E5198374F0>, 3)

前面的部分为图片,后面的3为target,至于这个target是什么我们来打断点看下:
在这里插入图片描述

可以看到3对应的是cat

所以此时我们可以这么写代码:

img, target = test_set[0]
print(img)
print(target)

打印的结果跟上面一样,说明此时数据集的第一个是一个cat的图片
,所以说明我们拿img可以直接拿到图片

如果想展示这个图片的话使用image.show()方法即可:

image.show()

因为CIFAR10数据集中默认的图片类型为PIL,所以我们也可以使用transforms将我们的类型转变为tensor数据类型并且在tensorboard中进行展示,来看代码:

import torchvision
from torch.utils.tensorboard import SummaryWriter

# 因为CIFAR10这个数据集当中的所有数据的类型都为PIL类型,所以我们需要使用transforms将其转变为tensor类型
dataset_transform = torchvision.transforms.Compose([
    torchvision.transforms.ToTensor()
])

"""
train_set是我们的训练数据集,所以train我们设置为true,
创建一个名为datasets的文件夹,把我们下载的数据保存进去
train后面默认的值为true
因为我们自己没有准备数据集,要从网上的torchvision中去下载我们的数据集,所以此处的download为true
transform=dataset_transform意为将我们数据集中的图片类型从PIL类型转变为tensor类型
"""

train_set = torchvision.datasets.CIFAR10(root="./datasets", train=True, transform=dataset_transform, download=True)
# test_set为测试数据集,所以train为false
test_set = torchvision.datasets.CIFAR10(root="./datasets", train=False, transform=dataset_transform, download=True)

writer = SummaryWriter("logs")

# 这里是我们想显示测试数据集的前十张图片
for i in range(10):
    img, target = test_set[i]
    writer.add_image("test_set", img, i)

writer.close()

Dataload

之前我们说过Datasset就是告诉我们数据集在哪,以及第一张和第二张数据是什么,包括告诉我们数据集中有多少数据,我们只需要将Dataset实例化放到我们的Dataload中即可

那么到底什么是Dataloader呢?
Dataloader就是将我们Dataset中的数据加载到我们的神经网络当中去,然后Dataloader将从Dataset中取数据,至于怎么取如何取将在Dataloader中进行一个参数的设置来决定的

下面来看下官网中的描述:
首先来看的就是我们在Dataloader中的参数的设置:

dataset (Dataset) – dataset from which to load the data.

batch_size (int, optional) – how many samples per batch to load (default: 1).

shuffle (bool, optional)set to True to have the data reshuffled at every epoch (default: False).

sampler (Sampler or Iterable, optional) – defines the strategy to draw samples from the dataset. Can be any Iterable with __len__ implemented. If specified, shuffle must not be specified.

batch_sampler (Sampler or Iterable, optional) – like sampler, but returns a batch of indices at a time. Mutually exclusive with batch_size, shuffle, sampler, and drop_last.

num_workers (int, optional) – how many subprocesses to use for data loading. 0 means that the data will be loaded in the main process. (default: 0)

collate_fn (callable, optional) – merges a list of samples to form a mini-batch of Tensor(s). Used when using batched loading from a map-style dataset.

pin_memory (bool, optional) – If True, the data loader will copy Tensors into CUDA pinned memory before returning them. If your data elements are a custom type, or your collate_fn returns a batch that is a custom type, see the example below.

drop_last (bool, optional)set to True to drop the last incomplete batch, if the dataset size is not divisible by the batch size. If False and the size of dataset is not divisible by the batch size, then the last batch will be smaller. (default: False)

timeout (numeric, optional)if positive, the timeout value for collecting a batch from workers. Should always be non-negative. (default: 0)

worker_init_fn (callable, optional) – If not None, this will be called on each worker subprocess with the worker id (an int in [0, num_workers - 1]) as input, after seeding and before data loading. (default: None)

generator (torch.Generator, optional) – If not None, this RNG will be used by RandomSampler to generate random indexes and multiprocessing to generate base_seed for workers. (default: None)

prefetch_factor (int, optional, keyword-only arg) – Number of samples loaded in advance by each worker. 2 means there will be a total of 2 * num_workers samples prefetched across all workers. (default: 2)

persistent_workers (bool, optional) – If True, the data loader will not shutdown the worker processes after a dataset has been consumed once. This allows to maintain the workers Dataset instances alive. (default: False)

我们拿打牌进行举例
batch_size就是我们从dataset中的牌的数据集中抽取多少张牌的意思

shuffle代表我们牌每次抽取完都要进行洗牌,其是为了判断两次牌的顺序是否一样,设置为true代表不一样,设置为false代表一样,默认为false
一般我们都设置为true,再通俗点讲就是取batch是否随机取, true就代表要随机抓取

num_workers 参与工作的线程数,即加载数据的时候采用单进程还是多进程,默认值为0即采用主进程进行加载,但是在windows可能会出现问题

drop_last 对最后不足batchsize的数据的处理方法,打个比方现在有100张牌,然后每次抽取3张,100对3取余等于33,最后的余数为1,我们的drop_last 就是看这个1我们是要还是不要,如果drop_last 为true,代表我们不要这个1,只需要99张牌即可,如果为false,就代表最终我们拿完99张牌后还需要把最后余下来的那张牌拿到手

代码示例

import torchvision

# 准备的测试数据集
from torch.utils.data import DataLoader

from torch.utils.tensorboard import SummaryWriter

test_data = torchvision.datasets.CIFAR10("./datasets", train=False, transform=torchvision.transforms.ToTensor())

# batch_size等于64相当于从dataset中随机每次取64个数据集
test_loader = DataLoader(dataset=test_data, batch_size=64, shuffle=True, num_workers=0, drop_last=True)

# 测试数据集中第一张图片及target,因为CIFAR10数据集中的__getItem__方法的返回值是img和target

img, target = test_data[0]
# 打印结果为torch.Size([3, 32, 32]),说明第一张图片是一个三通道的,高度32,宽32的
print(img.shape)
# 其target为3
print(target)

writer = SummaryWriter("dataloader")

# epoch代表轮数,可取0和1,这里是为了验证shuffle的
# shuffle为true时,第0轮的图片顺序跟第1轮的图片顺序不一样
for epoch in range(2):
    # 设置全局步长step = 0
    step = 0
    # 遍历dataloader从dataset所取到的数据
    for data in test_loader:
        imgs, targets = data
        writer.add_images("Epoch: {}".format(epoch), imgs, step)
        # 步长每次都加1,这样可以观察到每走一步我们的dataloader所取的图片有哪些
        step = step + 1

writer.close()

注意事项:
1:关于shuffle:
在这里插入图片描述

神经网络的基本骨架nn-module

在这里插入图片描述
在基本上所有的神经网络中,我们都必须要继承nn.Module这个类,官网中也给我了我们详细的介绍:其是所有类的一个基类,也就是父类
在这里插入图片描述
官网给的代码模板如下:

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

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1, 20, 5)
        self.conv2 = nn.Conv2d(20, 20, 5)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        return F.relu(self.conv2(x))

意思就是所有的类都要继承我们的nn.module类,然后要定义两个方法:一个是__init__方法,一个是forward方法,在__init__方法中,我们必须要显示的调用父类的init方法,即super().__init__()这句话是必须写的,底下的卷积操作我们可以自己定义操作,

forward顾名思义为前向传播,即我们给定一个输入,经过神经网络中的forward函数处理后,最终有了一个output的输出
总结:__init__方法内部定义我们卷积的方法,forward方法内部定义执行卷积方法的操作
在这里插入图片描述
其中forward函数中的这段代码解释如下:

x = F.relu(self.conv1(x))
return F.relu(self.conv2(x))

在这里插入图片描述
conv1就是卷积操作,然后relu就是非线性操作

代码示例

import torch
from torch import nn


class Tudui(nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, input):
        output = input + 1
        return output


tudui = Tudui()
# 给定一个输入
x = torch.tensor(1.0)
# 把输入x放到神经网络当中
output = tudui(x)
# 最终的输出结果为tensor(2.)
print(output)

卷积操作

还是先来看pytorch官网的描述吧:
这里我们首先介绍torch.nn.functional下的con2d,其实后面我们用的更多的是torch.nn
一般图像都是2D的,选这个点进去
在这里插入图片描述
然后来看官网给的一些参数:

在这里插入图片描述
input就是我们输入的图片,其对我们输入的图片是有尺寸要求的,即需要有batch,通道数,高度,宽度
weight就是权重,即为一个卷积核,其尺寸要求是要有一个输出,groups一般取1,然后高和宽
bias即为偏差
stride就是卷积核的步径
来看一个卷积操作的举例,我们的输入是一个5×5的一图像,最终我们经过卷积核后的卷积输出如下图所示,步长为1,最终的结果如下所示:
对于参数的一个要求在这里:
在这里插入图片描述
可以看到这里对于input参数和output参数的要求

在这里插入图片描述
下面我们将根据上面的图来进行一个简单的编码:
首先来看一段代码:

import torch

# 定义我们的输入,注意转成了tensor数据类型
input = 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]])

# 此处定义我们的卷积核
kernel = torch.tensor([[1, 2, 1],
                       [0, 1, 0],
                       [2, 1, 0]])

print(input.shape)
print(kernel.shape)

输出结果为:

torch.Size([5, 5])
torch.Size([3, 3])

这里可以看到并不符合我们最终的一个要求,原因是input和weight不但要求参数有高和宽,还有别的,但是这里并没有,所以我们需要reshape方法来重新指定,下面来看代码:

代码示例


import torch
import torch.nn.functional as F

# 定义我们的输入,注意转成了tensor数据类型
input = 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]])

# 此处定义我们的卷积核
kernel = torch.tensor([[1, 2, 1],
                       [0, 1, 0],
                       [2, 1, 0]])

"""
这里我们需要重新reshape一下input和卷积核的数据,满足我们卷积的一个输入
"""
input = torch.reshape(input, (1, 1, 5, 5))
kernel = torch.reshape(kernel, (1, 1, 3, 3))

#打印结果为torch.Size([1, 1, 5, 5])
print(input.shape)
#torch.Size([1, 1, 3, 3])
print(kernel.shape)

# 利用conv2d进行卷积操作,这里我们放入了三个形参,stride表示步长为1
output = F.conv2d(input, kernel, stride=1)
# 最终的输出结果为tensor([[[[10, 12, 12],
#                          [18, 16, 16],
#                           [13,  9,  3]]]])
print(output)

# 此时步长为2
output2 = F.conv2d(input, kernel, stride=2)
# 最终的输出结果为tensor([[[[10, 12],
#                       [13,  3]]]])
print(output2)

# 此时的步长为1,padding就是会在我们输入的图像两边进行一个填充
output3 = F.conv2d(input, kernel, stride=1, padding=1)
# 最终的输出结果为tensor([[[[ 1,  3,  4, 10,  8],
#                          [ 5, 10, 12, 12,  6],
#                            [ 7, 18, 16, 16,  8],
#                              [11, 13,  9,  3,  4],
#                                [14, 13,  9,  7,  4]]]])
print(output3)


注意事项:最后我们设置了一个padding = 1,意为在我们输入的图像两边进行一个填充,我们画了一个示意图便于理解
在这里插入图片描述

神经网络----卷积层

下面来看torch.nn中的conv2d,一般我们用这个多一点
先来看看里面有什么参数吧:

CLASStorch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode=‘zeros’, device=None, dtype=None)

在这里插入图片描述

in_channels:代表我们输入图片的通道数,一般彩色照片都是三个通达
out_channels:代表我们输出时候的通道数
kernel_size:卷积核的大小,如果值为3代表的就是3✖3的一个卷积核
stride:代表我们的步长
padding:意为在我们输入的图像两边进行一个填充
这五个是比较
dilation:就是卷积核之间的元素相差的距离
groups:一般都会一直设置为1
padding_model就是我们padding选择padding填充的时候按照什么模式进行一个填充,一般我们选择zeros进行一个填充
bias一般设置为true

接下来让我们对in_channels和out_channels做一个解释:先来看一张图:
在这里插入图片描述
这张图的意思就是in_channel的通道数为1,即输入的照片是5×5的,即一张图片,所以通道数为1,然后假设此时我们设置out_channel也就是我们输出的通道数为1的话,此时就只需要一个卷积核,而当out_channel等于2的时候,意味着我们就需要两个卷积核,最终也会得到两个输出值,如上图所示:

欧克那现在我们来看下代码把:

代码示例


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

dataset = torchvision.datasets.CIFAR10("datasets", train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)
# dataloader从我们的dataset中取数据,batch_size为一次取64张图片
dataloader = DataLoader(dataset, batch_size=64)


# 定义我们的卷积神经网络
class Tudui(nn.Module):
    def __init__(self):
        # 必须有的步骤,第一部需要先继承我们父类的__init__方法
        super(Tudui, self).__init__()
        # 输入通道因为是彩色图片,所以in_channels值为3,输出通道的大小我们指定out_channels为6,stride为步长等于1,
        self.conv1 = Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1, padding=0)

    # 定义我们神经网络的输入x
    def forward(self, x):
        # 然后让我们的输入结果x进行一个卷积操作后得到一个输出结果x
        x = self.conv1(x)
        return x


# 初始化我们的网络
tudui = Tudui()

writer = SummaryWriter("logs")

# 定义我们的初始步长为0
step = 0
# 获取我们dataloader中的数据
for data in dataloader:
    # 获取每一条数据的imgs和targets
    imgs, targets = data
    
    # 然后将我们的图片放入到我们的神经网络内部,经过forward方法进行一些卷积的操作
    # 这也更加验证了dataloader的作用就是将数据送到神经网络当中去
    output = tudui(imgs)
    
    # 输出结果为torch.Size([64, 3, 32, 32]),64是DataLoader中的batch_size,可以看到此时的图片的通道为3
    print(imgs.shape)
    
    # 输出结果为torch.Size([64, 6, 30, 30]),64是DataLoader中的batch_size,经过卷积后的通道变为了6
    print(output.shape)

    # 使用tensorboard进行更直观的显示
    writer.add_images("input", imgs, step)

    """
    注意我们output的某个输出结果为[64, 6, 30, 30],说明其最终经过卷积后的图片的通道为6
    但是使用add——images方法的时候我们的通道只能是3,所以此处我们需要使用不那么严谨的方法
    即使用torch.reshape方法来修改output,将其通道修改为3,但是batch_size我们并不知道
    所以我们设置其为-1,这样他会根据其他的值自己计算
    
    
    并且还有一个问题要注意的就是我们图像原本大小为32×32,经过卷积后变成了30✖30
    如果经过卷积后我们的图片大小想要保持不变的话,仍需要对原图片进行我们的填充,使用padding
    """
    output = torch.reshape(output, (-1, 3, 30, 30))

    writer.add_images("output", output, step)

    step = step + 1

最后的效果图如下所示:
在这里插入图片描述

神经网络–最大池化的使用

一般我们使用的都是nn.MaxPool12d
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
kernel_size是用于取最大值的一个窗口,设置为3就是33的窗口,给定数组就是对应数组的大小
stride就是步长,其默认值为kernel_size的大小,之前再卷积层的时候其默认大小为1,注意这里的区别
padding与之前一样
dilation:就是卷积核之间的元素相差的距离
在这里插入图片描述
return_indices一般用不到
ceil_mode设置为true和false比较有讲究,默认情况为false我们来看:
在这里插入图片描述
在我们最大池中,假设此时有一个3
3的一个最大值窗口,那么最终我们的步长也就是3,然后第一个最大值窗口选出来的数字为2
在这里插入图片描述
然后走三步:
在这里插入图片描述
发现此时没有3*3=9个元素了,那么此时ceil_mode的作用就出来啦,假设此时这是为true的话,最终我们选择的时候就要再这六个元素中找最大值,此时找到的最大值为3,假设为false,就不能找
然后继续往下:
在这里插入图片描述
假设ceil_mode此时为true的话,最终我们选择的时候就要再这六个元素中找最大值,找到的最大值为5,假设为false,就不能找
继续往下找
在这里插入图片描述
假设ceil_mode此时为true的话,最终我们选择的时候就要再这六个元素中找最大值,找到的最大值为1,假设为false,就不能找

所以最终的结果是这样的:
在这里插入图片描述
注意事项:池化操作是不改变我们的的图像大小的通道大小的
在这里插入图片描述

注意这里池化的时候我们给定的输入依然是(N,C,H,W)类型

代码示例

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("datasets", train=False, download=True,
                                       transform=torchvision.transforms.ToTensor())

dataloader = DataLoader(dataset, batch_size=64)

class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        # 再初始化方法中定义我们的池化操作
        self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=False)

    def forward(self, input):
        # forward当中执行我们的池化操作
        output = self.maxpool1(input)
        return output

# 初始化我们的网络
tudui = Tudui()

writer = SummaryWriter("logs_maxpool")
step = 0

for data in dataloader:
    imgs, targets = data
    writer.add_images("input", imgs, step)
    
    # 这里不同于卷积操作,池化的话最终的通道大小依然为之前的大小,所以不需要reshape操作
    # 传参的时候默认调用我们的forward方法
    output = tudui(imgs)
    
    writer.add_images("output", output, step)
    step = step + 1

writer.close()

在tensorboard中的效果:
在这里插入图片描述

神经网络–非线性激活

在这里插入图片描述
这里我们讲的就是我们的Relu函数以及我们Sigmoid函数
在这里插入图片描述

代码示例

import torch
import torchvision
from torch import nn
from torch.nn import ReLU, Sigmoid
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter



dataset = torchvision.datasets.CIFAR10("datasets", train=False, download=True,
                                       transform=torchvision.transforms.ToTensor())

dataloader = DataLoader(dataset, batch_size=64)

class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.relu1 = ReLU()
        self.sigmoid1 = Sigmoid()

    def forward(self, input):
        output = self.sigmoid1(input)
        return output

tudui = Tudui()

writer = SummaryWriter("logs_relu")
step = 0
for data in dataloader:
    imgs, targets = data
    writer.add_images("input", imgs, global_step=step)
    output = tudui(imgs)
    writer.add_images("output", output, step)
    step += 1

writer.close()

结果展示

在这里插入图片描述

神经网络–线性层及其他层介绍

正则化层

在这里插入图片描述
在这里插入图片描述
num_features中的c主要来源于图像中的通道,也就是使用shape方法的时候中表示通道的值
其他参数值采用默认的值即可

线性层

在这里插入图片描述
点进去linear:
在这里插入图片描述
可以看到此处提供了三个参数,in_features就好比下方图片中的input layer,out_features好比于我们Hidden layer,然后bias的值可以设置为true或者false.true代表我们箭头,input layer与Hidden layer之间有很多个箭头,每个箭头上都有函数来进行计算,可以看到式子有例如y = k1x1+b1这样的式子,当bias设置为true的时候,代表此时加这个b1,当设置为false的时候,代表不加这个b1.
在这里插入图片描述

代码示例

import torch
import torchvision
from torch import nn
from torch.nn import Linear
from torch.utils.data import DataLoader

dataset = torchvision.datasets.CIFAR10("datasets", train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)

dataloader = DataLoader(dataset, batch_size=64)


class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        # 这里定义我们的input_feature,即输入样本的大小,其值为196608,定义我们想获得的输出样本的大小,其值为output_feature为10
        self.linear1 = Linear(196608, 10)

    def forward(self, input):
        output = self.linear1(input)
        return output


tudui = Tudui()

for data in dataloader:
    imgs, targets = data
    # torch.Size([64, 3, 32, 32])
    print(imgs.shape)
    # 这里引入了flatten函数,
    output = torch.flatten(imgs)
    # torch.Size([196608])
    print(output.shape)
    # 对我们的output的结果进行linear操作
    output = tudui(output)
    # torch.Size([10])
    print(output.shape)

抓爆层在这里插入图片描述

其目的是为了防止过拟合

神经网络搭建实战

现在我们想要构建如下的神经网络:
在这里插入图片描述

原代码长这样:
在这里插入图片描述
当我们使用了Sequential之后的代码如下所示:

"""
在这里引入我们的seq(序列化)操作
"""
import torch
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.tensorboard import SummaryWriter


class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        # 注意如果一个神经网络中有很多个操作的时候,我们就需要
        self.model1 = Sequential(
            # 第一次输入的时候,根据图我们可以知道in_channel是3,out_channel是32,卷积核的大小是5
            # 但是在第一次卷积的时候图像大小没有变化,说明我们进行了图像的填充,使用了padding
            # 除了padding之外我们还需要计算我们的步长stride
            Conv2d(3, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, 5, padding=2),
            MaxPool2d(2),
            Flatten(),
            # 注意如果不摘掉Linear的输入值的话,建议可以打印一下flatten最后计算出来的值就是我们Linear的输入值
            Linear(1024, 64),
            Linear(64, 10)
        )

    def forward(self, x):
        x = self.model1(x)
        return x


tudui = Tudui()

# 在这里我们可以自定义我们的输入的图像为(64, 3, 32, 32)
input = torch.ones((64, 3, 32, 32))
# 然后通过我们自定义的网络
output = tudui(input)
# 最终的输出结果为torch.Size([64, 10])
print(output.shape)

writer = SummaryWriter("logs_seq")
# 注意这里使用的是add_graph方法,计算图的意思,第一个需要穿的参数是我们的model,也就是tudui,第二个参数是给这个model传一个input
writer.add_graph(tudui, input)
writer.close()

输出结果展示:
在这里插入图片描述

注意事项:
我们先来看这段代码:

 Conv2d(3, 32, 5, padding=2)

这段代码对应图片中的这个过程:

在这里插入图片描述
此时对于这个卷积函数来说,输入为3,输出为32,然后卷积核为5,

在第一次进行卷积的时候我们的图像大小没有发生改变,所以此时我们知道图像一定进行了填充,使用到了padding,同时针对这次过程我们还需要对步长stride进行计算,下面给出一张图来显示计算过程:
在这里插入图片描述
最终我们可以得出步长stride等于1,padding为2,计算过程如上.

损失函数与反向传播

损失函数

L1Loss函数

在这里插入图片描述
在这里插入图片描述
这里默认redution = “mean”,意思是按照平均值计算
l1loss定义的loss函数如下所示:
在这里插入图片描述

默认的ln的计算方式为|xn-yn|
代码示例

reduction='mean’的情况,这里默认redution = “mean”,意思是按照平均值计算

import torch
from torch.nn import L1Loss

inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)

inputs = torch.reshape(inputs, (1, 1, 1, 3))
targets = torch.reshape(targets, (1, 1, 1, 3))

# 注意这里的reduction='mean'默认取mean,意思就是取平均值
loss = L1Loss()
result = loss(inputs, targets)
# 最终的结果为tensor(0.6667)
# 计算过程为(1-1)+(2-2)+(5-3)/3 = 0.667
print(result)

reduction='sum’的情况


import torch
from torch.nn import L1Loss

inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)

inputs = torch.reshape(inputs, (1, 1, 1, 3))
targets = torch.reshape(targets, (1, 1, 1, 3))

# 注意这里的reduction='sum',意思就是取和
loss = L1Loss(reduction="sum")
result = loss(inputs, targets)
# 最终的结果为tensor(2.)
# 计算过程为(1-1)+(2-2)+(5-3) = 2.0
print(result)

MSELoss

MSELoss定义的loss函数为ln在这里插入图片描述这里默认redution = “mean”,意思是按照平均值计算
然后ln默认的计算方式为(xn - yn)的平方

代码示例

import torch
from torch import nn

inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)

inputs = torch.reshape(inputs, (1, 1, 1, 3))
targets = torch.reshape(targets, (1, 1, 1, 3))

loss_mse = nn.MSELoss()
result_mse = loss_mse(inputs, targets)

# 结果为tensor(1.3333)
# 计算过程为(1-1)^2 + (2-2)^2 + (5-3)^2 = 4   ===> 4/3 = 1.333
print(result_mse)

交叉熵函数(CROSSENTROPYLOSS)

在这里插入图片描述
可以看到交叉熵函数对于分类问题是非常有用的

交叉熵函数规定的loss函数如下所示:
在这里插入图片描述
其输入输出如下所示:
在这里插入图片描述

loss函数与network的结合


import torchvision
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader

dataset = torchvision.datasets.CIFAR10("datasets", train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)

dataloader = DataLoader(dataset, batch_size=1)

class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.model1 = Sequential(
            Conv2d(3, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, 5, padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)
        )

    def forward(self, x):
        x = self.model1(x)
        return x


loss = nn.CrossEntropyLoss()
tudui = Tudui()
for data in dataloader:
    imgs, targets = data
    outputs = tudui(imgs)
    result_loss = loss(outputs, targets)
    # 反向传播能计算出每个需要调节的参数,这个参数对应的梯度,有了这个梯度我们就可以利用我们的优化器,让这个优化器根据这个梯度对这个参数进行 调整,以达到整体误差降低的效果
    # 注意我们进行反向传播的一定是经过loss之后的
    result_loss.backward()
    print("ok")

注意这里我们再loss之后引入了我们的反向传播:

反向传播的作用:反向传播能计算出每个需要调节的参数,这个参数对应的梯度,有了这个梯度我们就可以利用我们的优化器,让这个优化器根据这个梯度对这个参数进行调整,以达到整体误差降低的效果,下面我们来讲讲优化器的使用

优化器

优化器都在torch.optim下面:
在这里插入图片描述
在这里插入图片描述
核心部分在这个优化器所提供的这些算法:
在这里插入图片描述
并且我们点进去一个算法会发现:必有的两个参数是params和lr,params是我们的参数,lr是学习速率
在这里插入图片描述

优化器的套路其实就是定义一个优化器,然后先把优化器中每个参数对应的梯度清零,然后调用损失函数的backward,就是他的反向传播求出每一个节点的梯度,然后调用step方法对模型参数进行调优

代码示例


import torch
import torchvision
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.optim.lr_scheduler import StepLR
from torch.utils.data import DataLoader

dataset = torchvision.datasets.CIFAR10("datasets", train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)

dataloader = DataLoader(dataset, batch_size=1)


class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.model1 = Sequential(
            Conv2d(3, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, 5, padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)
        )

    def forward(self, x):
        x = self.model1(x)
        return x


loss = nn.CrossEntropyLoss()
tudui = Tudui()
# 这里的优化器我们选择随机梯度下降
# 其第一个参数传的是模型参数,第二个参数传的是学习率,一般建议一开始选择学习率大的进行学习,后面采用学习率小的来学习
optim = torch.optim.SGD(tudui.parameters(), lr=0.01)



# epoch就是一轮一轮的意思,意思就是进行多轮的训练,这里只是进行20轮的训练
for epoch in range(20):
    # 定义最开始的整体误差为0
    running_loss = 0.0

    for data in dataloader:
        imgs, targets = data

        # 把数据存到我们自定义的tudui网络当中,去计算网络的输出
        outputs = tudui(imgs)

        # 然后进行loss操作
        result_loss = loss(outputs, targets)

        # 首先第一步我们要将网络模型中每个可以调节的参数的对应的一个梯度调整为0
        # 循环每进行到这一步都会对上一步的梯度进行一个清零
        optim.zero_grad()

        # 接下来我们的优化器要对参数进行优化,但是优化器需要每一个参数的梯度
        # 所以我们需要针对loss的结果result_loss使用反向传播算法,然后得到了每一个可调节参数对应的梯度
        result_loss.backward()

        # 优化器.step会对每个参数进行调优
        optim.step()

        # 这里的loss就是整体的误差的求和,每一轮的误差都会进行求和
        running_loss = running_loss + result_loss
    print(running_loss)

torchvision中的VGG模型

代码示例


import torchvision
         
from torch import nn

vgg16_false = torchvision.models.vgg16(pretrained=False)
# 设置为true的时候说明我们使用的网络模型中的参数是经过训练的
vgg16_true = torchvision.models.vgg16(pretrained=True)

"""
底下打印的就是vgg16的模型结构
(classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU(inplace=True)
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace=True)
    (5): Dropout(p=0.5, inplace=False)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
  )
  可以看到我们的out_features的结果为1000
"""
print(vgg16_true)

train_data = torchvision.datasets.CIFAR10("datasets", train=True, transform=torchvision.transforms.ToTensor(),
                                          download=True)

# 为我们的vgg16模型中的classifier加一层linear层,把我们最终的输出变为10,起名字为add_linear
vgg16_true.classifier.add_module('add_linear', nn.Linear(1000, 10))

"""
打印后的结果为:
 (classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU(inplace=True)
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace=True)
    (5): Dropout(p=0.5, inplace=False)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
    (add_linear): Linear(in_features=1000, out_features=10, bias=True)   这里可以看到我们新加的
  )
"""
print(vgg16_true)




# 接下来演示我们修改模型中的某一行的内容,假设我们要修改的是classifier的第六行,将其linear修改为输入4096,输出10

"""
这里我们就先打印一下没有修改前的classifier:
 (classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU(inplace=True)
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace=True)
    (5): Dropout(p=0.5, inplace=False)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
  )
"""
print(vgg16_false)


# 修改我们classifier的第六行
vgg16_false.classifier[6] = nn.Linear(4096, 10)


"""
修改过后为:
(classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU(inplace=True)
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace=True)
    (5): Dropout(p=0.5, inplace=False)
    (6): Linear(in_features=4096, out_features=10, bias=True)
  )
"""
print(vgg16_false)



模型的保存与读取

使用现成的vgg16模型

模型的保存有两种方式,下面来看代码:
方式1:

torch.save(vgg16, "vgg16_method1.pth")

之后会在这里面生成一个v9916_method1.pth
在这里插入图片描述
加载一下模型:

注意:load中放入的路径是vgg16_method1.pth这个文件在python中的相对路径,并不是绝对路径

model = torch.load("vgg16_method1.pth")
print(model)

加载结果为:

VGG(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU(inplace=True)
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU(inplace=True)
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU(inplace=True)
    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU(inplace=True)
    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU(inplace=True)
    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (15): ReLU(inplace=True)
    (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (18): ReLU(inplace=True)
    (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (20): ReLU(inplace=True)
    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (22): ReLU(inplace=True)
    (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (25): ReLU(inplace=True)
    (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (27): ReLU(inplace=True)
    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (29): ReLU(inplace=True)
    (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
  (classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU(inplace=True)
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace=True)
    (5): Dropout(p=0.5, inplace=False)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
  )
)

可以看到打印出来的不但有结构,还有参数

方式2:
此方式只保存模型参数,是官方推荐的:

这里的意思将vgg16中的参数保存成python中的字典结构
注意这里就不再保存网络结构了

torch.save(vgg16.state_dict(), "vgg16_method2.pth")

我们来试着加载一下:

vgg16 = torchvision.models.vgg16(pretrained=False)
vgg16.load_state_dict(torch.load("vgg16_method2.pth"))
print(vgg16)

加载结果如下所示:

VGG(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU(inplace=True)
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU(inplace=True)
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU(inplace=True)
    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU(inplace=True)
    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU(inplace=True)
    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (15): ReLU(inplace=True)
    (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (18): ReLU(inplace=True)
    (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (20): ReLU(inplace=True)
    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (22): ReLU(inplace=True)
    (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (25): ReLU(inplace=True)
    (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (27): ReLU(inplace=True)
    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (29): ReLU(inplace=True)
    (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
  (classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU(inplace=True)
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace=True)
    (5): Dropout(p=0.5, inplace=False)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
  )
)

自定义网络模型存取

这里有一个陷阱是需要大家注意的,假设此时我们自定义了一个网络结构并保存到我们的一个python文件中,如下所示:

import torch
from torch import nn


# 陷阱
class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3)

    def forward(self, x):
        x = self.conv1(x)
        return x


tudui = Tudui()
torch.save(tudui, "tudui_method1.pth")

然后在另一个python文件中此时我们要加载这个网络模型,使用了以下语句


import torch


model = torch.load('tudui_method1.pth')
print(model)

最终结果如下所示:

Tudui(
  (conv1): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1))
)

完整的训练模型套路

# -*- coding: utf-8 -*-
# 作者:小土堆
# 公众号:土堆碎念
import torch
import torchvision
from torch.utils.tensorboard import SummaryWriter

# 准备数据集
from torch import nn
from torch.utils.data import DataLoader
# CIFAR10数据集是一个分类数据集
train_data = torchvision.datasets.CIFAR10(root="datasets", train=True, transform=torchvision.transforms.ToTensor(),
                                          download=True)
test_data = torchvision.datasets.CIFAR10(root="datasets", train=False, transform=torchvision.transforms.ToTensor(),
                                         download=True)

# length 长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# 如果train_data_size=10, 训练数据集的长度为:10
# 这里的写法是字符串格式化写法,意思会把format中的变量把{}这部分替换掉
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))

# 利用 DataLoader 来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)


class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64 * 4 * 4, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x


# 创建网络模型
tudui = Tudui()

# 损失函数,因为是分类问题,可以使用交叉熵函数
loss_fn = nn.CrossEntropyLoss()

# 优化器
# learning_rate = 0.01
# 0.01的另一种写法:1e-2=1 x (10)^(-2) = 1 /100 = 0.01
learning_rate = 1e-2
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)

# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 10

# 添加tensorboard
writer = SummaryWriter("logs_train")

for i in range(epoch):
    print("-------第 {} 轮训练开始-------".format(i + 1))

    # 训练步骤开始
    tudui.train()
    for data in train_dataloader:
        imgs, targets = data
        outputs = tudui(imgs)
        loss = loss_fn(outputs, targets)

        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # 统计的全局训练次数加1
        total_train_step = total_train_step + 1

        # 统计每训练100次的时候的loss值是多少
        if total_train_step % 100 == 0:
            print("训练次数:{}, Loss: {}".format(total_train_step, loss.item()))
            # 统计完成后将其通过tensorboard显示每一次的变化
            writer.add_scalar("train_loss", loss.item(), total_train_step)




    # 底下是测试步骤,其是为了测试我们的这个训练有没有训练好,接下来  测试步骤开始
    tudui.eval()
    total_test_loss = 0
    total_accuracy = 0
    # 注意这里因为是测试,所以要用no_grad方法,代表是没有了梯度了,这样就不会进行调优了
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            outputs = tudui(imgs)
            # 注意这里的loss只是我们测试数据集中的loss,并不是整体的loss
            loss = loss_fn(outputs, targets)
            # 把每次的loss都加到total_test_loss里面
            # 注意因为loss是tensor数据类型,例如我们打印loss的结果就是tensor(5),但是使用item后就是5,即取他的数字
            total_test_loss = total_test_loss + loss.item()
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("整体测试集上的Loss: {}".format(total_test_loss))
    print("整体测试集上的正确率: {}".format(total_accuracy / test_data_size))
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy / test_data_size, total_test_step)

    # 每次测试完毕后测试次数加1
    total_test_step = total_test_step + 1

    torch.save(tudui, "tudui_{}.pth".format(i))
    print("模型已保存")

writer.close()

利用GPU训练

GPU训练的第一种方式

在这里插入图片描述
方法很简单:找到上述三种变量,调用.cuda就可以啦
1:网络模型
在这里插入图片描述
2:数据
在这里插入图片描述
3:损失函数:
在这里插入图片描述

代码示例


import torch
import torchvision
from torch.utils.tensorboard import SummaryWriter

# from model import *
# 准备数据集
from torch import nn
from torch.utils.data import DataLoader

train_data = torchvision.datasets.CIFAR10(root="datasets", train=True, transform=torchvision.transforms.ToTensor(),
                                          download=True)
test_data = torchvision.datasets.CIFAR10(root="datasets", train=False, transform=torchvision.transforms.ToTensor(),
                                         download=True)

# length 长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# 如果train_data_size=10, 训练数据集的长度为:10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))

# 利用 DataLoader 来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)


# 创建网络模型
class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64 * 4 * 4, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x


tudui = Tudui()

if torch.cuda.is_available():
    # 这里调用.cuda
    tudui = tudui.cuda()

# 损失函数
loss_fn = nn.CrossEntropyLoss()
if torch.cuda.is_available():
    loss_fn = loss_fn.cuda()
# 优化器
# learning_rate = 0.01
# 1e-2=1 x (10)^(-2) = 1 /100 = 0.01
learning_rate = 1e-2
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)

# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 10

# 添加tensorboard
writer = SummaryWriter("../logs_train")

for i in range(epoch):
    print("-------第 {} 轮训练开始-------".format(i + 1))

    # 训练步骤开始
    tudui.train()
    for data in train_dataloader:
        imgs, targets = data
        if torch.cuda.is_available():
            imgs = imgs.cuda()
            targets = targets.cuda()
        outputs = tudui(imgs)
        loss = loss_fn(outputs, targets)

        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0:
            print("训练次数:{}, Loss: {}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)

    # 测试步骤开始
    tudui.eval()
    total_test_loss = 0
    total_accuracy = 0
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            if torch.cuda.is_available():
                imgs = imgs.cuda()
                targets = targets.cuda()
            outputs = tudui(imgs)
            loss = loss_fn(outputs, targets)
            total_test_loss = total_test_loss + loss.item()
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("整体测试集上的Loss: {}".format(total_test_loss))
    print("整体测试集上的正确率: {}".format(total_accuracy / test_data_size))
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy / test_data_size, total_test_step)
    total_test_step = total_test_step + 1

    torch.save(tudui, "tudui_{}.pth".format(i))
    print("模型已保存")

writer.close()

GPU训练的第二种方式

1:首先定义设备
在这里插入图片描述

2:其次定义模型
在这里插入图片描述
3:然后定义损失函数
在这里插入图片描述
4:最后定义数据
在这里插入图片描述

代码示例


import torch
import torchvision
from torch.utils.tensorboard import SummaryWriter

# from model import *
# 准备数据集
from torch import nn
from torch.utils.data import DataLoader

# 定义训练的设备
device = torch.device("cuda")

train_data = torchvision.datasets.CIFAR10(root="datasets", train=True, transform=torchvision.transforms.ToTensor(),
                                          download=True)
test_data = torchvision.datasets.CIFAR10(root="datasets", train=False, transform=torchvision.transforms.ToTensor(),
                                         download=True)

# length 长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# 如果train_data_size=10, 训练数据集的长度为:10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))


# 利用 DataLoader 来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

# 创建网络模型
class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x
tudui = Tudui()

tudui = tudui.to(device)

# 损失函数
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.to(device)
# 优化器
# learning_rate = 0.01
# 1e-2=1 x (10)^(-2) = 1 /100 = 0.01
learning_rate = 1e-2
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)

# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 10

# 添加tensorboard
writer = SummaryWriter("../logs_train")

for i in range(epoch):
    print("-------第 {} 轮训练开始-------".format(i+1))

    # 训练步骤开始
    # train是训练的时候可以写
    tudui.train()
    for data in train_dataloader:
        imgs, targets = data
        imgs = imgs.to(device)
        targets = targets.to(device)
        outputs = tudui(imgs)
        loss = loss_fn(outputs, targets)

        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0:
            print("训练次数:{}, Loss: {}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)

    # 测试步骤开始
    # eval是测试的时候可以写
    tudui.eval()
    total_test_loss = 0
    total_accuracy = 0
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            imgs = imgs.to(device)
            targets = targets.to(device)
            outputs = tudui(imgs)
            loss = loss_fn(outputs, targets)
            total_test_loss = total_test_loss + loss.item()
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("整体测试集上的Loss: {}".format(total_test_loss))
    print("整体测试集上的正确率: {}".format(total_accuracy/test_data_size))
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy/test_data_size, total_test_step)
    total_test_step = total_test_step + 1

    torch.save(tudui, "tudui_{}.pth".format(i))
    print("模型已保存")

writer.close()

完整的模型验证套路

也可以说是测试,demo套路,即利用已经训练好的模型,然后给他提供输入

# -*- coding: utf-8 -*-
# 作者:小土堆
# 公众号:土堆碎念
import torch
import torchvision
from PIL import Image
from torch import nn

image_path = "image/dog.jpg"

image = Image.open(image_path)
# image是PIL类型
print(image)
# 注意要加上这句话
image = image.convert('RGB')
# 这里指定我们的图片大小为32*32
transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32, 32)),
                                            torchvision.transforms.ToTensor()])

# 此时的image是tensor数据类型
image = transform(image)
# 打印结果为torch.Size([3, 32, 32])
print(image.shape)

class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x

# map_location=torch.device('cpu')出现的原因是假设我们此时加载的文件是采用gpu训练的,
# 假设我们的文件是在gpu上保存的,那么此时要在cpu上运行的话要从gpu映射到cpu上
# 就需要使用map_location
model = torch.load("tudui_0.pth", map_location=torch.device('cpu'))
"""
打印的model的结果为:
Tudui(
  (model): Sequential(
    (0): Conv2d(3, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (2): Conv2d(32, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (4): Conv2d(32, 64, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (6): Flatten(start_dim=1, end_dim=-1)
    (7): Linear(in_features=1024, out_features=64, bias=True)
    (8): Linear(in_features=64, out_features=10, bias=True)
  )
)
"""
print(model)

# 因为没有指定输入的batch_size,所以要reshape以下
image = torch.reshape(image, (1, 3, 32, 32))
# 代表测试
model.eval()
with torch.no_grad():
    output = model(image)

# tensor([[-1.3097,  0.4625,  0.3236,  0.8633,  0.7195,  0.9539,  0.9882,  0.6664,
#          -1.7272, -0.2835]])
print(output)

# 打印结果为tensor([6]),因为tudui_0.pth对应的模型结构是一个分类问题,所以最终得到的依然是看我们所给图片是那个的类别的概率最大
# 最终跟所给的类别概率最大的那个下标将会被返回
print(output.argmax(1))

注意事项:
为什么要加image = image.convert(‘RGB’)这句话的原因:
在这里插入图片描述

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值