PyTorch基础知识

1. PyTorch中如何加载数据 — Dataset

PyTorch如何读取数据主要涉及两个类:Dataset 和 Dataloader。
Dataset:主要是提供一种方式去获取数据及其label(真实的label数据),还会对获取到的数据进行编号(从0开始编号),可以为后面的工具或者网络根据编号去读取相应的数据。
Dataset主要实现的功能:1、如何获取每一个数据及其label 2、告诉我们总共有多少数据。
Dataloader:为后面的网络提供不同的数据形式(给数据进行打包,将数据进行封装然后一并输入到后面的网络中)。

使用Jupyter了解Dataset的具体使用(官方文档中的具体如何使用)如下图(方式一和二):
Jupyter中,shift+enter(回车键) —> 运行代码的快捷键;enter(回车键) —> 光标来到下一行;In [*] —> In[1] 代表此块代码已经运行结束。

在这里插入图片描述
在这里插入图片描述

使用Dataset 读取数据

from torch.utils.data import Dataset
from PIL import Image
import os # os ---> python当中关于系统的一个库

# MyData类继承Dataset类
class MyData(Dataset):

    # 初始化 ---> 当该类创建实例时,就要运行的一个函数
    def __init__(self , root_dir, label_dir):

        self.root_dir = root_dir
        self.label_dir = label_dir
        # os.path.join 将root_dir的路径和label_dir路径拼接起来
        self.path = os.path.join(root_dir,label_dir)
        # os.listdir 获取self.path路径下的所有图片地址 返回的是列表list
        self.img_path = os.listdir(self.path)

    # 获取所有图片中的某一个图片
    def __getitem__(self, index):
        # 某一个图片的名称   index ---> 索引
        img_name = self.img_path[index]
        # 每一个图片的地址
        img_item_path = os.path.join(self.root_dir,self.label_dir,img_name)
        # 读取具体的图片
        img = Image.open(img_item_path)
        # label
        label = self.label_dir
        # __getitem__ 返回 img 和 label
        return img, label

    # 判断数据集有多长  上述列表 list 的长度是多少 ---> 数据集就有多大
    def len(self):
        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)
# 整个数据集 = 两个数据集相加
train_dataset = ants_dataset + bees_dataset

2. Tensorboard的使用

先提及一下 transform,transform在 Dataset 中很常用,transform主要是对图像进行一个变化,比如图像需要统一到一定的尺寸或对图像中的每个数据进行一个类的转化。

Tensorboard在训练模型的过程中很有用。
TensorBoard必须是一个 tensor 数据类型。

通过 loss 可以知道训练过程是否按照我们的预想变化或在训练过程中是否是一个正常的状态。

TensorBoard中add_scalar()函数的使用

from torch.utils.tensorboard import SummaryWriter

# 创建 SummaryWriter类的实例
writer = SummaryWriter("logs")
# 在 writer 实例中 主要使用到的两个方法 writer.add_image() 和 writer.add_scalar()
# writer.add_image()
# add_scalar()函数中参数的解读: tag ---> 图表的标题  scalar_value ---> 数值(也就是y轴)
# global_step ---> 训练到多少步(也就是x轴)

# y = x
for i in range(100): # 此时 i 是从 0 ~ 99
    writer.add_scalar("y = x",i,i)
writer.close()

执行上述代码后,项目文件内的变化

在这里插入图片描述

如何打开刚刚新生成的文件 logs\events.out.tfevents.1723298402.LAPTOP-RB76HDM1.14492.0
答:方式一:使用Anaconda命令行
方式二:在PyCharm的Terminal中使用命令行(logdir = 事件文件所在文件夹名)

tensorboard --logdir=logs
tensorboard --logdir=logs --port=6007

终端Terminal输入命令行,以打开tensorboard。见下图
在这里插入图片描述
在这里插入图片描述

TensorBoard中add_image()函数的使用

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


# 创建 SummaryWriter类的实例
writer = SummaryWriter("logs")
# 在 writer 实例中 主要使用到的两个方法 writer.add_image() 和 writer.add_scalar()
# writer.add_image()
# add_scalar()函数中参数的解读: tag ---> 图表的标题  scalar_value ---> 数值(也就是y轴)
# global_step ---> 训练步骤(也就是x轴)


image_path = "dataset/train/bees_image/39672681_1302d204d1.jpg" # 相对地址
img_PIL = Image.open(image_path)
# 将PIL类型转换为numpy类型的图片格式   注: 这种做法需要在add_image()函数中指定shape中每一个数字表示的含义
img_array = np.array(img_PIL)
# type函数 ---> 输出变量的类型
print(type(img_array))
print(img_array.shape)

# add_image()函数中参数的解读: tag ---> 图表的标题   img_tensor ---> 图像的数据类型
# global_step ---> 训练步骤
writer.add_image("test", img_array,2,dataformats='HWC') # HWC ---> Height Width Channel
# y = x
for i in range(100): # 此时 i 是从 0 ~ 99
    writer.add_scalar("y = 2x",3*i,i)
writer.close()

在这里插入图片描述

3. Transforms

torchvision 中的transforms
transforms 指的就是pycharm中 transforms.py文件,transforms.py文件相当于一个工具箱,里面有不同的class,不同的class也就是代表有不同的作用。
transforms 主要是对图片进行一些变化。
对transforms的使用:拿一些特定格式的图片,通过 transforms.py文件中的函数,完成图片格式的转换,转换成我们需要的tensor格式图片。当然也存在一些对图片的其它操作,例如指定图片大小等。

常见的图像数据类型如下图:1、PIL Image 2、ndarray 3、tensor
在这里插入图片描述

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

# python 中 transforms的用法
# 通过 transforms.ToTensor去看两个问题
# 问题1: transforms在python该如何使用?
# 问题2: Tensor数据类型相比于其它图片数据类型以及普通数据类型有什么区别?(即为什么我们需要Tensor数据类型)
# 绝对路径 D:\python_projects\learn_pytorch\dataset\train\ants_image\0013035.jpg
# 相对路径 dataset/train/ants_image/0013035.jpg
img_path = "dataset/train/ants_image/0013035.jpg"
# Image是python中内置的图片打开的一个库
img = (Image.open(img_path))
print(img)

# 使用 TensorBoard
writer = SummaryWriter("logs")

# 先看上述的问题1
tensor_trans = transforms.ToTensor() # transforms.ToTensor() ---> 就是创建了一个ToTensor的实例对象
tensor_img = tensor_trans.__call__(img)
print(tensor_img)

# 直接传入 tensor 数据类型的图片
# 因为这里是tensor类型的图像,通道数的位置是对的 所以并不需要设置HWC
writer.add_image("Tensor_img", tensor_img)
# 需要关闭
writer.close()

# 再看上述的问题2
# 答:Tensor数据类型就可以理解为是一个包装了神经网络所需要的一些理论基础的一些参数

在这里插入图片描述

归一化:归一化的目的就是为了让不同的特征在数值上保持一致,避免某些特征对模型的影响过大,从而更好的学习到数据中的模式和关系。

