【Pytorch学习笔记】Day01 - Pytorch的基本操作



文章目录

  • 【Pytorch学习笔记】Day01 - Pytorch的基本操作
  • 一、创建Tensor
  • 二、数据操作
  • 2.1 算术操作
  • 2.2 索引
  • 2.3 改变形状
  • 2.4 Tensor、NumPy 和 标量 的 互通
  • 2.5 线性代数相关函数
  • 三、Tensor的广播机制
  • 四、运算的内存开销
  • 五、Tensor在CPU和GPU之间相互移动



一、创建Tensor

        在PyTorch中,torch.Tensor是存储和变换数据的主要工具。Tensor与NumPy的多维数组相似,但提供GPU计算和自动求梯度等额外功能,更适用于深度学习。

        "tensor"可以翻译成“张量”,可以看作一个多维数组。标量看成0维张量,向量看作1维张量,矩阵可以看作是2维张量。

        创建Tensor的函数有多种,可以简单记忆下。

函数

功能

Tensor(*sizes)

基础构造函数

tensor(data,)

类似np.array的构造函数

ones(*sizes)

创建全1的Tensor

zeros(*sizes)

创建全0的Tensor

eye(*sizes)

对角线为1,其他为0

arrange(s,e,step)

从s到e,步长为step

linspace(s,e,steps)

从s到e,均匀切分成steps份}

rand/randn(*sizes)

均匀/标准分布

normal(mean,std)/(uniform(from,to)

正态分布/均匀分布

randperm(m)

随机整数

利用 import torch 导入 pytorch 。创建一个 4 x 3 empty的 Tensor:

x = torch.empty(4,3)
print(x)

可以看到行列序号都是从1开始。
输出:

tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])

其效果应该和x = torch.zeors(4,3, dtype=torch.long) 一样,创建5 x 3 long型全0 Tensor。

还可以直接根据数据创建:

x = torch.tensor([4.5,3])
print(x)

输出:

tensor([4.5000, 3.0000])

还可以通过现有的Tensor创建,默认会重用输入的Tensor的一些属性,如数据类型,除非重新自定义。

x = x.new_ones(4, 3, dtype=torch.float64) #返回的tensor默认具有相同的torch.dtype和torch.device
print(x)

x = torch.randn_like(x, dtype=torch.float) #指定新的数据类型
print(x)

输出:

tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], dtype=torch.float64)
tensor([[-1.1401,  1.2186, -1.2832],
        [ 0.1070,  0.2736, -0.2655],
        [-1.7234,  0.2947,  1.6515],
        [ 0.2684, -0.4540, -0.7197]])

可以通过shape或者size()来获取Tensor的形状:

print(x.size())
print(x.shape)

输出:

torch.Size([4, 3])
torch.Size([4, 3])

P.S.:以上创建方法都可以在创建时指定数据类型dtype和存放device(CPU/GPU)

二、数据操作

Tensor包含多种相关操作。

2.1 算术操作

在PyTorch中,同一种有多种不同写法,以加法为例。
形式一:

x = torch.ones(4,3)
y = torch.rand(4,3)
print(x)
print(y)
print(x + y)

形式二:

print(torch.add(x,y))

result = torch.empty(4,3)
torch.add(x,y,out=result)
print(result)

形式三:

y.add_(x)
print(y)

P.S.:Pytorch操作inplace版本都有后缀_,例如x.copy_(y),x.t_()
输出都是:

tensor([[1.3187, 1.1892, 1.9779],
        [1.3361, 1.4684, 1.8732],
        [1.8074, 1.2719, 1.8999],
        [1.3116, 1.2101, 1.1632]])

2.2 索引

        我们可以用类似NumPy的索引操作来访问Tensor的一部分,要注意索引出的结果和原数据是共享内存的,修改其索引,原数据也会改变。

x = torch.rand(4,3)
print(x)
y = x[0,:]   #取出第一排,所有列
y += 1
print(y)
print(x[0,:]) #源tensor也被修改

输出:

tensor([[0.6372, 0.0263, 0.7210],
        [0.7333, 0.4041, 0.9346],
        [0.2979, 0.7893, 0.6025],
        [0.7531, 0.8830, 0.6843]])
tensor([1.6372, 1.0263, 1.7210])
tensor([1.6372, 1.0263, 1.7210])

除了常用的索引选择方式,PyTorch还提供了一些高级的选择函数:

函数

功能

index_select(input, dim, index)

在指定维度dim上选取,比如选取某些行、某些列

masked_select(input, mask)

a[a>0],使用ByteTensor进行选取

nonzero(input)

非0元素的下标

