1 框架介绍与安装¶
- 本章节主要带领大家学习使用深度学习框架 PyTorch:
PyTorch 介绍¶

- 在2017年1月, Facebook的人工智能研究院 (FAIR) 向世界推出了PyTorch. 这个基于Torch的框架, 以其Python语言作为前端, 同时为深度学习研究者和开发者提供了两大核心优势:
- 一是强大的GPU加速张量计算能力, 其并行计算能力在当时与NumPy相媲美.
- 二是内置的自动微分系统, 使得构建深度神经网络变得更加直观和高效.

- 2018年10月, 在NeurIPS 2018会议上, Facebook宣布了PyTorch 1.0的发布. 这个版本的推出, 标志着PyTorch在商业化进程中取得了重要进展.
- 在2019年前, Tensorflow一直作为深度学习系统中的领头存在, 而以2019年为分界线, Pytorch异军突起, 逐渐成为了开发者和研究人员最为喜爱的框架. 随着Pytorch的不断普及和完善, 其生态也越发蓬勃.
- 在AI领域, huggingface社区的开源的transformers库使用pytorch实现了市面上绝大多数开源的预训练模型.
- 微软的分布式训练框架deepspeed也支持Pytorch, 由于Pytorch备受研究人员的青睐, 近年来绝大多数开源神经网络架构都采用Pytorch实现.
PyTorch 安装¶
- https://github.com/pytorch/pytorch