内置函数 call 的使用,见下面程序及截图

class Person:
    # 内置函数 call 的使用
    # 下划线  __  表示是一种内置函数
    # 内置函数 __call__
    # 定义内置 call 函数
    def __call__(self,name):
        print("__call__" + "Hello " + name)

    def hello(self,name):
        print("hello" + name)

person = Person()
# 内置函数 __call__ 的话,可以不使用 对象名.函数名的方式进行调用函数。可以直接使用对象名()的方式来进行调用
person("zhangsan")

person.hello("lisi")

在这里插入图片描述

常见的transforms 即 transforms.py文件中常见的class类,文件中不同的类有不同的作用。

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

# 常见的 transforms ---> ToTensor类  Normalize类(归一化)  Resize类  Compose类  RandomCrop类

writer = SummaryWriter("logs")

# 此时图片是 PIL数据类型
img = Image.open("images/pytorch.png")
print(img)

# 1. ToTensor类 的使用
trans_totensor = transforms.ToTensor()
# 将 PIL数据类型的图片 转换为 Tensor数据类型的图片
img_tensor = trans_totensor(img)
# 将Tensor数据类型的图片放入TensorBoard中
writer.add_image("ToTensor", img_tensor)



# 2. Normalize类(归一化) 的使用
print(img_tensor[0][0][0]) # [0][0][0] ---> 第一层第一行第一列
# 第一个[ ] ---> 图片RGB 三个信道 [0.5,0.5,0.5]
# 第二个[ ] ---> 每个信道的标准差  [0.5,0.5,0.5]
trans_norm = transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
# img_norm 是经过变换(归一化)的一个图片
img_norm = trans_norm.forward(img_tensor)

print(img_norm[0][0][0]) # [0][0][0] ---> 第一层第一行第一列(第一个通道第一行第一列)

writer.add_image("Normalize", img_norm)


# 3. Resize类 的使用
print(img.size)
trans_resize = transforms.Resize((512,512)) # sequence序列是用小括号()   (512,512) ---> sequence序列代表(h, w)即高度和宽度
# PIL类型的img -> Resize -> PIL类型的img
img_resize = trans_resize.forward(img)
# PIL类型的img -> ToTensor -> Tensor类型的img
img_resize = trans_totensor(img_resize) # 两个同名的img_resize 执行完成后 后者会将前者的值覆盖
writer.add_image("Resize", img_resize,0) # 0 ---> 第 0 步
print(img_resize)


# 4. Compose类 的使用
trans_resize_2 = transforms.Resize(512) # 512 ---> int 一个数值
trans_compose = transforms.Compose([trans_resize_2,trans_totensor]) # Compose() ---> 将trans_resize_2转换为trans_totensor(tensor数据类型)
img_resize_2 = trans_compose(img)
writer.add_image("Resize",img_resize_2,1)


# 5. RandomCrop类 的使用
trans_random = transforms.RandomCrop(512) # 512 ---> int 数值
trans_compose_2 = transforms.Compose([trans_random,trans_totensor])
for i in range(10):
    img_crop = trans_compose_2(img)
    writer.add_image("RandomCrop",img_crop,i)

writer.close()

# 注:Resize输入两个参数(即sequence序列)时,输出图片高和宽的像素点数量会按照我们序列中设定的值进行输出,
#    而只输入一个参数(即int)时,代表的是图片中最短的那个边输出的像素点数量

# 补充知识:一张图像由无数个像素点组成,tensor数据类型就是分层的思想
# 一张图像分成三维的形状,分成无数层,每一层又可以再分为行和列

在这里插入图片描述
在这里插入图片描述

4. torchvision

torchvision 中数据集的使用

import torchvision

# ./ ---> 表示当前目录  创建 dataset 文件夹 同时将下载的数据集保存到dataset文件夹中
train_set = torchvision.datasets.CIFAR10(root="./dataset",train=True,download=True)
test_set = torchvision.datasets.CIFAR10(root="./dataset",train=False,download=True)

print(test_set[0])
print(train_set.classes)

img, target = test_set[0]
print(img)
print(target) # target就是 label  标签 类别
print(train_set.classes[target])

在这里插入图片描述

torchvision 中常见 dataset 的使用 —> CIFAR-10 dataset

import torchvision

# torchvision 中常见 dataset 的使用

# ./ ---> 表示当前目录
train_set = torchvision.datasets.CIFAR10(root="./dataset",train=True,download=True)
test_set = torchvision.datasets.CIFAR10(root="./dataset",train=False,download=True)

print(test_set[0])
print(train_set.classes)

img, target = test_set[0]
print(img)
print(target) # target就是 label  标签 类别
print(train_set.classes[target])
img.show()

在这里插入图片描述

数据集 dataset 和 transforms 结合在一起使用

import torchvision
from torch.utils.tensorboard import SummaryWriter

# torchvision 中常见数据集(dataset)的使用方式 以及 transforms 与 dataset 的结合使用

# 设置 ToTensor 的 transforms
dataset_transforms = torchvision.transforms.Compose([
    torchvision.transforms.ToTensor()
])

# ./ ---> 表示当前目录
# 参数中 transform=dataset_transforms 将设置 ToTensor 的 transforms 应用到数据集中的每一张图片都转换为tensor数据类型
# 参数中 download=True 推荐一直设置为True
train_set = torchvision.datasets.CIFAR10(root="./dataset",train=True,transform=dataset_transforms,download=True) # train=True 训练集
test_set = torchvision.datasets.CIFAR10(root="./dataset",train=False,transform=dataset_transforms,download=True) # train=False 测试集

# print(test_set[0])
# print(train_set.classes)
#
# img, target = test_set[0]
# print(img)
# print(target) # target就是 label  标签 类别
# print(train_set.classes[target])
# img.show()

print(test_set[0])  # 此时已经是 tensor 数据类型的图片
# 使用 TensorBoard
writer = SummaryWriter(log_dir='./logs')
for i in range(10):
    img, target = test_set[i]
    writer.add_image("test_set",img,i)

writer.close()

在这里插入图片描述

5. DataLoader

dataset 与 dataloader
dataset 只是告诉程序我们要使用的数据集在什么位置以及告知我们数据集中数据的具体信息;
dataloader 是一个加载器,把我们的数据加载到神经网络当中。dataloader 所做的事情就是每次从 dataset 中取出数据。

在这里插入图片描述

使用dataloader 可能遇到的问题,见下图

在这里插入图片描述
在这里插入图片描述

DataLoader 的使用

import torchvision
from torch.utils.data import DataLoader