2.3 改变形状

(1)用 view( shape ) 来改变 Tensor 的形状,将原 Tensor 改为其他的维度。该函数返回一个有__相同数据__但不同大小的 Tensor。通俗一点,就是__改变矩阵维度__,相当于 Numpy 中的 resize() 或者 Tensorflow 中的 reshape() 。

x = torch.randn(4, 4)
print(x.size())
y = x.view(16)
print(y.size())
z = x.view(-1, 8)  #-1表示不确定有几行,但是肯定有8列,因为16个元素8列,所以有2行,-1那里会自动替换为2
print(z.size())
m = x.view(2, 2, 4) #也可以变为更多维度
print(m.size())

输出:

torch.Size([4, 4])
torch.Size([16])
torch.Size([2, 8])
torch.Size([2, 2, 4])

值得注意的是:一共有多少个元素,那你使用view改变形状就要乘起来正好是这么多元素,否则一定会出错。

(2)view( -1 )
若我们需要转换维度到一维,有一种简单的方式,即参数为-1。

a = torch.Tensor([[1, 2, 3], [4, 5, 6]]) #定义一个 2*3 的 Tensor
a = a.view(-1)
print(a)

输出:

tensor([1., 2., 3., 4., 5., 6.])

从结果可以看出,其将每一行拼成了一行。
在实际操作中,我们经常看到这个写法:

data.contiguous().view(-1)

contiguous()是为了保证一个Tensor是连续的,这样才能被view()处理。

(3) view_as( other )
返回被视作与给定Tensor相同大小的原Tensor。等效于:self.view(tensor.size())。

a = torch.arange(0, 6)
a = a.view(2, 3)
print(a)
b = torch.arange(1,7)
print(b)
b = b.view_as(a)
print(b)

输出:

tensor([[0, 1, 2],
        [3, 4, 5]])
tensor([1, 2, 3, 4, 5, 6])
tensor([[1, 2, 3],
        [4, 5, 6]])

(4)data的互通
        view( )返回新的 Tensor 与源 Tensor 虽然形状可能不同,但是数据是共享的,即更改其中一个的data,另一个也会改变,view仅仅是改变了对这个张量的观察角度,其数据仍然是那些数据。那如果我们想创建一个副本怎么做呢?可以先用clone( )函数创造一个副本,然后再用view( )。

x = torch.rand(2,4)
print(x)
x_cp = x.clone().view(8)
x -= 1
print(x)
print(x_cp)

输出:

tensor([[0.2856, 0.0738, 0.7755, 0.4160],
        [0.8703, 0.2018, 0.7837, 0.4459]])
tensor([[-0.7144, -0.9262, -0.2245, -0.5840],
        [-0.1297, -0.7982, -0.2163, -0.5541]])
tensor([0.2856, 0.0738, 0.7755, 0.4160, 0.8703, 0.2018, 0.7837, 0.4459])

可以看到x变化了,但是x的克隆x_cp没有变化,且形状通过view改变了。

2.4 Tensor、NumPy 和 标量 的 互通

(1)item( )
        另外一个常用的函数就是item( ),它可以将一个标量 Tensor 转换成一个 Python Number:

x = torch.randn(1)
print(x)
print(x.item())

输出:

tensor([-1.2288])
-1.2288298606872559

(2) Tensor 转 NumPy
可以用numpy( )函数直接转换:

a = torch.ones(5)
b = a.numpy()
print(type(a),type(b))  # a是tensor,b是numpy
print(a, b)

输出:

<class 'torch.Tensor'> <class 'numpy.ndarray'>
tensor([1., 1., 1., 1., 1.]) [1. 1. 1. 1. 1.]

(3) NumPy 转 Tensor
创建 Tensor 的另外一种方式,利用 NumPy 转 Tensor:

a = torch.ones(5)
b = a.numpy()
c = torch.from_numpy(b)
print(a, b, c)
print(type(a), type(b), type(c))

输出:

tensor([1., 1., 1., 1., 1.]) [1. 1. 1. 1. 1.] tensor([1., 1., 1., 1., 1.])
<class 'torch.Tensor'> <class 'numpy.ndarray'> <class 'torch.Tensor'>

可以看到c又变回了Tensor。
还应该注意的是,numpy( )和from_numpy( )产生的数组,共享相同的内存,因此改变其中一个时,另一个也会改变!

a = torch.ones(5)
b = a.numpy()
c = torch.from_numpy(b)
print(a, b, c)
print(type(a), type(b), type(c))
a += 1
print(a, b, c)

输出:

