PyTorch深度学习快速入门教程(绝对通俗易懂!)小土堆

根据B站视频整理的笔记,看完基本可以入门Pytorch。

1 环境配置

检查显卡:

  • 在命令行底部右键打开任务管理器
    • 也可以查看到GPU的型号

conda

#conda

  • 配置一个特定的环境
1
conda create -n [env name] python=[python version]
  • 激活环境
1
conda activate [env name]
  • 查看创建过的环境
1
conda info -e

技巧pip

  • 查看工具包
1
pip list

安装Pytorch

  • 检查电脑的GPU是否支持pytorch
    打开命令行,输入nvidia-smi
  • 查看驱动版本 Driver Version
    • 需要保持版本号大于coda的需求的
    • 如果不满足,可以去英伟达的官网更新驱动

选好后,输入conda命令即可

检验安装

1
2
3
>>python
>>import torch # 没有报错就是安装成功
>>torch.cuda.is_available() # 检查是否可以使用GPU
  • torch.cuda.is_available()返回False
    进行以下步骤进行排除:
  1. 进入https://www.nvidia.cn/geforce/technologies/cuda/supported-gpus/

    1. 检查是否支持cuda
  2. 检查驱动版本nvidia-smi

    1. 不够高就去更新

在正常使用一段时间后,安装各种包突然又返回False或者各种冲突。
解决方案:

  • 卸载torch全部重来:conda remove pytorch torchvision

2 编辑器的选择

PyCharm

配置PyCharm

一些技巧

python的console,可以检查一些变量或者一些命令、方法,简便直观。

Jupyter

jupyter 配置

在安装cuda的时候,这个默认安装在base的环境中。但是base中没有安装torch。可以再在base里面安装一次torch,但是还是在之前安装的torch环境中安装一下jupyter吧~

  1. 安装一个个包
    nb_conda 是一个用于 Jupyter Notebook 的插件,它可以让你在 Notebook 中使用 Conda 环境。通过运行 conda install nb_conda,你可以将这个插件安装到你的 Conda 环境中,然后在 Jupyter Notebook 中使用。这样你就可以方便地在 Notebook 中管理和切换不同的 Conda 环境了。
1
conda install nb_conda
  1. 在命令行中切换到对应的项目目录
    最开始在C盘
  1. 创建项目

3 Python的两大法宝函数

  • dir(): 打开,看见
  • help(): 说明书

4 浅对比PyCharm,python控制台和Jupyter

  1. rerun的区别
  • PyCharm会全部重新运行。
  • 控制台:从错误的地方开始运行
  • notebook:任意行为块,每一块重运行。

5 PyTorch加载数据

  • Dataset
  • Dataaloader

Dataset:

  • 获取的数据是混乱的,但是可以进行编号
  • 可以获取数据和label
    • 如何获取每一个数据和label
    • 总共有多少个数据

Dataloader:

  • 对Dataset进行打包
  • 提供不同的数据形式

#os的用法

  • os.path.join(dir1,dir2):可以根据系统自动拼接地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
'''  
@Project :pythonProject @File :read_data.py
@IDE :PyCharm @Author :周大猛
@Date :2024/05/02 23:50 '''
from torch.utils.data import Dataset
from PIL import Image
import os

class MyDataset(Dataset):
def __init__(self, root_dir, label_dir):
self.root_dir = root_dir
self.label_dir = label_dir
self.path = os.path.join(root_dir, label_dir)
self.img_path = os.listdir(self.path)
def __getitem__(self, index):
"""
读取每一个图片
:param index: :return:
""" img_name = self.img_path[index]
img_item_path = os.path.join(self.path, img_name)
img = Image.open(img_item_path)
label = self.label_dir
return img, label

def __len__(self):
"""获得数据集的长度"""
return len(self.img_path)


if __name__ == '__main__':
root_dir = "dataset/train"
ants_label_dir = "ants"
bees_label_dir = "bees"
ants_dataset = MyDataset(root_dir,ants_label_dir)
bees_dataset = MyDataset(root_dir,bees_label_dir)

