Pytorch学习笔记(2)——Pytorch基础

第二章 PyTorch基础

PyTorch是Facebook团队于2017年1月发布的一个深度学习框架,虽然晚于TensorFlow、Keras等框架,但自发布之日起,其关注度就在不断上升,目前在GitHub上的热度已超过Theano、Caffe、MXNet等框架。

PyTorch采用Python语言接口来实现编程,非常容易上手。它就像带GPU的Numpy,与Python一样都属于动态框架。PyTorch继承了Torch灵活、动态的编程环境和用户友好的界面,支持以快速和灵活的方式构建动态神经网络,还允许在训练过程中快速更改代码而不妨碍其性能,支持动态图形等尖端AI模型的能力,是快速实验的理想选择。

2.1-2.3 为什么选择PyTorch+PyTorch的安装配置

PyTorch是一个建立在Torch库之上的Python包,旨在加速深度学习应用。它提供一种类似Numpy的抽象方法来表征张量(或多维数组),可以利用GPU来加速训练。
PyTorch官网:https://PyTorch.org/

import torch
print(torch.__version__)
1.7.0

2.4 Numpy与Tensor

Tensor自称为神经网络界的Numpy,它与Numpy相似,二者可以共享内存,且之间的转换非常方便和高效。不过它们也有不同之处,最大的区别就是Numpy会把ndarray放在CPU中进行加速运算,而由Torch产生的Tensor会放在GPU中进行加速运算(假设当前环境有GPU)。

2.4.1 Tensor概述

对Tensor的操作很多,从接口的角度来划分,可以分为两类:
1)torch.function,如torch.sum、torch.add等;
2)tensor.function,如tensor.view、tensor.add等。
这些操作对大部分Tensor都是等价的,如torch.add(x,y)与x.add(y)等价。

如果从修改方式的角度来划分,可以分为以下两类:
1)不修改自身数据,如x.add(y),x的数据不变,返回一个新的Tensor。
2)修改自身数据,如x.add_(y)(运行符带下划线后缀),运算结果存在x中,x被修改。

x = torch.tensor([1,2,3])
y = torch.tensor([4,5,6])
z = x.add(y)
print(z)
z = torch.add(x,y)
print(z)
x.add_(y)
print(x)
tensor([5, 7, 9])
tensor([5, 7, 9])
tensor([5, 7, 9])

2.4.2 创建Tensor

在这里插入图片描述

x = torch.Tensor(2,3)
print(x)
print(x.size())
print(x.shape)
tensor([[9.8091e-45, 0.0000e+00, 0.0000e+00],
        [0.0000e+00, 0.0000e+00, 0.0000e+00]])
torch.Size([2, 3])
torch.Size([2, 3])
y = torch.Tensor(x.size())
print(y.size())
torch.Size([2, 3])

注意torch.Tensor与torch.tensor的几点区别:
1)torch.Tensor是torch.empty和torch.tensor之间的一种混合,但是,当传入数据时,torch.Tensor使用全局默认dtype(FloatTensor),而torch.tensor是从数据中推断数据类型。
2)torch.tensor(1)返回一个固定值1,而torch.Tensor(1)返回一个大小为1的张量,它是随机初始化的值。

x = torch.Tensor(1)
y = torch.tensor(1)
print(x)
print(x.type())
print(y)
print(y.type())
tensor([5.6687e-34])
torch.FloatTensor
tensor(1)
torch.LongTensor
x = torch.eye(2,2)
print(x)
tensor([[1., 0.],
        [0., 1.]])
x = torch.zeros(2,3)
print(x)
tensor([[0., 0., 0.],
        [0., 0., 0.]])
x = torch.linspace(1,10,10)
print(x)
tensor([ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10.])
x = torch.rand(2,3)
print(x)
x = torch.randn(2,3)
print(x)
tensor([[0.4624, 0.4173, 0.8935],
        [0.1000, 0.4286, 0.0069]])
tensor([[ 1.1595,  1.1701,  0.0564],
        [-1.5159,  0.7730,  1.9790]])

2.4.3 修改Tensor的形状

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sj70gitE-1605945971792)(attachment:%E6%88%AA%E5%B1%8F2020-11-20%20%E4%B8%8A%E5%8D%889.49.47.png)]

x = torch.randn(2,3)
print(x.size())
print(x.dim())
torch.Size([2, 3])
2

