- 第一部分:深度学习和机器学习
- 一、机器学习任务
- 二、ML和DL区别
- (1)数据加载
- (2)模型实现
- (3)训练过程
- 第二部分:Pytorch部分
- 一、学习资源
- 二、自动求导机制
- 2.1 torch.Tensor类
- 2.2 Function类
- 2.3 雅克比矩阵
- 2.4 代码栗子
- 三、基本配置
- 3.1 导包
- 3.2 超参数设置
- 3.3 GPU的设置
- Reference
回顾我们在完成一项机器学习任务时的步骤:
(1)首先需要对数据进行预处理,其中重要的步骤包括数据格式的统一和必要的数据变换,同时划分训练集和测试集。
(2)接下来选择模型,并设定损失函数和优化函数,以及对应的超参数(当然可以使用sklearn这样的机器学习库中模型自带的损失函数和优化器)。
(3)最后用模型去拟合训练集数据,并在验证集/测试集上计算模型表现。
深度学习和机器学习在流程上类似,但在代码实现上有较大的差异。
(1)首先,由于深度学习所需的样本量很大,一次加载全部数据运行可能会超出内存容量而无法实现;
(2)同时还有批(batch)训练等提高模型表现的策略,需要每次训练读取固定数量的样本送入模型中训练,因此深度学习在数据加载上需要有专门的设计。
在模型实现上,深度学习和机器学习也有很大差异:
-
由于深度神经网络层数往往较多,同时会有一些用于实现特定功能的层(如卷积层、池化层、批正则化层、LSTM层等),因此深度神经网络往往需要“逐层”搭建,或者预先定义好可以实现特定功能的模块,再把这些模块组装起来。这种“定制化”的模型构建方式能够充分保证模型的灵活性,也对代码实现提出了新的要求。
-
接下来是损失函数和优化器的设定。这部分和经典机器学习的实现是类似的。但由于模型设定的灵活性,因此损失函数和优化器要能够保证反向传播能够在用户自行定义的模型结构上实现。
上述步骤完成后就可以开始训练了。
(1)GPU的概念和GPU用于并行计算加速的功能,不过程序默认是在CPU上运行的,因此在代码实现中,需要把模型和数据“放到”GPU上去做运算,同时还需要保证损失函数和优化器能够在GPU上工作。
(2)如果使用多张GPU进行训练,还需要考虑模型和数据分配、整合的问题。
(3)后续计算一些指标还需要把数据“放回”CPU。这里涉及到了一系列有关于GPU的配置和操作。
(4)深度学习中训练和验证过程最大的特点在于读入数据是按批的,每次读入一个批次的数据,放入GPU中训练,然后将损失函数反向传播回网络最前面的层,同时使用优化器调整网络参数。这里会涉及到各个模块配合的问题。训练/验证后还需要根据设定好的指标计算模型表现。
经过以上步骤,一个深度学习任务就完成了。
第二部分:Pytorch部分 一、学习资源(1) Awesome-pytorch-list:目前已获12K Star,包含了NLP,CV,常见库,论文实现以及Pytorch的其他项目。
(2)PyTorch官方文档:官方发布的文档,十分丰富。
(3)Pytorch-handbook:GitHub上已经收获14.8K,pytorch手中书。
(4)PyTorch官方社区:在这里你可以和开发pytorch的人们进行交流。
除此之外,还有很多学习pytorch的资源,b站,stackoverflow,知乎等多多利用。
PyTorch 中,所有神经网络的核心是 autograd包。autograd包为张量上的所有操作提供了自动求导机制。它是一个在运行时定义 ( define-by-run )的框架,这意味着反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的。
2.1 torch.Tensor类torch.Tensor是这个包的核心类。如果设置它的属性.requires_grad 为 True,那么它将会追踪对于该张量的所有操作。当完成计算后可以通过调用.backward(),来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad属性。
注意:在 y.backward() 时,如果 y 是标量,则不需要为 backward() 传入任何参数;否则,即如果 y 不是标量,需要传入一个与 y 同形的Tensor。
2.2 Function类要阻止一个张量被跟踪历史,可以调用.detach()方法将其与计算历史分离,并阻止它未来的计算记录被跟踪。为了防止跟踪历史记录(和使用内存),可以将代码块包装在 with torch.no_grad():中。在评估模型时特别有用,因为模型可能具有 requires_grad = True 的可训练的参数,但是我们不需要在此过程中对他们进行梯度计算。
还有一个类对于autograd的实现非常重要:Function。Tensor和Function 互相连接生成了一个无环图 (acyclic graph),它编码了完整的计算历史。每个张量都有一个.grad_fn属性,该属性引用了创建 Tensor自身的Function(除非这个张量是用户手动创建的,即这个张量的grad_fn是 None )。
如果需要计算导数,可以在 Tensor 上调用 .backward()。如果Tensor 是一个标量(即它包含一个元素的数据),则不需要为 backward()指定任何参数,但是如果它有更多的元素,则需要指定一个gradient参数,该参数是形状匹配的张量。
2.3 雅克比矩阵数学上,若有向量函数
y
⃗
=
f
(
x
⃗
)
vec{y}=f(vec{x})
y
=f(x
),那么
y
⃗
vec{y}
y
关于
x
⃗
vec{x}
x
的梯度就是一个雅可比矩阵:
J
=
(
∂
y
1
∂
x
1
⋯
∂
y
1
∂
x
n
⋮
⋱
⋮
∂
y
m
∂
x
1
⋯
∂
y
m
∂
x
n
)
J=left(begin{array}{ccc}frac{partial y_{1}}{partial x_{1}} & cdots & frac{partial y_{1}}{partial x_{n}} \ vdots & ddots & vdots \ frac{partial y_{m}}{partial x_{1}} & cdots & frac{partial y_{m}}{partial x_{n}}end{array}right)
J=⎝⎜⎛∂x1∂y1⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ym⎠⎟⎞
而 torch.autograd 这个包就是用来计算一些雅可比矩阵的乘积的。例如,如果
v
v
v 是一个标量函数
l
=
g
(
y
⃗
)
l = g(vec{y})
l=g(y
) 的梯度:
v
=
(
∂
l
∂
y
1
⋯
∂
l
∂
y
m
)
v=left(begin{array}{lll}frac{partial l}{partial y_{1}} & cdots & frac{partial l}{partial y_{m}}end{array}right)
v=(∂y1∂l⋯∂ym∂l)
由链式法则,我们可以得到:
v
J
=
(
∂
l
∂
y
1
⋯
∂
l
∂
y
m
)
(
∂
y
1
∂
x
1
⋯
∂
y
1
∂
x
n
⋮
⋱
⋮
∂
y
m
∂
x
1
⋯
∂
y
m
∂
x
n
)
=
(
∂
l
∂
x
1
⋯
∂
l
∂
x
n
)
v J=left(begin{array}{lll}frac{partial l}{partial y_{1}} & cdots & frac{partial l}{partial y_{m}}end{array}right)left(begin{array}{ccc}frac{partial y_{1}}{partial x_{1}} & cdots & frac{partial y_{1}}{partial x_{n}} \ vdots & ddots & vdots \ frac{partial y_{m}}{partial x_{1}} & cdots & frac{partial y_{m}}{partial x_{n}}end{array}right)=left(begin{array}{lll}frac{partial l}{partial x_{1}} & cdots & frac{partial l}{partial x_{n}}end{array}right)
vJ=(∂y1∂l⋯∂ym∂l)⎝⎜⎛∂x1∂y1⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ym⎠⎟⎞=(∂x1∂l⋯∂xn∂l)
注意:grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零。
2.4 代码栗子# -*- coding: utf-8 -*-
"""
Created on Fri Oct 15 21:07:32 2021
@author: 86493
"""
import torch
# require_grad=True用来追踪计算历史
x = torch.ones(2, 2, requires_grad = True)
print(x)
print('-' * 50)
# 对张量作指数运算
y = x ** 2
print(y)
# y是计算的结果,所以又grad_fn属性
print(y.grad_fn)
print('-' * 50)
z = y * y * 3
out = z.mean() # 计算所有元素的平均值
print("z:", z)
print("out:", out)
print('-' * 50)
# requires_grad默认为False
a = torch.randn(2, 2)
print("初始的a值为:n", a)
a = ((a * 3) / (a - 1))
print("运算后的a值为:n", a)
print(a.requires_grad) # 默认为False
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn) # b是计算的结果,所有它有grad_fn属性
print('-' * 50)
# ==================================
# 求梯度
out.backward() # out是一个标量
print(x.grad) # 输入导数d(out)/dx
print('-' *50)
# 再来反向传播,注意grad是累加的(加多一次梯度)
# out2.backward()
# print(x.grad)
out3 = x.sum()
# 一般在反向传播前把梯度清零(以防累加)
x.grad.data.zero_()
out3.backward()
print(x.grad)
print('-' *50)
# 雅克比向量积
x = torch.randn(3, requires_grad = True)
print(x)
y = x * 2
i = 0
# norm是求该tensor的L2范数
while y.data.norm() < 1000:
y = y * 2
i = i + 1
print("y:n", y, 'n')
print("i:", i)
v = torch.tensor([0.1, 1.0, 0.0001],
dtype = torch.float)
y.backward(v)
print("x.grad:n", x.grad)
# 可以通过将代码块包装在with torch.no_grad()中
# 来阻止autograd跟踪设置了requires_grad=True
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
print('-' *50)
# 想修改tensor的数值,又不希望被autograd记录
# 即不会影响反向传播,可以对tensor.data操作
x = torch.ones(1, requires_grad = True)
print("x: ", x)
print(x.data) # 还是一个tensor
# 但是已经独立于计算图之外
print(x.data.requires_grad)
y = 2 * x
# 只改变了值,不会记录在计算图,所以不会影响梯度传播
x.data *= 100
y.backward()
# 更改data值也会影响tensor的值
print(x)
print(x.grad)
结果为:
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
--------------------------------------------------
tensor([[1., 1.],
[1., 1.]], grad_fn=)
--------------------------------------------------
z: tensor([[3., 3.],
[3., 3.]], grad_fn=)
out: tensor(3., grad_fn=)
--------------------------------------------------
初始的a值为:
tensor([[-0.5364, -0.5926],
[-0.5702, -0.7497]])
运算后的a值为:
tensor([[1.0474, 1.1163],
[1.0894, 1.2855]])
False
True
--------------------------------------------------
tensor([[3., 3.],
[3., 3.]])
--------------------------------------------------
tensor([[1., 1.],
[1., 1.]])
--------------------------------------------------
tensor([ 0.4216, 0.1233, -0.3729], requires_grad=True)
y:
tensor([ 863.4903, 252.5478, -763.7181], grad_fn=)
i: 10
x.grad:
tensor([2.0480e+02, 2.0480e+03, 2.0480e-01])
True
True
False
--------------------------------------------------
x: tensor([1.], requires_grad=True)
tensor([1.])
False
runfile('D:/桌面文件/matrix/code/Torch/grad.py', wdir='D:/桌面文件/matrix/code/Torch')
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
--------------------------------------------------
tensor([[1., 1.],
[1., 1.]], grad_fn=)
--------------------------------------------------
z: tensor([[3., 3.],
[3., 3.]], grad_fn=)
out: tensor(3., grad_fn=)
--------------------------------------------------
初始的a值为:
tensor([[ 0.1064, -1.0084],
[-0.2516, -0.4749]])
运算后的a值为:
tensor([[-0.3570, 1.5063],
[ 0.6030, 0.9660]])
False
True
--------------------------------------------------
tensor([[3., 3.],
[3., 3.]])
--------------------------------------------------
tensor([[1., 1.],
[1., 1.]])
--------------------------------------------------
tensor([-0.8706, -1.1828, -0.8192], requires_grad=True)
y:
tensor([ -891.5447, -1211.1826, -838.8481], grad_fn=)
i: 9
x.grad:
tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])
True
True
False
--------------------------------------------------
x: tensor([1.], requires_grad=True)
tensor([1.])
False
tensor([100.], requires_grad=True)
tensor([2.])
三、基本配置
3.1 导包
常见的有torch、torch.nn、torch.utils.data.Dataset、torch.utils.data.DataLoader、torch.optimizer、numpy、os等,具体可以在官网查对应的API,另外还有比如:
- 涉及到表格信息的读入很可能用到pandas;
- 对于不同的项目可能还需要导入一些更上层的包如cv2等。
- 如果涉及可视化还会用到matplotlib、seaborn等。
- 涉及到下游分析和指标计算也常用到sklearn
import os import numpy as np import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader import torch.optim as optimizer3.2 超参数设置
- batch size
- 初始学习率(初始)
- 训练次数(max_epochs)
- GPU配置
batch_size = 16 lr = 1e-4 max_epochs = 1003.3 GPU的设置
记得提前import os。
# GPU设置
import os
# 方案一:使用os.environ,如果使用GPU不需要设置
os.environ['CUDA_VISIBLE_DEVICES'] = '0.1'
# 方案二:使用"device"
# 后续要使用GPU的变量用.to(device)
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")
Reference
(1)2.5.4 PyTorch中的非标量反向传播
(2)datawhale的pytorch笔记