# 拼接数据集,按顺序拼接。
train_loader = ants_dataset + bees_dataset

5.1 TensorBoard的使用

  • 对图像进行变化:统一尺寸等
  • 对图像进行展示

SummaryWriter

原文部分介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class SummaryWriter:  
"""Writes entries directly to event files in the log_dir to be consumed by TensorBoard.

The `SummaryWriter` class provides a high-level API to create an event file in a given directory and add summaries and events to it. The class updates the file contents asynchronously. This allows a training program to call methods to add data to the file directly from the training loop, without slowing down training. """
def __init__(
self,
log_dir=None,
comment="",
purge_step=None,
max_queue=10,
flush_secs=120,
filename_suffix="",
):
"""Create a `SummaryWriter` that will write out events and summaries to the event file.

Args: log_dir (str): Save directory location. Default is runs/**CURRENT_DATETIME_HOSTNAME**, which changes after each run. Use hierarchical folder structure to compare between runs easily. e.g. pass in 'runs/exp1', 'runs/exp2', etc. for each new experiment to compare across them. comment (str): Comment log_dir suffix appended to the default ``log_dir``. If ``log_dir`` is assigned, this argument has no effect. purge_step (int): When logging crashes at step :math:`T+X` and restarts at step :math:`T`, any events whose global_step larger or equal to :math:`T` will be purged and hidden from TensorBoard. Note that crashed and resumed experiments should have the same ``log_dir``. max_queue (int): Size of the queue for pending events and summaries before one of the 'add' calls forces a flush to disk. Default is ten items. flush_secs (int): How often, in seconds, to flush the pending events and summaries to disk. Default is every two minutes. filename_suffix (str): Suffix added to all event filenames in the log_dir directory. More details on filename construction in tensorboard.summary.writer.event_file_writer.EventFileWriter.
Examples::
from torch.utils.tensorboard import SummaryWriter
# create a summary writer with automatically generated folder name. writer = SummaryWriter() # folder location: runs/May04_22-14-54_s-MacBook-Pro.local/
# create a summary writer using the specified folder name. writer = SummaryWriter("my_experiment") # folder location: my_experiment
# create a summary writer with comment appended. writer = SummaryWriter(comment="LR_0.1_BATCH_16") # folder location: runs/May04_22-14-54_s-MacBook-Pro.localLR_0.1_BATCH_16/
"""

三种用法

  1. 默认保存到一个路径 writer = SummaryWriter() # folder location: runs/May04_22-14-54_s-MacBook-Pro.local/
  2. 自定义保存到的文件夹 writer = SummaryWriter("my_experiment") # folder location: my_experiment
  3. 可以对文件名加入一些comments writer = SummaryWriter(comment="LR_0.1_BATCH_16") # folder location: runs/May04_22-14-54_s-MacBook-Pro.localLR_0.1_BATCH_16/

writer.add_scalar()

效果

打开方法:

  • 指定路径 --logdir
  • 指定端口 --port
1
tensorboard --logdir=[logs] --port=[6007]

但是运行多次之后可能显示图像会出bug,可以选择删掉之前的log

writer.add_image()

  • 读取图片
  • 识别类型:
    • numpy
    • tensor
    • string

但是我们常用的PIL的Image是JpegImageFile类型,所以不符合,需要转换,或者直接用别的方法读取图片,如OpenCV

在使用numpy读取图片的时候,每个通道的顺序可能与add_image默认的顺序不一样,可以ctrl进入add_image查看手册,手动设定通道顺序

1
2
3
4
5
6
7
8
9
10
11
from PIL import Image  
from torch.utils.tensorboard import SummaryWriter
import numpy as np

writer = SummaryWriter('logs')
image_path = "dataset/train/ants/5650366_e22b7e1065.jpg"
iamge_PIL = Image.open(image_path)
img_array = np.array(iamge_PIL) # 但是这里形状不对,np读出来之后,通道在最后:(375, 500, 3)
print(img_array.shape)

writer.add_image("image", img_array, 1,dataformats='HWC') # 根据官方文档里面,指定type的顺序