# train=False ---> 测试集
test_data = torchvision.datasets.CIFAR10(root="./dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True)
# batch_size=4 ---> 每次从 test_data 测试集中取出4个数据进行打包 是4个4个的取数据,不是只读了4个
# shuffle=True ---> 是指数据集在每轮训练中打乱,也就是在每个epoch开始时打乱数据
test_loader = DataLoader(test_data, batch_size=4, shuffle=True, num_workers=0, drop_last=False)

# 测试数据集中第一张图片及target
img, target = test_data[0]
print(img.shape)
# target 不是标签,是标签存放的位置,也就是标签列表所对应的下标. 标签列表是class,真正的标签是class[target]
print(target)

for data in test_loader:
    imgs, targets = data
    print(imgs.shape)
    print(targets)


# 补充额外知识: __getitem__() ---> 进行对象索引访问取值的时候自动调用.就像列表索引取值那样.是一个魔法(术)方法
# 一个epoch打乱一次,一个epoch代表数据集的数据全部用过一遍

在这里插入图片描述

主要讲解DataLoader中参数drop_last的使用

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

# train=False ---> 测试集
test_data = torchvision.datasets.CIFAR10(root="./dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True)
# batch_size=64 ---> 每次从 test_data 测试集中取出64个数据进行打包
# shuffle=True ---> 是指数据集在每轮训练中打乱,也就是在每个epoch开始时打乱数据
# drop_last=True ---> 将不足以64个数据完成打一个包 的那部分数据舍弃掉 
# drop_last=False 的话 ---> 所以数据都保留,不会进行数据的舍弃
test_loader = DataLoader(test_data, batch_size=64, shuffle=True, num_workers=0, drop_last=True)

# 测试数据集中第一张图片及target
img, target = test_data[0]
print(img.shape)
# target 不是标签,是标签存放的位置,也就是标签列表所对应的下标. 标签列表是class,真正的标签是class[target]
print(target)

# 使用 TensorBoard
writer = SummaryWriter(log_dir='logs')
step = 0

for data in test_loader:
    imgs, targets = data
    # print(imgs.shape)
    # print(targets)
    writer.add_images("test_data_drop_last", imgs, step)
    step += 1

writer.close()


# 补充额外知识: __getitem__() ---> 进行对象索引访问取值的时候自动调用.就像列表索引取值那样.是一个魔法(术)方法
# 一个epoch打乱一次,一个epoch代表数据集的数据全部用过一遍

在这里插入图片描述

主要讲解DataLoader中参数shuffle的使用

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

# train=False ---> 测试集
test_data = torchvision.datasets.CIFAR10(root="./dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True)
# batch_size=64 ---> 每次从 test_data 测试集中取出64个数据进行打包
# shuffle=True ---> 是指数据集在每轮训练中打乱,也就是在每个epoch开始时打乱数据
# drop_last=True ---> 将不足以64个数据完成打一个包 的那部分数据舍弃掉
test_loader = DataLoader(test_data, batch_size=64, shuffle=True, num_workers=0, drop_last=True)

# 测试数据集中第一张图片及target
img, target = test_data[0]
print(img.shape)
# target 不是标签,是标签存放的位置,也就是标签列表所对应的下标. 标签列表是class,真正的标签是class[target]
print(target)

# 使用 TensorBoard
writer = SummaryWriter(log_dir='logs')

for epoch in range(2):
    step = 0
    for data in test_loader:
        imgs, targets = data
        # print(imgs.shape)
        # print(targets)
        writer.add_images("Epoch:{}".format(epoch), imgs, step)
        step += 1

writer.close()


# 补充额外知识: __getitem__() ---> 进行对象索引访问取值的时候自动调用.就像列表索引取值那样.是一个魔法(术)方法
# 一个epoch打乱一次,一个epoch代表数据集的数据全部用过一遍

在这里插入图片描述

6. nn.Module

神经网络的基本骨架 —— nn.Module的使用
Module —> CLASS —> torch.nn.Module
可以理解为 Module 给所有神经网络提供了一个模板,我们只需继承 nn.Module 然后就可以拿这个模板来用

神经网络的工具主要在 TORCH.NN 内,torch.nn 可以搭建一些神经网络。

神经网络骨架的基本搭建

PyTorch官方文档的简单使用,见下图(PyTorch版本 —> 1.8.1)

在这里插入图片描述
在这里插入图片描述

搭建自己的神经网络Demo,见如下代码和截图

import torch
from torch import nn

# 搭建一个自己的神经网络

# (nn.Module) ---> 继承 nn.Module
class Network(nn.Module):
    def __init__(self):
        # super ---> 调用父类方法
        super().__init__()

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

network = Network()
# 1.0 ---> 就是一个0维度的张量,其实就是数字 1
x = torch.tensor(1.0)
output = network.forward(x)
print(output)


# 补充额外知识: 张量是PyTorch中各种维度数据形式的统称
# 0维就是一个数, 1维就是一个数组, 2维就是矩阵, 3维是空间矩阵, 以此类推...

在这里插入图片描述

6.1 卷积层(Convolution Layers)

在这里插入图片描述

卷积核(滤波器) 也叫 fliter,卷积核初始随机生成(一开始自己给定初值),然后通过大量的数据计算学习得到最优解。卷积核上每个位置相当于一个权重weight,比如一个3×3的卷积核,就是9个权重weight,训练的目的就是为了学习这9个权重,根据优化方法逐渐偏向最优解。实际网络训练过程中,卷积核中的参数是自己学习出来的,我们只需要在初始化的时候随机赋值即可。
卷积是提取特征值的。

stride —> 步长

在这里插入图片描述

padding —> 填充 padding一般情况下设置为 padding = 0(即0是padding的默认值) 即默认情况下是不会进行填充的。
padding = 1 —> 填充上下左右。
填充更关注于边缘的特征。
为什么需要填充?
答:如果不进行填充,那么图片边缘的图像特征就会缺失。

在这里插入图片描述

import torch
import torch.nn.functional as F

# 5 × 5 的矩阵 ---> 二维矩阵
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 卷积核
kernel = torch.tensor([
    [1, 2, 1],
    [0, 1, 0],
    [2, 1, 0]
])

# PyTorch所提供的图片尺寸变换 torch.reshape()
# (1, 1, 5, 5) ---> N C H W
# N ---> batch_size ---> 1  C ---> Channel ---> 1  H ---> Height ---> 5  W ---> Width ---> 5
# reshape()会根据我们所设置的图片尺寸进行转换
input = torch.reshape(input, (1, 1, 5, 5))
kernel = torch.reshape(kernel, (1, 1, 3, 3))

# 查看尺寸
print(input.shape)
print(kernel.shape)

output = F.conv2d(input, kernel, stride=1)
print(output)

output2 = F.conv2d(input, kernel, stride=2)
print(output2)

output3 = F.conv2d(input, kernel, stride=1, padding=1)
print(output3)

# 补充额外知识: batch_size ---> 就是取出图片的数量进行打包

# 矩阵 ---> 二维张量(平面) ---> 通道数为 1

在这里插入图片描述

卷积层的使用

在这里主要讲述 Conv2d,因为对于图像的话,图像是一个二维的矩阵。

在这里插入图片描述

Conv2d参数介绍,见下图

注:kernel_size参数 —> 我们只需要指定尺寸大小即可,其中具体的数值不需要我们设置,因为卷积核本身就是要进行学习的参数。
in_channels参数 —> 输入图片的通道数
out_channels参数 —> 输出图片的通道数。out_channels可以理解为是卷积核的数量。

在这里插入图片描述

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

# train=False ---> 测试数据集
dataset = torchvision.datasets.CIFAR10("data", train=False, transform=torchvision.transforms.ToTensor(), download=True)

# 将准备好的数据集放到 DataLoader  中去加载
dataloader = DataLoader(dataset, batch_size=64)

# 搭建自己的一个简单神经网络
class Net(nn.Module):
    def __init__(self):
        # super ---> 用于完成父类的初始化
        super(Net, self).__init__()
        # 定义卷积层
        self.conv1 = Conv2d(3, 6, 3, 1, 0)

    # forward ---> 前向传播
    def forward(self, x):
            x = self.conv1(x)
            return x

# 首先初始化我们的神经网络
net = Net()
# 查看神经网络的结构
# print(net)

# 使用 TensorBoard
writer = SummaryWriter("logs")

step = 0
for data in dataloader:
    imgs, targets = data
    # 此时 output 是经过我们简单神经网络 forward函数 卷积之后的图像
    output = net(imgs)
    print(imgs.shape)
    print(output.shape)
    # torch.Size([64, 3, 32, 32])
    writer.add_images("input", imgs, step)
    # torch.Size([64, 6, 30, 30])
    output = torch.reshape(output, (-1, 3, 30, 30))
    writer.add_images("output", output, step)

    step += 1

writer.close()



# 补充知识: 彩色图像 ---> RGB ---> 故 in_channels 为 3

# 父类 nn.module 中定义了一个 call函数, 这个 call函数会自动调用 forward函数,
# 当通过对象传入参数时, 会自动调用 call函数, 从而调用 forward函数.

# ./ 表示当前目录     ../ 表示上级目录/父级目录

经过卷积操作之后原始图像会变小。图像尺寸(height width)由 32×32 —> 30×30 见如下截图

在这里插入图片描述
在这里插入图片描述

7. 池化层

最大池化操作:输出池化核所覆盖输入图像中数值的最大值

在这里插入图片描述

在这里插入图片描述

MaxPool2d参数介绍,见下图

在这里插入图片描述

MaxPool2d ceil_mode 参数 图解介绍
技巧:floor地板,ceil天花板
ceil 允许有出界部分,floor 不允许

在这里插入图片描述

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

import torch
from torch import nn
from torch.nn import MaxPool2d

# 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]
])

# -1 ---> 占位符,(在我们不知道具体数值的情况下)使之自动计算该维度的大小
input = torch.reshape(input, (-1, 1, 5, 5))
print(input.shape)


# 搭建神经网络
class Net(nn.Module):
    # 首先进行父类函数的重写
    def __init__(self):
        # 对父类进行一个初始化
        super(Net, self).__init__()
        # 最大池化层
        # ceil_mode默认是 False
        self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=True)

    def forward(self, x):
        # 将 x 放入到最大池化当中
        output = self.maxpool1(x)
        return output


