配置 conda 环境
conda 的路径选择:D:\program\miniconda\Scripts\conda.exe
使终端前有括号表示环境的操作
先将 conda 初始化:conda init
再新开一个终端就好了
检验是否可用 GPU
import torch
print(torch.cuda.is_available())
安装 jupyter:conda install -c conda-forge nb_conda
安装好后终端输入 jupyter notebook 即可打开
python 学习中的两大法宝函数
每一个 package 可以看作一个工具箱,下面两个函数就是探索工具箱的法宝:
dir():打开、看见——让我们知道工具箱里以及工具箱的分隔区里有什么东西help():说明书——让我们知道每个工具的使用方法
加载数据
环境配置好后,正式进入 PyTorch 的第一步:把数据加载进来。先认识几个相关概念:
dataset:提供一种方式去获取数据及其 label。dataloader:对 dataset 的数据做一个”打包”,为后面的网络提供不同的数据形式。os:operating system,操作系统。PIL:python image library。
在数据准备阶段使用以下代码:首先创建文件夹,把所有图片按以下逻辑分类;再创建一个类,使我可以使用一个变量就调用其中一个数据集,如 ants_dataset = MyData(root_dir, image_ants, label_ants, transform),这里 ants_dataset 就可以直接调用蚂蚁的数据集了。

