想搞定 PyTorch 核心操作?这份张量指南超实用!从创建、运算到梯度计算、模型保存全拆解,帮你快速夯实深度学习基础~📌点赞关注小编哦~
一、张量创建
1.1 基本创建方式
1)torch.tensor 根据指定数据创建张量 (最常用)
2)torch.Tensor 根据形状创建张量,其也可用来创建指定数据的张量
3)torch.IntTensor、torch.FloatTensor、torch.DoubleTensor 创建指定类型的张量
Tensor是PyTorch中最基础的数据结构,简单说就是多维矩阵,能借助GPU加速运算,是承载数据和运算的核心,其基本数据类型如下
数据类型 | CPU 张量(类型别名) | GPU 张量(类型别名) | 备注(dtype 对应) |
---|---|---|---|
8位无符号整型(uint8) | torch.ByteTensor | torch.cuda.ByteTensor | 范围 0~255,对应 dtype=torch.uint8 |
8位有符号整型(int8) | torch.CharTensor | torch.cuda.CharTensor | 范围 -128~127,对应 dtype=torch.int8 |
16位有符号整型(int16) | torch.ShortTensor | torch.cuda.ShortTensor | 对应 dtype=torch.int16 |
32位有符号整型(int32) | torch.IntTensor | torch.cuda.IntTensor | 对应 dtype=torch.int32 |
64位有符号整型(int64) | torch.LongTensor | torch.cuda.LongTensor | 修正拼写错误,对应 dtype=torch.int64 |
16位浮点型(半精度,float16) | torch.HalfTensor | torch.cuda.HalfTensor | GPU 加速常用,对应 dtype=torch.float16 |
32位浮点型(float32) | torch.FloatTensor | torch.cuda.FloatTensor | 对应 dtype=torch.float32(默认类型) |
64位浮点型(float64) | torch.DoubleTensor | torch.cuda.DoubleTensor | 对应 dtype=torch.float64 |
布尔类型(bool) | torch.BoolTensor | torch.cuda.BoolTensor | 对应 dtype=torch.bool(Python 布尔映射) |
import numpy as np
import torch
# 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. 列表,下面代码使用默认元素类型 float32
data = [[10., 20., 30.], [40., 50., 60.]]
data = torch.tensor(data)
print(data)
print('-' * 50)
# 2. 创建指定形状的张量
def test02():
# 1. 创建2行3列的张量,默认dtype为float32
data = torch.Tensor(2, 3)
print(data)
# 2. 注意:如果传递列表,则创建包含指定元素的张量
data = torch.Tensor([10])
print(data)
data = torch.Tensor([10, 20])
print(data)
print('-' * 50)
# 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([2, 3]) # int16
# data = torch.LongTensor([2, 3]) # int64
# data = torch.FloatTensor([2.5, 3.3]) # float32
# print(data)
print('-' * 50)
if __name__ == '__main__':
test01()
test02()
test03()
# 运行结果
tensor(10)
tensor([[ 0.7219, 0.7726, -1.1605],
[ 0.1611, 0.4240, -0.0588]], dtype=torch.float64)
tensor([[10., 20., 30.],
[40., 50., 60.]])
--------------------------------------------------
tensor([[1.7608e+13, 1.2752e-42, 3.0000e+01],
[4.0000e+01, 5.0000e+01, 6.0000e+01]])
tensor([10.])
tensor([10., 20.])
--------------------------------------------------
tensor([[1434459200, 910, 1056259574],
[1075273419, 1062076309, 1059627882]], dtype=torch.int32)
tensor([-2, -3], dtype=torch.int32)
1.2 创建线性和随机张量
1)torch.arange 和 torch.linspace 创建线性张量
2)torch.random.init_seed 和 torch.random.manual_seed 随机种子设置
-
torch.random.initial_seed() 用于获取当前随机数生成器的初始种子值
在没有手动设置随机种子的情况下,PyTorch 的随机数生成器会根据系统时钟等因素自动生成一个初始种子,调用这个函数就可以查看这个自动生成的种子值 -
torch.random.manual_seed(seed) 用于手动设置随机数生成器的种子
当你设置了一个固定的种子值后,后续的随机数生成操作就会变得可复现,即每次运行代码,只要设置相同的随机种子,就会得到相同的随机数序列
3)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())
# 3. 随机种子的复现作用测试
def test03():
# 设置随机种子
torch.random.manual_seed(100)
# 创建一个随机初始化的张量
tensor1 = torch.randn(3, 3)
print("Tensor 1:\n", tensor1)
# 再次设置相同的随机种子
torch.random.manual_seed(100)
# 再次创建一个随机初始化的张量
tensor2 = torch.randn(3, 3)
print("Tensor 2:\n", tensor2)
if __name__ == '__main__':
test01()
#test02()
#test03()
📌关于计算机中的随机数的原理?
- 本质: 计算机中没有真随机数,全都是 伪随机数!!!
- 伪随机数: 都是一个起始值,按照一系列确定的公式计算出来的值!!!
- 为什么要给一个初始化种子(100)?就是给这个确定的起始值
1.3 创建 01张量
1)torch.ones 和 torch.ones_like 创建全1张量
2)torch.zeros 和 torch.zeros_like 创建全0张量
3)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)
print('-' * 50)
# 2. 创建全1张量
def test02():
# 1. 创建指定形状全1张量
data = torch.ones(2, 3)
print(data)
# 2. 根据张量形状创建全1张量
data = torch.ones_like(data)
print(data)
print('-' * 50)
# 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]])
1.4 张量类型的转换
1)tensor.type(torch.DoubleTensor)
2)torch.double
import torch
def 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()
print(data.dtype)
data = data.int()
print(data.dtype)
data = data.long()
print(data.dtype)
data = data.float()
print(data.dtype)
if __name__ == "__main__":
test()
# 运行结果
torch.int64
torch.float64
torch.float64
torch.int16
torch.int32
torch.int64
torch.float32
📌关于模型参数的精度?
np.random.randn()
— 默认精度为 float64torch.randn()
— PyTorch中的浮点值默认精度为 float32- 混合精度训练:
float32
与float16
、bfloat16
混合训练(节省显存) - DeepSeek-V3:
float8 + float16 + float32
混合训练
二、张量数值运算
2.1 张量基本运算
基本运算中,包括 add、sub、mul、div、neg 等函数, 以及这些函数的带下划线的版本 add_、sub_、mul_、div_、neg_, 其中带下划线的版本为修改原数据
import numpy as np
import torch
def test():
data = torch.randint(0, 10, [2, 3])
print(data)
print('-' * 50)
# 1. 不修改原数据
new_data = data.add(10) # 等价 new_data = data + 10
print(new_data)
print(data)
print('-' * 50)
# 2. 直接修改原数据
# 注意: 带下划线的函数为修改原数据本身
data.add_(10) # 等价 data += 10
print(data)
# 3. 其他函数
print(data.sub(100)) #对张量 data 中的每个元素都减去 100
print(data.mul(100))
print(data.div(100))
print(data.neg())
print(data.abs())
if __name__ == '__main__':
test()
# 运行结果
tensor([[8, 7, 4],
[6, 0, 1]])
--------------------------------------------------
tensor([[18, 17, 14],
[16, 10, 11]])
tensor([[8, 7, 4],
[6, 0, 1]])
--------------------------------------------------
tensor([[18, 17, 14],
[16, 10, 11]])
tensor([[-82, -83, -86],
[-84, -90, -89]])
tensor([[1800, 1700, 1400],
[1600, 1000, 1100]])
tensor([[0.1800, 0.1700, 0.1400],
[0.1600, 0.1000, 0.1100]])
tensor([[-18, -17, -14],
[-16, -10, -11]])
tensor([[18, 17, 14],
[16, 10, 11]])
2.2 阿达玛积
阿达玛积指的是矩阵对应位置的元素相乘
import numpy as np
import torch
def 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 * data2
print(data)
if __name__ == '__main__':
test()
# 运行结果
tensor([[ 5, 12],
[21, 32]])
--------------------------------------------------
tensor([[ 5, 12],
[21, 32]])
2.3 点积运算
点积运算要求第一个矩阵 shape: (n, m),第二个矩阵 shape: (m, p), 两个矩阵点积运算 shape 为: (n, p)
运算符 @ 用于进行两个矩阵的点乘运算
torch.mm 用于进行两个矩阵点乘运算,要求输入的矩阵为 2 维
torch.bmm 用于批量进行矩阵点乘运算,要求输入的矩阵为 3 维
📌torch.matmul 对进行点乘运算的两矩阵形状没有限定.
a. 对于输入都是二维的张量相当于 mm 运算.
b. 对于输入都是三维的张量相当于 bmm 运算
c. 对数输入的 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 @ data2
print(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.matmul 的区别
def test02():
# matmul 可以两个维度可以不同
# 第一个张量: (3, 4, 5)
# 第二个张量: (5, 4)
# torch.mm 不可以相乘,而 matmul 则可以相乘
print(torch.matmul(torch.randn(2, 3, 4, 5), torch.randn(1, 5, 4)).shape)
# print(torch.matmul(torch.randn(5, 4), torch.randn(3, 4, 5)).shape)
# print(torch.mm(torch.randn(3, 4, 5), torch.randn(5, 4)).shape)
# print(torch.mm(torch.randn(5, 4), torch.randn(3, 4, 5)).shape)
# 3. torch.mm 函数的用法(注:实际用的是 torch.bmm,这里注释可能笔误)
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([2, 3, 4, 4])
torch.Size([3, 4, 8])
2.4 指定运行设备
PyTorch 默认会将张量创建在 CPU 控制的内存中, 即: 默认的运算设备为 CPU。我们也可以将张量创建在 GPU 上, 能够利用对于矩阵计算的优势加快模型训练。 将张量移动到 GPU 上有三种方法:
- 使用 cuda 方法 2. 直接在 GPU 上创建张量 3. 使用 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 + data2
print(data)
if __name__ == '__main__':
test01()
test02()
test03()
test04()
三、张量类型转换
3.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] = 100
data_numpy[0] = 100
print(data_tensor)
print(data_numpy)
# 2. 使用 torch.tensor 函数
def test02():
data_numpy = np.array([2, 3, 4])
data_tensor = torch.tensor(data_numpy)
# numpy 和 tensor 不共享内存
# data_numpy[0] = 100
data_tensor[0] = 100
print(data_tensor)
print(data_numpy)
# 3. 标量张量和数字的转换
def test03():
# 当张量只包含一个元素时,可以通过 item 函数提取出该值
data = torch.tensor([30, ])
print(data.item())
data = torch.tensor(30)
print(data.item())
# data1 = torch.tensor([1.55, 2.55])
data1 = torch.tensor([1.55])
print(data1.item())
if __name__ == '__main__':
test01()
test02()
test03()
<class 'torch.Tensor'>
<class 'numpy.ndarray'>
tensor([100, 3, 4])
[100 3 4]
tensor([100, 3, 4], dtype=torch.int32)
[2 3 4]
30
30
1.5499999523162842
📌Python代码中如何比较两个数的大小?
方法1: 直接比较(适用于整数/精确值)
if x == y:
print('hello world')
else:
print('bad!')
方法2: 浮点数差值比较(避免精度误差)
if (x - y) < 1e-6: # 设定极小区间(如1e-6)
print('hello world')
else:
print('bad!')
四、张量拼接操作
张量的拼接操作在神经网络搭建过程中是非常常用的方法,例如:在后面将要学习到的残差网络、注意力机制中都使用到了张量拼接
4.1 torch.cat 函数的使用
import torch
def test01():
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(new_data)
print('-' * 50)
# 2. 按1维度拼接
new_data = torch.cat([data1, data2], dim=1)
print(new_data.shape)
print(new_data)
print('-' * 50)
# 3. 按2维度拼接
new_data = torch.cat([data1, data2], dim=2)
print(new_data.shape)
print(new_data)
print('-' * 50)
'''
new_data = torch.cat([data1, data2], dim=-1)
print(new_data.shape)
new_data = torch.cat([data1, data2], dim=-3)
print(new_data.shape)
'''
def test02():
data1 = torch.randint(0, 10, [2, 3])
data2 = torch.randint(0, 10, [2, 3])
# data2= torch.randint(0, 10, [2, 3, 4])
print(data1)
print(data2)
new_data1 = torch.cat([data1, data2], dim=0)
print('-----------------------------')
new_data2 = torch.stack([data1, data2], dim=0)
print(new_data1.shape)
print(new_data2.shape)
print(new_data1)
print(new_data2)
#print('-' * 50)
# x = torch.Tensor([1])
# y = torch.Tensor([2])
# z = torch.stack([x, y],dim=0)
# print(z)
'''
new_data = torch.stack([data1, data2], dim=1)
print(new_data.shape)
print(new_data)
print('---------------------')
new_data = torch.stack([data1, data2], dim=2)
print(new_data.shape)
print(new_data)
'''
if __name__ == '__main__':
test01()
test02()
tensor([[[4, 9, 8, 2],
[2, 7, 5, 0],
[5, 0, 5, 0],
[1, 7, 2, 4],
[5, 6, 1, 3]],
[[6, 3, 2, 9],
[3, 5, 6, 8],
[3, 8, 8, 6],
[2, 2, 0, 8],
[2, 1, 7, 4]],
[[8, 0, 3, 6],
[4, 6, 1, 6],
[6, 3, 6, 6],
[3, 1, 2, 6],
[5, 5, 8, 1]]])
tensor([[[5, 9, 2, 6],
[5, 3, 4, 5],
[4, 3, 2, 6],
[2, 9, 5, 0],
[7, 9, 5, 0]],
[[3, 7, 9, 0],
[4, 2, 1, 5],
[0, 4, 6, 1],
[3, 3, 9, 4],
[1, 1, 3, 0]],
[[4, 5, 4, 6],
[7, 6, 2, 0],
[8, 4, 9, 5],
[3, 9, 7, 1],
[5, 8, 0, 2]]])
--------------------------------------------------
torch.Size([6, 5, 4])
tensor([[[4, 9, 8, 2],
[2, 7, 5, 0],
[5, 0, 5, 0],
[1, 7, 2, 4],
[5, 6, 1, 3]],
[[6, 3, 2, 9],
[3, 5, 6, 8],
[3, 8, 8, 6],
[2, 2, 0, 8],
[2, 1, 7, 4]],
[[8, 0, 3, 6],
[4, 6, 1, 6],
[6, 3, 6, 6],
[3, 1, 2, 6],
[5, 5, 8, 1]],
[[5, 9, 2, 6],
[5, 3, 4, 5],
[4, 3, 2, 6],
[2, 9, 5, 0],
[7, 9, 5, 0]],
[[3, 7, 9, 0],
[4, 2, 1, 5],
[0, 4, 6, 1],
[3, 3, 9, 4],
[1, 1, 3, 0]],
[[4, 5, 4, 6],
[7, 6, 2, 0],
[8, 4, 9, 5],
[3, 9, 7, 1],
[5, 8, 0, 2]]])
--------------------------------------------------
torch.Size([3, 10, 4])
tensor([[[4, 9, 8, 2],
[2, 7, 5, 0],
[5, 0, 5, 0],
[1, 7, 2, 4],
[5, 6, 1, 3],
[5, 9, 2, 6],
[5, 3, 4, 5],
[4, 3, 2, 6],
[2, 9, 5, 0],
[7, 9, 5, 0]],
[[6, 3, 2, 9],
[3, 5, 6, 8],
[3, 8, 8, 6],
[2, 2, 0, 8],
[2, 1, 7, 4],
[3, 7, 9, 0],
[4, 2, 1, 5],
[0, 4, 6, 1],
[3, 3, 9, 4],
[1, 1, 3, 0]],
[[8, 0, 3, 6],
[4, 6, 1, 6],
[6, 3, 6, 6],
[3, 1, 2, 6],
[5, 5, 8, 1],
[4, 5, 4, 6],
[7, 6, 2, 0],
[8, 4, 9, 5],
[3, 9, 7, 1],
[5, 8, 0, 2]]])
--------------------------------------------------
torch.Size([3, 5, 8])
tensor([[[4, 9, 8, 2, 5, 9, 2, 6],
[2, 7, 5, 0, 5, 3, 4, 5],
[5, 0, 5, 0, 4, 3, 2, 6],
[1, 7, 2, 4, 2, 9, 5, 0],
[5, 6, 1, 3, 7, 9, 5, 0]],
[[6, 3, 2, 9, 3, 7, 9, 0],
[3, 5, 6, 8, 4, 2, 1, 5],
[3, 8, 8, 6, 0, 4, 6, 1],
[2, 2, 0, 8, 3, 3, 9, 4],
[2, 1, 7, 4, 1, 1, 3, 0]],
[[8, 0, 3, 6, 4, 5, 4, 6],
[4, 6, 1, 6, 7, 6, 2, 0],
[6, 3, 6, 6, 8, 4, 9, 5],
[3, 1, 2, 6, 3, 9, 7, 1],
[5, 5, 8, 1, 5, 8, 0, 2]]])
📌torch.cat() 和 torch.stack() 的区别?
torch.cat()
:连接两个张量,指定维度(如dim=0
),结果维度不变!!!torch.stack()
:拼接两个张量,指定维度(如dim=0
),结果维度+1(在指定dim
上新增一个维度!!!)
五、张量索引操作
我们在操作张量时,经常需要去进行获取或者修改操作,掌握张量的花式索引操作是必须的一项能力
import torch
# 1. 简单行、列索引
def test01(data):
print("简单行、列索引:")
print("第0行数据:", data[0]) # 获取第0行数据
print("所有行的第0列数据:", data[:, 0]) # 获取所有行的第0列数据
print('-' * 50)
# 2. 列表索引
def test02(data):
print("列表索引:")
# 返回 (0, 1)、(1, 2) 两个位置的元素
print("(0,1)和(1,2)位置元素:", data[[0, 1], [1, 2]])
# 返回 0、1 行的 1、2 列共4个元素(二维索引)
print("0-1行的1-2列元素:\n", data[[[0], [1]], [1, 2]])
print('-' * 50)
# 3. 范围索引
def test03(data):
print("范围索引:")
# 前3行的前2列数据(行:0-2,列:0-1)
print("前3行前2列:\n", data[:3, :2])
# 第2行到最后一行的前2列数据(行:2-末尾,列:0-1)
print("第2行及以后的前2列:\n", data[2:, :2])
print('-' * 50)
# 4. 布尔索引
def test04(data):
print("布尔索引:")
# 第2列大于5的行数据
print("第2列>5的行:\n", data[data[:, 2] > 5])
# 第1行大于5的列数据
print("第1行>5的列:", data[:, data[1] > 5])
print('-' * 50)
# 5. 多维索引(三维张量)
def test05():
print("多维索引(三维张量):")
# 创建3×4×5的三维随机整数张量(0-9)
data = torch.randint(0, 10, [3, 4, 5])
print("原始三维张量:\n", data)
print("=" * 50)
print("第0层的4×5矩阵:\n", data[0, :, :]) # 第0个维度全取
print("每一层的第0行:\n", data[:, 0, :]) # 第1个维度取第0行
print("每一层每一行的第0列:\n", data[:, :, 0]) # 第2个维度取第0列
print("张量形状:", data.shape)
print('-' * 50)
if __name__ == '__main__':
# 定义一个5行6列的二维张量(0-9随机整数),供test01-test04使用
data_2d = torch.randint(0, 10, [5, 6])
print("基础二维张量:\n", data_2d)
print("=" * 50)
# 依次调用索引测试函数(传入二维张量)
test01(data_2d)
test02(data_2d)
test03(data_2d)
test04(data_2d)
# 测试三维张量索引(内部独立定义数据)
test05()
# 运行结果
基础二维张量:
tensor([[1, 1, 7, 9, 6, 3],
[4, 7, 2, 1, 3, 4],
[6, 6, 0, 9, 9, 6],
[0, 2, 9, 6, 3, 1],
[7, 4, 2, 6, 7, 4]])
==================================================
简单行、列索引:
第0行数据: tensor([1, 1, 7, 9, 6, 3])
所有行的第0列数据: tensor([1, 4, 6, 0, 7])
--------------------------------------------------
列表索引:
(0,1)和(1,2)位置元素: tensor([1, 2])
0-1行的1-2列元素:
tensor([[1, 7],
[7, 2]])
--------------------------------------------------
范围索引:
前3行前2列:
tensor([[1, 1],
[4, 7],
[6, 6]])
第2行及以后的前2列:
tensor([[6, 6],
[0, 2],
[7, 4]])
--------------------------------------------------
布尔索引:
第2列>5的行:
tensor([[1, 1, 7, 9, 6, 3],
[0, 2, 9, 6, 3, 1]])
第1行>5的列: tensor([[1],
[7],
[6],
[2],
[4]])
--------------------------------------------------
多维索引(三维张量):
原始三维张量:
tensor([[[4, 1, 8, 9, 7],
[5, 4, 3, 3, 4],
[2, 0, 8, 6, 3],
[8, 7, 2, 7, 4]],
[[8, 9, 1, 9, 7],
[9, 6, 1, 1, 2],
[2, 9, 7, 5, 8],
[7, 7, 0, 9, 4]],
[[6, 6, 3, 3, 1],
[7, 6, 7, 1, 6],
[3, 4, 2, 5, 2],
[3, 5, 3, 0, 1]]])
==================================================
第0层的4×5矩阵:
tensor([[4, 1, 8, 9, 7],
[5, 4, 3, 3, 4],
[2, 0, 8, 6, 3],
[8, 7, 2, 7, 4]])
每一层的第0行:
tensor([[4, 1, 8, 9, 7],
[8, 9, 1, 9, 7],
[6, 6, 3, 3, 1]])
每一层每一行的第0列:
tensor([[4, 5, 2, 8],
[8, 9, 2, 7],
[6, 7, 3, 3]])
张量形状: torch.Size([3, 4, 5])
张量操作是 PyTorch 的基石,吃透这些能为深度学习项目铺路。趁热打铁,用这些知识开启你的 AI 开发实战吧!
六、张量形状操作
-
学习目标
-
掌握reshape, transpose, permute, view, contiguous, squeeze, unsqueeze等函数使用
-
在我们后面搭建网络模型时,数据都是基于张量形式的表示,网络层与层之间很多都是以不同的 shape 的方式进行表现和运算,我们需要掌握对张量形状的操作,以便能够更好处理网络各层之间的数据连接
6.1 reshape 函数的用法
- reshape 函数可以在保证张量数据不变的前提下改变数据的维度,将其转换成指定的形状,在后面的神经网络学习时,会经常使用该函数来调节数据的形状,以适配不同网络层之间的数据传递。
import torch
import numpy as np
def 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])
6.2 transpose 和 permute 函数的使用
-
transpose 函数可以实现交换张量形状的指定维度,例如:一个张量的形状为 (2,3,4) 可以通过 transpose 函数把 3 和 4 进行交换,将张量的形状变为 (2,4,3)
-
📌permute 函数可以一次交换更多的维度
import torch
import numpy as np
def test():
data = torch.tensor(np.random.randint(0, 10, [3, 4, 5]))
print('data shape:', data.shape)
# 1、交换1和2维度
new_data = torch.transpose(data, 1, 2)
print('new_data shape:', new_data.shape)
data[0,0,0] = 1000
print("data:",data)
print("new_data:", new_data)
print("new_data shape:", new_data.size())
print("="*50)
# 2、将 data 的形状修改为 (4, 5, 3)
new_data = torch.transpose(data, 0, 1)
print('new_data shape:', new_data.shape)
new_data = torch.transpose(new_data, 1, 2)
print('new_data shape:', new_data.shape)
print("="*50)
# 3、使用 permute 函数将形状修改为 (4, 5, 3)
new_data = torch.permute(data, [1, 2, 0])
print('new_data shape:', new_data.shape)
if __name__ == '__main__':
test()
# 运行结果
data shape: torch.Size([3, 4, 5])
new_data shape: torch.Size([3, 5, 4])
data: tensor([[[1000, 4, 6, 0, 7],
[ 2, 4, 9, 6, 3],
[ 4, 8, 8, 9, 3],
[ 6, 6, 4, 6, 1]],
[[ 1, 4, 2, 0, 5],
[ 0, 0, 0, 7, 9],
[ 5, 0, 2, 2, 4],
[ 1, 2, 5, 6, 0]],
[[ 1, 2, 9, 1, 4],
[ 3, 2, 0, 7, 6],
[ 9, 5, 4, 2, 7],
[ 7, 9, 1, 1, 1]]], dtype=torch.int32)
new_data: tensor([[[1000, 2, 4, 6],
[ 4, 4, 8, 6],
[ 6, 9, 8, 4],
[ 0, 6, 9, 6],
[ 7, 3, 3, 1]],
[[ 1, 0, 5, 1],
[ 4, 0, 0, 2],
[ 2, 0, 2, 5],
[ 0, 7, 2, 6],
[ 5, 9, 4, 0]],
[[ 1, 3, 9, 7],
[ 2, 2, 5, 9],
[ 9, 0, 4, 1],
[ 1, 7, 2, 1],
[ 4, 6, 7, 1]]], dtype=torch.int32)
new_data shape: torch.Size([3, 5, 4])
==================================================
new_data shape: torch.Size([4, 3, 5])
new_data shape: torch.Size([4, 5, 3])
==================================================
new_data shape: torch.Size([4, 5, 3])
6.3 view 和 contigous 函数的用法
- 📌 view 函数也可以用于修改张量的形状,但是其用法比较局限,只能用于存储在整块内存中的张量。在 PyTorch 中,有些张量是由不同的数块组成的,
它们并没有存储在整块的内存中,view 函数无法对这样的张量进行变形处理
。例如一个张量经过了 transpose 或者 permute 函数的处理之后,就无法使用 view 函数进行形状操作
import torch
import numpy as np
def test():
data = torch.tensor([[10, 20, 30], [40, 50, 60]])
print('data shape:', data.shape)
# 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
print("="*50)
# new_data = new_data.view(2, 3) # RuntimeError
new_data = new_data.reshape(2,3)
print('new_data.shape:',new_data.shape)
print(new_data.is_contiguous())
print("="*50)
# 需要先使用 contiguous 函数转换为整块内存的张量,再使用 view 函数
print('new_data.contiguous():', 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
==================================================
new_data.shape: torch.Size([2, 3])
True
==================================================
new_data.contiguous(): True
new_data shape: torch.Size([2, 3])
6.4 squeeze 和 unsqueze 函数的用法
- squeeze 函数用于删除 shape 为 1 的维度,unsqueeze 在指定维度添加 1,以增加数据的形状。
import torch
import numpy as np
def 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维度增加一个维度(原代码中维度索引为 -1 ,即最后一维)
new_data = data.unsqueeze(-1)
print('new_data shape:', new_data.size()) # torch.Size([1, 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])
七、 张量运算函数
- 学习目标
- 掌握张量相关运算函数
7.1 常见运算函数
- PyTorch 为每个张量封装很多实用的计算函数,例如计算均值、平方根、求和等等
import torch
def test():
data = torch.randint(0, 10, [2, 3], dtype=torch.float64)
print(data)
print('-' * 50)
# 1. 计算均值
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.log10())
print(data.log2())
if __name__ == '__main__':
test()
# 运行结果
tensor([[2., 7., 1.],
[0., 1., 3.]], dtype=torch.float64)
--------------------------------------------------
tensor(2.3333, dtype=torch.float64)
tensor([1., 4., 2.], dtype=torch.float64)
tensor([3.3333, 1.3333], dtype=torch.float64)
--------------------------------------------------
tensor(14., dtype=torch.float64)
tensor([2., 8., 4.], dtype=torch.float64)
tensor([10., 4.], dtype=torch.float64)
--------------------------------------------------
tensor([[ 4., 49., 1.],
[ 0., 1., 9.]], dtype=torch.float64)
--------------------------------------------------
tensor([[1.4142, 2.6458, 1.0000],
[0.0000, 1.0000, 1.7321]], dtype=torch.float64)
--------------------------------------------------
tensor([[7.3891e+00, 1.0966e+03, 2.7183e+00],
[1.0000e+00, 2.7183e+00, 2.0086e+01]], dtype=torch.float64)
--------------------------------------------------
tensor([[0.6931, 1.9459, 0.0000],
[ -inf, 0.0000, 1.0986]], dtype=torch.float64)
tensor([[0.3010, 0.8451, 0.0000],
[ -inf, 0.0000, 0.4771]], dtype=torch.float64)
tensor([[1.0000, 2.8074, 0.0000],
[ -inf, 0.0000, 1.5850]], dtype=torch.float64)
八、 自动微分模块
-
学习目标
-
掌握梯度计算
-
自动微分(Autograd)模块对张量做了进一步的封装,具有自动求导功能。自动微分模块是构成神经网络训练的必要模块,在神经网络的反向传播过程中,Autograd 模块基于正向计算的结果对当前的参数进行微分计算,从而实现网络权重参数的更新
8.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
# 注意:
# 由于求导的结果必须是标量
# 而 f1 的结果是: tensor([120., 420., ...])
# 所以,不能直接自动微分
# 需要将结果计算为标量才能进行计算
f2 = f1.mean() # f2 = 1/2 * x
# 自动微分
f2.backward()
# 打印 x 变量的梯度
print(x.grad)
if __name__ == '__main__':
test01()
test02() # 若需测试可取消注释
# 运行结果
tensor(20., dtype=torch.float64)
tensor([5., 10., 15., 20.], dtype=torch.float64)
8.2 控制梯度计算
- 我们可以通过一些方法使得在
requires_grad=True
的张量在某些时候计算不进行梯度计算。
##### 1. 控制不计算梯度
def test01():
x = torch.tensor(10, requires_grad=True, dtype=torch.float64)
print(x.requires_grad)
# 第一种方式:对代码进行装饰
with torch.no_grad():
y = x ** 2
print(y.requires_grad)
# 第二种方式:对函数进行装饰
@torch.no_grad()
def my_func(x):
return x ** 2
print(my_func(x).requires_grad)
# 第三种方式
torch.set_grad_enabled(False)
y = x ** 2
print(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 + 20
f2 = f1.mean()
print(f'f2=', f2)
# 默认张量的 grad 属性会累计历史梯度值
# 所以,需要我们每次手动清理上次的梯度
# 注意:一开始梯度不存在,需要做判断
if x.grad is not None:
x.grad.data.zero_()
f2.backward()
print(x.grad)
print('-' * 50)
# 3. 梯度下降优化最优解
def test03():
# y = x**2
x = torch.tensor(10., requires_grad=True, dtype=torch.float64)
count = 0
for _ in range(50000):
# 正向计算
f = x ** 2
# 梯度清零
if x.grad is not None:
x.grad.data.zero_()
# 反向传播计算梯度
f.backward()
# 更新参数 (variable) data: Tensor | Any
x.data = x.data - 0.001 * x.grad
count += 1
if count % 500 == 0:
print(f'{count:10} {x.data}')
if __name__ == '__main__':
# test01()
# test02()
test03()
if __name__ == '__main__':
test01()
九、模型的保存加载
- 学习目标
- 掌握PyTorch保存模型的方法
- 神经网络的训练有时需要几天、几周,甚至几个月,为了在每次使用模型时避免高代价的重复训练,我们就需要将模型序列化到磁盘中,使用的时候反序列化到内存中
9.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')
9.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')
9.3 加载模型参数
# 创建一个与保存时相同结构的模型
model = SimpleModel()
# 加载模型的参数
model.load_state_dict(torch.load('model_weights.pth'))
print(model)
# 保存模型的参数
# torch.save(model.state_dict(), 'model_weights.pth')
# print(model)
# print('---------------------')
# print(model.state_dict())
# 保存完整的模型
# torch.save(model, 'model_weights_1.pth')
# print(model)
9.4 加载全部模型
model = torch.load('model.pth')
print(model)
9.5 注意事项
- 模型结构:如果你只保存了模型的参数,那么在加载时需要确保你有与保存时相同的模型结构
- 设备兼容性:如果你在一个设备上保存了模型(例如GPU),而在另一个设备上加载(例如CPU),你可能需要使用
map_location
参数来指定设备:device = torch.device('cpu') model.load_state_dict(torch.load('model_weights.pth', map_location=device))
📌吃透这些技巧,PyTorch 开发难题迎刃而解,让模型训练更顺畅,助你在 AI 实践中快速进阶!