net = Net()
output = net(input)
print(output)

在这里插入图片描述

为什么要进行最大池化?/ 最大池化的作用是什么?
答:最大池化是神经网络中必不可缺的,最大池化的目的就是保留我们输入的特征,同时把神经网络中训练数据量减小,对于整个神经网络来说需要进行计算的参数就变少了,进而训练就会更加快速。
最大池化的作用保留数据特征,减少数据量。

注:卷积提取特征,卷积会改变通道数;池化降维,池化保留图片特征、减少计算量、加快运行速度,池化不改变通道数

最大池化操作 图片前后对比 见如下代码和截图

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

dataloader = DataLoader(dataset, batch_size=64)

# 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]
# ])
#
# # -1 ---> 占位符,使之自动计算该维度的大小
# input = torch.reshape(input, (-1, 1, 5, 5))
# print(input.shape)


# 搭建神经网络
class Net(nn.Module):
    # 首先进行父类函数的重写
    def __init__(self):
        # 对父类进行一个初始化
        super(Net, self).__init__()
        # 最大池化层
        # ceil_mode默认是 False
        self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=True)

    def forward(self, x):
        # 将 x 放入到最大池化当中
        output = self.maxpool1(x)
        return output


net = Net()
# output = net(input)
# print(output)

writer = SummaryWriter("logs_maxpool")

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

writer.close()

在这里插入图片描述

8. 非线性激活

神经网络 —— 非线性激活
非线性激活主要是给神经网络引入一些非线性的特征
常用的非线性激活(激活函数) —> nn.ReLU,nn.Sigmoid

在这里我们以 ReLU 为例,一般情况下我们建议采用 inplace= False,因为这样可以保留我们的原始数据,防止数据丢失。
在这里插入图片描述
在这里插入图片描述

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

# 非线性变换 --- ReLU

input = torch.tensor([
    [1, -0.5],
    [-1, 3]
])

input = torch.reshape(input, (-1, 1, 2, 2))

print(input.shape)



# 定义神经网络
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # ReLU 中参数 inplace 默认就是False 我们一般推荐 inplace=False
        self.relu1 = ReLU(inplace=False)

    def forward(self, x):
        output = self.relu1(x)
        return output

net = Net()
output = net(input)
print(output)

在这里插入图片描述

关于非线性激活 Sigmoid

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

# 非线性变换 --- Sigmoid

input = torch.tensor([
    [1, -0.5],
    [-1, 3]
])

input = torch.reshape(input, (-1, 1, 2, 2))

print(input.shape)

dataset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=torchvision.transforms.ToTensor())

dataloader = DataLoader(dataset, batch_size=64)


# 定义神经网络
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.sigmoid1 = Sigmoid()

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

net = Net()

writer = SummaryWriter('./logs')

step = 0
for data in dataloader:
    imgs, targets = data
    writer.add_images("input", imgs, step)
    outputs = net(imgs)
    writer.add_images("output", outputs, step)
    step += 1

writer.close()


# 补充知识: 图像三个通道RGB的取值都是 0 - 255,用ReLU等于没有变化,所以这里使用Sigmoid进行图像演示
#          使用Sigmoid才能映射到 0 - 1 的区间.

在这里插入图片描述

9. 线性层(Linear Layers)及其它层

神经网络 —— 其它层介绍
Dropout Layers 是为了防止过拟合。过拟合(Overfitting)是指模型在训练集上表现较好,但在测试集或实际应用中表现较差的现象。
Distance Functions是计算两个值之间的误差。
Normalization —> 归一化; Regularization —> 正则化。用的都比较多。

线性层即全连接层

在这里插入图片描述

在这里插入图片描述

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

# 线性层(Linear Layers)/全连接层

dataset = torchvision.datasets.CIFAR10(root='./data', train=False, transform=torchvision.transforms.ToTensor(), download=True)
dataloader = DataLoader(dataset, batch_size=64, drop_last=True)

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.linear1 = Linear(196608, 10)

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

net = Net()

