点击上方“小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达

 Datawhale干货 

作者:李祖贤,Datawhale高校群成员,深圳大学

随着深度学习的发展,深度学习框架开始大量的出现。尤其是近两年,Google、Facebook、Microsoft等巨头都围绕深度学习重点投资了一系列新兴项目,他们也一直在支持一些开源的深度学习框架。目前研究人员正在使用的深度学习框架不尽相同,有 TensorFlow 、Pytorch、Caffe、Theano、Keras等。

cc3b832ff31c101825555a355541536b.png

这其中,TensorFlow和Pytorch占据了深度学习的半壁江山。前几天分享了TensorFlow的基本教程后,很多人在后台留言说能不能写写Pytorch入门。本着粉丝的诉求必须满足的原则,熬夜干,有了今天的文章。所以你懂我意思吧,记得转发、点赞、在看三联。

本文结合Pytorch官方教程、邱锡鹏老师的《神经网络与深度学习》和李沐老师的《动手学深度学习》,为大家介绍的一下Pytorch深度学习框架。具体目录如下:

443e285403eadfea6675a4811b1be453.png

一、数据操作

import torch

1.1 创建TENSOR

# 创建未初始化的Tensor
x = torch.empty(5,3)
print(x)

8857662920c4ad6027b11f6ba569d06e.png

# 创建随机初始化的Tensor
x = torch.rand(5,3)
print(x)

e7a10cce7ef3b15eec75302d65439d07.png

# 创建全为0的Tensor
x = torch.zeros(5,3,dtype=torch.long)
print(x)

b933e8aa91eb9d09b3dac764a804d21f.png

# 根据数据创建Tensor
x = torch.tensor([5.5,3])
print(x)

be9c5aa43262e4d5026200515ca30322.png

# 修改原Tensor为全1的Tensor
x = x.new_ones(5,3,dtype=torch.float64)
print(x)


# 修改数据类型
x = torch.rand_like(x,dtype=torch.float64)
print(x)

148005804af4145f90cfc4e5f2493f14.png

# 获取Tensor的形状
print(x.size())
print(x.shape)
# 注意:返回的torch.Size其实就是⼀一个tuple, ⽀支持所有tuple的操作。

5d90183c6431a637f371f10387f59167.png

e49fd12cf202ddd58952e721d10e5bcc.png

这些创建方法都可以在创建的时候指定数据类型dtype和存放device(cpu/gpu)。

1.2 操作

1.2.1 算术操作

在PyTorch中,同⼀种操作可能有很多种形式,下⾯面⽤用加法作为例子。

# 形式1:
y = torch.rand(5,3)
print(x+y)

a10d187ac8e88d273c3e4c0c541ddcda.png

# 形式2
print(torch.add(x,y))
# 还可以指定输出
result = torch.empty(5, 3)
torch.add(x, y, out=result)
print(result)

8c8a998052650d8391d03064329d4fa9.png

# 形式3
y.add_(x)
print(y)

43647d3a6992523ca7cb8946b7c0d522.png

1.2.2 索引

我们还可以使⽤类似NumPy的索引操作来访问 Tensor 的一部分,需要注意的是:索引出来的结果与原数据共享内存,也即修改⼀个,另⼀个会跟着修改。

y = x[0,:]
y += 1
print(y)
print(x[0,:])  # 观察x是否改变了

2a64a6daf21502658dd597bb5bcf309c.png

1.2.3 改变形状

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

y = x.view(15)
z = x.view(-1,5) #  -1所指的维度可以根据其他维度的值推出来
print(x.size(),y.size(),z.size())

41e4aa4ecb926f13fc3288637d99c472.png

x += 1
print(x)
print(y)

d3ff7ef01973733be6c61d32e7a2441b.png

所以如果我们想返回⼀个真正新的副本(即不共享内存)该怎么办呢?Pytorch还提供了⼀ 个 reshape() 可以改变形状,但是此函数并不能保证返回的是其拷贝,所以不推荐使用。推荐先 ⽤ clone 创造一个副本然后再使⽤ view 。