修改add_image的第二个参数step,可以在进度条处出现拉出新的图

writer.add_graph(net,input)

可以查看网络的结构

1
2
3
4
# 查看网络结构的方法  
writer = SummaryWriter("./logs")
writer.add_graph(net,input)
writer.close()

5.2 Transform

指的是:transforms.py文件,里面又很多的“工具”:

  • toTensor
  • resize

拿特定格式的图片,丢进去,得到需要的图片结果。

引入的方式

1
from torchvision import transforms

ToTensor

1
2
tensor_trans = transforms.ToTensor() # 实例化这个工具  
tensor_img = tensor_trans(image) # 使用这个工具,输出一个结果

为什么需要Tensor这个数据类型?

  • tensor包含了神经网络中使用的一些参数

另一种读取方式:nparray
使用OpenCV.

导入opencv的方法:

1
pip install opencv-python

归一化Normalization

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

均值和标准差都是0.5

1
2
3
4
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('Normal_img', img_norm)

Resize()

1
2
3
4
5
6
7
8
9
10
# Resize  
print(img.size)
trans_resize = transforms.Resize((512,512))
# img PIL --> resize --> img_resize PIL
img_resize = trans_resize(img)
# img_resize PIL --》to_tensor --> img_resize tensor
img_resize = trans_totensor(img_resize)
writer.add_image('Resize_img', img_resize,0)
print(img_resize)
writer.close()

Compose()

1
2
3
4
5
6
# Compose - resize -2  
trans_resize_2 = transforms.Resize(64)

# 这里列表的顺序需要保证前一个的输出类型是后一个的输入类型。
trans_compose = transforms.Compose([trans_resize_2,trans_totensor,])
img_resize_2 = trans_compose(img)

RandomCrop()随机裁剪

按照设定的尺寸随机在图片内裁剪规定尺寸大小的图片。

1
2
3
4
5
6
# RandomCrop  
trans_random = transforms.RandomCrop(64)
trans_compose_2 = transforms.Compose([trans_random,trans_totensor,])
for i in range(10):
img_crop = trans_compose_2(img)
writer.add_image('Random_img', img_crop,i)

总结

  • 关注输入和输出的类型
  • 多看官方文档
  • 看初始化的参数
  • 输出类型可以print查看,或者debug

6 Torchvision的数据集使用

官网链接

如果下载速度太慢,可以将下载路径粘贴到迅雷中进行下载。

数据集的参数设置很多都是相同的,教程中以CIFAR10为例:

  • 设置数据集路径
  • 设置训练or测试集合
  • transform要做的操作
  • download:是否要网络下载(准备好了就False,没准备就True)
1
2
3
4
import torchvision  

train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True)
test_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=True)

可以在transform参数,设置对数据集的操作,也是可以打包送进去的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import torchvision  
from torch.utils.tensorboard import SummaryWriter

dataset_transform = torchvision.transforms.Compose([

torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),

])

train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=dataset_transform)
test_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=dataset_transform)

print(test_set[0])

writer = SummaryWriter(log_dir='./logs')

for i in range(10):
img, label = test_set[i]
writer.add_image('test_img', img, i)

7 Dataloader的使用

将数据加载到神经网络中。

如何取数据可以由Dataloader进行设置。

常用参数设置:

  • batch_size
  • shuffle:洗牌
  • num_workers:多少个进行进行加载(但是win上有时候出现错误)
  • drop_last:分组除不尽的时是否舍去一些数据。

DataLoader会分别把数据集的数据和label,按照batch_size的大小,进行打包。

如果设置了shuffle,一个epoch打乱一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

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

data_transforms = transforms.Compose([transforms.ToTensor(),])
test_data = torchvision.datasets.CIFAR10(root='./data', train=False, download=True,transform=data_transforms)

test_loader = DataLoader(dataset=test_data, batch_size=64, shuffle=True,num_workers=0, drop_last=False)


img, label = test_data[0]
print(img.shape)
print(label)

writer = SummaryWriter('./logs')