for data in dataloader:
    imgs, targets = data
    print(imgs.shape)
    # torch.flatten() ---> 摊平,变成一行
    output = torch.flatten(imgs)
    print(output.shape)
    output = net(output)
    print(output.shape)


# 补充知识: 经过全连接层的数据是一个向量,在神经网络中需要进行数据转化,
#          才能将卷积层中的数据放置到全连接层中

在这里插入图片描述

10. Sequential 的使用

搭建实战 —> CIFAR10 Model,并通过其搭建了解 Sequential 的使用。

CIFAR10 Model Structure见下图
在这里插入图片描述
在这里插入图片描述

import torch
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.tensorboard import SummaryWriter

# 搭建 CIFAR10 Model 实战 以及 Sequential 的使用

# 搭建网络
class Net(nn.Module):
    def __init__(self):
        # 父类的初始化
        super(Net, self).__init__()
        # self.conv1 = Conv2d(3, 32, 5, padding=2)
        # self.maxpool1 = MaxPool2d(2)
        # self.conv2 = Conv2d(32, 32, 5, padding=2)
        # self.maxpool2 = MaxPool2d(2)
        # self.conv3 = Conv2d(32, 64, 5, padding=2)
        # self.maxpool3 = MaxPool2d(2)
        # Flatten 将数据摊平
        # self.flatten = Flatten()
        # self.linear1 = Linear(1024, 64)
        # self.linear2 = Linear(64, 10)

        # Sequential 的使用. Sequential()括号内写网络的结构
        # 注意: 不同的层与层之间要用 逗号 ,  进行分割
        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.conv1(x)
        # x = self.maxpool1(x)
        # x = self.conv2(x)
        # x = self.maxpool2(x)
        # x = self.conv3(x)
        # x = self.maxpool3(x)
        # x = self.flatten(x)
        # x = self.linear1(x)
        # x = self.linear2(x)

        # 使用 Sequential 之后的用法
        x = self.model1(x)
        return x


net = Net()
# 输出网络结构
print(net)
# (64, 3, 32, 32) --- N batch_size C channels H height W width
input = torch.ones((64, 3, 32, 32))
output = net(input)
print(output.shape)

writer = SummaryWriter("logs_sequential")
# add_graph() ---> 计算图
writer.add_graph(net, input)

writer.close()

在这里插入图片描述
add_graph()

在这里插入图片描述

11. 损失函数与反向传播

torch.nn内的 Loss Functions(损失函数)
Loss越小越好。
Loss Functions —> 来衡量误差。
Loss Functions 的作用是什么?
答:1、计算实际输出和目标(我们想要的结果)之间的差距;2、为我们更新输出提供一定的依据(反向传播)。

在使用 Loss Functions 的时候,一定要根据我们的需求去使用。
在使用的过程中,注意 Loss Functions 要求输入的形状(shape)是什么样的形式以及输出的形状(shape)是什么样。

import torch
from torch.nn import L1Loss, MSELoss, CrossEntropyLoss

# dtype=torch.float32 ---> 将 [1, 2, 3] 转换为浮点数
inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)

# (1, 1, 1, 3) ---> 1 batch_size   1 channel  1 height  3 width
inputs = torch.reshape(inputs, (1, 1, 1, 3))
targets = torch.reshape(targets, (1, 1, 1, 3))

# L1Loss
# L1Loss 实例化
loss = L1Loss()
# 将inputs 和 targets 放到L1Loss里面去计算,返回计算结果赋值给result
result = loss(inputs, targets)

print(result)


# MSELoss ---> 均方误差(MeanSquaredError)
loss_mse = MSELoss()
result_mse = loss_mse(inputs, targets)
print(result_mse)


# CrossEntropyLoss(交叉熵损失函数)
# 交叉熵损失函数 适用于多分类问题,它可以处理任何数量的类别,
# 不仅仅是二分类,也可以是多分类问题.
x = torch.tensor([0.1, 0.2, 0.3])
y = torch.tensor([1])
# (1, 3) ---> 1 N batch_size   3 C Classes(类别数量)
x = torch.reshape(x, (1, 3))
loss_cross = CrossEntropyLoss()
result_cross = loss_cross(x, y)
print(result_cross)



# 补充额外知识: tensor在产生时会自带维度的,reshape是为了保证其维度是我们想要的,
#             符合某个想使用函数的特定要求.

在这里插入图片描述

如何在神经网络当中使用 Loss Functions(损失函数),见如下程序及截图

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

# 神经网络当中使用 Loss Functions(损失函数)

dataset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=1)


class Net(nn.Module):
    def __init__(self):
        super(Net, 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 Function
loss = CrossEntropyLoss()


# 创建网络
net = Net()
for data in dataloader:
    imgs, targets = data
    # outputs ---> 将输入的 imgs 通过上面的神经网络 所得到的一个输出
    outputs = net(imgs)
    print(outputs)
    print(targets)
    result_loss = loss(outputs, targets)
    print(result_loss)
    # 反向传播
    result_loss.backward()
    print("ok")

在这里插入图片描述
注:反向传播 —> 计算出每个结点的参数,然后我们就有了每个结点参数的梯度,之后就可以选择合适的优化器根据梯度对这些参数进行优化,以使整个 loss 达到降低目的。(反向传播来计算梯度,根据梯度来更新参数,实现 loss 最小化。)

12. 优化器

在官方文档中,所有的优化器都是集中在 torch.optim 中。

优化器的使用,见如下程序及截图

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

# transform=torchvision.transforms.ToTensor() ---> 将数据集转换为 tensor 数据类型
dataset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=torchvision.transforms.ToTensor())

dataloader = DataLoader(dataset, batch_size=1)