代码的主要功能和意图
-
自定义数据集(
MyData,继承父类Dataset):-
作用: 从文件系统中加载图像和对应的标签。
-
方式: 图像和标签存储在不同的文件夹中,通过文件名来匹配图像和标签。
-
实现:
-
重写了
Dataset的__init__,__getitem__, 和__len__方法。 -
使用
os.listdir列出图像和标签文件,并通过排序保证一一对应。 -
使用
Image.open加载图像,并应用预定义的变换(如调整大小、转换为张量)。 -
标签通过读取文件内容(
f.readline())获取。
-
-
-
数据集合并:
-
作用: 将两个不同的数据集(
ants_dataset和bees_dataset)合并为一个训练数据集。 -
方式: 使用
+将两个数据集对象合并为一个新的数据集。
-
-
数据加载(
DataLoader):-
作用: 将数据集分成批次(batches)并加载到内存中。
-
方式:
-
设置
batch_size=1和num_workers=2,表示每次加载 1 个样本,并使用多线程加速数据加载。 -
使用
torchvision.transforms对图像进行预处理(调整大小为 256x256,转换为张量)。
-
-
-
TensorBoard 可视化:
-
作用: 将数据可视化,以检查数据加载是否正确。
-
方式:
-
使用
SummaryWriter创建日志文件。 -
通过
add_image方法将指定索引的样本(train_dataset[119])写入 TensorBoard 日志。 -
使用
writer.close()关闭日志写入。
-
-
-
调试和测试:
-
意图: 检查数据加载和预处理是否正确。
-
方式:
- 通过注释掉的 for 循环,逐个批次打印数据的形状(
j['img'].shape)和类型(type(j))。
- 通过注释掉的 for 循环,逐个批次打印数据的形状(
-
from torch.utils.data import Dataset, DataLoader
import numpy as np
from PIL import Image
import os
from torchvision import transforms
from torch.utils.tensorboard import SummaryWriter
from torchvision.utils import make_grid
writer = SummaryWriter("logs")
class MyData(Dataset):
def __init__(self, root_dir, image_dir, label_dir, transform):
self.root_dir = root_dir
self.image_dir = image_dir
self.label_dir = label_dir
self.label_path = os.path.join(self.root_dir, self.label_dir)
self.image_path = os.path.join(self.root_dir, self.image_dir)
self.image_list = os.listdir(self.image_path)
self.label_list = os.listdir(self.label_path)
self.transform = transform
# 因为label 和 Image文件名相同,进行一样的排序,可以保证取出的数据和label是一一对应的
self.image_list.sort()
self.label_list.sort()
def __getitem__(self, idx):
img_name = self.image_list[idx]
label_name = self.label_list[idx]
img_item_path = os.path.join(self.root_dir, self.image_dir, img_name)
label_item_path = os.path.join(self.root_dir, self.label_dir, label_name)
img = Image.open(img_item_path)
with open(label_item_path, 'r') as f:
label = f.readline()
# img = np.array(img)
img = self.transform(img)
sample = {'img': img, 'label': label}
return sample
def __len__(self):
assert len(self.image_list) == len(self.label_list)
return len(self.image_list)
if __name__ == '__main__':
transform = transforms.Compose([transforms.Resize((256, 256)), transforms.ToTensor()])
root_dir = "dataset/train"
image_ants = "ants_image"
label_ants = "ants_label"
ants_dataset = MyData(root_dir, image_ants, label_ants, transform)
image_bees = "bees_image"
label_bees = "bees_label"
bees_dataset = MyData(root_dir, image_bees, label_bees, transform)
train_dataset = ants_dataset + bees_dataset
# transforms = transforms.Compose([transforms.Resize(256, 256)])
dataloader = DataLoader(train_dataset, batch_size=1, num_workers=2)
writer.add_image('error', train_dataset[119]['img'])
writer.close()
# for i, j in enumerate(dataloader):
# # imgs, labels = j
# print(type(j))
# print(i, j['img'].shape)
# # writer.add_image("train_data_b2", make_grid(j['img']), i)
#
# writer.close()
Tensorboard 的使用
数据能加载之后,还需要一个能”看见”训练过程的工具。Tensorboard 是用来查看训练过程的图像变化的,能够实时监控训练过程。
- 安装 Tensorboard 命令:
pip install tensorboard - 设置 Tensorboard 日志存放的文件夹:终端输入
tensorboard --logdir=logs——logdir 等号后面接的是 tensorboard 日志目录名称 - 指定主机对应的 Tensorboard 端口:
tensorboard --logdir=logs --port=6007——这样 Tensorboard 打开的窗口就变成了 http://localhost:6007/
小结:我对于 Tensorboard 的感觉就是一个强力的辅助工具,在训练一个网络时,我可以在代码中添加几行 Tensorboard 的代码,使训练过程被实时记录。
from torch.utils.tensorboard import SummaryWriter
import numpy as np
from PIL import Image
writer=SummaryWriter("logs")#设置tensorboard日志目录名称
image_path="hymenoptera_data/hymenoptera_data/train/bees/16838648_415acd9e3f.jpg"
img_PIL=Image.open(image_path)
img_array=np.array(img_PIL)
writer.add_image("bee",img_array,1,dataformats='HWC')#看函数名,‘添加图片’,然后传参
for i in range(100):
writer.add_scalar("y=2x",2*i,i)#看函数名,‘添加数值图表’,设置损失函数的图也用这个
writer.close()
Transforms 的使用
数据读进来之后往往还要做预处理,这就轮到 Transforms 了。它实质指的是 Transforms.py 这个预先定义好的模块,相当于一个工具箱,需要什么功能直接调用即可。
那么如何使用 Transforms 呢?用如下代码进行解释。
from PIL import Image
from torchvision import transforms
img_path="hymenoptera_data/hymenoptera_data/train/ants/0013035.jpg"
img = Image.open(img_path)
tensor_trans = transforms.ToTensor()
tensor_img = tensor_trans(img)
print(tensor_img)
使用的逻辑是:tensor_trans 是我们借助 transforms 里的 ToTensor 类打造的一个工具,它的作用就是将传入的图片转换为张量,tensor_img 就是将 img 转化为张量的结果。
常见的 Transforms
熟悉了 Transforms 的基本用法后,这一节逐个过一遍常用的几个变换。在此之前先补充一个 Python 小知识——__call__ 方法。
__call__ 方法介绍
以如下代码为例:
class person:
def __call__(self, name):
self.name = name
print("__call__"+"hello"+name)
def hello(self, name):
print("hello"+name)
person = person()
person("zhangsan")
person.hello("lisi")
运行结果为:
__call__hellozhangsan
hellolisi
小结:__call__ 方法能够让创建的实例可以不用打点调用,可以直接后加括号调用。
ToTensor 的使用
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
writer = SummaryWriter("logs")#这里writer就成了tensorboard的一个实例,“logs”是设置的tensorboard日志目录名称
img = Image.open("图片路径")
print(img)
trans_totensor = transforms.ToTensor()
img_tensor = trans_totensor(img)
writer.add_image("totensor", img_tensor)
writer.close()
这里的流程:
- 创建一个 tensorboard 的实例
writer; - 创建一个图片变量
img; - 使用 transforms 的 ToTensor 创建一个工具
trans_totensor; - 使用这个工具将 img 转换为张量
img_tensor; - 再调用 tensorboard 的函数
add_image,将图片张量加载到 tensorboard 中; - 关闭。
Normalize 的使用
基于上段代码
trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
img_norm = trans_norm(img_tensor)
writer.add_image("norm", img_norm)
流程:
- 使用 transforms 的 Normalize 创建一个工具
trans_norm,其中第一个参数为平均值 mean[channel],第二个参数为标准差 std[channel],计算方式为 output[channel] = (input[channel] - mean[channel]) / std[channel]; - 将图片张量使用
trans_norm进行标准化; - 再调用 tensorboard 的函数
add_image,将标准化后的图片张量加载到 tensorboard 中。
Resize 的使用
trans_resize = transforms.Resize((512, 512))
img_resize = trans_resize(img)
img_resize = trans_totensor(img_resize)
writer.add_image("resize", img_resize, 0)
writer.close()
流程:
- 使用 transforms 的 Resize 创建一个工具
trans_resize,传的参数为更改之后的图片大小; - 将 img 的尺寸更改为了 512×512;
- 将更改尺寸后的
img_resize使用trans_totensor转换成张量; - 再调用 tensorboard 的函数
add_image,将更改尺寸后的图片张量加载到 tensorboard 中。
Compose() 用法
Compose() 中的参数需要是一个列表。Python 中,列表的表示形式为 [数据1, 数据2, …];在 Compose 中,数据需要是 transforms 类型,所以得到 Compose([transforms参数1, transforms参数2, …])。
trans_resize_2 = transforms.Resize(512)
trans_compose = transforms.Compose([trans_resize_2, trans_totensor])
img_resize_2 = trans_compose(img)
writer.add_image("resize_2", img_resize_2, 1)
transforms.Compose的作用是将几个变换(Transform对象)组合成一个单一的变换。这个组合后的变换可以应用于图像数据,按照列表中提供的顺序依次执行每个变换。
这里的流程:
- 创建一个变量
trans_resize_2,功能为重新规定尺寸; - 将重新规定尺寸和转为张量组合起来,顺序为先更改尺寸,再转为张量;
- 将 img 进行 compose 中的组合操作;
- 再调用 tensorboard 的函数
add_image,将处理后的图片张量加载到 tensorboard 中。
也可以不定义变量 trans_resize_2,如下:
transforms.Compose([ transforms.CenterCrop(10), # 中心裁剪,尺寸为10x10 transforms.PILToTensor(), # 将PIL图像转换为tensor transforms.ConvertImageDtype(torch.float), # 转换图像的数据类型为浮点型 ])
torchvision 中的数据集使用
前面加载的是自己整理的数据集,torchvision 里还内置了很多可以直接下载使用的公开数据集。torchvision 是 pytorch 官网中有关视觉的部分:pytorch 官网 —> Docs —> PyTorch Domains —> torchvision(左上角可以选版本号,视频中的版本为 v0.9.0)。
import torchvision
train_set = torchvision.datasets.CIFAR10("../data", train=True, download=True)
test_set = torchvision.datasets.CIFAR10("../data", train=False, download=True)
print(train_set[0])
print(test_set.classes)
img, target = train_set[0]
print(img)
print(target)
如上,括号里传的参数可以通过官网里找到,如下图:

这段代码实现了:
- 创建了一个训练集,是调用 datasets 中的 CIFAR10,将 train 设置为 True;创建了一个测试集,是调用 datasets 中的 CIFAR10,将 train 设置为 False;
- 输出测试集的第一个数据及类型;
- 将图像和标签都分别对应;
- 输出图像和标签。
这段代码运行结果如下:
Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ../data\cifar-10-python.tar.gz#下载数据集
100%|██████████| 170498071/170498071 [01:43<00:00, 1650531.07it/s]
Extracting ../data\cifar-10-python.tar.gz to ../data
Files already downloaded and verified
(<PIL.Image.Image image mode=RGB size=32x32 at 0x27EA615B1C8>, 6)#print(train_set[0]),此为元组类型,第一个为图像,后一个为标签
['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']#print(test_set.classes),此为输出了图像的所有类别
<PIL.Image.Image image mode=RGB size=32x32 at 0x27EF87874C8>#print(img)
6#print(target)
DataLoader 的使用
数据集有了,还要解决”怎么把数据取出来喂给网络”的问题,这就是 DataLoader 的职责。
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
test_data = torchvision.datasets.CIFAR10("../data", train=False, transform=torchvision.transforms.ToTensor(),download=False)
test_loader = DataLoader(dataset=test_data,batch_size=64,shuffle=True,num_workers=0,drop_last=False)
writer = SummaryWriter("logs")
i=1
for data in test_loader:
imgs,targets = data
writer.add_images("test_data",imgs,i)
i+=1
writer.close()
这段代码的逻辑:
- 实例化数据集,设置各项参数;
- 实例化抓取数据,设置各项参数,比如数据集是什么、size 是多大、是否打乱、是否多进程加载数据、是否摒弃不完整的 batch;
- 遍历抓取的数据,并将其加载到 tensorboard 中。
神经网络的基本骨架-nn.Module 的使用
数据准备的部分到此告一段落,从这一节开始进入网络搭建,torch.nn 就是搭建神经网络的工具箱。
https://pytorch.org/docs/stable/nn.html,这个网站就是介绍搭建神经网络的工具:
Containers—>基本骨架
Convolution Layers—>卷积层
Pooling layers —>池化层
Padding Layers —>填充层
Non-linear Activations (weighted sum, nonlinearity) —>非线性激活(加权和,非线性)
Non-linear Activations (other) —>非线性激活(其他)
Normalization Layers —>归一化层
Recurrent Layers—>循环层
Transformer Layers—>Transformer 层
Linear Layers—>线性层
Dropout Layers—>Dropout 层
Sparse Layers—>稀疏层
Distance Functions—>距离函数
Loss Functions—>损失函数
Vision Layers—>视觉层
Shuffle Layers—>随机排列层
DataParallel Layers (multi-GPU, distributed)—>数据并行层(多 GPU,分布式)
Utilities—>公用
Quantized Functions—>量化函数
Lazy Modules Initialization—>惰性模块初始化
- Aliases—>别名
import torch.nn as nn
import torch.nn.functional as F
class Model(nn.Module):
def __init__(self) -> None:
super().__init__()
self.conv1 = nn.Conv2d(1, 20, 5)
self.conv2 = nn.Conv2d(20, 20, 5)
def forward(self, x):
x = F.relu(self.conv1(x))
return F.relu(self.conv2(x))
class Model(nn.Module):这里定义了一个类叫 model,继承了父类 nn.Module;super().__init__():这里是继承父类的初始化。
def forward(self, x):
x = F.relu(self.conv1(x))
return F.relu(self.conv2(x))
这里短短三行代码,其实经历的过程为:
输入 x —> conv1 卷积 —> relu 非线性 —> conv2 卷积 —> relu 非线性 —> 输出
神经网络-卷积层
有了 nn.Module 这个骨架,接下来往里面填具体的层,先从最核心的卷积层开始。这里是对 torch.nn 中的 convolution Layers 进行介绍。
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
Parameters 参数
-
in_channels (int) – 输入图像中的通道数
-
out_channels (int) – 卷积产生的通道数
-
padding (int, tuple or str, optional) – 添加到输入的所有四个边的填充。默认值:0
-
groups (int, optional) – 从输入通道到输出通道的阻塞连接数。默认值:1
-
bias (bool, optional) – 如果 是
True,则向输出添加可学习的偏差。默认:True -
padding_mode (str, optional) –
'zeros'、'reflect''replicate'或'circular'.默认:'zeros'
这里注意,in_channels 和 out_channels 不一定一样,比如 in_channels=1,out_channels=2,就是两个卷积核对其卷积,形成两个输出就是两个输出通道。
卷积后图片大小计算公式:

import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("../data", train=False, transform=torchvision.transforms.ToTensor(), download=True)
dataloader = DataLoader(dataset=dataset, batch_size=64, shuffle=True, num_workers=0, drop_last=False)
class mutou(nn.Module):
def __init__(self):
super(mutou, self).__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, padding=0)
def forward(self, x):
x = self.conv1(x)
return x
mutou = mutou()
writer = SummaryWriter("logs")
i=0
for data in dataloader:
imgs, targets = data
print(imgs.shape)
outputs = mutou(imgs)
print(outputs.shape)
writer.add_images("input", imgs, global_step=i)
outputs = torch.reshape(outputs, (-1, 3, 30, 30))
writer.add_images("output", outputs, global_step=i)
i += 1
writer.close()
以上为一个简易应用卷积层的代码。
神经网络-最大池化的使用
卷积之后常接池化层,用来压缩特征、减小数据量。这里是对 torch.nn 中的 pooling Layers 进行介绍。
torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
Parameters 参数
-
stride (Union[int, Tuple[int, int]]) – 窗口的步幅。默认值为
kernel_size -
dilation (Union[int, Tuple[int, int]]) – 控制窗口中元素步幅的参数,窗口不是连在一起的,而是中间有空洞的
-
return_indices (bool) – 如果是
True,将返回最大索引以及输出。对torch.nn.MaxUnpool2d以后有用 -
ceil_mode (bool) – 当 True 时,将使用 ceil 而不是 floor 来计算输出形状
当窗口内的元素不完整时,ceil 为继续进行池化,floor 为舍弃,放弃这个池化
池化后大小:

在卷积的基础上加了池化,相比上一段代码,变化在于新增了 maxpool:
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("../data", train=False, transform=torchvision.transforms.ToTensor(), download=True)
dataloader = DataLoader(dataset=dataset, batch_size=64, shuffle=True, num_workers=0, drop_last=False)
class mutou(nn.Module):
def __init__(self):
super(mutou, self).__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, padding=0)
self.maxpool = nn.MaxPool2d(kernel_size=3, ceil_mode=False)
def forward(self, x):
x = self.conv1(x)
x = self.maxpool(x)
return x
mutou = mutou()
writer = SummaryWriter("logs")
i=0
for data in dataloader:
imgs, targets = data
print(imgs.shape)
outputs = mutou(imgs)
print(outputs.shape)
writer.add_images("input_pool", imgs, global_step=i)
outputs = torch.reshape(outputs, (-1, 3, 10, 10))
writer.add_images("output_pool", outputs, global_step=i)
i += 1
writer.close()
神经网络-非线性激活
接下来是非线性激活,它的作用是给网络引入非线性。这里是对 torch.nn 中的 Non-linear Activations (weighted sum, nonlinearity) 和 Non-linear Activations (other) 进行介绍。
torch.nn.ReLU(inplace=False)
参数 inplace 表示是否原地修改:
inplace=True:原地修改。例如 input=-1,执行 ReLU(input, inplace=True) 后,input 变为 0;inplace=False:不改动原值,结果由返回值给出。例如 input=-1,执行 output=ReLU(input, inplace=False) 后,input 仍为 -1,output 为 0。
基于上面代码拓展,新添加了 ReLU:
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("../data", train=False, transform=torchvision.transforms.ToTensor(), download=False)
dataloader = DataLoader(dataset=dataset, batch_size=64, shuffle=True, num_workers=0, drop_last=False)
class mutou(nn.Module):
def __init__(self):
super(mutou, self).__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, padding=0)
self.maxpool = nn.MaxPool2d(kernel_size=3, ceil_mode=False)
self.relu = nn.ReLU()
def forward(self, x):
x = self.conv1(x)
x = self.maxpool(x)
x = self.relu(x)
return x
mutou = mutou()
writer = SummaryWriter("logs")
i=0
for data in dataloader:
imgs, targets = data
print(imgs.shape)
outputs = mutou(imgs)
print(outputs.shape)
writer.add_images("input_relu", imgs, global_step=i)
outputs = torch.reshape(outputs, (-1, 3, 10, 10))
writer.add_images("output_relu", outputs, global_step=i)
i += 1
writer.close()
神经网络-线性层及其他层介绍
网络的最后通常要把特征映射成分类输出,这就要用到线性层。这里是对 torch.nn 中的 Linear Layers 进行介绍。
torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)
Parameters 参数
-
in_features (int) – 每个输入样本的大小
-
out_features (int) – 每个输出样本的大小
-
bias (bool) – 如果设置为
False,则层将不会学习加性偏差。默认:True
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("../data", train=False, transform=torchvision.transforms.ToTensor(),
download=False)
dataloader = DataLoader(dataset=dataset, batch_size=64, shuffle=True, num_workers=0, drop_last=True)
class mutou(nn.Module):
def __init__(self):
super(mutou, self).__init__()
self.linear1 = nn.Linear(196608, 10)
def forward(self, input):
output = self.linear1(input)
return output
mutou = mutou()
for data in dataloader:
imgs, targets = data
print(imgs.shape)
output = torch.flatten(imgs)
print(output.shape)
output = mutou(output)
print(output.shape)
线性层其实就是将所有的参数最后浓缩为一个几维的向量,这个几维根据你是几分类来决定。
最后再经过 softmax 函数变为概率。
神经网络-搭建小实战和 sequential 的使用
各种层都认识了,这一节把它们串起来搭一个完整的小网络。这里是对 torch.nn 中的 Containers 中的 Sequential 进行介绍。
torch.nn.Sequential(args: Module*)
这个模块的作用就是一个顺序容器,模块将按照它们在构造函数中传递的顺序添加到其中。
def forward(self, input):
input = self.conv1(input)
input = self.maxpool1(input)
input = self.conv2(input)
input = self.maxpool2(input)
input = self.conv3(input)
input = self.maxpool3(input)
input = self.flatten(input)
# input = self.fc(input)
# input = self.fc2(input)
return input
xd = xd()
print(xd)
input = torch.ones((64, 3, 32, 32))
output = xd(input)
print(output.shape)
以下是一个简单的卷积神经网络:
import torch
from torch import nn
from torch.utils.tensorboard import SummaryWriter
class xd(nn.Module):
def __init__(self):
super(xd, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels=3, out_channels=32, kernel_size=5, stride=1, padding=2),
nn.MaxPool2d(kernel_size=2),
nn.Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
nn.MaxPool2d(kernel_size=2),
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=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, input):
input = self.model(input)
return input
xd=xd()
print(xd)
input = torch.ones((64, 3, 32, 32))
output = xd(input)
print(output.shape)
writer = SummaryWriter("logs_seq")
writer.add_graph(xd, input)
writer.close()
这里主要使用了 sequential 的功能,然后使用了 tensorboard 的 add_graph,里面的图很帅!每个框框双击还能展开,里面有详尽的过程图。