x_cp = x.clone().view(15)
x -= 1
print(x)
print(x_cp)

1dc0a9ab1e108b6d2ade55a1b064ddaf.png

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

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

2face2bda24628218a787b6cced6c1d0.png

70e2f7f1bb14ccb4e7281c9cd3616dbb.png

1.2.4 线性代数

官方文档:https://pytorch.org/docs/stable/torch.html

1.3 广播机制

前⾯我们看到如何对两个形状相同的 Tensor 做按元素运算。当对两个形状不同的 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)

bf56a9c24b9da62f35b6c2aa4e500cc2.png

1.4 Tensor和Numpy相互转化

我们很容易⽤ numpy() 和 from_numpy() 将 Tensor 和NumPy中的数组相互转换。但是需要注意的⼀点是:这两个函数所产生的的 Tensor 和NumPy中的数组共享相同的内存(所以他们之间的转换很快),改变其中⼀个时另⼀个也会改变!!!

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

3f232c72f7fc73515af05cfd0a8ee721.png

a += 1
print(a,b)

c11960269ff9d9ee0ce18ced4c1349b9.png

b += 1
print(a,b)

203a5d9c6028ff89ff84e28114ea6a9c.png

使⽤ from_numpy() 将NumPy数组转换成 Tensor :

import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
print(a,b)

b75ca385df876e40b6f2510f70075993.png

a += 1
print(a,b)
b += 1
print(a,b)
9cc1aa39a1524c11ca2bfeba78b42535.png

1.5 GPU运算

# let us run this cell only if CUDA is available
# We will use ``torch.device`` objects to move tensors in and out of GPU
if torch.cuda.is_available():
    device = torch.device("cuda")          # a CUDA device object
    y = torch.ones_like(x, device=device)  # directly create a tensor on GPU
    x = x.to(device)                       # or just use strings ``.to("cuda")``
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # ``.to`` can also change dtype together!

3e3ea6d3d1b78ff1f7e483f9669f3be9.png

二、自动求梯度(非常重要)

很多人看到这里是懵的,因为为什么会得出导数的结果,在这里我给出自动求导的一些原理性的知识,希望能帮助大家更好的学习pytorch这个重要的框架。

该autograd软件包是PyTorch中所有神经网络的核心。让我们首先简要地访问它,然后我们将去训练我们的第一个神经网络。

该autograd软件包可自动区分张量上的所有操作。这是一个按运行定义的框架,这意味着您的backprop是由代码的运行方式定义的,并且每次迭代都可以不同。

如果想了解数值微分数值积分和自动求导的知识,可以查看邱锡鹏老师的《神经网络与深度学习》第四章第五节:

下载地址:https://nndl.github.io/

在这里简单说说自动微分的原理吧:我们的目标是求

 outside_default.png 

在 outside_default.png 处的导数。我们的做法是利用链式法则分解为一系列的操作:

951fb6e8bf76fa24a3151788c5c75d00.png

3ffff81bc8065034d048b27d93496223.png

ec024ac47db53b10f67142a19713040d.png

2.1 张量及张量的求导(Tensor)

# 加入requires_grad=True参数可追踪函数求导
x = torch.ones(2,2,requires_grad=True)
print(x)
print(x.grad_fn)

d6174bfdf832c85307b24f48df75a298.png

# 进行运算
y = x + 2
print(y)
print(y.grad_fn)  # 创建了一个加法操作<AddBackward0 object at 0x0000017AF2F86EF0>

d917a20f0349b39203afe9d031f552c3.png

像x这种直接创建的称为叶子节点,叶子节点对应的 grad_fn 是 None 。

print(x.is_leaf,y.is_leaf)

f9dd8a2de6c50416c85e234197cfe341.png

# 整点复杂的操作
z = y * y * 3
out = z.mean()
print(z,out)

d5cb6b85bff64e9de994cadab38a8020.png

.requires_grad_( ... )改变requires_grad 的属性。
a = torch.randn(2,2)    # 缺失情况下默认 requires_grad = False
a = ((a*3)/(a-1))
print(a.requires_grad)  # False
a.requires_grad_(True)
print(a.requires_grad)
b = (a*a).sum()
print(b.grad_fn)

