PyTorch基础知识学习

1、张量

在深度学习中,我们通常将数据以张量的形式进行表示,比如我们用三维张量表示一个RGB图像,四维张量表示视频。

几何代数中定义的张量是基于向量和矩阵的推广,比如我们可以将标量视为零阶张量,矢量可以视为一阶张量,矩阵就是二阶张量。

张量是PyTorch里面基础的运算单位,与Numpy的ndarray相同都表示的是一个多维的矩阵。 与ndarray的最大区别就是,PyTorch的Tensor可以在 GPU 上运行,而 numpy 的 ndarray 只能在 CPU 上运行,在GPU上运行大大加快了运算速度

张量是现代机器学习的基础。它的核心是一个数据容器。

这里有一些存储在各种类型张量的公用数据集类型:

  • 3维 = 时间序列

  • 4维 = 图像

  • 5维 = 视频

例子:一个图像可以用三个字段表示:

(width, height, channel) = 3D

但是,在机器学习工作中,我们经常要处理不止一张图片或一篇文档——我们要处理一个集合。我们可能有10,000张郁金香的图片,这意味着,我们将用到4D张量:

(batch_size, width, height, channel) = 4D

1.1 tensor的创建

先导入pytorch

import torch

1. 随机初始化矩阵 我们可以通过torch.rand()的方法,构造一个随机初始化的矩阵:

x = torch.rand(4, 3) 

2. 全0矩阵的构建,可以通过torch.zeros()构造一个矩阵全为 0,并且通过dtype设置数据类型为 long。除此以外,我们还可以通过torch.zero_()和torch.zeros_like()将现有矩阵转换为全0矩阵.

x = torch.zeros(3, 4, dtype=torch.long)

3. 全1矩阵的构建,可以通过torch.ones()构造一个矩阵全为 1。

y = torch.ones(2, 2)

4. 初始化一个单位矩阵,即对角线为1,其它全为0。

z = torch.eye(2,2)

5. 我们可以通过torch.tensor()直接使用数据,构造一个张量:

x = torch.tensor([5.5, 6.6])

 输出:

tensor([5.5000, 6.6000])

 6. 基于已经存在的 tensor,创建一个 tensor :

x = x.new_ones(4, 3) 
# 创建一个新的全1矩阵tensor,返回的tensor默认具有相同的torch.dtype和torch.device
# 也可以像之前的写法 x = torch.ones(4, 3, dtype=torch.double)
print(x)
x = torch.randn_like(x, dtype=torch.float)
# 重置数据类型
print(x)
# 结果会有一样的size
# 获取它的维度信息
print(x.size())
print(x.shape)

结果:

tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], dtype=torch.float64)
tensor([[ 2.7311, -0.0720,  0.2497],
        [-2.3141,  0.0666, -0.5934],
        [ 1.5253,  1.0336,  1.3859],
        [ 1.3806, -0.6965, -1.2255]])
torch.Size([4, 3])
torch.Size([4, 3])

7. 通过arange(s, e, step)创建Tensor,从s到e(左闭右开),步长为step。s默认为0,step默认为1。

x = torch.arange(0, 4, 1) # 等价于x = torch.arange(4)
x

输出:

tensor([0, 1, 2, 3])

8. 通过linspace(s,e,steps),从s到e,均匀分成step份

x = torch.linspace(0, 20, 5)
x

输出:

tensor([ 0.,  5., 10., 15., 20.])

9. 通过rand/randn(sizes),rand是[0,1)均匀分布;randn是服从N(0,1)的正态分布

x = torch.rand(6)
y = torch.randn(6)
x, y

输出:

(tensor([0.9811, 0.5684, 0.6658, 0.6848, 0.1899, 0.5334]),
 tensor([-1.3188,  1.0657,  0.2854,  1.0657,  0.4781,  0.1059]))

10. 通过normal(mean,std,size),产生正态分布(均值为mean,标准差是std)

x = torch.normal(mean=0.,std=1.,size=(2,2))
x

输出:

tensor([[ 0.1056,  0.4166],
        [-0.4476,  0.8033]])

1.2 张量的属性

  • .dtype:元素的属性。
  • .shape:张量的形状。
  • .device:张量所在的设备上(cpu、gpu)。

torch.numel():得出张量的元素个数(也就是张量的shape的乘积)。

e = torch.rand(2,4)
torch.numel(e)
8

张量的操作

1. 加法操作

# 方式1
y = torch.rand(4, 3) 
print(x + y)

# 方式2
print(torch.add(x, y))

# 方式3 in-place,原值修改
y.add_(x) 
print(y)

2. 索引操作:(类似于numpy)

需要注意的是:索引出来的结果与原数据共享内存,修改一个,另一个会跟着修改。如果不想修改,可以考虑使用copy()等方法。