# 多次让dataloader取数据,shuffle就会在每次的epoch影响取值,True会打乱数据集
for epoch in range(10):
step = 0
for data in test_loader:
imgs, labels = data
# 注意这里用的是images
writer.add_images('Epoch:{}'.format(epoch), imgs,step)
step += 1

writer.close()

8 网络搭建

https://pytorch.org/docs/stable/nn.html

8.1 Containers

最常用的模块,提供神经网络的最基本的框架

nn.Module

https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import torch.nn as nn
import torch.nn.functional as F

class Model(nn.Module):
def __init__(self):
# 调用父类的初始化
super().__init__()

self.conv1 = nn.Conv2d(1, 20, 5)
self.conv2 = nn.Conv2d(20, 20, 5)

def forward(self, x):
# 神经网络的前向传播
x = F.relu(self.conv1(x))
return F.relu(self.conv2(x)) # 这个demo进行了两次非线性卷积

8.2 卷积层操作与卷积层

(1)卷积操作

基本原理

在全连接(Affine)层中存在忽略了数据形状,它直接将整个图片拉成一维数据输入到了神经网络。

因此导致了,形状中含有的空间信息被忽略。

卷积层的优点就是,可以保持形状的不变,或许能更好的理解图片的形状信息。

卷积层的输入输出被称为特征图,。
!PyTorch深度学习快速入门教程(绝对通俗易懂!)小土堆/Pasted image 20240513161728.png]]
主要用torch.nn 的部分,对functional封装更好

卷积核:(类似图像处理的滤波器)一个小矩阵,对图像矩阵进行一坨一坨的计算

  • stride = 滤波器每次移动的举例
    • 会影响最后得到的卷积输出的形状
    • 越大,输出的矩阵越小(?)

官网参数介绍:conv2d

  • input – input tensor of shape (minibatch,in_channels,𝑖𝐻,𝑖𝑊)(minibatch,in_channels,iH,iW)

    要设置batch的带线啊哦

  • weight – filters of shape (out_channels,in_channelsgroups,𝑘𝐻,𝑘𝑊)(out_channels,groupsin_channels​,kH,kW)

  • bias – optional bias tensor of shape (out_channels)(out_channels). Default: None

  • stride – the stride of the convolving kernel. Can be a single number or a tuple (sH, sW). Default: 1

  • padding (在图像左右两边对图像进行填充)–

    implicit paddings on both sides of the input. Can be a string {‘valid’, ‘same’}, single number or a tuple (padH, padW). Default: 0 padding='valid' is the same as no padding. padding='same' pads the input so the output has the same shape as the input. However, this mode doesn’t support any stride values other than 1.
    填充的内容默认为0
    也会对输出结构造成影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import torch  
import torch.nn.functional as F

input = torch.tensor([[1,2,0,3,1],
[0,1,2,3,1],
[1,2,1,0,0],
[5,2,3,1,1],
[2,1,0,1,1]])

kernel = torch.tensor([[1,2,1],
[0,1,0],
[2,1,0]])

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


# 转换成nn.conv需要的形状
input = torch.reshape(input,(1,1,5,5)) # shape:batch,通道,长,宽
kernel = torch.reshape(kernel,(1,1,3,3))

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

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

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

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

(2)卷积层

https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html#torch.nn.Conv2d

Parameters

  • in_channels (int) – Number of channels in the input image

  • out_channels (int) – Number of channels produced by the convolution
    》 有几个卷积核,就会导致输出有几个维度,也就是channels

  • kernel_size (int or tuple) – Size of the convolving kernel

  • stride (int or tuple, optional) – Stride of the convolution. Default: 1

  • padding (int, tuple or str, optional) – Padding added to all four sides of the input. Default: 0

  • padding_mode (str, optional) – 'zeros''reflect''replicate' or 'circular'. Default: 'zeros'

  • dilation (int or tuple, optional) – Spacing between kernel elements. Default: 1

  • groups (int, optional) – Number of blocked connections from input channels to output channels. Default: 1

  • bias (bool, optional) – If True, adds a learnable bias to the output. Default: True