新版本新增了reshape方法,类似于numpy

y = x.reshape(3,2)
print(x)
print(y)
tensor([[ 0.1842,  0.2380, -0.2833],
        [ 0.8476, -0.5895,  0.3825]])
tensor([[ 0.1842,  0.2380],
        [-0.2833,  0.8476],
        [-0.5895,  0.3825]])

最新版本的view貌似不像书里面说的那样了,也不改变原来的

x.view(3,2)
print(x)
tensor([[ 0.1842,  0.2380, -0.2833],
        [ 0.8476, -0.5895,  0.3825]])
y = x.view(-1)
print(x)
print(y)
print(y.shape)
tensor([[ 0.1842,  0.2380, -0.2833],
        [ 0.8476, -0.5895,  0.3825]])
tensor([ 0.1842,  0.2380, -0.2833,  0.8476, -0.5895,  0.3825])
torch.Size([6])

增加一个维度

z = y.unsqueeze(0)
print(z)
print(z.shape)
print(z.numel())
tensor([[ 0.1842,  0.2380, -0.2833,  0.8476, -0.5895,  0.3825]])
torch.Size([1, 6])
6

强制类型转换float–>long

z = z.long()
print(z)
tensor([[0, 0, 0, 0, 0, 0]])

2.4.4 索引操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qG0gAseW-1605945971794)(attachment:image.png)]

x = torch.randn(2,3)
print(x)
tensor([[ 1.1864, -0.9864,  2.3644],
        [-0.8501,  0.2563,  1.3528]])
print(x[0,:])
print(x[:,-1])
tensor([ 1.1864, -0.9864,  2.3644])
tensor([2.3644, 1.3528])
mask = (x>0)
y = x.masked_select(mask)
print(y)
tensor([1.1864, 2.3644, 0.2563, 1.3528])

获取指定索引对应的值,输出根据以下规则得到
o u t [ i ] [ j ] = i n p u t [ i n d e x [ i ] [ j ] ] [ j ] ,   i f   d i m = = 0 out[i][j] = input[index[i][j]][j] ,\ if\ dim == 0 out[i][j]=input[index[i][j]][j], if dim==0
o u t [ i ] [ j ] = i n p u t [ i ] [ i n d e x [ i ] [ j ] ] ,   i f   d i m = = 1 out[i][j] = input[i][index[i][j]] ,\ if\ dim == 1 out[i][j]=input[i][index[i][j]], if dim==1

index = torch.LongTensor([[0,0,1]])
y = x.gather(0,index)
print(y)
tensor([[ 1.1864, -0.9864,  1.3528]])
index = torch.LongTensor([[0,1,1],
                          [1,1,1]])
y = x.gather(1,index)
print(y)
tensor([[ 1.1864, -0.9864, -0.9864],
        [ 0.2563,  0.2563,  0.2563]])

2.4.5 广播机制

x = torch.arange(0,40,10).reshape(4,1)
print(x)
print(x.shape)
y = torch.arange(0,3,1)
print(y)
print(y.shape)
tensor([[ 0],
        [10],
        [20],
        [30]])
torch.Size([4, 1])
tensor([0, 1, 2])
torch.Size([3])
z = x+y
print(z)
tensor([[ 0,  1,  2],
        [10, 11, 12],
        [20, 21, 22],
        [30, 31, 32]])

numpy和tensor之间的转换

a = x.numpy()
print(a)
print(type(a))
[[ 0]
 [10]
 [20]
 [30]]
<class 'numpy.ndarray'>
b = torch.from_numpy(a)
print(b)
print(type(b))
tensor([[ 0],
        [10],
        [20],
        [30]])
<class 'torch.Tensor'>

2.4.6 逐元素操作(element-wise)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BYozPb3Z-1605945971796)(attachment:%E6%88%AA%E5%B1%8F2020-11-20%20%E4%B8%8B%E5%8D%882.16.06.png)]

这些操作均会创建新的Tensor,如果需要就地操作,可以使用这些方法的下划线版本,例如abs_。

import torch
x = torch.Tensor([[1,2,3]])
y = torch.Tensor([[4],
                  [5],
                  [6]])
print(x)
print(y)
z = x.add(y)
print(z)
z = x.mul(y)
print(z)
tensor([[1., 2., 3.]])
tensor([[4.],
        [5.],
        [6.]])
