- 参考:
- 《深度学习框架PyTorch入门与实践》—— 陈云
- 《动手学深度学习(PyTorch版)》—— 李沐
- 注意:由于本文是jupyter文档转换来的,代码不一定可以直接运行,有些注释是jupyter给出的交互结果,而非运行结果!!
文章目录
- PyTorch中,张量
torch.Tensor
是存储和变换数据的主要工具 Tensor
和NumPy的多维数组ndarray
非常类似,与之相比,Tensor
提供GPU计算和自动求梯度等更多功能,使其更加适合深度学习。
1. 创建 Tensor
- 创建方式和 NumPy 的
ndarray
大同小异,一些常见的 API 如下
- 示例:
import torch print('创建一个 5x3 的未初始化tensor') x = torch.empty(5,3) print(x,'\n') print('创建一个 5x3 的随机初始化tensor,数据是整型') x = torch.IntTensor(5,3) # rnadn() 生成指定维度浮点型数据,数据服从均值为0,方差为1的正态分布 print(x,'\n') print('创建一个 5x3 的随机初始化tensor,数据是浮点型') x = torch.FloatTensor(5,3) # rnadn() 生成指定维度浮点型数据,数据服从均值为0,方差为1的正态分布 print(x,'\n') print('创建一个 5x3 的随机初始化tensor,数据是浮点型,服从 0-1 区间均匀分布') x = torch.rand(5,3) # rnad() 生成指定维度浮点型数据,数据服从 0-1 区间均匀分布 print(x,'\n') print('创建一个 5x3 的随机初始化tensor,数据是浮点型,服从均值为0,方差为1的正态分布') x = torch.rand(5,3) # rnadn() 生成指定维度浮点型数据,数据服从均值为0,方差为1的正态分布 print(x,'\n') print('根据起、止、步长创建一个左闭右开的 range 序列,类似Numpy中的 arange()') x = torch.arange(0,11,2,dtype = torch.int) # rnadn() 生成指定维度浮点型数据,数据服从均值为0,方差为1的正态分布 print(x,'\n') print('创建一个 5x3 的 long 型全零 Tensor') x = torch.zeros(5,3,dtype=torch.long) print(x,'\n') print('直接根据pythons数据创建,可以指定数据类型') x = torch.tensor([5.5,3],dtype = torch.int32) print(x,'\n') print('通过现有的tensor来创建,默认重用数据类型等属性,除非自定义这些属性') x = torch.ones(5,3,dtype=torch.float64) print(x) print("借用形状并且指定新的数据类型") x = torch.randn_like(x,dtype=torch.float) print(x)
- 可以通过
.shape
或者.size()
来获取Tensor的形状,返回的torch.Size
是一个tuple, 支持所有tuple的操作x = torch.zeros(5,5) print(x.size()) # torch.Size([5, 5]) print(x.shape) # torch.Size([5, 5])
2. 操作
- PyTorch中的 Tensor 支持超过一百种操作,包括转置、索引、切片、数学运算、线性代数、随机数等等,可参考 官方文档
2.1 算数操作
-
在Pytorch中,同一种操作可能有很多形式,以加法为例
x = torch.tensor([[5,3],[4,6]]) y = torch.tensor([[5,3],[4,6]]) # 方法1:直接用运算符号 print(x+y,'\n') # 方法2:使用torch.add静态方法,可以通过 out 参数指定输出(要求尺寸一致) res = torch.empty(2,2) torch.add(x,y,out = res) print(res,'\n') # 方法3:inplace形式(这种形式会修改y的值) y.add_(x) print(y,'\n')
-
注:Pytorch 操作的 inplace 版本都有后缀
_
,如y.add_(x)
,y.copy_(y)
,y.t_()
等,inplace 方式会修改y
的值,可以避免重新开辟内存,速度较快
2.2 索引
- 可以使用类似 NumPy 的索引操作来访问 Tensor 的一部分,需要注意的是:索引出来的结果与原数据共享内存,也即修改一个,另一个会跟着修改
x = torch.tensor([[5,3],[4,6]]) print(x) y = x[0,:][0] y += 1 x[0,:][1] -= 1 print(x) # 输出 tensor([[5, 3], [4, 6]]) tensor([[6, 2], [4, 6]])
- 还有很多特殊索引,可以等用到的时候再查
x = torch.tensor([[1,2],[3,4],[5,6]]) print(torch.index_select(x,1,torch.tensor([1]))) print(torch.masked_select(x,x>4)) # 输出 tensor([[2], [4], [6]]) tensor([5, 6])
2.3 改变Tensor形状
-
使用
tensor.view()
来改变Tensor
的形状。注意view()
返回的Tensor与源Tensor是共享data的,即更改其中一个,另外一个也会跟着改变。(顾名思义,view仅仅是改变了对这个张量的观察角度,内部数据并未改变)x = torch.randn(5,3) y = x.view(15) z = x.view(-1,5) # -1 所指的维度可以由其他维度的值推导出来 print(x.size(),y.size(),z.size()) # torch.Size([5, 3]) torch.Size([15]) torch.Size([3, 5])
-
如果想要返回真正的副本,可以先使用
Tensor.clone()
方法创建副本,再用Tensor.view()
方法修改形状,如下x = torch.rand(5,3) y = x.clone().view(15) y[y<0.5] = 0 print(x) print(y) # 输出 tensor([[0.2987, 0.1410, 0.2219], [0.3753, 0.8129, 0.4814], [0.1228, 0.0604, 0.6366], [0.1716, 0.1970, 0.1454], [0.8926, 0.3561, 0.2427]]) tensor([0.0000, 0.0000, 0.0000, 0.0000, 0.8129, 0.0000, 0.0000, 0.0000, 0.6366, 0.0000, 0.0000, 0.0000, 0.8926, 0.0000, 0.0000])
注:使用
clone
还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源Tensor -
另一个常用函数是
Tensor.item()
,它将一个 标量 Tensor转为 Python memberx = torch.FloatTensor(1) print(x) print(x.item()) # 输出 tensor([1.4013e-45]) 1.401298464324817e-45
2.4 线性代数
- 常用方法,可以具体用时再具体查
3. 广播机制
-
两个形状相同的Tensor,可以直观地进行按元素计算
-
两个形状不同的Tensor,在进行按元素计算时,可能触发
广播(boardcasting)
机制:先适当地复制元素使得两个 Tensor 形状相同,然后按元素运算,例如x = torch.arange(1, 3).view(1, 2) # size = 2 -> size = (1,2) print(x) y = torch.arange(1, 4).view(3, 1) # size = 3 -> size = (3,1) print(y) print(x + y) # 输出 tensor([[1, 2]]) tensor([[1], [2], [3]]) tensor([[2, 3], [3, 4], [4, 5]])
这里
x
和y
分别是 (1,2) 和 (3,1) 的矩阵,如果要计算x + y
,那么x
中第一行的2个元素广播(复制)到第二行和第三行y
中第一列的3个元素广播(复制)到第二列
如此,就可以对2个 (3,2) 的矩阵按元素相加。
4. 运算的内存开销
-
索引操作不会开辟新内存,而像
y = x + y
这样的运算是会新开内存的,然后将y
指向新内存 -
我们可以使用Python自带的
id
函数判断是否开辟了新内存:如果两个实例的ID
一致,那么它们所对应的内存地址相同;反之则不同(说明开辟了新内存)x = torch.tensor([1, 2]) y = torch.tensor([3, 4]) id_before = id(y) y = y + x print(id(y) == id_before) # False
-
如果想指定结果到原来的内存中,比如把
x + y
的结果写进y
对应的内存中,不开新空间,有以下方法- 使用索引进行替换操作:
y[:] = x + y
- 使用运算函数中的
out
参数进行设置:torch.add(x, y, out=y)
- 使用自加运算符 += 等:
y += x
- 使用 inplace 版本的运算函数:
y.add_(x)
注:虽然
view
返回的Tensor与源Tensor是共享data的,但是依然是一个新的Tensor(因为Tensor除了包含data外还有一些其他属性),二者id
(内存地址)并不一致。 - 使用索引进行替换操作:
5. Tensor 和 ndarray 的相互转换
-
PyTorch中的Tensor和NumPy中的ndarray可以很方便地相互转换,这样我们就可以把 NumPy 强大的矩阵运算能力和Tensor支持GPIU加速的优势相结合,所有在CPU上的Tensor(除了
CharTensor
)都支持与NumPy ndarray相互转换 -
常用的转换方法
- Tensor -> ndarray:
ndarray = Tensor.numpy()
- ndarray -> Tensor:
Tensor = torch.from_numpy(ndarray)
需要注意的一点是:这两个函数所产生的的 Tensor 和 ndarray 共享相同的内存(所以他们之间的转换很快),改变其中一个时另一个也会改变!!!
# Tensor -> ndarray a = torch.ones(5) b = a.numpy() print(a, b) # tensor([1., 1., 1., 1., 1.]) [1. 1. 1. 1. 1.] a += 1 print(a, b) # tensor([2., 2., 2., 2., 2.]) [2. 2. 2. 2. 2.] b += 1 print(a, b) # tensor([3., 3., 3., 3., 3.]) [3. 3. 3. 3. 3.]
# ndarray -> Tensor import numpy as np a = np.ones(5) b = torch.from_numpy(a) print(a, b) # [1. 1. 1. 1. 1.] tensor([1., 1., 1., 1., 1.], dtype=torch.float64) a += 1 print(a, b) # [2. 2. 2. 2. 2.] tensor([2., 2., 2., 2., 2.], dtype=torch.float64) b += 1 print(a, b) # [3. 3. 3. 3. 3.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)
- Tensor -> ndarray:
-
还有一个常用的 ndarray -> Tensor 的方法是直接使 ndarray 构造 Tensor,即使用
torch.tensor()
方法。此方法总是会进行数据拷贝(就会消耗更多的时间和空间),所以返回的Tensor和原来的数据不再共享内存a = np.ones(5) c = torch.tensor(a) a += 1 print(a, c) # [2. 2. 2. 2. 2.] tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
6. Tensor on GPU
-
在创建 Tensor 时,默认是在CPU处理的,通过设置
device
参数可以将其创建为在GPU处理的Tensor -
用
Tensor.to()
方法,可以将Tensor在CPU与GPU之间相互移动 -
使用
Tensor.cuda()
方法,可以把CPU Tensor转换为GPU Tensorx = torch.tensor([1, 2]) # 这样直接创建的Tensor是在CPU处理的 # 以下代码只有在PyTorch GPU版本上才会执行 if torch.cuda.is_available(): device = torch.device("cuda") # GPU y = torch.ones_like(x, device=device) # 通过设置 device 参数,直接创建在GPU处理的 Tensor x = x.to(device) # 等价于 .to("cuda") z = x + y print(z) print(z.to("cpu", torch.double)) # to()还可以同时更改数据类型 # 输出 tensor([2, 3], device='cuda:0') tensor([2., 3.], dtype=torch.float64)
x = torch.tensor([1, 2]) y = torch.tensor([2, 3]) if torch.cuda.is_available(): x = x.cuda() y = y.cuda() z = x + y print(z) # tensor([3, 5], device='cuda:0')