pytorch学习笔记-张量(Tensor)操作
张量是深度学习中必不可少的内容,虽然十分基础但是在整个深度学习中每个地方都会涉及到,下面从创建、运算、广播以及转换等方面记录一下学习心得,学习过程中参考了动手学深度学习(pytorch版本)和pytorch官方文档。
导入包:import torch
创建Tensor
torch.empty(5, 3) #创建一个5*3的张量,并不进行初始化
torch.rand(5, 3) #创建一个5*3的张量,并随机初始化
torch.zeros(5, 3, dtype = torch.long) #创建一个5*3的全零张量,指定数据类型为long
torch.tensor([[1, 2, 3], [4, 5, 6]]) #创建自定义张量
x = x.new_ones(5, 3) # 返回的tensor默认具有与x相同的torch.dtype和torch.devic
x = torch.randn_like(x, dtype=torch.float) # 指定新的数据类型
还有一些常见的创建张量的操作如下表
命令 | 含义 |
| 基础的Tensor构造函数 |
| 将数据转换成 |
| 全1张量 |
| 全0张量 |
| 对角线为1,其他为0 |
| 范围步长 |
| 从s到e,均匀切分成steps份 |
| 均匀标准分布 |
| 正太均匀分布 |
| 随机排列 |
Note:也可以通过torch.cuda.Tensor(*size)
将数据放在GPU上
张量操作
加法操作
# 形式一
y = torch.rand(5, 3)
print(x + y)
# 形式二
torch.add(x, y)
result = torch.empty(5, 3)
torch.add(x, y, out=result)
# 形式三,加到y上面
y.add_(x)
Note:后缀_表示inplace
,例如x.copy_(y), x.t_()
表示在原地操作并替代
索引切片
我们还可以使用类似NumPy的索引操作来访问Tensor的一部分,需要注意的是:这里索引出来的结果与原数据共享内存,也即修改一个,另一个会跟着修改。
y = x[0, :]
y += 1 # x也会被跟着修改
除了常用的索引选择数据之外,PyTorch还提供了一些高级的选择函数如下表:
函数 | 操作 |
| 在指定维度dim上选取,比如选取某些行、某些列 |
| 例子如上,a[a>0],使用ByteTensor进行选取 |
| 非0元素的下标 |
| 根据index,在dim维度上选取数据,输出的size与index一样 |
改变形状
# 用view()来改变Tensor的形状,仅仅是改变观看的视角,其内存地址不会发生改变,因此对改变后的张量进行操作,原张量也会改变
y = x.view(15)
z = x.view(-1, 5)
所以如果我们想返回一个真正新的副本(即不共享data内存)该怎么办呢?Pytorch还提供了一个reshape()
可以改变形状,但是此函数并不能保证返回的是其拷贝,所以不推荐使用。推荐先用clone
创造一个副本然后再使用view
# 这里x与x_cp相互之间是独立的
x_cp = x.clone().view(15)
x -= 1
Note:使用clone
还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源Tensor
。
pytorch中另外一个常用的函数就是item()
, 它可以将一个标量Tensor
转换成一个Python number
# 返回的是一个数,将一个标量转成一个数
x = torch.randn(1)
print(x)
print(x.item())
下表中提供了一些代数运算
函数 | 功能 |
| 对角线元素之和(矩阵的迹) |
| 对角线元素 |
| 矩阵的上三角/下三角,可指定偏移量 |
| 矩阵乘法,batch的矩阵乘法 |
| 矩阵运算 |
| 转置 |
| 内积/外积 |
| 求逆矩阵 |
| 奇异值分解 |
Note:其他运算可以参考官方文档
广播机制
x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)
###
tensor([[2, 3],
[3, 4],
[4, 5]])
###
由于x和y分别是1行2列和3行1列的矩阵,如果要计算x + y,那么x中第一行的2个元素被广播(复制)到了第二行和第三行,而y中第一列的3个元素被广播(复制)到了第二列。如此,就可以对2个3行2列的矩阵按元素相加。
运算的内存开销
索引操作是不会开辟新内存的,而像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
y[:] = y + x #不开辟新的内存
print(id(y) == id_before) # True
torch.add(x, y, out=y) # y += x, y.add_(x) 都不开辟新的内存空间
print(id(y) == id_before) # True
tensor与numpy转换
tensor转numpy
a = torch.ones(5)
b = a.numpy()
print(a, b)
a += 1
print(a, b)
b += 1
print(a, b)
###
tensor([1., 1., 1., 1., 1.]) [1. 1. 1. 1. 1.]
tensor([2., 2., 2., 2., 2.]) [2. 2. 2. 2. 2.]
tensor([3., 3., 3., 3., 3.]) [3. 3. 3. 3. 3.]
###
numpy转tensor
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
print(a, b)
a += 1
print(a, b)
b += 1
print(a, b)
###
[1. 1. 1. 1. 1.] tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
[2. 2. 2. 2. 2.] tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
[3. 3. 3. 3. 3.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)
###
如果直接将numpy作为参数传入torch.tensor()
实现转换,则新的张量与原来的数组不共享内存,会开辟新的内存空间。
c = torch.tensor(a)
a += 1
print(a, c)
## [4. 4. 4. 4. 4.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)
torch.tensor()
实现转换,则新的张量与原来的数组不共享内存,会开辟新的内存空间。
c = torch.tensor(a)
a += 1
print(a, c)
## [4. 4. 4. 4. 4.] tensor([3., 3., 3., 3., 3.], dtype=torch.float64)