PyTorch入门教程

最近在学习PyTorch,网上资料有些杂乱,进行了稍许整理,望能有所助益。

PyTorch简介

  • Torch是 PyTorch的前身,其底层语言相同,但使用不同的上层包装语言。Torch是一个支持大量机器学习算法的科学计算框架。
  • PyTorch是基于Torch的python开源机器学习库,由FaceBook人工智能小组开发。

    为什么PyTorch?

  • 不仅能够实现强大的GPU加速,同时还支持动态神经网络。
  • TensorFlow和Caffe都是命令式的编程语言,是静态的,想要改变网络的结构,就必须从头开始。而PyTorch通过反向求导技术,零延迟的任意改变神经网络行为,而且实现速度快,十分灵活。
  • 对比于Tensorflow,更加简洁直观,底层代码也更容易看懂。

    缺点

  • 全面性处于劣势,不支持快速傅里叶、沿维翻转张量和检查无穷与非数值张量。
  • 针对移动端、嵌入式部署以及高性能服务器端的部署其性能表现有待提升。
  • 框架较新,社区没有那么强大,在文档方面其C库大多数没有文档。

    PyTorch安装

  • 下载安装Anaconda,提供了python包管理与环境管理的功能。
  • 可以使用pip安装或者conda:
  • pip3 install http://download.pytorch.org/whl/cpu/torch-0.4.0-cp36-cp36m-win_amd64.whl(链接根据不同python版本而定,py3.6 CPU)

    conda install pytorch cuda80 -c pytorch GPU版本
    conda install pytorch-cpu -c pytorch CPU版本

  • 以上安装完成之后还需要执行:
  • pip3 install torchvision

  • 可以通过import torch检测是否安装成功。

    做足准备工作以后,就要真正学习pytorch了!

  • PyTorch运算


    PyTorch中tensor的运算和Numpy的array如出一辙。
    • 不加初始化地构建矩阵:torch.empty(row, column)
    • 构建一个随机初始化矩阵:x = torch.rand(5, 3),输出:
      在这里插入图片描述
    • 构建全0矩阵:x = torch.zeros(5, 3, dtype=torch.long),第三个参数指定数据类型。
    • 直接构建一个张量:x = torch.tensor([5.5, 3.2]),参数为一个array。
    • 获取维度信息:print(x.size()),输出为:
      在这里插入图片描述
    • 求和有多种方式:
      1. 直接求和 y = torch.rand(5, 3) print(x + y)
      2. print(torch.add(x, y))
      3. result = torch.empty(5, 3) torch.add(x, y, out=result)
      4. y.add_(x)注意 任何使张量会发生变化的操作都有一个前缀 ‘_’。例如x.copy_(y)
    • 可以使用标准的NumPy类似的索引操作,例如print(x[:, 1])打印x第一列元素。
    • 改变tensor的形状,使用torch.view,注意大小不能改变。
          x = torch.randn(4, 4)
          y = x.view(16)
          z = x.view(-1, 8)  # the size -1 is inferred from other dimensions
          print(x.size(), y.size(), z.size())
      
      结果如图所示: 在这里插入图片描述
    • 获取只有一个元素的tensor值:x.item()

      NumPy与Tensor相互转换

      1. N2T:b = a.numpy(),注意,转换后的tensor与numpy指向同一地址,改变一方的值另一个也会随之改变。
      2. T2N:b = torch.from_numpy(a)

    PyTorch自动微分


    PyTorch中所有神经网络的核心是 Autograd 自动求导包。torch.Tensor是包的核心类,Autograd软件包为Tensors上的所有操作提供自动微分。

    • 如果设置属性 .requires_grad=True,则跟踪针对tensor的所有操作。
    • 完成计算后,调用.backward()来自动计算所有梯度。该tensor的梯度将累积到.grad属性中。
    • 若要停止tensor历史记录的跟踪,调用y=x.detach(),则y为独立于计算图之外的tensor。
    • 若要停止跟踪历史记录和使用内存,可使用with torch.no_grad():包装代码块,在测试阶段使用。

    除了显示设置requires_grad属性外,还有一个规则:

    如果一个tensor Y由其他tensor X 1 X_1 X1, X 2 X_2 X2…计算得到,则只要 X i X_i Xi任意一个requires_grad为True,则Y的requires_grad即为True。

    该属性可以用来“冻结”model的一部分使其参数不变,微调网络中的其他部分。如下代码所示,只调整最后的FC层参数。

    	model = torchvision.models.resnet18(pretrained=True)
    	for param in model.parameters():
    	    param.requires_grad = False
    	model.fc = nn.Linear(512, 100)
    	optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)
    

    那么,Autograd如何记录历史信息呢?

    概念上来说,它为每条数据记录了一个有向无环图,即计算图,如 y = x ∗ s i n ( x ∗ a + b ) y=x*sin(x*a+b) y=xsin(xa+b)计算图如下所示,沿着计算图应用链式求导法则就可以求出梯度:

    在这里插入图片描述

    可以看出,PyTorch中的计算图的每个结点都是一个Function对象,这个对象可以使用apply()进行操作。

    在前向传播过程中,Autograd一边执行着前向计算,一边搭建一个graph,这个graph的结点是用于计算梯度的函数,这些函数结点保存在对应Tensor的.grad_fn中;而反向传播就利用这个graph计算梯度。

    举个例子:

    	x = torch.ones(2, 2, requires_grad=True) # 设置requires_grad为True,默认为False
    	y = x + 2
    	print(y.requires_grad) # 因为y由x计算得到,故其结果为True
    	z = y * y * 3
    	# z.requires_grad_(False) # 会报错,告诉你只能改变叶子变量的状态
    	# print(z.grad_fn) # 打印Function节点操作
    	out = z.mean()
    	out.backward() # 进行反向传播
    	print(x.grad)
    

    其结果为:
    在这里插入图片描述
    分析:链式求导 ∂ o u t ∂ x = ∂ o u t ∂ z ⋅ ∂ z ∂ y ⋅ ∂ y ∂ x = 1 4 ⋅ 6 y ⋅ 1 \frac{\partial out}{\partial x}=\frac{\partial out}{\partial z}\cdot \frac{\partial z}{\partial y}\cdot\frac{\partial y}{\partial x}=\frac 14 \cdot6y\cdot1 xout=zoutyzxy=416y1

    在使用Tensor.backward()时,如果Tensor是标量(如上面例子),则无需指定任何参数。但若它有多个元素,则需要指定一个gradient参数来指定张量的形状。
    当输出tensor为向量时,需要指定一个grad_output参数,举例如下:

    	x = torch.randn(3, requires_grad=True)
    	y = x * x * x
    	gradients = torch.tensor([0.1, 1.0, 0.01])
    	y.backward(gradients)
    	print("=========y对x求导 =======")
    	print(x.grad)
    	print("=========数学求导=======")
    	# y=x^3 求导 3x^2,然后乘以权重系数 0.1,1.0,0.01 比较结果
    	print(3 * x * x)
    

    PyTorch神经网络


    神经网络可以通过 torch.nn 包来构建。现在以手写数字识别任务为例,使用LeNet网络结构,我们分步来看PyTorch如何构建及训练该网络。
    在这里插入图片描述

    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__()
            self.conv1 = nn.Conv2d(1, 6, 5) # 卷积参数介绍:输入通道数、卷积核数量、卷积核大小
            self.conv2 = nn.Conv2d(6, 16, 5)
            self.fc1 = nn.Linear(16 * 5 * 5, 120) # 全连接层:y = Wx + b
            self.fc2 = nn.Linear(120, 84)
            self.fc3 = nn.Linear(84, 10)
        def forward(self, x):
            x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) # 最大池化层:2*2
            x = F.max_pool2d(F.relu(self.conv2(x)), 2) # 2*2可简写为2
            x = x.view(-1, self.num_flat_features(x)) # 调用num_flat_features(x),计算flat维度作为fc层输入
            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:]
            num_features = 1
            for s in size:
                num_features *= s
            return num_features
    net = Net()
    print(net)
    

    其输出为:

    Net(
      (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
      (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
      (fc1): Linear(in_features=400, out_features=120, bias=True)
      (fc2): Linear(in_features=120, out_features=84, bias=True)
      (fc3): Linear(in_features=84, out_features=10, bias=True)
    )
    
    2.前馈得到输出

    在Net类定义时,参数为nn.Module,其包括网络层和一个方法forward(input)它会返回输出(output)。
    先来看下模型参数,一个模型可训练的参数可以通过调用net.parameters()返回.

    params = list(net.parameters())
    print(len(params)) # 结果为10,五个网络层,每个网络层包括2个,一个为矩阵参数,一个为bias
    print(params[0].size())  # 第一层卷积层参数,torch.Size([6, 1, 5, 5])
    

    然后随机生成一个32x32的输入:

    input = torch.randn(1, 1, 32, 32)
    out = net(input)
    print(out)
    

    调用forward得到输出结果。

    3.计算loss
    target = torch.randn(10) # 使用随机数模拟标准输出
    target = target.view(1, -1) # 改变维度,与输出一致
    criterion = nn.MSELoss()
    loss = criterion(out, target)
    print(loss)
    
    4. 反向传播梯度

    有了loss,就可以进行梯度的反向传播了。首先,看一下传播路径(计算图):

    input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
          -> view -> linear -> relu -> linear -> relu -> linear
          -> MSELoss
          -> loss
    

    调用loss.backward(),整个图都会微分,而且所有的在图中的requires_grad=True的张量将会使其grad张量累计梯度。但是注意,首先要清空现存梯度

    net.zero_grad() # 清空现存梯度
    print(net.conv1.bias.grad) # 输出反向传播之前的梯度
    loss.backward()
    print(net.conv1.bias.grad) # 输出反向传播之后的梯度
    

    得到输出:

    conv1.bias.grad before backward
    tensor([0., 0., 0., 0., 0., 0.])
    conv1.bias.grad after backward
    tensor([-0.0054,  0.0011,  0.0012,  0.0148, -0.0186,  0.0087])
    
    5. 更新网络参数
    • 可以使用python进行随机梯度下降, w e i g h t = w e i g h t − l e a r n i n g r a t e ∗ g r a d i e n t weight = weight - learning_rate * gradient weight=weightlearningrategradient
    	learning_rate = 0.01 #设置学习率
    	params = list(net.parameters()) # 列出参数,查看参数在更新前后的变化
    	print(params[1]) # 第一个卷积层的bias
    	for f in net.parameters():
    	    f.data.sub_(f.grad.data * learning_rate) # 对所有参数进行更新
    	print(params[1])
    
    • 可以使用神经网络包使用其他更新规则,先引入import torch.optim as optim
    	optimizer = optim.SGD(net.parameters(), lr=0.01) # 创建一个optimizer
    	# in your training loop:
    	optimizer.zero_grad()   # zero the gradient buffers
    	output = net(input)
    	loss = criterion(out, target)
    	loss.backward()
    	optimizer.step()    # Does the update
    

    参考文档:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值