x = torch.rand(4,3)
# 取第二列
print(x[:, 1]) 
tensor([0.2363, 0.8675, 0.6395])
y = x[0,:]
y += 1
print(y)
print(x[0, :]) # 源tensor也被改了了
tensor([1.9719, 1.2363, 1.3924, 1.3280])
tensor([1.9719, 1.2363, 1.3924, 1.3280])

3. 维度变换

张量的维度变换常见的方法有torch.view()torch.reshape(),下面我们将介绍第一中方法torch.view()

x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8) # -1是指这一维的维数由其他维度决定
print(x.size(), y.size(), z.size())
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])

注: torch.view() 返回的新tensor与源tensor共享内存(其实是同一个tensor),更改其中的一个,另外一个也会跟着改变。(顾名思义,view()仅仅是改变了对这个张量的观察角度)

x += 1
print(x)
print(y) # 也加了了1
tensor([[ 1.3019,  0.3762,  1.2397,  1.3998],
        [ 0.6891,  1.3651,  1.1891, -0.6744],
        [ 0.3490,  1.8377,  1.6456,  0.8403],
        [-0.8259,  2.5454,  1.2474,  0.7884]])
tensor([ 1.3019,  0.3762,  1.2397,  1.3998,  0.6891,  1.3651,  1.1891, -0.6744,
         0.3490,  1.8377,  1.6456,  0.8403, -0.8259,  2.5454,  1.2474,  0.7884])

上面我们说过torch.view()会改变原始张量,但是很多情况下,我们希望原始张量和变换后的张量互相不影响。为了使创建的张量和原始张量不共享内存,我们需要使用第二种方法torch.reshape(), 同样可以改变张量的形状,但是此函数并不能保证返回的是其拷贝值所以官方不推荐使用。推荐的方法是我们先用 clone() 创造一个张量副本然后再使用 torch.view()进行函数维度变换 。

注:使用 clone() 还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源 Tensor .

4.  取值操作

如果我们有一个元素 tensor ,我们可以使用 .item() 来获得这个 value,而不获得其他性质:

x = torch.randn(1) 
print(type(x)) 
print(type(x.item()))
<class 'torch.Tensor'>
<class 'float'>

5. 连接操作cat

我们得保证除了concat 的维度以外,其它的维度都是一样的

对于二维矩阵,dim=0是竖着拼;dim=1是横着拼。

例子1:

a = torch.rand([2,2])
b = torch.rand([2,3])
a,b
(tensor([[0.9564, 0.4581],
         [0.7868, 0.2596]]),
 tensor([[0.9580, 0.8823, 0.4170],
         [0.3594, 0.5210, 0.3029]]))
torch.cat([a,b],dim=1)
tensor([[0.9564, 0.4581, 0.9580, 0.8823, 0.4170],
        [0.7868, 0.2596, 0.3594, 0.5210, 0.3029]])

例子2:

c = torch.rand([3,3])
d = torch.rand([2,3])
c,d
(tensor([[0.3705, 0.7832, 0.5149],
         [0.0846, 0.4519, 0.0494],
         [0.3132, 0.0761, 0.6926]]),
 tensor([[0.7486, 0.8534, 0.7502],
         [0.6891, 0.2949, 0.7704]]))
torch.cat([c,d],dim=0)
tensor([[0.3705, 0.7832, 0.5149],
        [0.0846, 0.4519, 0.0494],
        [0.3132, 0.0761, 0.6926],
        [0.7486, 0.8534, 0.7502],
        [0.6891, 0.2949, 0.7704]])

6. scatter

dim的那个维度值用index里面的替代,其他的维度不变,然后用src里面的值去替换。

对于三维张量:

举个二维张量例子: 

src = torch.arange(1,11).reshape(2,5)
src
tensor([[ 1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10]])
index = torch.tensor([[0,1,2],[0,1,4]])
c = torch.zeros(3,5, dtype=src.dtype).scatter_(1,index,src)
c
tensor([[1, 2, 3, 0, 0],
        [6, 7, 0, 0, 8],
        [0, 0, 0, 0, 0]])

dim=1,所以c的第一个维度不变,第二个维度用index里面的替代,然后用src里面的值去替换。

也就是c[0][0] = src[0][0]=1,c[0][1] = src[0][1]=2,c[0][2] = src[0][2]=3,c[1][0]=src[1][0]=6,c[1][1] = src[1][1]=7,c[1][4]=src[1][4]=8

7. torch.split(tensor,split_size,dim=0),划分

split_size是数字的情况

a = torch.arange(10).reshape(5,2)
a
torch.split(a,3) #3代表划分的大小(每块行数为3),也就是把5整除3,不能整除的算一个
(tensor([[0, 1],
         [2, 3],
         [4, 5]]),
 tensor([[6, 7],
         [8, 9]]))

split_size是列表的情况

torch.split(a,[1,4]) # 第一块行数为1,第二块行数为4
(tensor([[0, 1]]),
 tensor([[2, 3],
         [4, 5],
         [6, 7],
         [8, 9]]))