- 安装:
pip install torch==2.1.0- 通过本章节的学习, 同学们将会了解Pytorch的发展历史, 并掌握 PyTorch 深度学习框架的安装.
1 张量的创建¶
学习目标¶
- 掌握张量创建
PyTorch 是一个 Python 深度学习框架,它将数据封装成张量(Tensor)来进行运算。PyTorch 中的张量就是元素为同一种数据类型的多维矩阵。在 PyTorch 中,张量以 "类" 的形式封装起来,对张量的一些运算、处理的方法被封装在类中。
1. 基本创建方式¶
- torch.tensor 根据指定数据创建张量
- torch.Tensor 根据形状创建张量, 其也可用来创建指定数据的张量
- torch.IntTensor、torch.FloatTensor、torch.DoubleTensor 创建指定类型的张量
import torch
import numpy as np
import random# 1. 根据已有数据创建张量
def test01():# 1. 创建张量标量data = torch.tensor(10)print(data)# 2. numpy 数组, 由于 data 为 float64, 下面代码也使用该类型data = np.random.randn(2, 3)data = torch.tensor(data)print(data)# 3. 列表, 下面代码使用默认元素类型 float32data = [[10., 20., 30.], [40., 50., 60.]]data = torch.tensor(data)print(data)# 2. 创建指定形状的张量
def test02():# 1. 创建2行3列的张量, 默认 dtype 为 float32data = torch.Tensor(2, 3)print(data)# 2. 注意: 如果传递列表, 则创建包含指定元素的张量data = torch.Tensor([10])print(data)data = torch.Tensor([10, 20])print(data)# 3. 使用具体类型的张量
def test03():# 1. 创建2行3列, dtype 为 int32 的张量data = torch.IntTensor(2, 3)print(data)# 2. 注意: 如果传递的元素类型不正确, 则会进行类型转换data = torch.IntTensor([2.5, 3.3])print(data)# 3. 其他的类型data = torch.ShortTensor() # int16data = torch.LongTensor() # int64data = torch.FloatTensor() # float32data = torch.DoubleTensor() # float64if __name__ == '__main__':test02()
程序输出结果:
tensor(10)
tensor([[ 0.1345, 0.1149, 0.2435],[ 0.8026, -0.6744, -1.0918]], dtype=torch.float64)
tensor([[10., 20., 30.],[40., 50., 60.]])
tensor([[0.0000e+00, 3.6893e+19, 2.2018e+05],[4.6577e-10, 2.4158e-12, 1.1625e+33]])
tensor([10.])
tensor([10., 20.])
tensor([[ 0, 1610612736, 1213662609],[ 805308409, 156041223, 1]], dtype=torch.int32)
tensor([2, 3], dtype=torch.int32)
2. 创建线性和随机张量¶
- torch.arange 和 torch.linspace 创建线性张量
- torch.random.init_seed 和 torch.random.manual_seed 随机种子设置
- torch.randn 创建随机张量
import torch# 1. 创建线性空间的张量
def test01():# 1. 在指定区间按照步长生成元素 [start, end, step)data = torch.arange(0, 10, 2)print(data)# 2. 在指定区间按照元素个数生成data = torch.linspace(0, 11, 10)print(data)# 2. 创建随机张量
def test02():# 1. 创建随机张量data = torch.randn(2, 3) # 创建2行3列张量print(data)# 2. 随机数种子设置print('随机数种子:', torch.random.initial_seed())torch.random.manual_seed(100)print('随机数种子:', torch.random.initial_seed())if __name__ == '__main__':test02()
程序输出结果:
tensor([0, 2, 4, 6, 8])
tensor([ 0.0000, 1.2222, 2.4444, 3.6667, 4.8889, 6.1111, 7.3333, 8.5556,9.7778, 11.0000])
tensor([[-0.5209, -0.2439, -1.1780],[ 0.8133, 1.1442, 0.6790]])
随机数种子: 4508475192273306739
随机数种子: 100
3. 创建01张量¶
- torch.ones 和 torch.ones_like 创建全1张量
- torch.zeros 和 torch.zeros_like 创建全0张量
- torch.full 和 torch.full_like 创建全为指定值张量
import torch# 1. 创建全0张量
def test01():# 1. 创建指定形状全0张量data = torch.zeros(2, 3)print(data)# 2. 根据张量形状创建全0张量data = torch.zeros_like(data)print(data)# 2. 创建全1张量
def test02():# 1. 创建指定形状全0张量data = torch.ones(2, 3)print(data)# 2. 根据张量形状创建全0张量data = torch.ones_like(data)print(data)# 3. 创建全为指定值的张量
def test03():# 1. 创建指定形状指定值的张量data = torch.full([2, 3], 10)print(data)# 2. 根据张量形状创建指定值的张量data = torch.full_like(data, 20)print(data)if __name__ == '__main__':test01()test02()test03()
程序输出结果:
tensor([[0., 0., 0.],[0., 0., 0.]])
tensor([[0., 0., 0.],[0., 0., 0.]])
tensor([[1., 1., 1.],[1., 1., 1.]])
tensor([[1., 1., 1.],[1., 1., 1.]])
tensor([[10, 10, 10],[10, 10, 10]])
tensor([[20, 20, 20],[20, 20, 20]])
4. 张量元素类型转换¶
- tensor.type(torch.DoubleTensor)
- torch.double()
import torchdef test():data = torch.full([2, 3], 10)print(data.dtype)# 将 data 元素类型转换为 float64 类型# 1. 第一种方法data = data.type(torch.DoubleTensor)print(data.dtype)# 转换为其他类型# data = data.type(torch.ShortTensor)# data = data.type(torch.IntTensor)# data = data.type(torch.LongTensor)# data = data.type(torch.FloatTensor)# 2. 第二种方法data = data.double()print(data.dtype)# 转换为其他类型# data = data.short()# data = data.int()# data = data.long()# data = data.float()if __name__ == '__main__':test()
程序输出结果:
torch.int64
torch.float64
torch.float64
5. 小节¶
在本小节中,我们主要学习了以下内容:
1. 创建张量的方式1. torch.tensor 根据指定数据创建张量2. torch.Tensor 根据形状创建张量, 其也可用来创建指定数据的张量3. torch.IntTensor、torch.FloatTensor、torch.DoubleTensor 创建指定类型的张量
- 创建线性和随机张量 - torch.arange 和 torch.linspace 创建线性张量
- torch.random.init_seed 和 torch.random.manual_seed 随机种子设置
- torch.randn 创建随机张量
- 创建01张量 - torch.ones 和 torch.ones_like 创建全1张量
- torch.zeros 和 torch.zeros_like 创建全0张量
- torch.full 和 torch.full_like 创建全为指定值张量
- 张量元素类型转换 - tensor.type(torch.DoubleTensor)
- torch.double()
2 张量数值计算¶
学习目标¶
- 掌握张量基本运算
- 掌握阿达玛积、点积运算
- 掌握PyTorch指定运算设备
PyTorch 计算的数据都是以张量形式存在, 我们需要掌握张量各种运算. 并且, 我们可以在 CPU 中运算, 也可以在 GPU 中运算.
1. 张量基本运算¶
基本运算中,包括 add、sub、mul、div、neg 等函数, 以及这些函数的带下划线的版本 add_、sub_、mul_、div_、neg_,其中带下划线的版本为修改原数据。
import numpy as np
import torchdef test():data = torch.randint(0, 10, [2, 3])print(data)print('-' * 50)# 1. 不修改原数据new_data = data.add(10) # 等价 new_data = data + 10print(new_data)print('-' * 50)# 2. 直接修改原数据# 注意: 带下划线的函数为修改原数据本身data.add_(10) # 等价 data += 10print(data)# 3. 其他函数print(data.sub(100))print(data.mul(100))print(data.div(100))print(data.neg())if __name__ == '__main__':test()
程序输出结果:
tensor([[3, 7, 4],[0, 0, 6]])
--------------------------------------------------
tensor([[13, 17, 14],[10, 10, 16]])
--------------------------------------------------
tensor([[13, 17, 14],[10, 10, 16]])
tensor([[-87, -83, -86],[-90, -90, -84]])
tensor([[1300, 1700, 1400],[1000, 1000, 1600]])
tensor([[0.1300, 0.1700, 0.1400],[0.1000, 0.1000, 0.1600]])
tensor([[-13, -17, -14],[-10, -10, -16]])
2. 阿达玛积¶
阿达玛积指的是矩阵对应位置的元素相乘.
import numpy as np
import torchdef test():data1 = torch.tensor([[1, 2], [3, 4]])data2 = torch.tensor([[5, 6], [7, 8]])# 第一种方式data = torch.mul(data1, data2)print(data)print('-' * 50)# 第二种方式data = data1 * data2print(data)print('-' * 50)if __name__ == '__main__':test()
程序输出结果:
tensor([[ 5, 12],[21, 32]])
--------------------------------------------------
tensor([[ 5, 12],[21, 32]])
--------------------------------------------------
3. 点积运算¶
点积运算要求第一个矩阵 shape: (n, m),第二个矩阵 shape: (m, p), 两个矩阵点积运算 shape 为: (n, p)。
- 运算符 @ 用于进行两个矩阵的点乘运算
- torch.mm 用于进行两个矩阵点乘运算, 要求输入的矩阵为2维
- torch.bmm 用于批量进行矩阵点乘运算, 要求输入的矩阵为3维
- torch.matmul 对进行点乘运算的两矩阵形状没有限定. - 对于输入都是二维的张量相当于 mm 运算.
- 对于输入都是三维的张量相当于 bmm 运算
- 对数输入的 shape 不同的张量, 对应的最后几个维度必须符合矩阵运算规则
import numpy as np
import torch# 1. 点积运算
def test01():data1 = torch.tensor([[1, 2], [3, 4], [5, 6]])data2 = torch.tensor([[5, 6], [7, 8]])# 第一种方式data = data1 @ data2print(data)print('-' * 50)# 第二种方式data = torch.mm(data1, data2)print(data)print('-' * 50)# 第三种方式data = torch.matmul(data1, data2)print(data)print('-' * 50)# 2. torch.mm 和 torch.matmull 的区别
def test02():# matmul 可以两个维度可以不同# 第一个张量: (3, 4, 5)# 第二个张量: (5, 4)# torch.mm 不可以相乘,而 matmul 则可以相乘print(torch.matmul(torch.randn(3, 4, 5), torch.randn(5, 4)).shape)print(torch.matmul(torch.randn(5, 4), torch.randn(3, 4, 5)).shape)# 3. torch.mm 函数的用法
def test03():# 批量点积运算# 第一个维度为 batch_size# 矩阵的二三维要满足矩阵乘法规则data1 = torch.randn(3, 4, 5)data2 = torch.randn(3, 5, 8)data = torch.bmm(data1, data2)print(data.shape)if __name__ == '__main__':test01()test02()test03()
程序输出结果:
tensor([[19, 22],[43, 50],[67, 78]])
--------------------------------------------------
tensor([[19, 22],[43, 50],[67, 78]])
--------------------------------------------------
tensor([[19, 22],[43, 50],[67, 78]])
--------------------------------------------------
torch.Size([3, 4, 4])
torch.Size([3, 5, 5])
torch.Size([3, 4, 8])
4. 指定运算设备¶
PyTorch 默认会将张量创建在 CPU 控制的内存中, 即: 默认的运算设备为 CPU。我们也可以将张量创建在 GPU 上, 能够利用对于矩阵计算的优势加快模型训练。将张量移动到 GPU 上有两种方法:
- 使用 cuda 方法
- 直接在 GPU 上创建张量
- 使用 to 方法指定设备
import torch# 1. 使用 cuda 方法
def test01():data = torch.tensor([10, 20 ,30])print('存储设备:', data.device)# 如果安装的不是 gpu 版本的 PyTorch# 或电脑本身没有 NVIDIA 卡的计算环境# 下面代码可能会报错data = data.cuda()print('存储设备:', data.device)# 使用 cpu 函数将张量移动到 cpu 上data = data.cpu()print('存储设备:', data.device)# 输出结果:# 存储设备: cpu# 存储设备: cuda:0# 存储设备: cpu# 2. 直接将张量创建在 GPU 上
def test02():data = torch.tensor([10, 20, 30], device='cuda:0')print('存储设备:', data.device)# 使用 cpu 函数将张量移动到 cpu 上data = data.cpu()print('存储设备:', data.device)# 输出结果:# 存储设备: cuda:0# 存储设备: cpu# 3. 使用 to 方法
def test03():data = torch.tensor([10, 20, 30])print('存储设备:', data.device)data = data.to('cuda:0')print('存储设备:', data.device)# 输出结果:# 存储设备: cpu# 存储设备: cuda:0# 4. 存储在不同设备的张量不能运算
def test04():data1 = torch.tensor([10, 20, 30], device='cuda:0')data2 = torch.tensor([10, 20, 30])print(data1.device, data2.device)# RuntimeError: Expected all tensors to be on the same device,# but found at least two devices, cuda:0 and cpu!data = data1 + data2print(data)if __name__ == '__main__':test04()
程序输出结果:
存储设备: cpu
存储设备: cuda:0
存储设备: cpu
存储设备: cuda:0
存储设备: cpu
存储设备: cpu
存储设备: cuda:0
cuda:0 cpu
5. 小节¶
在本小节中,我们主要学习的主要内容如下:
- 张量基本运算函数 add、sub、mul、div、neg 等函数, add_、sub_、mul_、div_、neg_ 等 inplace 函数
- 张量的阿达玛积运算 mul 和运算符 * 的用法
- 点积运算: - 运算符 @ 用于进行两个矩阵的点乘运算
- torch.mm 用于进行两个矩阵点乘运算, 要求输入的矩阵为2维
- torch.bmm 用于批量进行矩阵点乘运算, 要求输入的矩阵为3维
- torch.matmul 对进行点乘运算的两矩阵形状没有限定. - 对于输入都是二维的张量相当于 mm 运算.
- 对于输入都是三维的张量相当于 bmm 运算
- 对数输入的 shape 不同的张量, 对应的最后几个维度必须符合矩阵运算规则
- 将变量移动到 GPU 设备的方法,例如: cuda 方法、直接在 GPU 上创建张量、使用 to 方法指定设备
3 张量类型转换¶
学习目标¶
- 掌握张量类型转换方法
张量的类型转换也是经常使用的一种操作,是必须掌握的知识点。在本小节,我们主要学习如何将 numpy 数组和 PyTorch Tensor 的转化方法.
1. 张量转换为 numpy 数组¶
使用 Tensor.numpy 函数可以将张量转换为 ndarray 数组,但是共享内存,可以使用 copy 函数避免共享。
# 1. 将张量转换为 numpy 数组
def test01():data_tensor = torch.tensor([2, 3, 4])# 使用张量对象中的 numpy 函数进行转换data_numpy = data_tensor.numpy()print(type(data_tensor))print(type(data_numpy))# 注意: data_tensor 和 data_numpy 共享内存# 修改其中的一个,另外一个也会发生改变# data_tensor[0] = 100data_numpy[0] = 100print(data_tensor)print(data_numpy)if __name__ == '__main__':test01()
2. numpy 转换为张量¶
- 使用 from_numpy 可以将 ndarray 数组转换为 Tensor,默认共享内存,使用 copy 函数避免共享。
- 使用 torch.tensor 可以将 ndarray 数组转换为 Tensor,默认不共享内存。
# 1. 使用 from_numpy 函数
def test01():data_numpy = np.array([2, 3, 4])# 将 numpy 数组转换为张量类型# 1. from_numpy# 2. torch.tensor(ndarray)# 浅拷贝data_tensor = torch.from_numpy(data_numpy)# nunpy 和 tensor 共享内存# data_numpy[0] = 100data_tensor[0] = 100print(data_tensor)print(data_numpy)# 2. 使用 torch.tensor 函数
def test02():data_numpy = np.array([2, 3, 4])data_tensor = torch.tensor(data_numpy)# nunpy 和 tensor 不共享内存# data_numpy[0] = 100data_tensor[0] = 100print(data_tensor)print(data_numpy)if __name__ == '__main__':test01()test02()
3. 标量张量和数字的转换¶
对于只有一个元素的张量,使用 item 方法将该值从张量中提取出来。
# 3. 标量张量和数字的转换
def test03():# 当张量只包含一个元素时, 可以通过 item 函数提取出该值data = torch.tensor([30,])print(data.item())data = torch.tensor(30)print(data.item())if __name__ == '__main__':test03()
程序输出结果:
30
30
小节¶
在本小节中, 我们主要学习了 numpy 和 tensor 互相转换的规则, 以及标量张量与数值之间的转换规则。
4 张量拼接操作¶
学习目标¶
- 掌握torch.cat torch.stack使用
张量的拼接操作在神经网络搭建过程中是非常常用的方法,例如: 在后面将要学习到的残差网络、注意力机制中都使用到了张量拼接。
1. torch.cat 函数的使用¶
torch.cat 函数可以将两个张量根据指定的维度拼接起来.
import torchdef test():data1 = torch.randint(0, 10, [3, 5, 4])data2 = torch.randint(0, 10, [3, 5, 4])print(data1)print(data2)print('-' * 50)# 1. 按0维度拼接new_data = torch.cat([data1, data2], dim=0)print(new_data.shape)print('-' * 50)# 2. 按1维度拼接new_data = torch.cat([data1, data2], dim=1)print(new_data.shape)print('-' * 50)# 3. 按2维度拼接new_data = torch.cat([data1, data2], dim=2)print(new_data.shape)if __name__ == '__main__':test()
程序输出结果:
tensor([[[6, 8, 3, 5],[1, 1, 3, 8],[9, 0, 4, 4],[1, 4, 7, 0],[5, 1, 4, 8]],[[0, 1, 4, 4],[4, 1, 8, 7],[5, 2, 6, 6],[2, 6, 1, 6],[0, 7, 8, 9]],[[0, 6, 8, 8],[5, 4, 5, 8],[3, 5, 5, 9],[3, 5, 2, 4],[3, 8, 1, 1]]])
tensor([[[4, 6, 8, 1],[0, 1, 8, 2],[4, 9, 9, 8],[5, 1, 5, 9],[9, 4, 3, 0]],[[7, 6, 3, 3],[4, 3, 3, 2],[2, 1, 1, 1],[3, 0, 8, 2],[8, 6, 6, 5]],[[0, 7, 2, 4],[4, 3, 8, 3],[4, 2, 1, 9],[4, 2, 8, 9],[3, 7, 0, 8]]])
--------------------------------------------------
torch.Size([6, 5, 4])
--------------------------------------------------
torch.Size([3, 10, 4])
tensor([[[6, 8, 3, 5, 4, 6, 8, 1],[1, 1, 3, 8, 0, 1, 8, 2],[9, 0, 4, 4, 4, 9, 9, 8],[1, 4, 7, 0, 5, 1, 5, 9],[5, 1, 4, 8, 9, 4, 3, 0]],[[0, 1, 4, 4, 7, 6, 3, 3],[4, 1, 8, 7, 4, 3, 3, 2],[5, 2, 6, 6, 2, 1, 1, 1],[2, 6, 1, 6, 3, 0, 8, 2],[0, 7, 8, 9, 8, 6, 6, 5]],[[0, 6, 8, 8, 0, 7, 2, 4],[5, 4, 5, 8, 4, 3, 8, 3],[3, 5, 5, 9, 4, 2, 1, 9],[3, 5, 2, 4, 4, 2, 8, 9],[3, 8, 1, 1, 3, 7, 0, 8]]])
2. torch.stack 函数的使用¶
torch.stack 函数可以将两个张量根据指定的维度叠加起来.
import torchdef test():data1= torch.randint(0, 10, [2, 3])data2= torch.randint(0, 10, [2, 3])print(data1)print(data2)new_data = torch.stack([data1, data2], dim=0)print(new_data.shape)new_data = torch.stack([data1, data2], dim=1)print(new_data.shape)new_data = torch.stack([data1, data2], dim=2)print(new_data.shape)if __name__ == '__main__':test()
程序输出结果:
tensor([[5, 8, 7],[6, 0, 6]])
tensor([[5, 8, 0],[9, 0, 1]])
torch.Size([2, 2, 3])
torch.Size([2, 2, 3])
torch.Size([2, 3, 2])
3. 小节¶
张量的拼接操作也是在后面我们经常使用一种操作。cat 函数可以将张量按照指定的维度拼接起来,stack 函数可以将张量按照指定的维度叠加起来。
5 张量索引操作¶
学习目标¶
- 掌握张量不同索引操作
我们在操作张量时,经常需要去进行获取或者修改操作,掌握张量的花式索引操作是必须的一项能力。
1. 简单行、列索引¶
准备数据
import torchdata = torch.randint(0, 10, [4, 5])
print(data)
print('-' * 50)
程序输出结果:
tensor([[0, 7, 6, 5, 9],[6, 8, 3, 1, 0],[6, 3, 8, 7, 3],[4, 9, 5, 3, 1]])
--------------------------------------------------
# 1. 简单行、列索引
def test01():print(data[0])print(data[:, 0])print('-' * 50)if __name__ == '__main__':test01()
程序输出结果:
tensor([0, 7, 6, 5, 9])
tensor([0, 6, 6, 4])
--------------------------------------------------
2. 列表索引¶
# 2. 列表索引
def test02():# 返回 (0, 1)、(1, 2) 两个位置的元素print(data[[0, 1], [1, 2]])print('-' * 50)# 返回 0、1 行的 1、2 列共4个元素print(data[[[0], [1]], [1, 2]])
if __name__ == '__main__':test02()
程序输出结果:
tensor([7, 3])
--------------------------------------------------
tensor([[7, 6],[8, 3]])
3. 范围索引¶
# 3. 范围索引
def test03():# 前3行的前2列数据print(data[:3, :2])# 第2行到最后的前2列数据print(data[2:, :2])
if __name__ == '__main__':test03()
程序输出结果:
tensor([[0, 7],[6, 8],[6, 3]])
tensor([[6, 3],[4, 9]])
4. 布尔索引¶
# 布尔索引
def test():# 第2列大于5的行数据print(data[data[:, 2] > 5])# 第1行大于5的列数据print(data[:, data[1] > 5])
if __name__ == '__main__':test04()
程序输出结果:
tensor([[0, 7, 6, 5, 9],[6, 3, 8, 7, 3]])
tensor([[0, 7],[6, 8],[6, 3],[4, 9]])
5. 多维索引¶
# 多维索引
def test05():data = torch.randint(0, 10, [3, 4, 5])print(data)print('-' * 50)print(data[0, :, :])print(data[:, 0, :])print(data[:, :, 0])if __name__ == '__main__':test05()
程序输出结果:
tensor([[[2, 4, 1, 2, 3],[5, 5, 1, 5, 0],[1, 4, 5, 3, 8],[7, 1, 1, 9, 9]],[[9, 7, 5, 3, 1],[8, 8, 6, 0, 1],[6, 9, 0, 2, 1],[9, 7, 0, 4, 0]],[[0, 7, 3, 5, 6],[2, 4, 6, 4, 3],[2, 0, 3, 7, 9],[9, 6, 4, 4, 4]]])
--------------------------------------------------
tensor([[2, 4, 1, 2, 3],[5, 5, 1, 5, 0],[1, 4, 5, 3, 8],[7, 1, 1, 9, 9]])
tensor([[2, 4, 1, 2, 3],[9, 7, 5, 3, 1],[0, 7, 3, 5, 6]])
tensor([[2, 5, 1, 7],[9, 8, 6, 9],[0, 2, 2, 9]])
6 张量形状操作¶
学习目标¶
- 掌握reshape, transpose, permute, view, contigous, squeeze, unsqueeze等函数使用
在我们后面搭建网络模型时,数据都是基于张量形式的表示,网络层与层之间很多都是以不同的 shape 的方式进行表现和运算,我们需要掌握对张量形状的操作,以便能够更好处理网络各层之间的数据连接。
1. reshape 函数的用法¶
reshape 函数可以在保证张量数据不变的前提下改变数据的维度,将其转换成指定的形状,在后面的神经网络学习时,会经常使用该函数来调节数据的形状,以适配不同网络层之间的数据传递。
import torch
import numpy as npdef test():data = torch.tensor([[10, 20, 30], [40, 50, 60]])# 1. 使用 shape 属性或者 size 方法都可以获得张量的形状print(data.shape, data.shape[0], data.shape[1])print(data.size(), data.size(0), data.size(1))# 2. 使用 reshape 函数修改张量形状new_data = data.reshape(1, 6)print(new_data.shape)if __name__ == '__main__':test()
程序运行结果:
torch.Size([2, 3]) 2 3
torch.Size([2, 3]) 2 3
torch.Size([1, 6])
2. transpose 和 permute 函数的使用¶
transpose 函数可以实现交换张量形状的指定维度, 例如: 一个张量的形状为 (2, 3, 4) 可以通过 transpose 函数把 3 和 4 进行交换, 将张量的形状变为 (2, 4, 3)
permute 函数可以一次交换更多的维度。
import torch
import numpy as npdef test():data = torch.tensor(np.random.randint(0, 10, [3, 4, 5]))print('data shape:', data.size())# 1. 交换1和2维度new_data = torch.transpose(data, 1, 2)print('data shape:', new_data.size())# 2. 将 data 的形状修改为 (4, 5, 3)new_data = torch.transpose(data, 0, 1)new_data = torch.transpose(new_data, 1, 2)print('new_data shape:', new_data.size())# 3. 使用 permute 函数将形状修改为 (4, 5, 3)new_data = torch.permute(data, [1, 2, 0])print('new_data shape:', new_data.size())if __name__ == '__main__':test()
程序运行结果:
data shape: torch.Size([3, 4, 5])
data shape: torch.Size([3, 5, 4])
new_data shape: torch.Size([4, 5, 3])
new_data shape: torch.Size([4, 5, 3])
3. view 和 contigous 函数的用法¶
view 函数也可以用于修改张量的形状,但是其用法比较局限,只能用于存储在整块内存中的张量。在 PyTorch 中,有些张量是由不同的数据块组成的,它们并没有存储在整块的内存中,view 函数无法对这样的张量进行变形处理,例如: 一个张量经过了 transpose 或者 permute 函数的处理之后,就无法使用 view 函数进行形状操作。
import torch
import numpy as npdef test():data = torch.tensor([[10, 20, 30], [40, 50, 60]])print('data shape:', data.size())# 1. 使用 view 函数修改形状new_data = data.view(3, 2)print('new_data shape:', new_data.shape)# 2. 判断张量是否使用整块内存print('data:', data.is_contiguous()) # True# 3. 使用 transpose 函数修改形状new_data = torch.transpose(data, 0, 1)print('new_data:', new_data.is_contiguous()) # False# new_data = new_data.view(2, 3) # RuntimeError# 需要先使用 contiguous 函数转换为整块内存的张量,再使用 view 函数print(new_data.contiguous().is_contiguous())new_data = new_data.contiguous().view(2, 3)print('new_data shape:', new_data.shape)if __name__ == '__main__':test()
程序运行结果:
data shape: torch.Size([2, 3])
new_data shape: torch.Size([3, 2])
data: True
new_data: False
True
new_data shape: torch.Size([2, 3])
4. squeeze 和 unsqueeze 函数的用法¶
squeeze 函数用删除 shape 为 1 的维度,unsqueeze 在每个维度添加 1, 以增加数据的形状。
import torch
import numpy as npdef test():data = torch.tensor(np.random.randint(0, 10, [1, 3, 1, 5]))print('data shape:', data.size())# 1. 去掉值为1的维度new_data = data.squeeze()print('new_data shape:', new_data.size()) # torch.Size([3, 5])# 2. 去掉指定位置为1的维度,注意: 如果指定位置不是1则不删除new_data = data.squeeze(2)print('new_data shape:', new_data.size()) # torch.Size([3, 5])# 3. 在2维度增加一个维度new_data = data.unsqueeze(-1)print('new_data shape:', new_data.size()) # torch.Size([3, 1, 5, 1])if __name__ == '__main__':test()
程序运行结果:
data shape: torch.Size([1, 3, 1, 5])
new_data shape: torch.Size([3, 5])
new_data shape: torch.Size([1, 3, 5])
new_data shape: torch.Size([1, 3, 1, 5, 1])
5. 小节¶
本小节带着同学们学习了经常使用的关于张量形状的操作,我们用到的主要函数有:
- reshape 函数可以在保证张量数据不变的前提下改变数据的维度.
- transpose 函数可以实现交换张量形状的指定维度, permute 可以一次交换更多的维度.
- view 函数也可以用于修改张量的形状, 但是它要求被转换的张量内存必须连续,所以一般配合 contiguous 函数使用.
- squeeze 和 unsqueeze 函数可以用来增加或者减少维度.
7 张量运算函数¶
学习目标¶
- 掌握张量相关运算函数
1. 常见运算函数¶
PyTorch 为每个张量封装很多实用的计算函数,例如计算均值、平方根、求和等等
import torchdef test():data = torch.randint(0, 10, [2, 3], dtype=torch.float64)print(data)print('-' * 50)# 1. 计算均值# 注意: tensor 必须为 Float 或者 Double 类型print(data.mean())print(data.mean(dim=0)) # 按列计算均值print(data.mean(dim=1)) # 按行计算均值print('-' * 50)# 2. 计算总和print(data.sum())print(data.sum(dim=0))print(data.sum(dim=1))print('-' * 50)# 3. 计算平方print(data.pow(2))print('-' * 50)# 4. 计算平方根print(data.sqrt())print('-' * 50)# 5. 指数计算, e^n 次方print(data.exp())print('-' * 50)# 6. 对数计算print(data.log()) # 以 e 为底print(data.log2())print(data.log10())if __name__ == '__main__':test()
程序运行结果:
tensor([[4., 0., 7.],[6., 3., 5.]], dtype=torch.float64)
--------------------------------------------------
tensor(4.1667, dtype=torch.float64)
tensor([5.0000, 1.5000, 6.0000], dtype=torch.float64)
tensor([3.6667, 4.6667], dtype=torch.float64)
--------------------------------------------------
tensor(25., dtype=torch.float64)
tensor([10., 3., 12.], dtype=torch.float64)
tensor([11., 14.], dtype=torch.float64)
--------------------------------------------------
tensor([[16., 0., 49.],[36., 9., 25.]], dtype=torch.float64)
--------------------------------------------------
tensor([[2.0000, 0.0000, 2.6458],[2.4495, 1.7321, 2.2361]], dtype=torch.float64)
--------------------------------------------------
tensor([[5.4598e+01, 1.0000e+00, 1.0966e+03],[4.0343e+02, 2.0086e+01, 1.4841e+02]], dtype=torch.float64)
--------------------------------------------------
tensor([[1.3863, -inf, 1.9459],[1.7918, 1.0986, 1.6094]], dtype=torch.float64)
tensor([[2.0000, -inf, 2.8074],[2.5850, 1.5850, 2.3219]], dtype=torch.float64)
tensor([[0.6021, -inf, 0.8451],[0.7782, 0.4771, 0.6990]], dtype=torch.float64)
8 自动微分模块¶
学习目标¶
- 掌握梯度计算
自动微分(Autograd)模块对张量做了进一步的封装,具有自动求导功能。自动微分模块是构成神经网络训练的必要模块,在神经网络的反向传播过程中,Autograd 模块基于正向计算的结果对当前的参数进行微分计算,从而实现网络权重参数的更新。
1. 梯度基本计算¶
我们使用 backward 方法、grad 属性来实现梯度的计算和访问.
import torch# 1. 单标量梯度的计算
# y = x**2 + 20
def test01():# 定义需要求导的张量# 张量的值类型必须是浮点类型x = torch.tensor(10, requires_grad=True, dtype=torch.float64)# 变量经过中间运算f = x ** 2 + 20# 自动微分f.backward()# 打印 x 变量的梯度# backward 函数计算的梯度值会存储在张量的 grad 变量中print(x.grad)# 2. 单向量梯度的计算
# y = x**2 + 20
def test02():# 定义需要求导张量x = torch.tensor([10, 20, 30, 40], requires_grad=True, dtype=torch.float64)# 变量经过中间计算f1 = x ** 2 + 20# 注意:# 由于求导的结果必须是标量# 而 f 的结果是: tensor([120., 420.])# 所以, 不能直接自动微分# 需要将结果计算为标量才能进行计算f2 = f1.mean() # f2 = 1/2 * x# 自动微分f2.backward()# 打印 x 变量的梯度print(x.grad)if __name__ == '__main__':test01()
程序运行结果:
tensor(20., dtype=torch.float64)
tensor([ 5., 10., 15., 20.], dtype=torch.float64)
2. 控制梯度计算¶
我们可以通过一些方法使得在 requires_grad=True 的张量在某些时候计算不进行梯度计算。
import torch# 1. 控制不计算梯度
def test01():x = torch.tensor(10, requires_grad=True, dtype=torch.float64)print(x.requires_grad)# 第一种方式: 对代码进行装饰with torch.no_grad():y = x ** 2print(y.requires_grad)# 第二种方式: 对函数进行装饰@torch.no_grad()def my_func(x):return x ** 2print(my_func(x).requires_grad)# 第三种方式torch.set_grad_enabled(False)y = x ** 2print(y.requires_grad)# 2. 注意: 累计梯度
def test02():# 定义需要求导张量x = torch.tensor([10, 20, 30, 40], requires_grad=True, dtype=torch.float64)for _ in range(3):f1 = x ** 2 + 20f2 = f1.mean()# 默认张量的 grad 属性会累计历史梯度值# 所以, 需要我们每次手动清理上次的梯度# 注意: 一开始梯度不存在, 需要做判断if x.grad is not None:x.grad.data.zero_()f2.backward()print(x.grad)# 3. 梯度下降优化最优解
def test03():# y = x**2x = torch.tensor(10, requires_grad=True, dtype=torch.float64)for _ in range(5000):# 正向计算f = x ** 2# 梯度清零if x.grad is not None:x.grad.data.zero_()# 反向传播计算梯度f.backward()# 更新参数x.data = x.data - 0.001 * x.gradprint('%.10f' % x.data)if __name__ == '__main__':test01()print('--------------------')test02()print('--------------------')test03()
程序运行结果:
True
False
False
False
tensor([ 5., 10., 15., 20.], dtype=torch.float64)
tensor([ 5., 10., 15., 20.], dtype=torch.float64)
tensor([ 5., 10., 15., 20.], dtype=torch.float64)
3. 梯度计算注意¶
当对设置 requires_grad=True 的张量使用 numpy 函数进行转换时, 会出现如下报错:
Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.
此时, 需要先使用 detach 函数将张量进行分离, 再使用 numpy 函数.
注意: detach 之后会产生一个新的张量, 新的张量作为叶子结点,并且该张量和原来的张量共享数据, 但是分离后的张量不需要计算梯度。
import torch# 1. detach 函数用法
def test01():x = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64)# Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.# print(x.numpy()) # 错误print(x.detach().numpy()) # 正确# 2. detach 前后张量共享内存
def test02():x1 = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64)# x2 作为叶子结点x2 = x1.detach()# 两个张量的值一样: 140421811165776 140421811165776print(id(x1.data), id(x2.data))x2.data = torch.tensor([100, 200])print(x1)print(x2)# x2 不会自动计算梯度: Falseprint(x2.requires_grad)if __name__ == '__main__':test01()test02()
程序运行结果:
10. 20.]
140495634222288 140495634222288
tensor([10., 20.], dtype=torch.float64, requires_grad=True)
tensor([100, 200])
False
4. 小节¶
本小节主要讲解了 PyTorch 中非常重要的自动微分模块的使用和理解。我们对需要计算梯度的张量需要设置 requires_grad=True 属性,并且需要注意的是梯度是累计的,在每次计算梯度前需要先进行梯度清零。
10 模型的保存加载¶
学习目标¶
- 掌握PyTorch保存模型的方法
- 神经网络的训练有时需要几天, 几周, 甚至几个月, 为了在每次使用模型时避免高代价的重复训练, 我们就需要将模型序列化到磁盘中, 使用的时候反序列化到内存中.
1: 保存模型参数¶
import torch
import torch.nn as nn# 假设我们有一个模型
class SimpleModel(nn.Module):def __init__(self):super(SimpleModel, self).__init__()self.fc = nn.Linear(10, 1)def forward(self, x):return self.fc(x)model = SimpleModel()# 保存模型的参数
torch.save(model.state_dict(), 'model_weights.pth')
¶
2: 保存全部模型¶
import torch
import torch.nn as nn# 假设我们有一个模型
class SimpleModel(nn.Module):def __init__(self):super(SimpleModel, self).__init__()self.fc = nn.Linear(10, 1)def forward(self, x):return self.fc(x)model = SimpleModel()# 保存全部模型
torch.save(model, 'model.pth')
3: 加载模型参数¶
# 创建一个与保存时相同结构的模型
model = SimpleModel()# 加载模型的参数
model.load_state_dict(torch.load('model_weights.pth'))
print(model)
4: 加载全部模型¶
model = torch.load('model.pth')
print(model)
- 注意📢:
- 模型结构: 如果你只保存了模型的参数, 那么在加载时需要确保你有与保存时相同的模型结构.
- 设备兼容性: 如果你在一个设备上保存了模型(例如GPU), 而在另一个设备上加载(例如CPU), 你可能需要使用
map_location参数来指定设备. device = torch.device('cpu') model.load_state_dict(torch.load('model_weights.pth', map_location=device))