1c1f6c94b1d76cdf70030ec330395b7f.png

2.2 梯度

现在让我们反向传播:因为out包含单个标量,out.backward()所以等效于out.backward(torch.tensor(1.))。

out.backward()
print(x.grad)

59c169998854758dd5a859f440c1bc58.png

# 再来反向传播⼀次,注意grad是累加的
out2 = x.sum()
out2.backward()
print(x.grad)


out3 = x.sum()
x.grad.data.zero_()
out3.backward()
print(x.grad)

132053a8a61f00d518df94d143830e46.png

三、神经网络设计的pytorch版本

这是一个简单的前馈网络。它获取输入,将其一层又一层地馈入,然后最终给出输出。神经网络的典型训练过程如下:

  • 定义具有一些可学习参数(或权重)的神经网络

  • 遍历输入数据集

  • 通过网络处理输入

  • 计算损失(输出正确的距离有多远)

  • 将梯度传播回网络参数

  • 通常使用简单的更新规则来更新网络的权重:weight = weight - learning_rate * gradient

f77c2d8aee2f17cb67220c9469fc019f.png

3.1 定义网络
import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):


    def __init__(self):
        super(Net,self).__init__()
        # 1 input image channel, 6 output channels, 3x3 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1,6,3)
        self.conv2 = nn.Conv2d(6,16,3)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16*6*6,120)   # 6*6 from image dimension
        self.fc2 = nn.Linear(120,84)
        self.fc3 = nn.Linear(84,10)


    def forward(self,x):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)),(2,2))  # CLASStorch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
        x = F.max_pool2d(F.relu(self.conv2(x)),2)
        x = x.view(-1,self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x


    def num_flat_features(self,x):
        size = x.size()[1:] # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        print(num_features)
        return num_features


net = Net()
print(net)

ed202cca117a59bb50a9f1156abe3c62.png

# 模型的可学习参数由返回 net.parameters()
params = list(net.parameters())
print(len(params))
print(params[0].size())  # conv1's .weight

b3d69f1101477e4cca9b0523a224783d.png

# 尝试一个32x32随机输入
input = torch.randn(1,1,32,32)
out = net(input)
print(out)

b361dd920ce54ccf816de7a3a2f2b781.png

# 用随机梯度将所有参数和反向传播器的梯度缓冲区归零:
net.zero_grad()
out.backward(torch.randn(1,10))

3.2 损失函数

output = net(input)
target = torch.randn(10)    # a dummy target, for example
target = target.view(-1,1)  # # make it the same shape as output
criterion = nn.MSELoss()


loss = criterion(output,target)
print(loss)

507fc41d522c05d9d9afd29e7eb694cd.png

我们现在的网络结构:

aa8140d5cc6986c9762408c82ddc8a6f.png

# 如果loss使用.grad_fn属性的属性向后移动,可查看网络结构
print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0])  # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU

748f9622eec5863b8bae2b34427925ed.png

3.3 更新权重

实践中使用的最简单的更新规则是随机梯度下降(SGD):

weight = weight - learning_rate * gradient

import torch.optim as optim


#  create your optimizer
optimizer = optim.SGD(net.parameters(),lr = 0.01)


# in your training loop:
optimizer.zero_grad()  # zero the gradient buffers
output = net(input)
loss = criterion(output,target)
loss.backward()
optimizer.step()

576

四、写到最后

今天,要讲的Pytorch基础教程到这里就结束了,相信大家通过上边的学习已经对Pytorch基础教程有了初步的了解。

关于Pytorch的项目实践,阿里天池「零基础入门NLP」学习赛中提供了Pytorch版实践教程,供学习参考(阅读原文直接跳转):

https://tianchi.aliyun.com/competition/entrance/531810/forum

 
 

好消息!

小白学视觉知识星球

开始面向外开放啦👇👇👇

 
 

d9a548f3da2fd82b9b9840c8fbf01127.png

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。


下载2:Python视觉实战项目52讲
在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。


下载3:OpenCV实战项目20讲
在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。


交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