根据这两个公式,推到论文中的padding和stride

#padding计算 #stride计算
如果卷积前后尺寸不变,padding = (卷积核尺寸-1)/2

#批处理
批处理:这里将多个图像打包成一个batch,让图像变成了四维数据(batch_num, channel,height,width)进行计算,加快运算效率

8.3 池化

#池化

特征

  • 无需学习参数(与卷积的不同)
    • 只是从目标区域获得最大值或者平均值
  • 通道数不发生变化
    • 计算按照通道独立进行
  • 对微笑的数据位置变化具有鲁棒性(健壮)

Parameters

  • kernel_size (Union[int, Tuple[int, int]__]) – the size of the window to take a max over

  • stride (Union[int, Tuple[int, int]__]) – the stride of the window. Default value is kernel_size

  • padding (Union[int, Tuple[int, int]__]) – Implicit negative infinity padding to be added on both sides

  • dilation (Union[int, Tuple[int, int]__]) – a parameter that controls the stride of elements in the window

  • return_indices (bool) – if True, will return the max indices along with the outputs. Useful for torch.nn.MaxUnpool2d later

  • ceil_mode (bool) – when True, will use ceil instead of floor to compute the output shape

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import torch  
import torch.nn as nn
import torch.nn.functional as F
import torchvision.datasets as datasets
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms

dataset = datasets.CIFAR10(root='./data', train=False, download=True,transform=transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=64, shuffle=True)
# 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]
# ],dtype=torch.float32)
#
# input = torch.reshape(input,(-1,1,5,5))
# print(input.shape)

class T(nn.Module):
def __init__(self):
super(T, self).__init__()
self.pool = nn.MaxPool2d(3,ceil_mode=False)

def forward(self, x):
output = self.pool(x)

return output


tt = T()
# result = tt(input)
# print(result.shape)
# print(result)
writer = SummaryWriter('./maxpool_logs')
step = 0
for data in dataloader:
imgs, labels = data
writer.add_image('Input', imgs,step,dataformats='NCHW')
outputs = tt(imgs)
"""
最大池化不会改变形状,
所以不用像卷积那样还要将得到的图片进行reshape
""" writer.add_image("Output", outputs,step,dataformats='NCHW')

step += 1

writer.close()

8.4 非线性激活

(1) ReLu(Rectified Linear Unit)

$$
y =
\begin{cases}
x & \text{if } x > 0 \
0 & \text{if } x \leq 0
\end{cases}
\tag{1}
$$
$$
\frac{\partial y}{\partial x} =
\begin{cases}
1 & \text{if } x > 0 \
0 & \text{if } x \leq 0
\end{cases}
\tag{2}
$$

这个inplace(替换):

  • True:直接把变换后的值,放到input的那个变量里面
  • False:把变换后的值,需要一个新的变量来接收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch  

i = torch.tensor([[-1, 2, 3], [4, -5, 6], [7, 8, -9]])
i = torch.reshape(i,(-1,1,3,3))

class DemoModule(torch.nn.Module):
def __init__(self):
super(DemoModule, self).__init__()
self.relu1 = torch.nn.ReLU(inplace=False)

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

mod = DemoModule()
output = mod(i)
print(output)

(2)Sigmoid

$$
\text{Sigmoid}(x) = \sigma(x) = \frac{1}{1 + \exp(-x)}
$$

8.5 Linear model

nn.Linear
Parameters

  • in_features (int) – size of each input sample

  • out_features (int) – size of each output sample(下一层要输出的个数)

  • bias (bool) – If set to False, the layer will not learn an additive bias. Default: True

就是全连接层。把数据摊平之后,进行kx+bias的变化,再输出到指定数目的节点去。

#torchflatten
torch.flatten:把数据展开到一维

9 损失函数与反向传播

损失函数

神经网络通过学习损失函数(Loss Function)寻找最优权重参数。

  1. 计算实际输出和目标之间的差距
  2. 为更新输出提供依据(反向传播),grad(梯度)