tensor([[5., 6., 7.],
        [6., 7., 8.],
        [7., 8., 9.]])
tensor([[ 4.,  8., 12.],
        [ 5., 10., 15.],
        [ 6., 12., 18.]])
z = x.exp()
print(z)
tensor([[ 2.7183,  7.3891, 20.0855]])
z = x.sigmoid()
print(z)
z = x.softmax(dim=1)
print(z)
print(z.sum(dim=1).item())
tensor([[0.7311, 0.8808, 0.9526]])
tensor([[0.0900, 0.2447, 0.6652]])
1.0

2.4.7 归并操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ep3f80fx-1605945971797)(attachment:%E6%88%AA%E5%B1%8F2020-11-20%20%E4%B8%8B%E5%8D%882.38.21.png)]

归并操作一般涉及一个dim参数,指定沿哪个维进行归并。另一个参数是keepdim,说明输出结果中是否保留维度1,缺省情况是False,即不保留。

x = torch.arange(0,6,1)
y = x.view(2,3)
print(y)
z = y.sum(dim=0,keepdim=True)
print(z)
z = y.sum(dim=0,keepdim=False)
print(z)
z = y.sum(dim=1)
print(z)
tensor([[0, 1, 2],
        [3, 4, 5]])
tensor([[3, 5, 7]])
tensor([3, 5, 7])
tensor([ 3, 12])
z = y.float().norm(dim=0,p=2)
print(z)
z = y.float().norm(dim=1,p=2)
print(z)
tensor([3.0000, 4.1231, 5.3852])
tensor([2.2361, 7.0711])

2.4.8 比较操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S7R51KHa-1605945971798)(attachment:%E6%88%AA%E5%B1%8F2020-11-20%20%E4%B8%8B%E5%8D%883.03.03.png)]

print(y)
tensor([[0, 1, 2],
        [3, 4, 5]])
z = y.max(dim=0)
print(z)
z = y.max(dim=1)
print(z)
torch.return_types.max(
values=tensor([3, 4, 5]),
indices=tensor([1, 1, 1]))
torch.return_types.max(
values=tensor([2, 5]),
indices=tensor([2, 2]))
z = y.topk(1,dim=0)
print(z)
z = y.topk(1,dim=1)
print(z)
torch.return_types.topk(
values=tensor([[3, 4, 5]]),
indices=tensor([[1, 1, 1]]))
torch.return_types.topk(
values=tensor([[2],
        [5]]),
indices=tensor([[2],
        [2]]))

dim等于几,从数组的角度看那个维度的值就在不断变化,其他维度保持不变

2.4.9 矩阵操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PmcfHLwZ-1605945971799)(attachment:%E6%88%AA%E5%B1%8F2020-11-20%20%E4%B8%8B%E5%8D%883.11.12.png)]

1)Torch的dot与Numpy的dot有点不同,Torch中的dot是对两个为1D张量进行点积运算,Numpy中的dot无此限制。
2)mm是对2D的矩阵进行点积,bmm对含batch的3D进行点积运算。
3)转置运算会导致存储空间不连续,需要调用contiguous方法转为连续。

x = torch.arange(0,6,1)
y = torch.arange(5,11,1)
print(x)
print(y)
z =x.dot(y)
print(z)
tensor([0, 1, 2, 3, 4, 5])
tensor([ 5,  6,  7,  8,  9, 10])
tensor(130)

mm才是矩阵乘法!!!

x = x.view(2,3)
y = y.view(3,2)
print(x)
print(y)
z = x.mm(y)
print(z)
tensor([[0, 1, 2],
        [3, 4, 5]])
tensor([[ 5,  6],
        [ 7,  8],
        [ 9, 10]])
tensor([[ 25,  28],
        [ 88, 100]])
x = x.unsqueeze(0)
y = y.unsqueeze(0)
print(x.shape)
print(y.shape)
z = x.bmm(y)
print(z)
torch.Size([1, 2, 3])
torch.Size([1, 3, 2])
tensor([[[ 25,  28],
         [ 88, 100]]])

2.4.10 PyTorch与Numpy比较

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EqWXVQT1-1605945971800)(attachment:%E6%88%AA%E5%B1%8F2020-11-20%20%E4%B8%8B%E5%8D%883.23.19.png)]