tensor([1., 1., 1., 1., 1.]) [1. 1. 1. 1. 1.] tensor([1., 1., 1., 1., 1.])
<class 'torch.Tensor'> <class 'numpy.ndarray'> <class 'torch.Tensor'>
tensor([2., 2., 2., 2., 2.]) [2. 2. 2. 2. 2.] tensor([2., 2., 2., 2., 2.])

另一种把Numpy数组转Tensor的方法则不然,torch.tensor( )方法会将数据进行拷贝,返回的Tensor和原来的数据不再共享内存:

a = torch.ones(5)
b = a.numpy()
c = torch.tensor(b)  # c先进行数据的拷贝,再转化为tensor
print(a, b, c)
print(type(a), type(b), type(c))
a += 1
print(a, b, c) # 发现a和b互通内存,但是c和它俩不再互通了

输出:

tensor([1., 1., 1., 1., 1.]) [1. 1. 1. 1. 1.] tensor([1., 1., 1., 1., 1.])
<class 'torch.Tensor'> <class 'numpy.ndarray'> <class 'torch.Tensor'>
tensor([2., 2., 2., 2., 2.]) [2. 2. 2. 2. 2.] tensor([1., 1., 1., 1., 1.])

2.5 线性代数相关函数

PyTorch支持一些线性代数相关的函数,可以直接使用,具体用法参考官方文档。

函数

功能

trace

对角线元素之和(即矩阵的迹)

diag

对角线元素

triu/tril

矩阵的上三角/下三角,可指定偏移量

mm/bmm

矩阵乘法,batch的矩阵乘法

addmm/addbmm/addmv/addr/baddbmm…

矩阵运算

t

转置

dot/cross

内积/外积

inverse

矩阵求逆

svd

奇异矩阵分解

Pytorch操作远不止以上这些,可以参考官方文档查看。

三、Tensor的广播机制

当两个形状相同的 Tensor 进行运算时,就是按元素运算。而当对两个形状不同的 Tensor 按元素运算时,会触发广播(Broadcasting)机制进行运算:先适当复制元素,使这两个Tensor形状相同后再按元素运算。例如:

x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
pring(x + y)

输出:

tensor([[1, 2]])
tensor([[1],
        [2],
        [3]])
tensor([[2, 3],
        [3, 4],
        [4, 5]])

可以看到,x的第一行被复制了两份到第二行第三行,y的第一列被复制了一份到第二列,实际上是这样走的:

x = [1, 2] -> [1, 2]       y = [1],    ->   [1, 1]
              [1, 2]           [2],         [2, 2]
              [1, 2]           [3].         [3, 3]
x + y = [1 + 1 , 2 + 1],               [2, 3],
        [1 + 2 , 2 + 2],         =     [3, 4],
        [1 + 3 , 2 + 3].               [4, 5].

四、运算的内存开销

索引操作是不会开辟新内存的,而 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) # 查看当前y的ID是否和之前一致

输出:

False

如果想指定结果到原来的 y 的内存,我们可以用前面介绍的索引来进行替换操作。在下面的例子中,我们把 x + y 的结果通过[ : ]索引写进 y 对应的内存中,一个 [ : ]应该表示全部索引。

x = torch.tensor([[1, 2],[3, 4]])
y = torch.tensor([[3, 4],[5, 6]])
id_before = id(y)
y[:] = y + x
print(y)
print(id(y) == id_before) # 查看当前y的ID是否和之前一致

输出:

tensor([[ 4,  6],
        [ 8, 10]])
True

我们还可以使用运算符全名函数中的 out 参数或者自加运算符 += (也即 add_( ))达到上述效果,例如 torch.add(x, y, out=y)和
y += x 或 y.add_(x)。

x = torch.tensor([[1, 2],[3, 4]])
y = torch.tensor([[3, 4],[5, 6]])
id_before = id(y)
torch.add(x, y, out=y) # y += x, y.add_(x),使用out参数指定输出到y里
print(y)
print(id(y) == id_before)

输出:

tensor([[ 4,  6],
        [ 8, 10]])
True

值得注意的是:虽然view返回的Tensor与源Tensor共享data,但是依然是一个新的Tensor,其除了包含data外还有一些其他属性,二者ID(内存地址)并不一致。

五、Tensor在CPU和GPU之间相互移动

使用方法 to( ) 可以将 Tensor 在 CPU 和 GPU 之间相互移动。

if torch.cuda.is_available():
    device = torch.device("cuda")          # GPU
    y = torch.ones_like(x, device=device)  # 直接创建一个在GPU上的Tensor
    x = x.to(device)                       # 等价于 .to("cuda")
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # to()还可以同时更改数据类型