#损失函数
常用的损失函数:

  • 均方误差(mean squared erro)
  • 交叉熵误差

只计算对应正确解标签的输出的自然对数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch.nn as nn  
import torch
loss = nn.L1Loss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
print(input)
print(target)
output = loss(input, target)
print(output)

loss_mes = nn.MSELoss()

result_mse = loss_mes(input, target)
print(result_mse)

反向传播

10 优化器

#优化器
用backward进行反向传播,计算出每一个节点的参数,有了参数梯度之后,就可以选择合适的优化器进行优化,对loss达到一个降低的目的。

https://pytorch.org/docs/stable/optim.html

SGD随机梯度下降

#SGD

  • 初始化
1
2
3
4
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
optimizer = optim.Adam([var1, var2], lr=0.0001)


  • 初始化参数
1
2
3
4
optim.SGD([
{'params': model.base.parameters(), 'lr': 1e-2},
{'params': model.classifier.parameters()}
], lr=1e-3, momentum=0.9)

Pytorch中的SGD

这里的代码使用了PyTorch中的optim.SGD优化器来训练模型。这个优化器采用了随机梯度下降(Stochastic Gradient Descent,SGD)的方法,并添加了动量(momentum)来加速训练过程。

具体解释如下:

  1. optim.SGD: 这是一个优化器,它实现了随机梯度下降算法。SGD是一种常用的优化算法,用于调整模型参数以最小化损失函数。

  2. params: 这里指定了要优化的参数集合。代码中将模型的参数分成了两组,分别设置了不同的学习率(learning rate,lr)。

    • {'params': model.base.parameters(), 'lr': 1e-2}:这表示模型的基础层(base)参数使用一个学习率为0.01(1e-2)的值进行优化。
  • {'params': model.classifier.parameters()}:这表示模型的分类器(classifier)层的参数。没有指定单独的学习率,因此这些参数将使用外层的学习率1e-3。
  1. lr: 学习率是一个超参数,控制每次参数更新的步长。这里有两个学习率:

    • 1e-2(0.01)用于基础层参数。
    • 1e-3(0.001)用于分类器层参数(外层指定的学习率)。
  2. momentum: 动量是一个超参数,用于加速SGD在相关方向上的收敛,并抑制震荡。动量项在参数更新时引入了历史梯度的累积,使得优化过程更稳定。这里设置的动量值为0.9。

综上所述,这段代码的含义是使用带有动量的随机梯度下降算法来优化模型的参数,其中基础层参数的学习率设置为0.01,分类器层参数的学习率设置为0.001。动量参数设置为0.9。这样可以在训练过程中更好地控制模型的更新步长和收敛速度。

  • 使用demo
1
2
3
4
5
6
7
8
9
10
11
for input, target in dataset:
# - 在进行反向传播和梯度计算之前,先将优化器中的所有参数的梯度缓存清零。
optimizer.zero_grad()
output = model(input)
# 计算模型输出 `output` 和目标标签 `target` 之间的损失(误差)
loss = loss_fn(output, target)
# 进行反向传播,计算损失相对于模型参数的梯度
loss.backward()
# - 使用计算得到的梯度,按照优化算法更新模型的参数。
# - 这里的 `optimizer` 是前面定义的优化器(如 `optim.SGD`),它根据参数的梯度和学习率来调整参数的值,使损失函数逐渐减小,从而优化模型。
optimizer.step()

训练部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
loss = nn.CrossEntropyLoss()# 交叉熵  
net = NeuralNetwork()
optimizer = torch.optim.SGD(net.parameters(), lr=0.01)
for epoch in range(20):
running_loss = 0.0 # 每次开始前,把loss设置为0
for data in test_loader:
"""
这个for相当于只对data进行了一轮的学习,
通常需要好几轮的学习,才能有所改善。 所以需要外层的epoch
""" imgs, labels = data
outputs = net(imgs)

result_loss = loss(outputs, labels)

optimizer.zero_grad() # 对之前的记录清零
result_loss.backward()
optimizer.step()
# print(result_loss)
running_loss = running_loss + result_loss
# 整体误差总和
print("epoch:{}, loss:{}".format(epoch, running_loss))