2.5 Tensor与Autograd

在神经网络中,一个重要内容就是进行参数学习,而参数学习离不开求导,那么PyTorch是如何进行求导的呢?
torch.autograd包就是用来自动求导的。Autograd包为张量上所有的操作提供了自动求导功能,而torch.Tensor和torch.Function为Autograd的两个核心类,它们相互连接并生成一个有向非循环图。

2.5.1 自动求导要点

有点晦涩,感觉有代码经验之后回来看更好一点。。。

为实现对Tensor自动求导,需考虑如下事项:
1)创建叶子节点(Leaf Node)的Tensor,使用requires_grad参数指定是否记录对其的操作,以便之后利用backward()方法进行梯度求解。requires_grad参数的缺省值为False,如果要对其求导需设置为True,然后与之有依赖关系的节点会自动变为True。
2)可利用requires_grad_()方法修改Tensor的requires_grad属性。可以调用.detach()或with torch.no_grad():,将不再计算张量的梯度,跟踪张量的历史记录。这点在评估模型、测试模型阶段中常常用到。
3)通过运算创建的Tensor(即非叶子节点),会自动被赋予grad_fn属性。该属性表示梯度函数。叶子节点的grad_fn为None。
4)最后得到的Tensor执行backward()函数,此时自动计算各变量的梯度,并将累加结果保存到grad属性中。计算完成后,非叶子节点的梯度自动释放。
5)backward()函数接收参数,该参数应和调用backward()函数的Tensor的维度相同,或者是可broadcast的维度。如果求导的Tensor为标量(即一个数字),则backward中的参数可省略。
6)反向传播的中间缓存会被清空,如果需要进行多次反向传播,需要指定backward中的参数retain_graph=True。多次反向传播时,梯度是累加的。
7)非叶子节点的梯度backward调用后即被清空。
8)可以通过用torch.no_grad()包裹代码块的形式来阻止autograd去跟踪那些标记为.requesgrad=True的张量的历史记录。这步在测试阶段经常使用。

2.5.2 计算图

计算图是一种有向无环图像,用图形方式来表示算子与变量之间的关系,直观高效。
y = w x , z = y + b y=wx,z=y+b y=wx,z=y+b

我们的目标是更新各叶子节点的梯度,根据复合函数导数的链式法则,不难算出各叶子节点的梯度。
δ z δ x = δ z δ y δ y δ x = w \frac{\delta z}{\delta x}=\frac{\delta z}{\delta y}\frac{\delta y}{\delta x}=w δxδz=δyδzδxδy=w
δ z δ w = δ z δ y δ y δ w = x \frac{\delta z}{\delta w}=\frac{\delta z}{\delta y}\frac{\delta y}{\delta w}=x δwδz=δyδzδwδy=x
δ z δ b = 1 \frac{\delta z}{\delta b}=1 δbδz=1

PyTorch调用backward()方法,将自动计算各节点的梯度,这是一个反向传播过程。且在反向传播过程中,autograd从当前根节点z反向溯源,利用导数链式法则,计算所有叶子节点的梯度,其梯度值将累加到grad属性中。对非叶子节点的计算操作(或Function)记录在grad_fn属性中,叶子节点的grad_fn值为None。

2.5.3 标量反向传播

y = w x , z = y + b y=wx,z=y+b y=wx,z=y+b

x = torch.Tensor([2])
w = torch.randn((1,),requires_grad=True)
b = torch.randn((1,),requires_grad=True)
print(x)
print(w)
print(b)
tensor([2.])
tensor([-0.6382], requires_grad=True)
tensor([0.4559], requires_grad=True)

实现前向传播

y = w.mul(x)
z = y.add(b)
print(y)
print(z)
tensor([-1.2764], grad_fn=<MulBackward0>)
tensor([-0.8205], grad_fn=<AddBackward0>)
print(x.requires_grad)
print(w.requires_grad)
print(b.requires_grad)
print(y.requires_grad)
print(z.requires_grad)
False
True
True
True
True
print(x.grad_fn)
print(w.grad_fn)
print(b.grad_fn)
print(y.grad_fn)
print(z.grad_fn)
None
None
None
<MulBackward0 object at 0x7fe8d694f518>
<AddBackward0 object at 0x7fe8d694f5c0>

反向传播