class Net(nn.Module):
    def __init__(self):
        super(Net, 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 Function
loss = CrossEntropyLoss()


# 创建网络
net = Net()
# 设置优化器   torch.optim.SGD() ---> 随机梯度下降
# params ---> 需要优化的模型参数
# lr ---> learning rate(学习速率),一般来说 lr 不能设置的太大也不能设置的太小
#         lr 设置太大的话模型训练起来就会很不稳定; 设置太小的话模型训练起来会很慢
#         lr 一般情况下推荐一开始的时候采用比较大的学习速率来进行学习,然后学习到后面的时候再采用比较小的学习速率来进行学习.
optim = torch.optim.SGD(params=net.parameters(), lr=0.01) # 定义一个优化器

for epoch in range(20):
    # 在每一轮开始之前都将 loss 设置为 0
    running_loss = 0.0
    for data in dataloader:
        imgs, targets = data
        # outputs ---> 将输入的 imgs 通过上面的神经网络 所得到的一个输出
        outputs = net(imgs)
        # result_loss ---> 网络的输出与真实的target之间的差距
        result_loss = loss(outputs, targets)


        # 优化器 step1 将模型中每一个可以调节的参数所对应的梯度调为 0
        optim.zero_grad() # 使用优化器对网络中的每个参数所对应的梯度进行清 0 操作
        # 反向传播 ---> 获得每个可以调节参数所对应的梯度
        result_loss.backward() # 调用损失函数的反向传播,计算出每个参数结点对应的梯度
        # step() ---> 优化器利用每个参数结点的梯度对模型权重(weight)中每个参数进行调优
        optim.step() # 调用优化器的 step

        running_loss += result_loss
    print(running_loss)

在这里插入图片描述

13. 现有网络模型的使用及修改

使用 PyTorch 提供给我们的网络模型

在这里插入图片描述

这里主要以 torchvision(图像相关)的现有网络模型(VGG分类模型)来进行举例

VGG 模型中常用的是 vgg16 vgg19

import torchvision
from torch import nn

# 现有网络模型 vgg16 的使用及修改


# weights=None ---> 只是加载网络模型,没有进行预训练 参数需要自己进行训练
vgg16_false = torchvision.models.vgg16(weights=None)
# weights="DEFAULT" ---> 预训练
vgg16_true = torchvision.models.vgg16(weights="DEFAULT") # weights="DEFAULT" ---> vgg16网络模型中的一些参数已经在数据集上训练好的,即在数据集上能达到较好的效果.

print(vgg16_true)


# 利用现有的网络模型 vgg16 来改动它的模型结构
vgg16_true.classifier.add_module("add_linear", nn.Linear(1000, 10))
# 查看添加后的网络模型结构
print(vgg16_true)


# 修改现有网络模型 vgg16
print(vgg16_false)
vgg16_false.classifier[6] = nn.Linear(4096, 10)
print(vgg16_false) # vgg16_false ---> 此时已经是修改之后的网络模型 vgg16

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14. 网络模型的保存与读取

模型保存的两种方式,程序代码及截图如下

import torch
import torchvision

# 模型保存(有两种方式)

# 新建一个网络模型
vgg16 = torchvision.models.vgg16(weights=None)
# 模型保存(保存方式一)  使用方式一保存,不仅保存了网络模型的结构,而且也保存了网络模型当中的一些参数
# "vgg16_method1.pth" ---> 指定保存的路径,推荐使用 .pth后缀
torch.save(vgg16, "vgg16_method1.pth")


# 模型保存(保存方式二)  官方推荐使用方式二 进行模型保存
# vgg16.state_dict() ---> 将 vgg16的状态保存为字典形式(字典:Python中的一个数据格式)
# "vgg16_method2.pth" ---> 指定路径
torch.save(vgg16.state_dict(), "vgg16_method2.pth") # 将vgg16 网络模型中的参数保存成字典,不会保存网络模型的结构,只保存网络模型当中的一些参数

在这里插入图片描述

模型加载的两种方式,程序代码及截图如下

import torch


# 模型读取(有两种方式,与模型保存相对应)

# 加载模型(方式1) ---> 对应于模型保存的方式一
model = torch.load("vgg16_method1.pth")
print(model)


# 加载模型(方式2) ---> 对应于模型保存的方式二
model = torch.load("vgg16_method2.pth")
print(model)

在这里插入图片描述
在这里插入图片描述

import torch
import torchvision

# 模型读取(有两种方式,与模型保存相对应)

# 加载模型(方式1) ---> 对应于模型保存的方式一
model = torch.load("vgg16_method1.pth")
print(model)


# 加载模型(方式2) ---> 对应于模型保存的方式二
# 恢复为原来的网络模型结构
vgg16 = torchvision.models.vgg16(weights=None) # 首先新建一个网络模型结构
vgg16.load_state_dict(torch.load("vgg16_method2.pth"))
# model = torch.load("vgg16_method2.pth")
# print(model)
print(vgg16)

在这里插入图片描述

15. 完整的模型训练套路

完整的模型训练套路,在这里以 CIFAR10 数据集为例,完成对此数据集的分类问题。

在这里插入图片描述
神经网络模型结构见如下程序(按照CIFAR10 model 结构)
model.py文件

import torch
from torch import nn

# 通常我们会将神经网络放到一个文件中,这样可以随处调用.


# 搭建我们的神经网络
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # Sequential() ---> 在这个序列当中来写我们的网络结构
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(32, 32, 5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(32, 64, 5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2),
            nn.Flatten(),
            nn.Linear(64 * 4 * 4, 64),
            nn.Linear(64, 10)
        )

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

# python 中的主方法
# 一般是在 main 主方法中测试 网络的正确性
# 验证网络的正确性 ---> 一般考察的是 给定网络一个特定的输入尺寸,然后查看输出的尺寸是否是我们想要的.
if __name__ == '__main__':
    net = Net()
    # 创建一个特定的输入尺寸
    # (64, 3, 32, 32) ---> (N, C, H, W) ---> (batch_size, channels, height, width)
    # batch_size=64 代表是 64张图片
    input = torch.ones((64, 3, 32, 32))
    output = net(input)
    # output.shape ---> 形状
    print(output.shape)

训练及测试所搭建的神经网络,见如下程序及截图
train.py文件

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

from model import *   # 导入上述我们自定义的神经网络

# 准备数据集
# train=True ---> 训练数据集
train_data = torchvision.datasets.CIFAR10(root='./data', train=True, transform=torchvision.transforms.ToTensor(), download=True)
# train=False ---> 测试数据集
test_data = torchvision.datasets.CIFAR10(root='./data', train=False, transform=torchvision.transforms.ToTensor(), download=True)

# 查看一下 训练数据集 及 测试数据集 各有多少张图片
# len() ---> length 长度 ---> 获得数据集的长度
train_data_size = len(train_data) # len(train_data) ---> 获得训练数据集的长度即多少张图片
test_data_size = len(test_data)
# Python中常用的字符串格式化   将format括号内的变量值,替换到{}
print("训练数据集的长度为:{}".format(train_data_size)) # 如果此时train_data_size=10,那么此时会输出字符串 ---> 训练数据集的长度为:10
print("测试数据集的长度为:{}".format(test_data_size))


# 利用 DataLoader 来加载数据集
# 使用 DataLoader 去加载训练集和测试集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)


# 创建我们的网络模型
net = Net()


# 创建损失函数
# 损失函数也是放在 nn package(包)里的
loss_function = nn.CrossEntropyLoss()  # CrossEntropyLoss() ---> 交叉熵损失函数


# 定义优化器
# 学习速率 一般会单独提出来设置为变量去定义 ---> 这样做是为了方便于我们进行修改.
# learning_rate = 0.01
learning_rate = 1e-2 # 1e-2 ---> 1 × 10^(-2) = 1 / 100 = 0.01
# SGD() ---> 随机梯度下降
optimizer = torch.optim.SGD(net.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))
    # 训练步骤开始   训练网络模型
    net.train() # 仅对网络中存在的某些层有作用,让网络进入训练状态
    for data in train_dataloader:
        imgs, targets = data  # 取得训练的数据
        outputs = net(imgs)  # 将训练数据送到网络中
        # outputs ---> 预测的输出   targets ---> 真实的target
        loss = loss_function(outputs, targets)  # 使用损失函数查看 outputs 与真实的 targets 之间的差距(误差)是多少

        # 优化器优化模型
        # 优化 ---> 首先要进行 梯度清零(利用优化器)
        optimizer.zero_grad()
        # 反向传播 ---> 得到每个参数结点的梯度
        loss.backward()
        # 根据参数结点的梯度 进行优化操作(参数优化)
        optimizer.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)

    # 测试步骤开始
    net.eval() # 仅对网络中存在的某些层有作用
    total_test_loss = 0
    total_accuracy = 0
    # 验证模型是否训练好 ---> 查看模型在测试数据集上的损失/正确率(使模型在测试数据集上跑一遍)
    # 在验证模型是否训练好的过程中,就不需要对模型进行调优操作,只需使用现有的模型进行测试即可.
    with torch.no_grad(): # 让网络模型中没有梯度 保证不会进行调优只是简单测试而已
        for data in test_dataloader: # 从测试数据集中取出数据
            imgs, targets = data
            outputs = net(imgs)
            loss = loss_function(outputs, targets) # loss ---> 是 tensor数据类型
            total_test_loss += loss.item()
            # argmax(1) ---> 1 代表方向为横向; 0 代表方向为纵向
            # argmax() 由PyTorch所提供,用于求出 (tensor数据类型中)横向/纵向数据中最大值所在的位置(下标索引)
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy += accuracy # total_accuracy ---> 预测对的个数
    print("整体测试集上的 Loss:{}".format(total_test_loss)) # 输出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 += 1


    # 使用之前的方式一 来保存每一轮训练的模型
    torch.save(net, "net_{}.pth".format(i+1))
    print("模型已保存")