11 现有网络模型的使用和修改

在pytorch的官方文档中,torchvision或torchtext等文件中,包含了相关领域中的经典网络模型。

11.1 VGG简介

常用的版本:

  • vgg16
    • pretrained:在ImageNet中与训练(这个数据集130G+,而且不能torchvision直接下载,需要自己寻找资源)
    • progress:下载进度条
  • vgg19

初始化的时候,True会下载参数(很大)

VGG16常被用来作为迁移学习等模型的前半部分,用于提取一些图像的特殊特征,在后续的模型中对这些特征进行一个进一步的学习。

利用现有的网络,套到自己的数据集上

VGG使用了ImageNet进行训练,输出的最后一层与ImageNet的类别数量相同,都是1000,如何把这个现有的模型改成我需要的模型?

之前的数据集为例

  • 方法一:把最后一个输出层后追加一层input为10000,output为10的线性层。
1
vgg16_false.add_module('add_linear', nn.Linear(1000, 10))

如果要加到上面那个括号(classifier)里面:

1
vgg16_false.classifier.add_module('add_linear', nn.Linear(1000, 10))
  • 方法二:直接修改模型最后一层

12 网络模型的保存与读取

12.1 保存模型与参数

这种方法可以保存模型的结构和模型的参数。
缺点:

  • 若模型较大,则保存文件也会很大

  • Save model

1
2
vgg16 = torchvision.models.vgg16(pretrained=False)
torch.save(vgg16, 'vgg16_method.pth')

第一个参数:模型
第二个参数:保存的文件名,通常用.pth作为文件类型

  • load model
1
2
# Load Method 1:  
model_1 = torch.load("vgg16_method.pth")

这个方式是存在陷阱的:
如果是自己定义了一个模型,对这个模型进行保存。则加载的时候会产生报错。

为了解决这个问题,则需要自己重新定义一次自定义的模型结构:

比如我在model文件创建并保存了数据,在load文件里面需要重新定义(无需new)一次这个模型,才能继续正常使用

12.2 保存模型参数

这个方法将模型的参数作为字典进行保存,所以加载的时候,要用字典加载的方式,放入新的模型中。

官方推荐的方法

  • Save model
1
torch.save(vgg16.state_dict(), 'vgg16_state_dict.pth')
  • load model
1
2
3
4
5
6
7
8
9
10
# Specify a path
PATH = "state_dict_model.pt"

# Save
torch.save(net.state_dict(), PATH)

# Load
model = Net()
model.load_state_dict(torch.load(PATH))
model.eval()

[!NOTE]
两种方式一定要对应。
※不知道为什么,在下个章节代码实现的时候,我无法用方法二的步骤正常创建模型

12 完整模型训练套路

以CIFAR10 为例

步骤说明:

  1. 初始化训练集、测试集,转换为Dataloader
  2. 初始化自己的模型
  3. 定义损失函数和优化器
  4. 定义训练次数 epoch
  5. 进行迭代epoch
    1. 从dataloader中每次取数据进行训练
      1. 得到output
      2. 得到output与labels的loss
      3. 优化器置零
      4. 反传播
      5. 优化器优化参数
    2. 在测试集中检测——取消grad

在流程中合适的地方对结果进行输出或者保存。

  1. 定义模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
'''  
@Project :pythonProject @File :MyModel.py
@IDE :PyCharm @Author :周大猛
@Date :2024/05/20 15:21 '''
import torch
from torch import nn


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

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


if __name__ == '__main__':
# 测试网络模型的正确性
model = TongModel()
# 64个图片,3个通道,尺寸32*32
input = torch.ones((64, 3, 32, 32))
output = model(input)
#torch.Size([64, 10]) 64个图片,10个数据表示每个类别的可能性
print(output.shape)

[!NOTE]
通常在专门的模型py文件中对模型进行定义,方便管理,修改、检查模型每层的正确性。

  1. 数据集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 准备训练数据  