损失函数与反向传播
网络能输出结果之后,还需要衡量输出与目标之间的差距,并据此反向传播梯度,这就是损失函数的工作。这里是对 torch.nn 中的 Loss Functions 进行介绍。
L1 损失函数
torch.nn.L1Loss(reduction='mean')
- reduction (str, optional) – 指定要应用于输出的缩减:
'none'|'mean'|'sum'。'none': 不应用缩减,'mean': 输出总和除以输出中的元素数,'sum': 输出将求和。注意:size_average并且reduce正在被弃用,同时,指定这两个 args 中的任何一个都将覆盖reduction。默认:'mean'
import torch
from torch import nn
inputs =torch.tensor([1,2,3],dtype=torch.float32)
labels = torch.tensor([1,2,5],dtype=torch.float32)
inputs = torch.reshape(inputs, (1,1,1,3))
labels = torch.reshape(labels, (1,1,1,3))
loss = nn.L1Loss()
L1Loss = loss(inputs, labels)
print(L1Loss)
#输出为:tensor(0.6667)
平方差损失函数(MSELoss)
torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
import torch
from torch import nn
inputs =torch.tensor([1,2,3],dtype=torch.float32)
labels = torch.tensor([1,2,5],dtype=torch.float32)
inputs = torch.reshape(inputs, (1,1,1,3))
labels = torch.reshape(labels, (1,1,1,3))
loss_mse = nn.MSELoss()
MSELoss = loss_mse(inputs, labels)
print(MSELoss)
#输出为:tensor(1.3333)
交叉熵损失函数(CrossEntropyLoss)
torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean', label_smoothing=0.0)
调用形式:
loss = nn.CrossEntropyLoss()
Loss = loss(inputs, labels)
反向传播
反向传播其实就一行指令:
loss_result.backward()
整体代码:
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("../data", train=False, transform=torchvision.transforms.ToTensor(),
download=False)
dataloader = DataLoader(dataset=dataset, batch_size=64, shuffle=True, num_workers=0, drop_last=True)
class xd(nn.Module):
def __init__(self):
super(xd, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels=3, out_channels=32, kernel_size=5, stride=1, padding=2),
nn.MaxPool2d(kernel_size=2),
nn.Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
nn.MaxPool2d(kernel_size=2),
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=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, input):
input = self.model(input)
return input
loss = nn.CrossEntropyLoss()
xd=xd()
for data in dataloader:
imgs, targets = data
outputs = xd(imgs)
loss_result = loss(outputs, targets)
loss_result.backward()
print('ok')
先设定损失函数,调用损失函数并且计算出结果,然后再反向传播。
优化器
算出了损失、反传了梯度,最后还要靠优化器来真正更新参数。优化器不在 torch.nn 中了,而是集中在 torch.optim 中。
调用方法:
for input, target in dataset:
optimizer.zero_grad() #每次循环对梯度清零
output = model(input)
loss = loss_fn(output, target)
loss.backward()
optimizer.step() #根据梯度来调整
完整结构中的体现:
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("../data", train=False, transform=torchvision.transforms.ToTensor(),
download=False)
dataloader = DataLoader(dataset=dataset, batch_size=64, shuffle=True, num_workers=0, drop_last=True)
class xd(nn.Module):
def __init__(self):
super(xd, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels=3, out_channels=32, kernel_size=5, stride=1, padding=2),
nn.MaxPool2d(kernel_size=2),
nn.Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
nn.MaxPool2d(kernel_size=2),
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=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, input):
input = self.model(input)
return input
loss = nn.CrossEntropyLoss()
xd=xd()
optim = torch.optim.SGD(xd.parameters(), lr=0.01) #设置优化器
for epoch in range(10):
for data in dataloader:
imgs, targets = data
outputs = xd(imgs)
loss_result = loss(outputs, targets)
optim.zero_grad() #每次循环对梯度清零
loss_result.backward()
optim.step() #根据梯度来调整
print(loss_result)
现有网络模型的使用及修改
除了从零搭建网络,也可以直接拿 torchvision 提供的现成模型来改。这里是围绕 PyTorch Domains —> torchvision —> Models and pre-trained weights 进行展开介绍。
import torchvision
from torch import nn
vgg16_false = torchvision.models.vgg16(pretrained=False)
vgg16_true = torchvision.models.vgg16(pretrained=True)
print(vgg16_true)
train_data = torchvision.datasets.CIFAR10('../data', train=True, transform=torchvision.transforms.ToTensor(),
download=True)
vgg16_true.classifier.add_module('add_linear', nn.Linear(1000, 10)) #在模型中加层
print(vgg16_true)
print(vgg16_false)
vgg16_false.classifier[6] = nn.Linear(4096, 10)#修改模型中的层
print(vgg16_false)
网络模型的保存与读取
模型改好或训练好之后,需要保存下来,之后再读取使用。保存有两种方式,读取时要与保存方式一一对应。
保存
import torch
import torchvision
vgg16 = torchvision.models.vgg16(pretrained=False)
# 保存方式1,模型结构+模型参数
torch.save(vgg16, "vgg16_method1.pth")
# 保存方式2,模型参数(官方推荐)
torch.save(vgg16.state_dict(), "vgg16_method2.pth")
读取
import torch
# 方式1-->保存方式1,加载模型
model = torch.load("vgg16_method1.pth")
# print(model)
# 方式2,加载模型
vgg16 = torchvision.models.vgg16(pretrained=False)
vgg16.load_state_dict(torch.load("vgg16_method2.pth"))
# model = torch.load("vgg16_method2.pth")
print(vgg16)
完整模型训练套路
前面各节都是零件:数据、网络、损失函数、优化器。这一节把它们组装成一个完整的训练流程,网络结构单独放在 model.py 中。
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from model import *
# 准备数据集
train_data = torchvision.datasets.CIFAR10(root="../data", train=True, transform=torchvision.transforms.ToTensor(),
download=True)
test_data = torchvision.datasets.CIFAR10(root="../data", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
# 长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# 如果 train_data_size=10,训练数据集的长度为:10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))
# 利用 DataLoader 来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
# 创建网络模型
tudui = Tudui()
# 损失函数
loss_fn = nn.CrossEntropyLoss()
# 优化器
optimizer = torch.optim.SGD(tudui.parameters(), lr=0.01)
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 添加tensorboard
writer = SummaryWriter("./logs_train")
# 训练的轮数
epoch = 10
for i in range(epoch):
print("-------第 {} 训练开始-------".format(i + 1))
# 训练步骤开始
for data in train_dataloader:
imgs, targets = data
outputs = tudui(imgs)
loss = loss_fn(outputs, targets)
# 优化器优化模型
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_train_step = total_train_step + 1
if total_train_step % 100 == 0:
print("训练次数:{}, Loss:{}".format(total_train_step, loss.item()))
writer.add_scalar("train_loss", loss.item(), total_train_step)
# 测试步骤开始
total_test_loss = 0
total_accuracy = 0
with torch.no_grad():
for data in test_dataloader:
imgs, targets = data
outputs = tudui(imgs)
loss = loss_fn(outputs, targets)
total_test_loss = total_test_loss + loss.item()
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy = total_accuracy + accuracy
print("整体测试集上的Loss:{}".format(total_test_loss))
print("整体测试集上的正确率:{}".format(total_accuracy / test_data_size))
writer.add_scalar("test_loss", total_test_loss, total_test_step)
writer.add_scalar("test_accuracy", total_accuracy, total_test_step)
total_test_step = total_test_step + 1
torch.save(tudui, "tudui_{}.pth".format(i))
print("模型已保存")
writer.close()
整体思路流程:准备数据集 —> 用 DataLoader 加载数据 —> 创建网络模型 —> 设置损失函数和优化器 —> 循环训练(前向计算、求损失、梯度清零、反向传播、更新参数)—> 每轮结束后在测试集上计算整体 Loss 和正确率 —> 保存模型。
利用 GPU 进行训练
训练流程跑通后,可以把计算搬到 GPU 上加速,有两种写法。
方法一
将网络模型、数据、损失函数调用 .cuda(),对应代码中所有 if torch.cuda.is_available(): 的部分:
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from model import *
# 准备数据集
train_data = torchvision.datasets.CIFAR10(root="../data", train=True, transform=torchvision.transforms.ToTensor(),
download=True)
test_data = torchvision.datasets.CIFAR10(root="../data", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
# 长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# 如果 train_data_size=10,训练数据集的长度为:10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))
# 利用 DataLoader 来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
# 创建网络模型
tudui = Tudui()
if torch.cuda.is_available():
print("使用GPU")
tudui = tudui.cuda()
# 损失函数
loss_fn = nn.CrossEntropyLoss()
if torch.cuda.is_available():
print("使用GPU")
loss_fn = loss_fn.cuda()
# 优化器
optimizer = torch.optim.SGD(tudui.parameters(), lr=0.01)
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 添加tensorboard
writer = SummaryWriter("./logs_train")
# 训练的轮数
epoch = 10
for i in range(epoch):
print("-------第 {} 训练开始-------".format(i + 1))
# 训练步骤开始
for data in train_dataloader:
imgs, targets = data
if torch.cuda.is_available():
print("使用GPU")
imgs = imgs.cuda()
targets = targets.cuda()
outputs = tudui(imgs)
loss = loss_fn(outputs, targets)
# 优化器优化模型
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_train_step = total_train_step + 1
if total_train_step % 100 == 0:
print("训练次数:{}, Loss:{}".format(total_train_step, loss.item()))
writer.add_scalar("train_loss", loss.item(), total_train_step)
# 测试步骤开始
total_test_loss = 0
total_accuracy = 0
with torch.no_grad():
for data in test_dataloader:
imgs, targets = data
if torch.cuda.is_available():
print("使用GPU")
imgs = imgs.cuda()
targets = targets.cuda()
outputs = tudui(imgs)
loss = loss_fn(outputs, targets)
total_test_loss = total_test_loss + loss.item()
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy = total_accuracy + accuracy
print("整体测试集上的Loss:{}".format(total_test_loss))
print("整体测试集上的正确率:{}".format(total_accuracy / test_data_size))
writer.add_scalar("test_loss", total_test_loss, total_test_step)
writer.add_scalar("test_accuracy", total_accuracy, total_test_step)
total_test_step = total_test_step + 1
torch.save(tudui, "tudui_{}.pth".format(i))
print("模型已保存")
writer.close()
方法二(更常用)
定义一个 device,然后模型、损失函数、数据都调用 .to(device):
对于模型和损失函数:tudui = tudui.to(device) 和 tudui.to(device) 一样,也就是不用再重新赋值。
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from model import *
#定义训练设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 准备数据集
train_data = torchvision.datasets.CIFAR10(root="../data", train=True, transform=torchvision.transforms.ToTensor(),
download=True)
test_data = torchvision.datasets.CIFAR10(root="../data", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
# 长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# 如果 train_data_size=10,训练数据集的长度为:10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))
# 利用 DataLoader 来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
# 创建网络模型
tudui = Tudui()
tuidui = tudui.to(device)
# 损失函数
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.to(device)
# 优化器
optimizer = torch.optim.SGD(tudui.parameters(), lr=0.01)
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 添加tensorboard
writer = SummaryWriter("./logs_train")
# 训练的轮数
epoch = 10
for i in range(epoch):
print("-------第 {} 训练开始-------".format(i + 1))
# 训练步骤开始
for data in train_dataloader:
imgs, targets = data
imgs = imgs.to(device)
targets = targets.to(device)
outputs = tudui(imgs)
loss = loss_fn(outputs, targets)
# 优化器优化模型
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_train_step = total_train_step + 1
if total_train_step % 100 == 0:
print("训练次数:{}, Loss:{}".format(total_train_step, loss.item()))
writer.add_scalar("train_loss", loss.item(), total_train_step)
# 测试步骤开始
total_test_loss = 0
total_accuracy = 0
with torch.no_grad():
for data in test_dataloader:
imgs, targets = data
imgs = imgs.to(device)
targets = targets.to(device)
outputs = tudui(imgs)
loss = loss_fn(outputs, targets)
total_test_loss = total_test_loss + loss.item()
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy = total_accuracy + accuracy
print("整体测试集上的Loss:{}".format(total_test_loss))
print("整体测试集上的正确率:{}".format(total_accuracy / test_data_size))
writer.add_scalar("test_loss", total_test_loss, total_test_step)
writer.add_scalar("test_accuracy", total_accuracy, total_test_step)
total_test_step = total_test_step + 1
torch.save(tudui, "tudui_{}.pth".format(i))
print("模型已保存")
writer.close()
完整的模型验证套路
模型训练好并保存之后,最后一步是加载它对真实图片做预测,检验实际效果。
import torch
import torchvision
from PIL import Image
from torch import nn
#加载需要识别的图片
image_path ="./imgs/dog.png"
image = Image.open(image_path)
print(image)
#将图片转换为模型能运行的样子
transform =torchvision.transforms.Compose([torchvision.transforms.Resize((32,32)),
torchvision.transforms.ToTensor()])
image = transform(image)
print(image.shape)
class Tudui(nn.Module):
def __init__(self):
super(Tudui, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3, 32, 5, 1, 2), # 输入通道数为3,输出通道数为32,卷积核大小为5x5,步长为1,填充为2
nn.MaxPool2d(2), # 最大池化层,池化窗口大小为2x2
nn.Conv2d(32, 32, 5, 1, 2), # 输入通道数为32,输出通道数为32,卷积核大小为5x5,步长为1,填充为2
nn.MaxPool2d(2), # 最大池化层,池化窗口大小为2x2
nn.Conv2d(32, 64, 5, 1, 2), # 输入通道数为32,输出通道数为64,卷积核大小为5x5,步长为1,填充为2
nn.MaxPool2d(2), # 最大池化层,池化窗口大小为2x2
nn.Flatten(), # 将多维张量展平为一维
nn.Linear(64 * 4 * 4, 64), # 全连接层,输入节点数为64 * 4 * 4,输出节点数为64
nn.Linear(64, 10) # 连接层,输入节点数为64,输出节点数为10
)
def forward(self, x):
x = self.model(x)
return x
#加载模型
model=torch.load("tudui_5.pth")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
print(model)
image = torch.reshape(image,(1,3,32,32))
image = image.to(device) # Move image to same device as model
#将图片喂给模型
model.eval()
with torch.no_grad():
output = model(image)
print(output)
#输出结果
print(output.argmax(1))