writer.close()

# 补充额外知识:验证集不是测试集,验证集与测试集是不一样的,
#            验证集是在训练中用的,测试集是在模型完全训练好后使用的.

# 经过 softmax 函数归一化之后才是真正的模型预测概率,相加等于1,每个数都属于区间[0,1].
# dropout 可以防止过拟合,过拟合(overfitting)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

补充:item() 在tensor数据类型中的使用

import torch
a = torch.tensor(5)
print(a)
print(a.item())

在这里插入图片描述

补充:PyTorch提供的 argmax() 函数的使用

import torch

outputs = torch.tensor([
    [0.1, 0.2],
    [0.3, 0.4]
])

# argmax(1) ---> 1代表横向来取最大值的位置
#                0代表纵向来取最大值的位置
print(outputs.argmax(1))
preds = outputs.argmax(1)

targets = torch.tensor([0, 1])
print(preds == targets)
print((preds == targets).sum()) # sum() ---> 在此时为 0 + 1 = 1

在这里插入图片描述

注:
在这里插入图片描述

16. 利用 GPU 训练

GPU 训练有两种方式,实现代码在 GPU 上进行训练。

方式一 见如下程序代码

需将 网络模型、数据(输入,标注(targets))、损失函数 放置到GPU上即可。
调用 .cuda() 函数,再将其原来的值进行覆盖

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

# 利用 GPU 训练 方式一

# 准备数据集
# train=True ---> 训练数据集
train_data = torchvision.datasets.CIFAR10(root='./data', train=True, transform=torchvision.transforms.ToTensor(), download=True)
# train=False ---> 测试数据集
test_data = torchvision.datasets.CIFAR10(root='./data', train=False, transform=torchvision.transforms.ToTensor(), download=True)

# 查看一下 训练数据集 及 测试数据集 各有多少张图片
# len() ---> length 长度 ---> 获得数据集的长度
train_data_size = len(train_data) # len(train_data) ---> 获得训练数据集的长度即多少张图片
test_data_size = len(test_data)
# Python中常用的字符串格式化   将format括号内的变量值,替换到{}
print("训练数据集的长度为:{}".format(train_data_size)) # 如果此时train_data_size=10,那么此时会输出字符串 ---> 训练数据集的长度为:10
print("测试数据集的长度为:{}".format(test_data_size))


# 利用 DataLoader 来加载数据集
# 使用 DataLoader 去加载训练集和测试集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # Sequential() ---> 在这个序列当中来写我们的网络结构
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(32, 32, 5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(32, 64, 5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2),
            nn.Flatten(),
            nn.Linear(64 * 4 * 4, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x
# 创建我们的网络模型
net = Net()
# 先判断是否有 cuda 可以使用
if torch.cuda.is_available():
    net.cuda() # 网络模型转移到 cuda 上面,并不需要将其原来的值进行覆盖,直接调用 .cuda()即可


# 创建损失函数
# 损失函数也是放在 nn package(包)里的
loss_function = nn.CrossEntropyLoss()  # CrossEntropyLoss() ---> 交叉熵损失函数
if torch.cuda.is_available():
    loss_function.cuda() # 损失函数转移到 cuda 上面,并不需要将其原来的值进行覆盖,直接调用 .cuda()即可

# 定义优化器
# 学习速率 一般会单独提出来设置为变量去定义 ---> 这样做是为了方便于我们进行修改.
# learning_rate = 0.01
learning_rate = 1e-2 # 1e-2 ---> 1 × 10^(-2) = 1 / 100 = 0.01
# SGD() ---> 随机梯度下降
optimizer = torch.optim.SGD(net.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))
    # 训练步骤开始   训练网络模型
    net.train() # 仅对网络中存在的某些层有作用,让网络进入训练状态
    for data in train_dataloader:
        imgs, targets = data  # 取得训练的数据
        if torch.cuda.is_available():
            imgs = imgs.cuda() # 输入转移到 cuda 上面,然后再将原来的值进行覆盖
            targets = targets.cuda() # 标注(targets)转移到 cuda 上面,然后再将原来的值进行覆盖
        outputs = net(imgs)  # 将训练数据送到网络中
        # outputs ---> 预测的输出   targets ---> 真实的target
        loss = loss_function(outputs, targets)  # 使用损失函数查看 outputs 与真实的 targets 之间的差距(误差)是多少

        # 优化器优化模型
        # 优化 ---> 首先要进行 梯度清零(利用优化器)
        optimizer.zero_grad()
        # 反向传播 ---> 得到每个参数结点的梯度
        loss.backward()
        # 根据参数结点的梯度 进行优化操作(参数优化)
        optimizer.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)

    # 测试步骤开始
    net.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() # 输入转移到 cuda 上面,然后再将原来的值进行覆盖
                targets = targets.cuda() # 标注(targets)转移到 cuda 上面,然后再将原来的值进行覆盖
            outputs = net(imgs)
            loss = loss_function(outputs, targets) # loss ---> 是 tensor数据类型
            total_test_loss += loss.item()
            # argmax(1) ---> 1 代表方向为横向; 0 代表方向为纵向
            # argmax() 由PyTorch所提供,用于求出 (tensor数据类型中)横向/纵向数据中最大值所在的位置(下标索引)
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy += accuracy # total_accuracy ---> 预测对的个数
    print("整体测试集上的 Loss:{}".format(total_test_loss)) # 输出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 += 1


    # 使用之前的方式一 来保存每一轮训练的模型
    torch.save(net, "net_{}.pth".format(i+1))
    print("模型已保存")