z.backward()
print(x.grad)
print(w.grad)
print(b.grad)
print(y.grad)
print(z.grad)
None
tensor([2.])
tensor([1.])
None
None

2.5.4 非标量反向传播

目标张量一般都是标量,如我们经常使用的损失值Loss,一般都是一个标量。但也有非标量的情况,后面将介绍的Deep Dream的目标值就是一个含多个元素的张量。那如何对非标量进行反向传播呢?PyTorch有个简单的规定,不让张量(Tensor)对张量求导,只允许标量对张量求导,因此,如果目标张量对一个非标量调用backward(),则需要传入一个gradient参数,该参数也是张量,而且需要与调用backward()的张量形状相同。
b a c k w a r d ( g r a d i e n t = N o n e , r e t a i n _ g r a p h = N o n e , c r e a t e _ g r a p h = F a l s e ) backward(gradient=None, retain\_graph=None, create\_graph=False) backward(gradient=None,retain_graph=None,create_graph=False)

2.6 使用Numpy实现机器学习

使用numpy硬撸。。。
首先,给出一个数组x,然后基于表达式 y = 3 x 2 + 2 y=3x^2+2 y=3x2+2,加上一些噪音数据到达另一组数据y。
然后,构建一个机器学习模型,学习表达式 y = w x 2 + b y=wx^2+b y=wx2+b的两个参数w、b。利用数组x,y的数据为训练数据。
最后,采用梯度梯度下降法,通过多次迭代,学习到w、b的值。

import numpy as np
import matplotlib.pyplot as plt

生成训练数据

x = np.linspace(-1,1,100).reshape(100,1)
y = 3*np.power(x,2)+2+0.2*np.random.rand(100,1)
print(x.shape)
print(y.shape)
(100, 1)
(100, 1)
plt.scatter(x,y)
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fa6K46ha-1605945971803)(output_78_0.png)]

初始化权重参数

w = np.random.rand(1,1)
b = np.random.rand(1,1)

训练(梯度下降法)

lr = 0.001

for i in range(800):
    y_pred = w*np.power(x,2)+b
    #计算损失
    loss = 0.5*np.power(y_pred-y,2)
    loss = np.sum(loss,axis=0)
    #计算梯度
    grad_w = np.sum((y_pred-y)*np.power(x,2),axis=0)
    grad_b = np.sum((y_pred-y),axis=0)
    #更新参数
    w -= lr*grad_w
    b -= lr*grad_b

展示结果

print(w)
print(b)
[[2.99219404]]
[[2.09981455]]
print(loss)
[0.16740311]
plt.plot(x,y_pred,'r-',label='predict')
plt.scatter(x,y,color='g',label='real')
plt.legend()
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fyYlzbSE-1605945971803)(output_86_0.png)]

2.7 使用Tensor和Autograd实现机器学习

import torch
import matplotlib.pyplot as plt
x = torch.linspace(-1,1,100).view(100,1)
y = 3*x.pow(2)+2+0.2*torch.rand(100,1)
print(x.shape)
print(y.shape)
torch.Size([100, 1])
torch.Size([100, 1])
plt.scatter(x,y)
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cfADCG0y-1605945971804)(output_91_0.png)]

w = torch.rand((1,1),requires_grad=True)
b = torch.rand((1,1),requires_grad=True)
lr = 0.001

for i in range(800):
    y_pred = w*x.pow(2)+b
    #计算损失
    loss = 0.5*(y_pred-y).pow(2)
    loss = loss.sum(axis=0)
    #计算梯度
    loss.backward() #和使用numpy的区别,不需要手动计算梯度了
    #更新参数
    with torch.no_grad():
        w -= lr*w.grad
        b -= lr*b.grad
    #梯度清零
    w.grad.zero_()
    b.grad.zero_()
print(w)
print(b)
tensor([[2.9757]], requires_grad=True)
tensor([[2.1031]], requires_grad=True)
print(loss)
tensor([0.1612], grad_fn=<SumBackward1>)
plt.plot(x.numpy(),y_pred.detach().numpy(),'r-',label='predict')
plt.scatter(x.numpy(),y.numpy(),color='g',label='real')
plt.legend()
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JSGf78k6-1605945971804)(output_96_0.png)]

2.8 小结

主要介绍了PyTorch的基础知识,是后面章节的重要支撑。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值