train_data = torchvision.datasets.CIFAR10(root='./data', train=True, download=True,transform=transforms.ToTensor())

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

# 查看数据集信息
train_data_size = len(train_data)
test_data_size = len(test_data)
print("Train data size: {}".format(train_data_size) )
print("Test data size: %d" % test_data_size)

# 使用dataloader加载数据集
train_dataloader = DataLoader(train_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)

[!NOTE]
下载好数据集后,用dataloader进行封装。

  1. 导入神经网络,初始化模型
  • 模型初始化
  • 损失函数
  • 优化器
  • 学习率
  • 训练进度
  • 测试进度
  • 迭代次数
  • 保存数据位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from MyModel import *  

# 创建网络模型
model = TongModel()

# 损失函数和优化器
loss_fn = nn.CrossEntropyLoss()
learning_rate = 1e-2
optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=0.9, weight_decay=5e-4)

# 设置训练参数
total_train_step = 0 # 记录训练次数
total_test_step = 0 # 记录测试次数
epoch = 100 # 训练的轮数

# 使用tensorboard记录数据
writer = SummaryWriter('./logs_train')
  1. 训练与测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
for i in range(epoch):  
print('Epoch {}/{}'.format(i, epoch))

# 训练开始
model.train() # 当模型有dropout等特殊层的时候,起作用
for data in train_dataloader:
imgs, labels = data
output = model(imgs)
# 计算输出和真实的损失
loss = loss_fn(output, labels)

# 优化器优化模型
optimizer.zero_grad() # 准备优化,先梯度清零
loss.backward() #得到每个参数的梯度
optimizer.step() # 对参数进行优化
total_train_step += 1
if total_train_step % 100 == 0:
print('Total train step:{}, Loss:{}' .format(total_train_step, loss.item()))
writer.add_scalar('Loss/train', loss.item(), total_train_step)
"""
每次训练一轮之后,需要知道本次训练之后在测试集上模型表现是否有进步。
在第一层的for中,对此进行检测。
这里不需要对模型进行调优。 需要知道在整个数据集上的loss
""" model.eval() # 与train()一个情况
total_test_loss = 0.0

# 计算ACC
total_accuracy = 0
with torch.no_grad():
# 没有梯度了
# 测试开始
for data in test_dataloader:
imgs, labels = data
output = model(imgs)
loss = loss_fn(output, labels)
total_test_step += 1
total_test_loss += loss.item()
accuracy = (output.argmax(dim=1)==labels).sum()
total_accuracy += accuracy.item()

print('Total test Loss:{}' .format(total_test_loss))
writer.add_scalar('Loss/test', total_test_loss, total_test_step)
print("整体测试集的正确率:{}".format(total_accuracy/test_data_size))
writer.add_scalar('Accuracy/test', total_accuracy/test_data_size, total_test_step)

torch.save(model.state_dict(), './modelData/model_{}.pt'.format(i))
writer.close()

13 使用GPU训练

有两种使用GPU的方式

13.1

.cuda

  • 网络模型
1
2
3
# 创建网络模型  
model = TongModel()
model = model.cuda()
  • 数据的输入和标注

在训练和测试的部分

1
2
3
imgs, labels = data  
imgs = imgs.cuda()
labels = labels.cuda()
  • 损失函数
1
2
3
# 损失函数和优化器  
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.cuda()

良好的写法:

13.2

方法二:.to(device)

  1. 定义训练的设备
    1
    2
       # 定义训练的设备  
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

然后之前该国的地方都改成

1
model.to(device)

14 完整模型验证套路

给训练好的模型提供输入。

与测试部分类似,大概流程如下:

  1. 准备数据(自己准备的,非数据集的测试部分或训练部分)
  2. 导入模型
  3. 模型初始化
  4. 模型参数初始化
  5. 在测试模式下,输入准备的数据
  6. 获得结果,并进行对比

15 Github开源代码

只说说注意事项:

  1. 仔细阅读README
  2. 参数部分可以在代码中找到描述
作者

Zhou

发布于

2024-05-22

更新于

2025-04-01

许可协议

评论

+ + +