简单行、列索引、列表索引、范围索引、布尔索引、多维索引
import torch # 数据 data = torch.randint(0,10,[4,5]) print(data)
tensor([[7, 6, 9, 4, 6],
[1, 9, 0, 9, 2],
[5, 7, 1, 7, 4],
[1, 2, 7, 2, 1]])"""1.简单行、列索引""" print(data[0]) # 第一行 print(data[:,0]) # 第一列
tensor([7, 6, 9, 4, 6])
tensor([7, 1, 5, 1])"""2.列表索引""" # 返回(0,1)、(1,2)两个位置的元素 print(data[[0,1],[1,2]])
tensor([6, 0])
# 返回0、1行的1、2列共4个元素 # print(data[[0],[1],[1,2]])
"""3.范围索引""" # 前3 行的前2列数据 print(data[:3,:2]) # 前2行到最后的前2列数据 print(data[2:,:2])
tensor([[7, 6],
[1, 9],
[5, 7]])
tensor([[5, 7],
[1, 2]])"""4.布尔索引""" # 第三列大于5的行数据 print(data[data[:,2]>5]) # 第二行大于5的列数据 print(data[:,data[1]>5])
tensor([[7, 6, 9, 4, 6],
[1, 2, 7, 2, 1]])
tensor([[6, 4],
[9, 9],
[7, 7],
[2, 2]])"""5.多维索引""" data = torch.randint(0,10,[3,4,5]) print(data) print('-'*50,'\n') print(data[0,:,:]) print(data[:,0,:]) print(data[:,:,0])
tensor([[[5, 5, 5, 9, 1],
[1, 5, 3, 8, 0],
[8, 3, 8, 2, 8],
[7, 2, 5, 0, 4]],[[4, 7, 2, 1, 3],
[9, 2, 7, 5, 2],
[0, 0, 4, 8, 7],
[3, 6, 6, 5, 3]],[[1, 7, 5, 5, 4],
[7, 8, 9, 5, 0],
[0, 0, 3, 5, 5],
[2, 0, 8, 3, 6]]])
--------------------------------------------------tensor([[5, 5, 5, 9, 1],
[1, 5, 3, 8, 0],
[8, 3, 8, 2, 8],
[7, 2, 5, 0, 4]])
tensor([[5, 5, 5, 9, 1],
[4, 7, 2, 1, 3],
[1, 7, 5, 5, 4]])
tensor([[5, 1, 8, 7],
[4, 9, 0, 3],
[1, 7, 0, 2]])
1.reshape 函数可以在保证张量数据不变的前提下改变数据的维度 2.transpose 函数可以实现交换张量形状的指定维度,permute可以一次交换更多的维度 3.view 函数也可以用于修改张量的形状,但是它要求被转换的张量内存必须连续,所以易班配合contiguous函数使用 4.squeeze 和 unsqueeze 函数可以用来增加或者减少维度
1、reshape 函数的用法
reshape 函数可以在保证张量数据不变的前提下改变数据的维度,将其转换成指定的形状,在神经网络学习时,会经常使用该函数来调节数据的形状,以适配不同网络层之间的数据传递
import torch data = torch.tensor([[10,20,30],[40,50,60]]) print(data) # tensor([[10, 20, 30], # [40, 50, 60]]) # 1.使用shape属性或者size方法都可以获得张量的形状 print(data.shape,data.shape[0],data.shape[1]) # torch.Size([2, 3]) 2 3 print(data.size(),data.size(0),data.size(1)) # torch.Size([2, 3]) 2 3 # 2.使用reshape函数修改张量形状 new_data = data.reshape(1,6) print(new_data) print(new_data.shape) # tensor([[10, 20, 30, 40, 50, 60]]) # torch.Size([1, 6])
2、transpose 和 permute 函数的使用
transpose 函数可以实现交换张量形状的指定维度, 例如: 一个张量的形状为 (2, 3, 4) 可以通过 transpose 函数把 3 和 4 进行交换, 将张量的形状变为 (2, 4, 3) permute 函数可以一次交换更多的维度
import torch import numpy as np data = torch.tensor(np.random.randint(0, 10, [3, 4, 5])) print('data shape:', data.size()) # data shape: torch.Size([3, 4, 5]) # 1. 交换1和2维度 new_data = torch.transpose(data, 1, 2) print('data shape:', new_data.size()) # data shape: torch.Size([3, 5, 4]) # 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()) # new_data shape: torch.Size([4, 5, 3]) # 3. 使用 permute 函数将形状修改为 (4, 5, 3) new_data = torch.permute(data, [1, 2, 0]) print('new_data shape:', new_data.size()) # new_data shape: torch.Size([4, 5, 3])
3、view 和 contigous 函数的用法
view 函数也可以用于修改张量的形状,但是其用法比较局限,只能用于存储在整块内存中的张量。 在 PyTorch 中,有些张量是由不同的数据块组成的,它们并没有存储在整块的内存中, view 函数无法对这样的张量进行变形处理,例如: 一个张量经过了 transpose 或者 permute 函数的处理之后,就无法使用 view 函数进行形状操作。
import torch data = torch.tensor([[10, 20, 30], [40, 50, 60]]) print('data shape:', data.size()) # data shape: torch.Size([2, 3]) # 1. 使用 view 函数修改形状 new_data = data.view(3, 2) print('new_data shape:', new_data.shape) # new_data shape: torch.Size([3, 2]) # 2. 判断张量是否使用整块内存 print('data:', data.is_contiguous()) # True # data: 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()) # True new_data = new_data.contiguous().view(2, 3) print('new_data shape:', new_data.shape) # new_data shape: torch.Size([2, 3])
4、 squeeze 和 unsqueeze 函数的用法
import torch import numpy as np 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])
1、常见运算函数
PyTorch 为每个张量封装很多实用的计算函数,例如计算均值、平方根、求和等等
import torch def 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([[7., 5., 9.],
[6., 1., 6.]], dtype=torch.float64)
--------------------------------------------------
tensor(5.6667, dtype=torch.float64)
tensor([6.5000, 3.0000, 7.5000], dtype=torch.float64)
tensor([7.0000, 4.3333], dtype=torch.float64)
--------------------------------------------------
tensor(34., dtype=torch.float64)
tensor([13., 6., 15.], dtype=torch.float64)
tensor([21., 13.], dtype=torch.float64)
--------------------------------------------------
tensor([[49., 25., 81.],
[36., 1., 36.]], dtype=torch.float64)
--------------------------------------------------
tensor([[2.6458, 2.2361, 3.0000],
[2.4495, 1.0000, 2.4495]], dtype=torch.float64)
--------------------------------------------------
tensor([[1.0966e+03, 1.4841e+02, 8.1031e+03],
[4.0343e+02, 2.7183e+00, 4.0343e+02]], dtype=torch.float64)
--------------------------------------------------
tensor([[1.9459, 1.6094, 2.1972],
[1.7918, 0.0000, 1.7918]], dtype=torch.float64)
tensor([[2.8074, 2.3219, 3.1699],
[2.5850, 0.0000, 2.5850]], dtype=torch.float64)
tensor([[0.8451, 0.6990, 0.9542],
[0.7782, 0.0000, 0.7782]], dtype=torch.float64)
自动微分(Autograd)模块对张量做了进一步的封装,具有自动求导功能。 自动微分模块是构成神经网络训练的必要模块,在神经网络的反向传播过程中, Autograd 模块基于正向计算的结果对当前的参数进行微分计算,从而实现网络权重参数的更新 pytorch 中非常重要的自动微分模型的使用和理解 对需要计算梯度的张量需要设置requires_grad=True属性, 并且需要注意的是梯度是累计的,在每次计算梯度前需要进行梯度清零。
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) # 3. 多标量梯度计算 # y = x1 ** 2 + x2 ** 2 + x1*x2 def test03(): # 定义需要计算梯度的张量 x1 = torch.tensor(10, requires_grad=True, dtype=torch.float64) x2 = torch.tensor(20, requires_grad=True, dtype=torch.float64) # 经过中间的计算 y = x1**2 + x2**2 + x1*x2 # 将输出结果变为标量 y = y.sum() # 自动微分 y.backward() # 打印两个变量的梯度 print(x1.grad, x2.grad) # 4. 多向量梯度计算 def test04(): # 定义需要计算梯度的张量 x1 = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64) x2 = torch.tensor([30, 40], requires_grad=True, dtype=torch.float64) # 经过中间的计算 y = x1 ** 2 + x2 ** 2 + x1 * x2 print(y) # 将输出结果变为标量 y = y.sum() # 自动微分 y.backward() # 打印两个变量的梯度 print(x1.grad, x2.grad) if __name__ == '__main__': test04()
tensor(20., dtype=torch.float64) tensor([ 5., 10., 15., 20.], dtype=torch.float64) tensor(40., dtype=torch.float64) tensor(50., dtype=torch.float64) tensor([1300., 2800.], dtype=torch.float64, grad_fn=
) tensor([50., 80.], dtype=torch.float64) tensor([ 70., 100.], 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 ** 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() # 默认张量的 grad 属性会累计历史梯度值 # 所以, 需要我们每次手动清理上次的梯度 # 注意: 一开始梯度不存在, 需要做判断 if x.grad is not None: x.grad.data.zero_() f2.backward() print(x.grad) # 3. 梯度下降优化最优解 def test03(): # y = x**2 x = 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.grad print('%.10f' % x.data) if __name__ == '__main__': test01() test02() 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 140421811165776 print(id(x1.data), id(x2.data)) x2.data = torch.tensor([100, 200]) print(x1) print(x2) # x2 不会自动计算梯度: False print(x2.requires_grad) if __name__ == '__main__': test01() test02()
[10. 20.]
1865743596496 1865743596496
tensor([10., 20.], dtype=torch.float64, requires_grad=True)
tensor([100, 200])
False