writer.close()

# 补充额外知识:验证集不是测试集,验证集与测试集是不一样的,
#            验证集是在训练中用的,测试集是在模型完全训练好后使用的.

# 经过 softmax 函数归一化之后才是真正的模型预测概率,相加等于1,每个数都属于区间[0,1].
# dropout 可以防止过拟合(overfitting)

在这里插入图片描述

方式二(常用的一种方式) 见如下程序代码

需将 网络模型、数据(输入,标注(targets))、损失函数 放置到GPU上即可。
调用 .to() 函数,即 .to(device) —> 到设备上去。
(1)device = torch.device(“cpu”) —> device是一个CPU
(2)device = torch.device(“cuda”)/torch.device(“cuda:0”) —> device是一个GPU

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

# 利用 GPU 训练 方式二 --- 常用的一种方式

# 定义训练的设备
# torch.device("cpu") ---> 在CPU上进行训练
# torch.device("cuda") ---> 在GPU上进行训练
device = torch.device("cuda")

# 准备数据集
# train=True ---> 训练数据集
train_data = torchvision.datasets.CIFAR10(root='./data', train=True, transform=torchvision.transforms.ToTensor(), download=True)
# train=False ---> 测试数据集
test_data = torchvision.datasets.CIFAR10(root='./data', train=False, transform=torchvision.transforms.ToTensor(), download=True)

# 查看一下 训练数据集 及 测试数据集 各有多少张图片
# len() ---> length 长度 ---> 获得数据集的长度
train_data_size = len(train_data) # len(train_data) ---> 获得训练数据集的长度即多少张图片
test_data_size = len(test_data)
# Python中常用的字符串格式化   将format括号内的变量值,替换到{}
print("训练数据集的长度为:{}".format(train_data_size)) # 如果此时train_data_size=10,那么此时会输出字符串 ---> 训练数据集的长度为:10
print("测试数据集的长度为:{}".format(test_data_size))


# 利用 DataLoader 来加载数据集
# 使用 DataLoader 去加载训练集和测试集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # Sequential() ---> 在这个序列当中来写我们的网络结构
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(32, 32, 5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(32, 64, 5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2),
            nn.Flatten(),
            nn.Linear(64 * 4 * 4, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x
# 创建我们的网络模型
net = Net()
net.to(device) # 将网络转移到我们所定义的训练设备上面去,并不需要将其原来的值进行覆盖,直接调用 .to()即可


# 创建损失函数
# 损失函数也是放在 nn package(包)里的
loss_function = nn.CrossEntropyLoss()  # CrossEntropyLoss() ---> 交叉熵损失函数
loss_function.to(device) # 将损失函数转移到我们所定义的训练设备上面去,并不需要将其原来的值进行覆盖,直接调用 .to()即可

# 定义优化器
# 学习速率 一般会单独提出来设置为变量去定义 ---> 这样做是为了方便于我们进行修改.
# learning_rate = 0.01
learning_rate = 1e-2 # 1e-2 ---> 1 × 10^(-2) = 1 / 100 = 0.01
# SGD() ---> 随机梯度下降
optimizer = torch.optim.SGD(net.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))
    # 训练步骤开始   训练网络模型
    net.train() # 仅对网络中存在的某些层有作用,让网络进入训练状态
    for data in train_dataloader:
        imgs, targets = data  # 取得训练的数据
        imgs = imgs.to(device) # 将输入转移到我们所定义的训练设备上面去,并将其原来的值进行覆盖
        targets = targets.to(device) # 将标注(targets)转移到我们所定义的训练设备上面去,并将其原来的值进行覆盖
        outputs = net(imgs)  # 将训练数据送到网络中
        # outputs ---> 预测的输出   targets ---> 真实的target
        loss = loss_function(outputs, targets)  # 使用损失函数查看 outputs 与真实的 targets 之间的差距(误差)是多少

        # 优化器优化模型
        # 优化 ---> 首先要进行 梯度清零(利用优化器)
        optimizer.zero_grad()
        # 反向传播 ---> 得到每个参数结点的梯度
        loss.backward()
        # 根据参数结点的梯度 进行优化操作(参数优化)
        optimizer.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)

    # 测试步骤开始
    net.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) # 将标注(targets)转移到我们所定义的训练设备上面去,并将其原来的值进行覆盖
            outputs = net(imgs)
            loss = loss_function(outputs, targets) # loss ---> 是 tensor数据类型
            total_test_loss += loss.item()
            # argmax(1) ---> 1 代表方向为横向; 0 代表方向为纵向
            # argmax() 由PyTorch所提供,用于求出 (tensor数据类型中)横向/纵向数据中最大值所在的位置(下标索引)
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy += accuracy # total_accuracy ---> 预测对的个数
    print("整体测试集上的 Loss:{}".format(total_test_loss)) # 输出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 += 1


    # 使用之前的方式一 来保存每一轮训练的模型
    torch.save(net, "net_{}.pth".format(i+1))
    print("模型已保存")

writer.close()

# 补充额外知识:验证集不是测试集,验证集与测试集是不一样的,
#            验证集是在训练中用的,测试集是在模型完全训练好后使用的.

# 经过 softmax 函数归一化之后才是真正的模型预测概率,相加等于1,每个数都属于区间[0,1].
# dropout 可以防止过拟合(overfitting)

在这里插入图片描述

17. 完整的模型验证套路

完整的模型验证套路就是利用已经训练好的模型,然后给其模型提供输入。

验证 —> 将我们训练好的模型应用到实际环境过程中。

import torch
import torchvision
from PIL import Image
from torch import nn

image_path = "imgs/dog3.png"
# 此时 image 已经是 PIL类型
image = Image.open(image_path)
print(image)
image = image.convert("RGB")

# Compose ---> 把几个transform(几个变换)  联立在一起
transform = torchvision.transforms.Compose([
    torchvision.transforms.Resize((32, 32)),
    torchvision.transforms.ToTensor()
])

# 将上述的 transform 给image 进行一个应用
image = transform(image)
# image.shape ---> 图像的大小
print(image.shape)


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # Sequential() ---> 在这个序列当中来写我们的网络结构
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(32, 32, 5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(32, 64, 5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=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上训练出来的模型 映射到CPU上来进行应用
model = torch.load("net_10.pth", map_location=torch.device("cpu"))
print(model)

image = torch.reshape(image, (1, 3, 32, 32))

# 将数据 转为GPU类型数据
# image = image.cuda()


# 将模型转换为 测试类型
model.eval()
with torch.no_grad():
    # 将上述的 image 输入到模型当中
    output = model(image)
print(output)
print(output.argmax(1))


# 补充额外知识: 利用GPU训练所得到的模型,需要使用GPU类型的数据(即调用 .cuda())去验证.

在这里插入图片描述
在线课程学习资源来源 bilibili up主页链接

注:此 blog 仅作为个人学习笔记记录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值