# PyTorch 学习 **Repository Path**: rzens/py-torch-learning ## Basic Information - **Project Name**: PyTorch 学习 - **Description**: PyTorch 学习 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2023-04-28 - **Last Updated**: 2024-04-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Tensorboard 对训练中间层的结果进行展示(其实也是对图像进行变换) ## 设置绘图数据存放位置 ```java writer = SummaryWriter("logs") # 此时的绘图数据将存放在与当前这个 .py 文件同级的 logs 文件夹下面 ``` ## SummaryWriter 的使用 主要使用两个方法: 1. writer.add_image() 2. writer.add_scalar(tag, x, y) ### writer.add_scalar(tag, scalar_value, global_step, ...) tag:图形题目 scalar_value:纵轴数据 global_step:横轴数据 #### 测试 ```python writer = SummaryWriter("logs") for i in range(1, 100): writer.add_scalar('y=x', i, i) # scalar: n.标量。即,数 writer.close() ``` 结果如图所示 ### TB 存在的问题 如果我们下次运行时,不修改 tag 属性,但是修改了 scalar_value 和 global_step,那么这次的数据将直接叠加。 推荐的做法是新建一个 logs 文件夹,当然,直接删除就的 logs 也是可以的。 ### 查看图形 `SummaryWriter` 会将 `logs` 们放在设定的文件夹里面。 正确的查看图形的方式是在设定的文件夹外面输入: ```bash tensorboard --logdir=logs --port=7007 ``` 再打开对应的网址(例如,可能是 `http://localhost:7007/` )进行查看。 ### .addImage(tag, img_tensor, global_step=None, ...) tag:图形题目 img_tensor: (type: torch.Tensor, numpy.ndarray, or string/blobname), Image data(图形数据) global_step:横轴数据 #### 回顾使用 Image 读取图像 ```python image_path = "Dataset/hymenoptera_data/train/ants/0013035.jpg" # 设置图片的相对路径 from PIL import Image img = Image.open(image_path) img.show() ``` #### 在 TB 中查看图片类型的图像 ```python from PIL import Image image_path = 'Dataset/hymenoptera_data/train/ants/0013035.jpg' img = Image.open(image_path) import numpy as np writer = SummaryWriter("logs") img_array = np.array(img) # 将 Image 类型转为 numpy 的张量类型。 ''' 可以打印这个 numpy.array, 发现图像的内部,其实是 shape 为 (H,W,C) 的张量 值在 [0,255] 之间,表示各个通道 (Chanel) 的颜色值 ''' print('numpy 的张量类型:\n' + str(img_array)) print('numpy 的张量形状:\n' + str(img_array.shape)) ''' numpy.array(img) 出的图像形状 (H, W, C) 比较特殊, 需要设置 dataformats 参数为 HWC。 默认为 (3, H, W) ''' writer.add_image("array", img_array, 1, dataformats='HWC') writer.close() # 务必关闭,不然 TB 无法显示出数据 ``` ```shell tensorboard --logdir=logs --port=7007 ``` TB 将展示出图片! #### 在 TB 中查看不同 step 的图片 - 保持 tag 不变,改变 step 属性 ```py def add_image_test(image_path, step): from PIL import Image img = Image.open(image_path) import numpy as np writer = SummaryWriter("logs") img_array = np.array(img) # 将 Image 类型转为 numpy 的张量类型。 ''' 可以打印这个 numpy.array, 发现图像的内部,其实是 shape 为 (H,W,C) 的张量 值在 [0,255] 之间,表示各个通道 (Chanel) 的颜色值 ''' print('numpy 的张量类型:\n' + str(img_array)) print('numpy 的张量形状:\n' + str(img_array.shape)) ''' numpy.array(img) 出的图像形状 (H, W, C) 比较特殊, 需要设置 dataformats 参数为 HWC。 默认为 (3, H, W) ''' writer.add_image("array", img_array, step, dataformats='HWC') writer.close() # 务必关闭,不然 TB 无法显示出数据 if __name__ == '__main__': add_image_test('Dataset/hymenoptera_data/train/bees/17209602_fe5a5a746f.jpg', 1) add_image_test('Dataset/hymenoptera_data/train/ants/5650366_e22b7e1065.jpg', 2) ``` #### 在 TB 中查看多个 tag 的图片 - 改变 tag ```py def add_image_test(tag, image_path, step): from PIL import Image img = Image.open(image_path) import numpy as np writer = SummaryWriter("logs") img_array = np.array(img) # 将 Image 类型转为 numpy 的张量类型。 ''' 可以打印这个 numpy.array, 发现图像的内部,其实是 shape 为 (H,W,C) 的张量 值在 [0,255] 之间,表示各个通道 (Chanel) 的颜色值 ''' print('numpy 的张量类型:\n' + str(img_array)) print('numpy 的张量形状:\n' + str(img_array.shape)) ''' numpy.array(img) 出的图像形状 (H, W, C) 比较特殊, 需要设置 dataformats 参数为 HWC。 默认为 (3, H, W) ''' writer.add_image(tag, img_array, step, dataformats='HWC') writer.close() # 务必关闭,不然 TB 无法显示出数据 if __name__ == '__main__': add_image_test('array2', 'Dataset/hymenoptera_data/train/bees/17209602_fe5a5a746f.jpg', 1) add_image_test('array2', 'Dataset/hymenoptera_data/train/ants/5650366_e22b7e1065.jpg', 2) ``` # Transforms > Transforms 主要是对图片进行一些转换。 ## 将图片转换成 Tensor ```py from PIL import Image from torchvision import transforms img_path = 'Dataset/hymenoptera_data/train/bees/17209602_fe5a5a746f.jpg' img = Image.open(img_path) # 使用 Tensor 数据类型 # 需要注意的是 transforms.ToTensor 是一个 class,而不是一个函数! tensor_tans = transforms.ToTensor() tensor_img = tensor_tans(img) # 这个写法其实是调用了 ToTensor 中提供的 __call__(pic) 方法 print(tensor_img) ``` ## 配合 TB ```python from PIL import Image from torch.utils.tensorboard import SummaryWriter from torchvision import transforms img_path = 'Dataset/hymenoptera_data/train/bees/17209602_fe5a5a746f.jpg' img = Image.open(img_path) writer = SummaryWriter("logs_tensor") # 设定本次绘图数据所在的文件夹 # 使用 Tensor 数据类型 # 需要注意的是 transforms.ToTensor 是一个 class,而不是一个函数! tensor_tans = transforms.ToTensor() tensor_img = tensor_tans(img) # 这个写法其实是调用了 ToTensor 中提供的 __call__(pic) 方法 print(tensor_img) writer.add_image('tensor_img', tensor_img, 1) # 使用 torch.tensor 时不需要再指定 dataformats writer.close() ``` ```shell tensorboard --logdir=logs_tensor --port=7007 ``` ## Normalize 正则化 ```python from PIL import Image from torch.utils.tensorboard import SummaryWriter from torchvision import transforms img_path = 'Dataset/hymenoptera_data/train/bees/17209602_fe5a5a746f.jpg' img = Image.open(img_path) writer = SummaryWriter("TransformsUse") tensor_tans = transforms.ToTensor() tensor_img = tensor_tans(img) # Normalize # output[channel] = (input[channel] - mean[channel]) / std[channel] print(" Norm 之前:" + str(tensor_img[0][0][0])) trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) img_norm = trans_norm(tensor_img) print(" Norm 之后:" + str(img_norm[0][0][0])) writer.add_image("Normalize", img_norm) writer.close() ``` 原图和正则化后的图片对比如下 愿图 正则化后图片 ## Reszie 放缩 ```python from PIL import Image from torch.utils.tensorboard import SummaryWriter from torchvision import transforms img_path = 'Dataset/hymenoptera_data/train/bees/17209602_fe5a5a746f.jpg' img = Image.open(img_path) writer = SummaryWriter("TransformsUse") tensor_tans = transforms.ToTensor() img_tensor = tensor_tans(img) # ##### # Resize print(img.size) writer.add_image("Resize", img_tensor, 0) trans_resize = transforms.Resize((512, 512)) # 这是实例化 img_resize = trans_resize(img) # 这是在调用 __call__(image) # 上面实现了 img(PIL) -> Resize -> img_resize(PIL) print(img_resize.size) img_resize_tensor = tensor_tans(img_resize) # 将图片转成 Tensor # 上面实现了 img(PIL) -> Resize -> img_resize(PIL) -> ToTensor -> img_resize_tensor(Tensor) writer.add_image("Resize", img_resize_tensor, 1) # ##### writer.close() ``` 原图和 Resize 后的图片(就像左右被压了一下一样)对比如下 愿图 ## Compose 连续变换 Compose 是为了能将几个[图形]变换组合在一起实施。 ```python from PIL import Image from torch.utils.tensorboard import SummaryWriter from torchvision import transforms img_path = 'Dataset/hymenoptera_data/train/bees/17209602_fe5a5a746f.jpg' img = Image.open(img_path) writer = SummaryWriter("TransformsUse") tensor_tans = transforms.ToTensor() img_tensor = tensor_tans(img) # Compose # 如果大小是一个int,图像的较小边缘将被匹配到这个数字。 trans_resize2 = transforms.Resize(512) # 上面这一句实现了 PIL->PIL。即,我们仅仅是Resize图形 # 将几个[图形]变换组合在一起实施。 # 本次 Compose 的操作是:1.Resize(512); 2.ToTensor() trans_compose = transforms.Compose([trans_resize2, tensor_tans]) # 上面这一句实现了 PIL->Tensor。Compose([,,,]) 需要特别注意序列中操作的前后的输入输出的匹配 img_resize_compose = trans_compose(img) writer.add_image("Resize", img_resize_compose, 2) writer.close() ``` ## RandomCrop 随机裁剪 在一个随机的位置裁剪给定的图像。 ```python from PIL import Image from torch.utils.tensorboard import SummaryWriter from torchvision import transforms img_path = 'Dataset/hymenoptera_data/train/bees/17209602_fe5a5a746f.jpg' img = Image.open(img_path) writer = SummaryWriter("TransformsUse") trans_toTensor = transforms.ToTensor() img_tensor = trans_toTensor(img) # RandomCrop # 在一个随机的位置裁剪给定的图像。 # 参数:size(序列或 int): # crop 出的期望输出尺寸。 # 如果大小是一个 int [size] 而不是序列 (h, w),则会产生一个正方形裁剪 (size,size)。 # 如果提供一个长度为 1(即,(size))的序列,它将被解释为 (size, size)。 trans_randomCrop = transforms.RandomCrop(100) # 将随机裁剪出 100×100 尺寸的图片 trans_randomCrop_compose = transforms.Compose([trans_randomCrop, trans_toTensor]) for i in range(10): img_randomCrop = trans_randomCrop_compose(img) writer.add_image("randomCrop", img_randomCrop, i) writer.close() ``` 如果 size 为 (H, W): ```python from PIL import Image from torch.utils.tensorboard import SummaryWriter from torchvision import transforms img_path = 'Dataset/hymenoptera_data/train/bees/17209602_fe5a5a746f.jpg' img = Image.open(img_path) writer = SummaryWriter("TransformsUse") trans_toTensor = transforms.ToTensor() img_tensor = trans_toTensor(img) # RandomCrop # 在一个随机的位置裁剪给定的图像。 # 参数:size(序列或 int): # crop 出的期望输出尺寸。 # 如果大小是一个 int [size] 而不是序列 (h, w),则会产生一个正方形裁剪 (size,size)。 # 如果提供一个长度为 1(即,(size))的序列,它将被解释为 (size, size)。 trans_randomCrop = transforms.RandomCrop((75, 100)) # 将随机裁剪出 100×100 尺寸的图片 trans_randomCrop_compose = transforms.Compose([trans_randomCrop, trans_toTensor]) for i in range(10): img_randomCrop = trans_randomCrop_compose(img) writer.add_image("randomCrop2", img_randomCrop, i) writer.close() ``` ## 不知道函数返回值怎么办? 1. `print()` 2. `print(type())` 3. debug🪰 # torchvision 中的数据集 ## torchvision 支持的常见数据集 torchvision 提供了对很多数据集操作的封装(例如,下载操作)。 torchvision 的官网地址是 [https://pytorch.org/vision/stable/datasets.html](https://pytorch.org/vision/stable/datasets.html)。 ```python # Torchvision 在模块中提供了许多内置数据集,以及用于构建自己的数据集的实用程序类。 # ##### # https://pytorch.org/vision/stable/generated/torchvision.datasets.CIFAR10.html # 可以打开上面这个网址,查看 tv 如何控制 CIFAR10 import torchvision # 可以按 ctrl+p 获得函数接受参数的提示 # 具体的参数含义应该去上面的网站上查阅 # 下载好的数据集不会重新下载的 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]) # 输出是一个 (, 3)。即 (图片, target) print(test_set.classes) # 查看数据集到底有几类(几个 [target]) img, target = test_set[0] print("img 信息:", img, "\ntarget:", target, "\n标签 label:", test_set.classes[target]) # 从上面的输出中可以看出 img 的类型是 PIL.Image.Image,因此我们可以直接 show 图片 img.show() ``` ## 将数据集转换成 tensor 类型处理 假如我们想要将数据集中的每个图片都变成数据集该怎么办呢? 提示:`transforms`、`Compose([])`、`ToTensor()` ```python # 如果还需要进行其他操作,例如剪切图片等,可以对 transform、Compose 和 Crop 搭配 import torchvision from torch.utils.tensorboard import SummaryWriter dataset_compose = torchvision.transforms.Compose([torchvision.transforms.ToTensor()]) # 下载好的数据集不会重新下载的 # 如果要使得 transform 在整个数据集上生效, # 则需要对 torchvision.datasets.[CIFAR10] 的 transform 设置,应用 transform train_set = torchvision.datasets.CIFAR10(root="./Dataset", train=True, transform=dataset_compose, download=True) test_set = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=dataset_compose, download=True) # 执行上面的代码,数据集会被自动下载和解压 writer = SummaryWriter("DatasetInTorchvisionTransform") for i in range(10): # 还记得 root="./Dataset", train=True, transform=dataset_compose,download=True 吗? # transform=dataset_compose 表示对数据进行了 Compose() 操作,即,ToTensor()。 img_tensor, target = test_set[i] writer.add_image("transform", img_tensor, i) writer.close() ``` 重点⚠:tensor 如何转换的? 1. 规定 `transforms` 操作:`dataset_compose = torchvision.transforms.Compose([torchvision.transforms.ToTensor])` 2. 在 torchvision 控制数据集时实施:`test_set[i] = torchvision.datasets.CIFAR10(root="./Dataset", train=True, transform=dataset_compose,download=True)` 3. 使用:`img_tensor, target=test_set[i]`。此时 `test_set[i]` 的返回值已经是图像的 tensor 和 类别对应值。 # DataLoader 官方地址:[https://pytorch.org/docs/stable/data.html?highlight=data#torch.utils.data.DataLoader](https://pytorch.org/docs/stable/data.html?highlight=data#torch.utils.data.DataLoader) ## 什么是 DataLoader? dataset 只是告诉模型数据的位置、内容项和内容项的 index(`__getitem__()` 控制), dataloader 所做的事情,就是去 dataset 中取得数据,就像是一双手,取什么、取多少、怎么取就是 loader 的职能,可以通过参数进行设置。 ```python # batch_size (int, optional) – how many samples per batch to load (default: 1). 即,一次牌局中,一次抓牌抓 batch_size 张牌。 # shuffle (bool, optional) – set to True to have the data reshuffled at every epoch (default: False). 即,第二次牌局洗不洗牌 # 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) # drop_last (bool, optional) – 例如,共 100 张牌,batch_size = 33,必定存在最后一次的牌数量为 1,不够 33。如果 True,则放弃不足 33 张的这最后一次抓牌操作。 # sampler (Sampler or Iterable, optional) - 定义了从数据集中抽取样本的策略。可以是任何有 __len__ 实现的 Iterable。如果指定,必须不指定shuffle。 import torchvision from torch.utils.data import DataLoader test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True) # 可以发现 torchvision.datasets.CIFAR10() 中重写了 __getitem__() 方法! test_loader = DataLoader(dataset=test_data, batch_size=4, shuffle=True, num_workers=0, drop_last=False) # 查看数据的内容 img_tensor, target = test_data[0] print("img_tensor.shape:", img_tensor.shape) print("target:", target) # 查看 dataloader 里面到底是什么? for data in test_loader: imgs, targets = data print("imgs.shape:", imgs.shape) print("targets:", targets) # 在这里可以看到 dataloader 里面,一个单位是一个 batch, # 一个 batch 里面是 batch_size 个 img_tensor 组成的数组和和 img_tensor 一一对应的 target。 ``` ## 配合 TB ```python import torchvision from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True) # 可以发现 torchvision.datasets.CIFAR10() 中重写了 __getitem__() 方法! test_loader = DataLoader(dataset=test_data, batch_size=64, shuffle=True, num_workers=0, drop_last=False) writer = SummaryWriter("DataLoader") # 查看数据的内容 img_tensor, target = test_data[0] print("img_tensor.shape:", img_tensor.shape) print("target:", target) step = 0 for data in test_loader: imgs, targets = data # 此时的 imgs 是很多个图片的集合(此处是 64 张) # 此时使用 .add_images(tag, img, step) writer.add_images("DataLoader", imgs, step) step = step + 1 writer.close() ``` image-20230421145739026 ## shuffle 参数 直接看效果 ### shuffle=True ```python import torchvision from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) test_loader = DataLoader(dataset=test_data, batch_size=64, shuffle=True, num_workers=0, drop_last=False) writer = SummaryWriter("DataLoader") for epoch in range(2): # 进行了两轮 loader 操作 step = 0 for data in test_loader: imgs, targets = data writer.add_images("Epoch: {}".format(epoch), imgs, step) step = step + 1 writer.close() ``` 当设置 `shuffle=True` 时,loader 加载数据的顺序明显不同。 image-20230421150451316 ### shuffle=False ```python import torchvision from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) test_loader = DataLoader(dataset=test_data, batch_size=64, shuffle=False, num_workers=0, drop_last=False) writer = SummaryWriter("DataLoader") for epoch in range(2): # 进行了两轮 loader 操作 step = 0 for data in test_loader: imgs, targets = data writer.add_images("Epoch: {}".format(epoch), imgs, step) step = step + 1 writer.close() ``` 当设置 `shuffle=False` 时,loader 加载数据的顺序相同。 image-20230421151910055 # torch.nn 官方网站:[https://pytorch.org/docs/stable/generated/torch.nn.Module.html](https://pytorch.org/docs/stable/generated/torch.nn.Module.html) ## 模型的基本结构 ```python # !官网代码 import torch.nn as nn import torch.nn.functional as F """ nn.Module 里面是最基本的模型的结构和内容,就像我们买了个赛车玩具, 但是我们可能会有自己的想法:例如,换轮胎,换马达。 所以,我们可以编写自己的模型类 MyModel,他继承 nn.Module,获得模型运行最基本的内容, 对于特殊的东西,我们在 __init__() 和 forward() 中进行更改。 """ class MyModel(nn.Module): def __init__(self): super().__init__() # 固定操作 self.conv1 = nn.Conv2d(1, 20, 5) # 这是最基本的卷积操作 self.conv2 = nn.Conv2d(20, 20, 5) # 神经网络程序的本质: x->forward()->o[utput] def forward(self, x): x = F.relu(self.conv1(x)) return F.relu(self.conv2(x)) ``` ## 模型如何输入输出 ```python import torch import torch.nn as nn class Model(nn.Module): def __init__(self): super().__init__() def forward(self, input): output = input + 1 return output model = Model() x = torch.tensor(1.0) o = model(x) print(o) ``` ## 卷积 官方网站和数学公式:[https://pytorch.org/docs/stable/generated/torch.nn.functional.conv2d.html](https://pytorch.org/docs/stable/generated/torch.nn.functional.conv2d.html) `torch.nn` 是对 `torch.nn.functional` 的进一步封装,便于各个操作的使用。 本文的卷积一般指 conv2d 操作。 image-20230421160500635 更加专业的术语: weight: 卷积核 ### 什么是卷积 卷积,即,两个矩阵的对应位置的数相乘和再相加。 下图的意思是,feature map 是卷积结果;input 是矩阵 A,褐色矩阵是矩阵 B;feature map = 卷积 (A, B) ![图片来源:https://zhuanlan.zhihu.com/p/76606892](https://raw.githubusercontent.com/rzens/photosResp/main/v2-317e68dfbeb20b9d3cf48f74cbf590c2_b.webp) ### (可选)验证卷积计算 真正在模型中执行卷积操作是直接用的 `nn.conv2d()` 而不是本处的 `nn.functional.conv2d`。 ```python import random import torch def random_two_dimensional_array(scope, height, width): i = 0 input = [] for i in range(0, height): j = 0 jnput = [] for j in range(0, width): jnput.append(random.randint(scope[0], scope[1])) j += 1 input.append(jnput) i += 1 return input # 定义输入 input_arrays = random_two_dimensional_array([1, 10], 5, 5) input_tensor = torch.tensor(input_arrays) print("input_tensor: \n", input_tensor) print("input_shape: ", input_tensor.shape) # torch.Size([5, 5]) # 定义卷积核 weight_arrays = random_two_dimensional_array([1, 3], 2, 2) weight_tensor = torch.tensor(weight_arrays) print("weight_tensor: \n", weight_tensor) print("weight_shape: ", weight_tensor.shape) # torch.Size([2, 2]) # 但是,此时的 shape 并不是 conv2d 所能接受的。因此需要进行对现在 input 和 weight 的 shape 转换 # conv2d 的输入要求 (batchSize,c,h,w) input_tensor = torch.reshape(input_tensor, (1, 1, 5, 5)) # 本句的意思为,转换成 batch_size 为 1,chanel 为 1,高为 5,宽为 5 的 shape weight_tensor = torch.reshape(weight_tensor, (1, 1, 2, 2)) import torch.nn.functional as F output = F.conv2d(input_tensor, weight_tensor, stride=1) print(output) output = F.conv2d(input_tensor, weight_tensor, stride=2) # 每次移动的步长会影响输出结果的 shape print(output) # padding # 若为 int,则上下左右都分别填 1 行/列 0; # 若为 (H, W),则上下填 H 行 0,左右分别填 W 行 0。 output3 = F.conv2d(input_tensor, weight_tensor, stride=1, padding=1) print(output3) ``` ### 卷积层 官网文档:[https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html](https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html) #### 参数 关注前 5 个参数即可 ![image-20230421204540266](https://raw.githubusercontent.com/rzens/photosResp/main/image-20230421204540266.png) 如何理解 padding 和 stride 参数(动画)?[https://github.com/vdumoulin/conv_arithmetic/blob/master/README.md](https://github.com/vdumoulin/conv_arithmetic/blob/master/README.md) 值得注意的是,动画中提到的 No,其实是不设置的意思,此时的参数们仍然具有默认值。 #### 如何理解 out_channels? 假设输入 channel(in_channels) 为 1,如果设置 out_channels 为 1,则卷积核 channel 也为 1; 但是如果 out_channels 被设置成 2,则卷积核的 channel 也会增加到 2,两个卷积核将被分别优化。 #### 卷积层实战 ```python import torchvision from torch import nn from torch.nn import Conv2d from torch.utils.data import DataLoader test_set = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) dataloader = DataLoader(test_set, batch_size=64) class Model(nn.Module): def __init__(self): super(Model, self).__init__() self.conv1 = Conv2d(in_channels=3, out_channels=6, kernel_size=3) def forward(self, x): x = self.conv1(x) return x model = Model() print(model) # 可以直接打印出结构 for data in dataloader: imgs, target = data output = model(imgs) print("imgs_shape (batch_size, channel, H, W)", imgs.shape) # ↓记住,输入输出都是 tensor 类型 # H, W 变小是因为卷积中的 kernel_size 设定 print("output.shape (batch_size, channel, H, W)", output.shape) ``` #### 卷积层内部是什么? 可以配合 TB 看看卷积层内部 ```python 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 test_set = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) dataloader = DataLoader(test_set, batch_size=64) class Model(nn.Module): def __init__(self): super(Model, self).__init__() self.conv1 = Conv2d(in_channels=3, out_channels=6, kernel_size=3) def forward(self, x): x = self.conv1(x) return x model = Model() print(model) # 可以直接打印出结构 writer = SummaryWriter("conv2d") step = 0 for data in dataloader: imgs, target = data output = model(imgs) print("imgs_shape (batch_size, channel, H, W)", imgs.shape) # torch.Size([64, 3, 32, 32]) # ↓记住,输入输出都是 tensor 类型 # H, W 变小是因为卷积中的 kernel_size 设定 print("output.shape (batch_size, channel, H, W)", output.shape) # torch.Size([64, 6, 30, 30]) writer.add_images("input", imgs, step) # 直接添加 output 到 TB 是不行的,因为 output 此时有 6 个通道 # 需要使用 reshape(output, ()) 进行变换 # reshape 会将 6 个通道的数据平展开,此时的 batch_size 会增加 # 此时,我们只知道 channel 会变小,但是 batch_size 如何变化不知道。 # 则可以设置 .reshape(-1, ...),表示 reshape 自己判断并设置 batch_size output_reshape = torch.reshape(output, (-1, 3, 30, 30)) writer.add_images("output", output_reshape, step) step += 1 ``` image-20230422194608518 从这个图片可见,愿 batch 中 (1,1) 的图像,在被卷积并 reshape 后,变成了 (1,1)、(1,2) 位置的图像,其余的图像类似。 ## 池化 ### 参数 image-20230422202205731 重点关注: - **kernel_size** – the size of the window to take a max over - **stride** – the stride of the window. Default value is `kernel_size` - **padding** – Implicit negative infinity padding to be added on both sides - **ceil_mode(default False)** – True->ceil mode:向上取整,在池化操作中,表示即使池化核走到超出愿输入的边界,也会计算并得出值;False->floor mode:向下取整,表示一旦核超出了矩阵边界,则放弃本次计算,无输出。 ### 什么是池化? 以 MAXPOOL2D 为例。若不单独声明,所以池化操作,默认指 MAXPOOL2D。 在池化核覆盖的矩阵范围内,选择最大的值作为输出矩阵对应位置的值。 ```python # 官方文档:https://pytorch.org/docs/stable/nn.html#pooling-layers # 什么是池化? MaxPool2d 一般被叫做下采样操作。对应的,MaxUnpool2d 被成为上采样 import random import torch from torch import nn from torch.nn import MaxPool2d def random_two_dimensional_array(scope, height, width): i = 0 outter = [] for i in range(0, height): j = 0 inner = [] for j in range(0, width): inner.append(random.randint(scope[0], scope[1])) j += 1 outter.append(inner) i += 1 return outter input = torch.tensor(random_two_dimensional_array(0, 5), 5, 5) input = torch.reshape(input, (-1, 1, 5, 5)) # -1 表示 reshape 自己确定这个值(这里是 batch_size) print(input.shape) class Model(nn.Module): def __init__(self): super(Model, self).__init__() self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=True) def forward(self, input): output = self.maxpool1(input) return output model = Model() output = model(input) print(output) ``` 此时会出现 `"max_pool2d" not implemented for 'Long'` 运行失败,因为 `MaxPool2d()` 默认支持的是 `torch.float32` 类型。因此,需要我们对 tensor 数据类型进行设定。 ```python # 官方文档:https://pytorch.org/docs/stable/nn.html#pooling-layers # 什么是池化? MaxPool2d 一般被叫做下采样操作。对应的,MaxUnpool2d 被成为上采样 import random import torch from torch import nn from torch.nn import MaxPool2d def random_two_dimensional_array(scope, height, width): i = 0 outter = [] for i in range(0, height): j = 0 inner = [] for j in range(0, width): inner.append(random.randint(scope[0], scope[1])) j += 1 outter.append(inner) i += 1 return outter input = torch.tensor(random_two_dimensional_array((0, 5), 5, 5), dtype=torch.float32) input = torch.reshape(input, (-1, 1, 5, 5)) # -1 表示 reshape 自己确定这个值(这里是 batch_size) print(input.shape) class Model(nn.Module): def __init__(self): super(Model, self).__init__() self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=True) def forward(self, input): output = self.maxpool1(input) return output model = Model() output = model(input) print(output) ``` ### 池化的目的是为了什么? 池化的目的是: 1. 使得图像数据变小(图像尺寸减小) 2. 保留图像的主要特征 ### 池化层的内部是什么? ```python import torchvision from torch import nn from torch.nn import MaxPool2d from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) dataloader = DataLoader(test_data, batch_size=64) class Model(nn.Module): def __init__(self): super(Model, self).__init__() self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=True) def forward(self, input): output = self.maxpool1(input) return output writer = SummaryWriter("MaxPool") model = Model() step = 0 for data in dataloader: imgs, targets = data writer.add_images("input", imgs, step) output = model(imgs) writer.add_images("output", output, step) step += 1 writer.close() ``` image-20230423100241071 # 激活函数-非线性激活 官网地址:[https://pytorch.org/docs/stable/generated/torch.nn.ReLU.html#torch.nn.ReLU](https://pytorch.org/docs/stable/generated/torch.nn.ReLU.html#torch.nn.ReLU) ReLU 函数图像 ![../_images/ReLU.png](https://raw.githubusercontent.com/rzens/photosResp/main/ReLU.png) ## ReLU 的简单使用 ``` python import random import torch from torch import nn from torch.nn import ReLU def random_two_dimensional_array(scope, height, width): i = 0 outter = [] for i in range(0, height): j = 0 inner = [] for j in range(0, width): inner.append(random.randint(scope[0], scope[1])) j += 1 outter.append(inner) i += 1 return outter input = torch.tensor(random_two_dimensional_array((-2, 2), 3, 3), dtype=torch.float32) output = torch.reshape(input, (-1, 1, 3, 3)) print(output.reshape) # 检查 tensor 的输入格式是否满足我们接下来输入 ReLU 的要求 class Model(nn.Module): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 重要参数 - inplace:在原来的位置替不替换?替换则原来的 input 内容将被改变。(默认)建议 False self.relu1 = ReLU() def forward(self, input): output = self.relu1(input) return output model = Model() output = model(input) print(input) print(output) ``` ## ReLU 后是什么? ### ReLU 后是什么? ```python import random 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 test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) dataloader = DataLoader(test_data, batch_size=64) class Model(nn.Module): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 重要参数 - inplace:在原来的位置替不替换?替换则原来的 input 内容将被改变。(默认)建议 False self.relu1 = ReLU() self.sigmoid1 = Sigmoid() def forward(self, input): output = self.relu1(input) return output model = Model() writer = SummaryWriter("ReLU") step = 0 for data in dataloader: imgs, targets = data writer.add_images("input", imgs, step) output = model(imgs) writer.add_images("output", output, step) step += 1 writer.close() ``` image-20230423104309161 ### sigmoid 后是什么? ```python import random 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 test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) dataloader = DataLoader(test_data, batch_size=64) class Model(nn.Module): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 重要参数 - inplace:在原来的位置替不替换?替换则原来的 input 内容将被改变。(默认)建议 False self.relu1 = ReLU() self.sigmoid1 = Sigmoid() def forward(self, input): output = self.sigmoid1(input) return output model = Model() writer = SummaryWriter("ReLU") step = 0 for data in dataloader: imgs, targets = data writer.add_images("input", imgs, step) output = model(imgs) writer.add_images("output", output, step) step += 1 writer.close() ``` image-20230423104544242 ## 为什么需要非线性的激活函数处理输出 因为非线性元素越多,越能拟合出更加完美的曲线元素。即,曲线函数都能被非线性函数的组合表示出来。 # 其他可能有用的层 ## [Normalization Layers(正则化层)](https://pytorch.org/docs/stable/nn.html#id1) > [Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift](https://arxiv.org/abs/1502.03167): > 正则化能够加快深度学习速度。 官方网站:[https://pytorch.org/docs/stable/generated/torch.nn.BatchNorm2d.html#torch.nn.BatchNorm2d](https://pytorch.org/docs/stable/generated/torch.nn.BatchNorm2d.html#torch.nn.BatchNorm2d) ## [Recurrent Layers](https://pytorch.org/docs/stable/nn.html#id1) 专门用于**文字识别**的特定网络结构。 ## [Transformer Layers](https://pytorch.org/docs/stable/nn.html#id1) 特定网络结构。和**注意力**相关。 ## [Dropout Layers](https://pytorch.org/docs/stable/nn.html#id1) 防止过拟合,常常用于自然语言处理。 ## [Linear Layers](https://pytorch.org/docs/stable/nn.html#id1) Water | Free Full-Text | Comparison of Multiple Linear Regression ... 本图中的线性层有 3 层。 image-20230423184608923 ### 线性层简单实践 1. 将图片 tensor 展开; 2. 送入线性层进行运算。 ```python import torch import torchvision from torch import nn from torch.nn import Linear from torch.utils.data import DataLoader test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) dataloader = DataLoader(test_data, batch_size=64) class Model(nn.Module): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.linear1 = Linear(196608, 10) # in_channel 是下面的 reshape 决定的,而不是我们随便写的! def forward(self, x): y = self.linear1(x) return y model = Model() for data in dataloader: imgs, targets = data print("imgs_shape", imgs.shape) # imgs_shape torch.Size([64, 3, 32, 32]) output = torch.reshape(imgs, (1, 1, 1, -1)) # (batch_size, channel, H, W),此处的 reshape 其实是把图片展开了 print("output_shape", output.shape) # output_shape torch.Size([1, 1, 1, 196608]) output = model(output) print("modeled_output_shape", output.shape) # modeled_output_shape torch.Size([1, 1, 1, 10]) ``` ### 使用专门的函数展开 tensor ```python import torch import torchvision from torch import nn from torch.nn import Linear from torch.utils.data import DataLoader test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) dataloader = DataLoader(test_data, batch_size=64) class Model(nn.Module): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.linear1 = Linear(196608, 10) # in_channel 是下面的 reshape 决定的,而不是我们随便写的! def forward(self, x): y = self.linear1(x) return y model = Model() for data in dataloader: imgs, targets = data print("imgs_shape", imgs.shape) # imgs_shape torch.Size([64, 3, 32, 32]) # output = torch.reshape(imgs, (1, 1, 1, -1)) # (batch_size, channel, H, W),此处的 reshape 其实是把图片展开了 # 放弃 .reshape() 转而使用 .flatten(tensor) output = torch.flatten(imgs) # output_shape torch.Size([196608]) print("output_shape", output.shape) # output_shape torch.Size([1, 1, 1, 196608]) output = model(output) print("modeled_output_shape", output.shape) # modeled_output_shape torch.Size([10]) ``` # Sequential sequential 能将上一节一系列层链接起来,将输入按照顺序顺序地、自动地执行。 ```python # @官网代码 # Using Sequential to create a small model. When the `model` is run, input will first be passed to `Conv2d(1,20,5)`. # The output of `Conv2d(1,20,5)` will be used as the input to the first `ReLU`; # the output of the first `ReLU` will become the input for `Conv2d(20,64,5)`. # Finally, the output of `Conv2d(20,64,5)` will be used as input to the second `ReLU` model = nn.Sequential( nn.Conv2d(1, 20, 5), nn.ReLU(), nn.Conv2d(20, 64, 5), nn.ReLU() ) # Using Sequential with OrderedDict. This is functionally the same as the above code model = nn.Sequential(OrderedDict([ ('conv1', nn.Conv2d(1, 20, 5)), ('relu1', nn.ReLU()), ('conv2', nn.Conv2d(20, 64, 5)), ('relu2', nn.ReLU()) ])) ``` ## 简单模型的实践 尝试复现下面的结构 ![On Classification of Distorted Images with Deep Convolutional Neural ...](https://raw.githubusercontent.com/rzens/photosResp/main/convnet_fig_mnist.png) > 注意⚠:在使用卷积时,会出现输入、输出矩阵的形状相同,因此必须计算使用多少 padding。如下图计算所示: > > ![image-20230423203039809](https://raw.githubusercontent.com/rzens/photosResp/main/image-20230423203039809.png) > > 其中,dilation 默认为 1 ```python from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linear class Model(nn.Module): def __init__(self): super(Model, self).__init__() self.conv1 = Conv2d(3, 32, 5, padding=2) # 按照仿制结构的图片,输入、输出矩阵的形状相同,因此必须计算使用多少 padding self.max_pool1 = MaxPool2d(2) # Conv2d(in_channel, out_channel, kernel_size, ...) self.conv2 = Conv2d(32, 32, 5, padding=2) self.max_pool2 = MaxPool2d(2) self.conv3 = Conv2d(32, 64, 5, padding=2) self.max_pool3 = MaxPool2d(2) self.flatten = Flatten() # 线性层 self.linear1 = Linear(1024, 64) self.linear2 = Linear(64, 10) def forward(self, x): x = self.conv1(x) x = self.max_pool1(x) x = self.conv2(x) x = self.max_pool2(x) x = self.conv3(x) x = self.max_pool3(x) x = self.flatten(x) x = self.linear1(x) x = self.linear2(x) return x model = Model() print(model) ``` ```shell # 输出模型结构 Model( (conv1): Conv2d(3, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2)) (max_pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (conv2): Conv2d(32, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2)) (max_pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (conv3): Conv2d(32, 64, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2)) (max_pool3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (flatten): Flatten(start_dim=1, end_dim=-1) (linear1): Linear(in_features=1024, out_features=64, bias=True) (linear2): Linear(in_features=64, out_features=10, bias=True) ) ``` ## 验证模型结构的正确性 本初仅验证`网络结构`!而不是网络输出。 使用 `.one(tensor_shape)` 快速生成参与验证的 tensor。 ```python import torch from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linear class Model(nn.Module): def __init__(self): super(Model, self).__init__() self.conv1 = Conv2d(3, 32, 5, padding=2) # 按照仿制结构的图片,输入、输出矩阵的形状相同,因此必须计算使用多少 padding self.max_pool1 = MaxPool2d(2) # Conv2d(in_channel, out_channel, kernel_size, ...) self.conv2 = Conv2d(32, 32, 5, padding=2) self.max_pool2 = MaxPool2d(2) self.conv3 = Conv2d(32, 64, 5, padding=2) self.max_pool3 = MaxPool2d(2) self.flatten = Flatten() # 线性层 self.linear1 = Linear(1024, 64) self.linear2 = Linear(64, 10) def forward(self, x): x = self.conv1(x) x = self.max_pool1(x) x = self.conv2(x) x = self.max_pool2(x) x = self.conv3(x) x = self.max_pool3(x) x = self.flatten(x) x = self.linear1(x) x = self.linear2(x) return x model = Model() input = torch.ones((64, 3, 32, 32)) output = model(input) print(output.shape) ``` ```shell # 输出 torch.Size([64, 10]) # 64 是 batch_size,即 64 张图片。 ``` 如果网络结构任一地方异常(例如,in_channel、out_channel 或者 in_feature、out_feature 出错),代码将抛出异常。 ## 查看某 1 层的输出 shape 我们可以直接注释 `forward()` 里某层后面的执行代码,此时的输出 shape 就是此层的输出 shape。 ## 使用 Sequential(layers, ...) 简化模型的代码编写 ```python import torch from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential class Model(nn.Module): def __init__(self): super(Model, 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 model = Model() print(model) input = torch.ones((64, 3, 32, 32)) output = model(input) print(output.shape) ``` ```shell # 输出 Model( (model1): 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) ) ) torch.Size([64, 10]) # 这个正确的输出表示模型的内部结构是正确的 ``` ## 配合 TB 可视化模型结构 ```python import torch from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential from torch.utils.tensorboard import SummaryWriter class Model(nn.Module): def __init__(self): super(Model, 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 model = Model() print(model) input = torch.ones((64, 3, 32, 32)) output = model(input) print(output.shape) writer = SummaryWriter("Model") writer.add_graph(model, input) writer.close() ``` ```shell tensorboard --logdir=Model ``` # 损失函数和反向传播 > 假设你参加了一场考试,试卷中有10道题目,每题分值为10分,总分为 100(target) 分。 > >你的目标是获得尽可能高的分数,在考试过程中,你可能会犯错,导致失去一部分分数,得到了 90(output) 分。 > >这种失去分数的量就是损失函数 (Loss),即 Loss = target 和实际 output 之间的差异。 > >在深度学习中,损失函数也是用来衡量模型预测值和真实值之间的差异,用于优化模型参数。 > >损失函数的选择取决于具体的问题和模型,不同的损失函数可以帮助模型更好地解决不同类型的问题。 > >例如,交叉熵损失函数通常用于分类问题,均方误差损失函数通常用于回归问题,对称交叉熵损失函数则可以用于处理类别不平衡问题。 官网地址:[https://pytorch.org/docs/stable/nn.html#loss-functions](https://pytorch.org/docs/stable/nn.html#loss-functions) ## Loss 的作用 1. 计算实际输出和目标之间的差距; 2. 为模型更新提供一定的依据(grad, n.梯度)(反向传播)。 ## Loss 简单使用 Loss 的具体公式可能很难懂,但是理解公式也是不必要的。 我们着重关注 Loss 的输入输出的 shape 即可。 ```python import torch from torch.nn import L1Loss # nn 提供的 Loss 的输入应该是浮点型!其实真实开发不需要关注这个 inputs = torch.tensor([1, 2, 3], dtype=torch.float32) targets = torch.tensor([1, 2, 5]) inputs = torch.reshape(inputs, (1, 1, 1, 3)) targets = torch.reshape(targets, (1, 1, 1, 3)) loss1 = L1Loss() result = loss1(inputs, targets) print(result) ``` ## 反向传播 ```python import torchvision from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential, CrossEntropyLoss from torch.utils.data import DataLoader test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) dataloader = DataLoader(test_data, batch_size=1) class Model(nn.Module): def __init__(self): super(Model, 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 model = Model() loss = CrossEntropyLoss() for data in dataloader: print("\n") imgs, targets = data output = model(imgs) print("output:", output) print("targets:", targets) result_loss = loss(output, targets) print("result_loss", result_loss) # 只有损失函数的输出才能被 backward result_loss.backward() ``` > 注意⚠:只有损失函数 (Loss) 的输出才能被反向传播 (backward())。 ## 优化器 官网地址:[https://pytorch.org/docs/stable/optim.html](https://pytorch.org/docs/stable/optim.html) ### 优化器简单案例 ```python for input, target in dataset: optimizer.zero_grad() # (必须的操作)将梯度清零,防止上一步遗留的梯度对本次的更新造成影响。 output = model(input) # 使用模型 loss = loss_fn(output, target) # 损失函数 loss.backward() # 反向传播,这是为了更新梯度信息,为参数更新指导 optimizer.step() # 依据梯度进行参数更新 ``` ### 优化器简单实践 ```python import torch.optim import torchvision from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential, CrossEntropyLoss from torch.utils.data import DataLoader test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) dataloader = DataLoader(test_data, batch_size=1) class Model(nn.Module): def __init__(self): super(Model, 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 model = Model() loss = CrossEntropyLoss() # 使用现阶段使用优化器主要关注 2 个参数↓ # lr(learn rate) 较大时,因为参数一次变化较大,模型的训练就会极其不稳定; # lr 较小时,模型因为每次变化较小,而过程漫长。 # lr 一般先设置较大值,随着训练的进行,值变小。 optim = torch.optim.SGD(model.parameters(), lr=0.01) for data in dataloader: # 请注意,我们现在相当于模型仅对数据集进行了 1 轮学习 print("\n") imgs, targets = data output = model(imgs) result_loss = loss(output, targets) optim.zero_grad() result_loss.backward() optim.step() print(result_loss) # 我们可以在每个 batch 中来看看 loss 情况 # 每个 batch 中的 loss 其实变化不是很大,而且 loss 数值较大 # 这是因为这个循环中的 batch 总是新的,模型可能还没学习好,所以 loss 很大 # 请注意,我们现在相当于模型仅对数据集进行了 1 轮学习 ``` 上面代码相当于模型仅对数据集进行了 1 轮学习,loss 较大,这很明显是不足的。 现在我们引入 epoch,其实就是使用循环,让模型对数据集进行多次的学习。 ### 简单但完整的模型训练过程 ```python import torch.optim import torchvision from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential, CrossEntropyLoss from torch.utils.data import DataLoader test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) dataloader = DataLoader(test_data, batch_size=64) class Model(nn.Module): def __init__(self): super(Model, 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 model = Model() loss = CrossEntropyLoss() # 使用现阶段使用优化器主要关注 2 个参数↓ # lr(learn rate) 较大时,因为参数一次变化较大,模型的训练就会极其不稳定; # lr 较小时,模型因为每次变化较小,而过程漫长。 optim = torch.optim.SGD(model.parameters(), lr=0.01) for epoch in range(20): # epoch: n.一轮一轮的意思。此处,我们进行 20 轮模型的训练 epoch_loss = 0.0 for data in dataloader: # print("\n") imgs, targets = data output = model(imgs) result_loss = loss(output, targets) optim.zero_grad() result_loss.backward() optim.step() epoch_loss += result_loss # 这里使用了+=,因为 result_loss 代表 batch 的损失函数, # 一个 batch 的 loss 变化不会变化很大, # 一个 epoch 的 loss 是对整个数据集的学习,是多个 batch 共同努力的结果, # 所以 epoch_loss += result_loss,表示一个 epoch 的训练结果受 batch 们的共同影响。 print(epoch_loss) # 现在,我们可以在每个 epoch 中来看看 loss 情况 # 随着对整个数据集不断的学习(即 epoch 数增长),loss 逐渐减小,模型开始能够正确分类数据到数据标签 ``` ```shell # 请注意到 loss 逐渐变小的表现 tensor(360.4489, grad_fn=) # 第 1 轮 tensor(356.3965, grad_fn=) tensor(342.1244, grad_fn=) ...... tensor(226.3018, grad_fn=) # 第 20 轮 ``` # 对现有网络模型的使用和修改 我们此时示例的模型为 VGG 系列。 官方地址:[https://pytorch.org/vision/stable/models/vgg.html](https://pytorch.org/vision/stable/models/vgg.html) ## VGG-16 我们可以下载完整的模型来看模型的结构。 ```python import torchvision vgg16_false = torchvision.models.vgg16(pretrained=False) # pretrained=False - 模型的参数是随机的,没有经过训练 # 如果是引入预训练的模型,则需要再下载这个模型的参数。下载操作是自动的 vgg16_true = torchvision.models.vgg16(pretrained=True) # pretrained=True - 模型的参数是已经经过预先训练了 print(vgg16_true) # 查看具体的模型结构 ``` VGG-16 结构: ```python 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)) ...... (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) ...... (6): Linear(in_features=4096, out_features=1000, bias=True) ) ) ``` 现在可以看到,VGG-16 的输出是 1000 个 feature,这是因为 VGG-16 原本的任务是分类 ImageNet。 ## 让 VGG-16 适用我们现在的任务 两个想法: 1. 再添加一层 Linear,让他实现 `Linear(in_features=1000, out_features=10, ...)`; 2. 直接修改模型结构,使得 `out_features=10`。 ### 向存在的模型添加层 ```python import torchvision from torch.nn import Linear # 如果是引入预训练的模型,则需要再下载这个模型的参数 vgg16_true = torchvision.models.vgg16(pretrained=True) # pretrained=True - 模型的参数是已经经过预先训练了 vgg16_true.add_module("added_name", Linear(1000, 10)) print(vgg16_true) # 查看具体的模型结构 ``` 模型现在的结构 ```python 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)) ...... (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) ...... (6): Linear(in_features=4096, out_features=1000, bias=True) ) (added_name): Linear(in_features=1000, out_features=10, bias=True) ) ``` #### 向模型的中间层添加层 我们可以尝试在模型中的模型中添加层。这个的意思说,上面,我们实现了向 VGG 中添加层,但是添加层明显位于了最后。我们现在需要将层添加到 classifier 这个子模型中。 ```python import torchvision from torch.nn import Linear # 如果是引入预训练的模型,则需要再下载这个模型的参数 vgg16_true = torchvision.models.vgg16(pretrained=True) # pretrained=True - 模型的参数是已经经过预先训练了 vgg16_true.classifier.add_module("added_name", Linear(1000, 10)) print(vgg16_true) # 查看具体的模型结构 ``` ```python 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)) ...... (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) ...... (6): Linear(in_features=4096, out_features=1000, bias=True) (added_name): Linear(in_features=1000, out_features=10, bias=True) ) ) ``` ### 修改模型结构的参数 ```python import torchvision from torch.nn import Linear # 如果是引入预训练的模型,则需要再下载这个模型的参数 vgg16_true = torchvision.models.vgg16(pretrained=True) # pretrained=True - 模型的参数是已经经过预先训练了 vgg16_true.classifier[6] = Linear(4096, 10) print(vgg16_true) # 查看具体的模型结构 ``` ```shell 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)) ...... (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) ...... (6): Linear(in_features=4096, out_features=10, bias=True) ) ) ``` # 模型的保持与读取 ## 保存模型及其参数(不推荐) 保存 ```python import torch import torchvision vgg16_false = torchvision.models.vgg16(pretrained=False) torch.save(vgg16_false, "vgg16.pth") # 保存模型及其参数 ``` 读取 ```python import torch vgg16_false = torch.load("vgg16.pth") print(vgg16_false) ``` ### 保存模型及其参数存在的陷阱 这个方法可能失效,因为 python 模型的本质是类,如果是自己写的模型,读取时可能不能被加载。 ## 仅保存参数(推荐) 保存参数 ```python import torch import torchvision vgg16_false = torchvision.models.vgg16(pretrained=False) torch.save(vgg16_false.state_dict(), "vgg16_state.pth") # 保存参数 ``` 加载参数到模型 ```python import torch import torchvision vgg16_false = torchvision.models.vgg16(pretrained=False) # 先建立模型 vgg16_false.load_state_dict(torch.load("vgg16_state.pth")) # 再填充参数 print(vgg16_false) # 检查模型 ``` # 完整的模型训练 建议一个文件放模型,一个文件进行模型的使用。 ## 模型 ```python # 可以一个文件专门写模型,一个文件写使用模型 # 本文件是模型代码文件 import torch from torch import nn from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear # 搭建神经网络 class Model(nn.Module): def __init__(self): super(Model, 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 # 验证模型结构的正确性 if __name__ == '__main__': model = Model() ox = torch.ones((64, 3, 32, 32)) oy = model(ox) print(model) print(oy.shape) ``` ## 使用模型 ```python # 可以一个文件专门写模型,一个文件写使用模型 # 本文件是使用模型文件 import torchvision from torch.nn import CrossEntropyLoss from torch.optim import SGD from torch.utils.data import DataLoader # 记得导入模型, from TrainModel import Model train_data = torchvision.datasets.CIFAR10(root="./Dataset", train=True, transform=torchvision.transforms.ToTensor()) test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) # 查看数据集大小 train_data_size = len(train_data) test_data_size = len(test_data) # 下面进行格式化的输出 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) # 1. 定义模型结构 model = Model() # 2. 定义损失函数 loss_tm = CrossEntropyLoss() # 3.定义优化器 # (可选)[3.2] 单独设置 learning_rate learning_rate = 0.01 # == 1e-2(python 允许的写法) == 1 × 10^(-2) optim_tm = SGD(model.parameters(), lr=learning_rate) # 4. 设置协助网络训练的一些参数 # 4.1 记录训练的次数 total_train_steps = 0 # 4.2 记录测试的次数 total_test_steps = 0 # 4.3 记录训练(学习)的轮数 epoch = 10 # 模型将对一个数据集学习 epoch 次 for i in range(epoch): print("第 {} 轮训练开始".format(i)) for data in train_dataloader: # 真正的开始让模型读取数据 imgs, targets = data ys = model(imgs) loss = loss_tm(ys, targets) # 优化器优化模型 optim = optim_tm.zero_grad() loss.backward() optim_tm.step() print("训练次数:{},损失:{}。".format(total_train_steps, loss)) total_train_steps += 1 ``` 值得注意的是,我们现在没有对模型的学习效果进行任何的验证!这肯定是不对的,接下来,我们使用验证数据集对模型的学习效果进行验证。 ## 在训练中验证模型 训练模型的关键: 1. 训练模型[,记录训练损失]。 2. 验证模型的能力[,记录训练损失]。 3. [适时保存模型及其参数]。 ```python # 可以一个文件专门写模型,一个文件写使用模型 # 本文件是使用模型文件 import torch import torchvision from torch.nn import CrossEntropyLoss from torch.optim import SGD from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter # 记得导入模型, from TrainModel import Model train_data = torchvision.datasets.CIFAR10(root="./Dataset", train=True, transform=torchvision.transforms.ToTensor()) test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) # 查看数据集大小 train_data_size = len(train_data) test_data_size = len(test_data) # 下面进行格式化的输出 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) # 1. 定义模型结构 model = Model() # 2. 定义损失函数 loss_tm = CrossEntropyLoss() # 3.定义优化器 # (可选)[3.2] 单独设置 learning_rate learning_rate = 0.01 # == 1e-2(python 允许的写法) == 1 × 10^(-2) optim_tm = SGD(model.parameters(), lr=learning_rate) # 4. 设置协助网络训练的一些参数 # 4.1 记录训练的次数 total_train_steps = 0 # 4.2 记录测试的次数 total_test_steps = 0 # 4.3 记录训练(学习)的轮数 epoch = 10 # 模型将对一个数据集学习 epoch 次 writer = SummaryWriter("Train") for i in range(epoch): print("第 {} 轮训练开始".format(i)) for data in train_dataloader: # 真正的开始让模型读取数据 imgs, targets = data ys = model(imgs) loss = loss_tm(ys, targets) # 优化器优化模型 optim = optim_tm.zero_grad() loss.backward() optim_tm.step() # print("训练次数:{},损失:{}。".format(total_train_steps, loss)) # .item() 函数的返回值必定是 基本数据类型值 而不是 tensor 等。 writer.add_scalar("train_loss", loss.item(), total_train_steps) total_train_steps += 1 # 当完成一轮训练时,正常流程是进行模型学习能力的验证 # Python 中的 with 语句用于 异常处理,封装了 try…except…finally,提高了易用性。 total_test_loss = 0 with torch.no_grad(): for data in test_dataloader: imgs, targets = data outputs = model(imgs) loss = loss_tm(outputs, targets) total_test_loss += loss.item() # 这个 epoch 下的整个 batch 累计的损失 print("验证损失:{}。".format(total_test_loss)) writer.add_scalar("test_loss", total_test_loss, total_train_steps) total_test_steps += 1 # torch.save(model, "model_{}.pth".format(epoch)) writer.close() ``` ## 如何查看模型的正确率 Loss 是用来指导模型训练的。 模型的输出才能检查模型的正确率。 上面代码的模型的输出: ```python tensor([ [ 6.0145e-02, -2.1819e-02, 3.9244e-02, -8.7494e-02, 4.4870e-03, -3.1030e-02, -6.7652e-02, -8.9449e-02, 3.0509e-03, 7.5401e-02], # 这是一张图片数据被模型输出的结果,argmax() 应用于此,求得模型预测的、最大可能性的、对应的 target 位置。 [ 4.9428e-02, -3.0955e-02, 3.4124e-02, -8.5581e-02, -2.7844e-02, -4.3696e-02, -8.2859e-02, -7.8345e-02, -6.5387e-03, 9.0945e-02], ...... ]) ``` batch 中的 targets: ```python tensor([ 4, 1, ......, ]) ``` ## argmax(axiax) 我们可以使用 `argmax()` 函数,求取模型输出 tensor 中的、1 张图像对应的输出的最大值(即,概率、可能性)的位置。设 i = argmax(......) 然后,如果 targets[i] == 输入对应的正确的 target,则正确个数 +1,否则 +0。 ### 按横轴查看最大值 index ```python import torch outputs = torch.tensor([[0.1, 0.2], [0.3, 0.4]]) print(outputs.argmax(1)) # 1 表示横着求最大值的位置 ``` ```python # 输出 tensor([1, # 第 0 个 list 中的 0.2 大,index 是 1 1]) # 第 1 个 list 中的 0.4 大,index 是 1 ``` ### 按纵轴查看最大值 index ```python import torch outputs = torch.tensor([[0.1, 0.2], [0.03, 0.4]]) print(outputs.argmax(0)) # 0 表示竖着求最大值的位置 ``` ```python # 输出 tensor([0, 1]) # 第 1 列 [0.1, 0.2] 中的 0.1 大,index 是 0; # 第 2 列 [0.1, 0.4] 中的 0.4 大,index 是 1; ``` ### 用于辅助查看模型正确率的特性 > 规定:为了实现更好的代码的解释,我们现在启用特殊的注释标签,来表示完整的代码被分块编写了。 > > \# !start: 完整的代码的开始位置 > > \# !part: 完整代码的一部分代码 > > \# !end: 完整代码的结尾部分 ```python import torch outputs = torch.tensor([[0.1, 0.2], [0.03, 0.4]]) print(outputs.argmax(1)) # 1 表示横着求最大值的位置 # 输出:tensor([1, 1]) preds = outputs.argmax(1) # predictions,预测。预测的标签对应的 index targets = torch.tensor([0, 1]) # 真实的标签对应的 index print(preds == targets) # 特别注意,tensor 的 == 表示 tensor 的每个元素进行对比 # 输出:tensor([False, True]) print((preds == targets).sum()) # 统计为 True 的元素个数,即预测正确的个数 # 输出:tensor(1) ``` 关键: 1. tensor 类型之间的 `==` 操作,是对每个元素进行比较,且返回结果的 shape 和 两个 tensor 的 shape 一样 ```python print(preds == targets) # 输出:tensor([False, True]) ``` 2. [True, True, False, ......, bool].sum() 可以求得这个 list 中的 True 个数 ### 查看正确率实战 注意⚠:需要导入模型文件!`from TrainModel import Model` ```python # 可以一个文件专门写模型,一个文件写使用模型 # 本文件是使用模型文件 import torch import torchvision from torch.nn import CrossEntropyLoss from torch.optim import SGD from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter from TrainModel import Model train_data = torchvision.datasets.CIFAR10(root="./Dataset", train=True, transform=torchvision.transforms.ToTensor()) test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) train_data_size = len(train_data) print("训练数据集的长度为:{}".format(train_data_size)) train_dataloader = DataLoader(train_data, batch_size=64) test_dataloader = DataLoader(test_data, batch_size=64) model = Model() loss_tm = CrossEntropyLoss() learning_rate = 0.01 optim_tm = SGD(model.parameters(), lr=learning_rate) total_train_steps = 0 total_test_steps = 0 epoch = 10 # 模型将对一个数据集学习 epoch 次 writer = SummaryWriter("Train") for i in range(epoch): print("第 {} 轮训练开始".format(i)) for data in train_dataloader: imgs, targets = data ys = model(imgs) loss = loss_tm(ys, targets) optim = optim_tm.zero_grad() loss.backward() optim_tm.step() writer.add_scalar("train_loss", loss.item(), total_train_steps) total_train_steps += 1 # 当完成一轮训练时,正常流程是进行模型学习能力的验证 total_test_loss = 0 total_accurate = 0 # 一次 epoch 后对模型的验证的正确个数 test_data_size = len(test_dataloader) # 查看总的测试集数据量大小,便于求正确率 with torch.no_grad(): for data in test_dataloader: imgs, targets = data outputs = model(imgs) loss = loss_tm(outputs, targets) total_test_loss += loss.item() accurate = (outputs.argmax(1) == targets).sum() # 看 True 的个数有多少,即预测出的 index 和 正确的 targets 中 index 是否相等 total_accurate += accurate # 积累每次测试新数据的准确度 print("验证损失:{}。".format(total_test_loss)) print("本次 epoch 正确率:{}。".format(total_accurate / test_data_size)) writer.add_scalar("test_loss", total_test_loss, total_train_steps) writer.add_scalar("accuracy", total_accurate / test_data_size, total_train_steps) total_test_steps += 1 writer.close() ``` ## 划分 train 和 eval 当我们验证模型性能时,我们不仅需要**禁用掉梯度**,防止模型在验证集上学习;我们也需要**禁止 dropout 和 batchNorm 等层变化**。 官网地址:[https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.train](https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.train) ```python # 可以一个文件专门写模型,一个文件写使用模型 # 本文件是使用模型文件 import torch import torchvision from torch.nn import CrossEntropyLoss from torch.optim import SGD from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter from TrainModel import Model train_data = torchvision.datasets.CIFAR10(root="./Dataset", train=True, transform=torchvision.transforms.ToTensor()) test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor()) train_data_size = len(train_data) print("训练数据集的长度为:{}".format(train_data_size)) train_dataloader = DataLoader(train_data, batch_size=64) test_dataloader = DataLoader(test_data, batch_size=64) model = Model() loss_tm = CrossEntropyLoss() learning_rate = 0.01 optim_tm = SGD(model.parameters(), lr=learning_rate) total_train_steps = 0 total_test_steps = 0 epoch = 10 # 模型将对一个数据集学习 epoch 次 writer = SummaryWriter("Train") for i in range(epoch): print("第 {} 轮训练开始".format(i)) model.train() # 将模型设置为 train 模式,dropout 和 bn 等正常使用 for data in train_dataloader: imgs, targets = data ys = model(imgs) loss = loss_tm(ys, targets) optim = optim_tm.zero_grad() loss.backward() optim_tm.step() writer.add_scalar("train_loss", loss.item(), total_train_steps) total_train_steps += 1 # 当完成一轮训练时,正常流程是进行模型学习能力的验证 model.eval() # 将模型设置为 eval 模式,dropout 和 bn 等被禁用掉 total_test_loss = 0 total_accurate = 0 # 一次 epoch 后对模型的验证的正确个数 test_data_size = len(test_dataloader) # 查看总的测试集数据量大小,便于求正确率 with torch.no_grad(): for data in test_dataloader: imgs, targets = data outputs = model(imgs) loss = loss_tm(outputs, targets) total_test_loss += loss.item() accurate = (outputs.argmax(1) == targets).sum() # 看 True 的个数有多少,即预测出的 index 和 正确的 targets 中 index 是否相等 total_accurate += accurate # 积累每次测试新数据的准确度 print("验证损失:{}。".format(total_test_loss)) print("本次 epoch 正确率:{}。".format(total_accurate / test_data_size)) writer.add_scalar("test_loss", total_test_loss, total_train_steps) writer.add_scalar("accuracy", total_accurate / test_data_size, total_train_steps) total_test_steps += 1 writer.close() ``` # 调用 GPU ## 使用 cuda 3 个地方使用 cuda: 1. 网络模型 image-20230426195745743 2. 数据(输入、标注、targets)(训练、测试数据集) image-20230426200036624 3. 损失函数 image-20230426195854918 然后,我们在这 3 个变量上加上 `.cuda()`,GPU 就被调用起来了。 ## 判断 cuda 可用再使用 如果我们没有 cuda,但是直接尝试使用 cuda,将报错。 所以应该先判断是否可以调用 cuda: `torch.cuda.is_available()` ```python if torch.cuda.is_available(): model = model.cuda() ``` ## 程序计时 ```python start_time = time.time() # 需要计时的程序段 end_time = time.time() print(end_time - start_time) # 输出是以 s 为单位 ``` ## 尝试可能的外部免费 GPU google colab 阿里云天池(无外网,但是提供数据集加速下载) ## 更加方便地使用 cuda 我们可以先利用 torch 提供的 .device() 指定训练时用的设备: ```python device = torch.device("cpu") ``` 然后,让模型、数据和 Loss 都使用这个 device: ```python model = Model() model = model.to(device) # 模型的重新赋值是可选的 ``` ```python loss_tm = CrossEntropyLoss() loss_tm = loss_tm.to(device) # Loss 的重新赋值是可选的 ``` ```python # 测试数据集和训练数据集都需要进行一下操作 imgs, targets = data imgs = imgs.to(device) targets = targets.to(device) ``` 完整代码: ```python import time import torch import torchvision from torch import nn from torch.nn import CrossEntropyLoss, Sequential, Conv2d, MaxPool2d, Flatten, Linear from torch.optim import SGD from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter # from TrainModel import Model train_data = torchvision.datasets.CIFAR10(root="./Dataset", train=True, transform=torchvision.transforms.ToTensor(), download=True) test_data = torchvision.datasets.CIFAR10(root="./Dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True) train_data_size = len(train_data) print("训练数据集的长度为:{}".format(train_data_size)) train_dataloader = DataLoader(train_data, batch_size=64) test_dataloader = DataLoader(test_data, batch_size=64) # 搭建神经网络 class Model(nn.Module): def __init__(self): super(Model, 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 # 定义训练的设备 if torch.cuda.is_available(): print("use cuda") # device = torch.device("cpu") device = torch.device("cuda") # device = torch.device("cuda:0") model = Model() model = model.to(device) loss_tm = CrossEntropyLoss() loss_tm = loss_tm.to(device) learning_rate = 0.01 optim_tm = SGD(model.parameters(), lr=learning_rate) total_train_steps = 0 total_test_steps = 0 epoch = 10 # 模型将对一个数据集学习 epoch 次 writer = SummaryWriter("Train") for i in range(epoch): start_time = time.time() print("第 {} 轮训练开始".format(i)) model.train() # 将模型设置为 train 模式,dropout 和 bn 等正常使用 for data in train_dataloader: imgs, targets = data imgs=imgs.to(device) targets=targets.to(device) ys = model(imgs) loss = loss_tm(ys, targets) optim = optim_tm.zero_grad() loss.backward() optim_tm.step() writer.add_scalar("train_loss", loss.item(), total_train_steps) total_train_steps += 1 # 当完成一轮训练时,正常流程是进行模型学习能力的验证 model.eval() # 将模型设置为 eval 模式,dropout 和 bn 等被禁用掉 total_test_loss = 0 total_accurate = 0 # 一次 epoch 后对模型的验证的正确个数 test_data_size = len(test_dataloader) # 查看总的测试集数据量大小,便于求正确率 with torch.no_grad(): for data in test_dataloader: imgs, targets = data imgs=imgs.to(device) targets=targets.to(device) outputs = model(imgs) loss = loss_tm(outputs, targets) total_test_loss += loss.item() accurate = (outputs.argmax(1) == targets).sum() # 看 True 的个数有多少,即预测出的 index 和 正确的 targets 中 index 是否相等 total_accurate += accurate # 积累每次测试新数据的准确度 print("验证损失:{}。".format(total_test_loss)) print("本次 epoch 正确率:{}。".format(total_accurate / test_data_size)) writer.add_scalar("test_loss", total_test_loss, total_train_steps) writer.add_scalar("accuracy", total_accurate / test_data_size, total_train_steps) total_test_steps += 1 # 需要计时的程序段 end_time = time.time() print("运行时间:", end_time - start_time) # 输出是以 s 为单位 writer.close() ``` ## IF-ELSE 语法糖 ```python device = torch.device("cuda" if torch.cuda.is_available() else "cpu") ``` # 模型验证/测试