8. torch.suqeeze(input, dim=None, *,  out=None)

把那些大小为1的维度移除掉。

比方说如果输入的维度为(A x 1 x B x C x 1 x D),经过squeeze之后,输出维度为(A x B x C x D)。再比如我们在模型中,最后的输出层会做一个全连接,可能会把某些维度映射为1,然后再通过squeeze把维度1去掉。

c = torch.randn(3,1,2,1,3)
c.shape # torch.Size([3, 1, 2, 1, 3])
d = torch.squeeze(c)
d.shape # torch.Size([3, 2, 3])

移除指定维度

d = torch.squeeze(c, dim=3) 
d.shape # torch.Size([3, 1, 2, 3])

9. torch.unsqueeze(input,dim) 增加维度

 在第一个维度上扩充一维。

10. torch.stack(tensors,dim=0,*,out=None)

沿着一个新的维度将一系列张量拼接起来。(所有的张量需要同样的大小)

和cat不同:cat是直接连起来;stack是把它堆叠起来,所以维度是有扩充的。

a = torch.randn(3,2)
b = torch.randn(3,2)
a,b
torch.stack([a,b],dim=0).shape # torch.Size([2, 3, 2])
torch.stack([a,b],dim=1).shape # torch.Size([3, 2, 2])

11. torch.take(input,index)

把任意的张量看成一维的张量,设置的index也是一维的,然后通过这个index去找这个张量。

 12. torch.transpose(input,dim0,dim1)

input是要转置的张量,

广播机制

当对两个形状不同的 Tensor 按元素运算时,可能会触发广播(broadcasting)机制:先适当复制元素使这两个 Tensor 形状相同后再按元素运算。

x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(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列的矩阵按元素相加。

2、自动求导 Autograd

2.1 自动求导基本操作

在张量创建时,通过设置 requires_grad 标识为Ture来告诉Pytorch需要对该张量进行自动求导,PyTorch会记录该张量的每一步操作历史并自动计算。

x = torch.rand(3, 3, requires_grad=True)
x
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], requires_grad=True)

PyTorch会自动追踪和记录与张量的所有操作,当计算完成后调用.backward()方法自动计算梯度并且将计算结果保存到grad属性中

out = x.mean()
out
tensor(1., grad_fn=<MeanBackward0>)
out.backward() # 因为 out 是一个标量,因此out.backward()和 out.backward(torch.tensor(1.)) 等价。
print(x.grad) # 输出导数 d(out)/dx
tensor([[0.1111, 0.1111, 0.1111],
        [0.1111, 0.1111, 0.1111],
        [0.1111, 0.1111, 0.1111]])

注意:grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零。

out.backward() # 再执行一次
print(x.grad) # 输出导数 d(out)/dx
tensor([[0.2222, 0.2222, 0.2222],
        [0.2222, 0.2222, 0.2222],
        [0.2222, 0.2222, 0.2222]])

在张量进行操作后,grad_fn已经被赋予了一个新的函数,这个函数引用了一个创建了这个Tensor类的Function对象。 Tensor和Function互相连接生成了一个非循环图,它记录并且编码了完整的计算历史。每个张量都有一个.grad_fn属性,如果这个张量是用户手动创建的那么这个张量的grad_fn是None。

x = torch.randn(3,3,requires_grad=True) # x是用户手动创建的
print(x.grad_fn)
None

为什么我们执行z.backward()方法会更新x.grady.grad呢? .grad_fn属性记录的就是这部分的操作。

grad_fn:记录并且编码了完整的计算历史。

再看一个例子:

x = torch.rand(3, 3, requires_grad=True)
z= x**2
z
tensor([[0.2807, 0.1602, 0.0162],
        [0.4240, 0.0202, 0.0088],
        [0.7178, 0.1874, 0.0153]], grad_fn=<PowBackward0>)
x
tensor([[0.5299, 0.4002, 0.1273],
        [0.6511, 0.1420, 0.0936],
        [0.8472, 0.4329, 0.1236]], requires_grad=True)
z.backward(torch.ones_like(x)) # 对z求导,在这里其实就是z' = 2x
x.grad
tensor([[1.0597, 0.8005, 0.2545],
        [1.3023, 0.2841, 0.1872],
        [1.6944, 0.8657, 0.2471]]) # 也就是x的两倍

2.2 Autograd过程解析

  1. 当我们执行z.backward()的时候。这个操作将调用z里面的grad_fn这个属性,执行求导的操作。
  2. 这个操作将遍历grad_fn的next_functions,然后分别取出里面的Function(AccumulateGrad),执行求导操作。这部分是一个递归的过程直到最后类型为叶子节点
  3. 计算出结果以后,将结果保存到他们对应的variable 这个变量所引用的对象(x和y)的 grad这个属性里面。
  4. 求导结束。所有的叶节点的grad变量都得到了相应的更新。

最终当我们执行完z.backward()之后,a和b里面的grad值就得到了更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值