Pytorch入门第一课——基础介绍

一、Pytorch简介

1.1 pytorch

Pytorch是Torch在Python上的衍生。因为Torch是一个使用Lua语言的神经网络库,Torch很好用,但是Lua不是特别流行,所有开发团队将Lua的Torch移植到了更流行的语言Python上。

1.2 动态图和静态图

几乎所有的框架都是基于计算图的,而计算图又可以分为静态计算图和动态计算图,静态计算图先定义再运行,一次定义多次运行,而动态计算图是在运行过程中被定义的,在运行的时候构建,可以多次构建多次运行。Pytorch使用的是动态图,TensorFlow使用的是静态图。在Pytorch中每一次前向传播(每一次运行代码)都会创建一幅新的计算图。
静态图一旦创建就不能修改,而且静态图定义的时候,使用了特殊的语法,就相当于新学一门语言,这意味着无法使用一些常用的Python语句,而是专门设计语法,同时在构建图的时候必须把所有可能出现的情况都包含进去,这就导致了静态图过于庞大,可能占用过高的显存。
动态图就没有这个问题,它可以使用Python的if,while,for-loop等基础语句,最终创建的计算图取决你执行的条件分支。所以,使用动态图的Pytorch的实现方式完全和Python的语法一致,简洁直观;而TensorFlow的实现不仅代码冗长,而且十分不直观。
动态图的思想简洁明了,更符合 人的思考过程。动态图的方式使得我们可以任意修改前向传播,还可以随时查看变量的值。如果说静态图框架好比C++,每次运行都要编译才行,那么动态图框架就是Python,动态执行,可以交互式查看修改。
动态图带来的另外一个优势是调试更容易,在Pytorch中,代码报错的地方,往往就是你代码写错的地方,而静态图需要先根据你的代码生成Graph对象,然后在session.run()时报错,这种报错几乎很难找到对应的代码中真正错误的地方。

1.3 为什么选择Pytorch

Pytorch是当前难得的简洁优雅且高效快速的框架。当前开源的框架中,没有哪一个能够在灵活性、易用性、速度这三个方面有两个能同时超过Pytorch。
(1)简洁:Pytorch的设计追求最少的封装,尽量避免重复造轮子。Pytorch的设计遵循
tensor–>variable(autograd)–>nn.Moudle三个由低到高的抽象层次,分别代表高维数组(张量)、自动求导(变量)和神经网络(层/模块),而且这三个抽象之间联系紧密,可以同时进行修改和操作。
(2)速度:Pytorch的灵活性不以速度为代价,运行速度比其他的框架比较快。
(3)易用:Pytorch是所有的框架中面向对象设计的最优雅的一个。接口设计来源于Torch,而Torch的接口设计以灵活性易用性而著称。Pytorch的设计最符合人们的思维,它让用户尽可能地专注于实现自己的想法,即所思即所得,不需要考虑太多关于框架本身的束缚。
(4)活跃的社区:Pytorch提供了完整的文档,循序渐进的指南。

1.4 Numpy和Torch

Torch自称为神经网络界的Numpy,因为它能将torch产生的tensor放在GPU中加速运算,就像Numpy会把array放在CPU中加速运算。Torch和Numpy有着很好的兼容性,可以自由的转换numpy的array和torch的tensor。
(1)格式转换

import torch
import numpy as np

np_data = np.arange(6).reshape((2,3))
torch_data = torch.from_numpy(np_data)
tensor2array = torch_data.numpy()               #tensor转为numpy的array
array2tensor = torch.from_numpy(np_data)        # array转为pytorch的tensor
print('\nnumpy',np_data,
      '\ntorch',torch_data,
      '\ntensor2array',tensor2array,
      '\narray2tensor', array2tensor
      )

运行结果:

在这里插入图片描述
(2)运算
乘法运算:

import torch as t
import numpy as np

data = [[1,2],[3,4]]
tensor_data = t.FloatTensor(data)  # 将data转为float32类型的tensor

print('\nmatmul',
      '\nnumpy:',np.matmul(data,data),
      '\ntorch:',t.mm(tensor_data,tensor_data)
    )

运算结果:
在这里插入图片描述
还有求绝对值,加法,减法,sin等,详情可见:http://pytorch.org/docs/torch.html

二、安装Pytorch

一般是安装两个部分:pytorch和torchvision。前者是Pytorch的主模块,后者是一些库,包括一些网络的预先训练好的model和各种图片等。

2.1 pip安装

在这里插入图片描述

pip3 install torch===1.3.0 torchvision===0.4.1 -f https://download.pytorch.org/whl/torch_stable.html

2.2 conda安装

在这里插入图片描述

conda install pytorch torchvision cudatoolkit=10.1 -c pytorch

2.3 编译安装

首先安装可选依赖

conda install numpy pyyaml mkl setuptools cmake gcc cffi

然后下载Pytorch源码

git clone https://github.com/pytorch/pytorch

最后编译安装

cd pytorch
python setup.py install

三、Pytorch基础知识

3.1 Tensor

Tensor是Pytorch中重要的数据结构,可认为是一个高维数组。他可以是一个数(标量)、一维数组(向量)、二维数组(矩阵)或更高维的数组。Tensor和numpy的array类似,但Tensor可以GPU加速。Tensor的使用和numpy的接口类似。

import numpy as np

x1 = t.Tensor(2,3)   #构建了2*3的矩阵,并未初始化
x2 = t.rand(3,4)      #使用[0,1]均匀分布随机初始化二维数组

print(x1,'\n',x2)
print(x1.size(),x2.size())   # 输出矩阵的形状

在这里插入图片描述
加法操作:

import torch as t
import numpy as np

x = t.rand(3,4)
y = t.rand(3,4)

# 三种加法
result1 = x + y

result2 = t.add(x,y)

result3 = t.Tensor(3,4)
t.add(x,y,out=result3) #指定输出加法结果到result3

print(result1,'\n',result2,'\n',result3)

在这里插入图片描述
.cuda方法:
Tensor可以通过.cuda方法转为GPU的Tensor,从而加速运算。

import torch as t
import numpy as np

x = t.rand(3,4)
y = t.rand(3,4)

if t.cuda.is_available():
    x = x.cuda()
    y = y.cuda()
    result = x + y

print(result)

在这里插入图片描述
Tensor还支持很多操作,包括数学运算、线性代数、选择、切片(与numpy类似)。

此外,Tensor和numpy的数组间互操作非常容易且快速。Tensor不支持的操作,可以先转为numpy数组处理,之后再转回Tensor。Tensor和numpy对象共享内存,所以他们之间的转换很快,几乎不会消耗资源。这意味着其中一个变了,另一个也会随之改变。

3.2 Autograd:自动微分

深度学习的算法实质上是通过反向传播求导数,Pytorch的Autograd模块实现了此功能。在Tensor上的所有操作,Autograd都能为他们自动提供微分,避免手动计算导数的复杂过程。
autograd.Variable类是Autograd中的核心类,它简单封装了Tensor,并支持几乎所有Tensor的操作。Tensor在被封装为Variable后,可以调用它的.backward实现反向传播,自动计算所有梯度。
Variable主要包含三个属性:
(1)data:保存Variable所包含的eTensor。
(2)grad:保存data对应的梯度,grad也是个Variable,而不是Tensor,他和data的形状一样。
(3)grad_fn:指向一个Function对象,这个Function用来反向传播计算输入的梯度。

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

Variable和Tensor具有几乎一致的接口,在实际使用中可以无缝切换。

3.3 神经网络

Autograd实现了反向传播功能,但是直接用来写深度学习代码在很多情况下还是有点复杂,torch.nn是专门为神经网络设计的模块化接口。nn构建于Autograd之上,可以用来定义和运行神经网络。nn.Module是nn中最重要的类,可以把它看作一个网络的封装,包含网络各层定义及forward方法,调用forward(input)方法,可返回前向传播的结果。
定义网络时,需要继承nn.Module,并实现它的forward方法,把网络中具有可学习参数的层放在构造函数__init__中。如果某一层(如ReLU)不具有可学习的参数,则既可以放在构造函数中,也可以不放。
一个简单的神经网络:

import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        # nn.Module子类的函数必须在构造函数中执行父类的构造函数
        # 下式等价于nn.Module.__init__(self)
        super(Net,self).__init__()

        # 卷积层
        # 卷积层’1‘表示输入图片为单通道,’6‘表示输出通道数,’5‘表示卷积核为5*5
        self.conv1 = nn.Conv2d(1,6,5)
        self.conv2 = nn.Conv2d(6,16,5)

        # 全连接层
        self.fc1 = nn.Linear(16*5*5,120)
        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))
        x = F.max_pool2d(F.relu(self.conv2(x)),2)

        # reshape,’-1‘表示自适应
        x = x.view(x.size()[0],-1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()
print(net)

输出结果:
在这里插入图片描述
只要在nn.Module的子类中定义了forward函数,backward函数就会被自动实现(利用Autograd)。在forward函数中可使用任何Variable支持的函数,还可以使用if,for循环,print,log等Python语法,写法和标准的Python写法一致。
网络的可学习参数通过net.parameters()返回,net.named_parameters可同时返回可学习的参数及名称。

...........
params = list(net.parameters())
print(len(params))
for name,parameters in net.named_parameters():
    print(name,':',parameters.size())

在这里插入图片描述
forward函数的输入和输出都是Variable,只有Variable才具有自动求导功能,Tensor是没有的,所以在输入时,需要把Tensor封装成Variable。不过0.4版本以后,这两个已经整合到了一起,不再需要这步了。
需要注意的是,torch.nn只支持mini-batches,不支持一次只输入一个样本,即一次必须是一个batch。如果只想输入一个样本,则用input.unsqueeze(0)将batch-size设为1.

3.4 损失函数

nn实现了神经网络种大多数的损失函数,例如nn.MSELoss用来计算均方误差,nn.CrossEntropyLoss用来计算交叉熵损失。
如果对loss进行反向传播溯源(使用grad_fn属性),可看到它的计算图如下:
在这里插入图片描述
当调用loss.backward()函数时,该图会动态生成并自动微分,也会自动计算图中参数的导数。

...........
input = t.randn(1,1,32,32)
output = net(input)
target = t.arange(0,10).reshape(1,10)
target = target.float()
criterion = nn.MSELoss()
loss = criterion(output,target)
net.zero_grad()
print('反向传播之前的conv1.bias的梯度')
print(net.conv1.bias.grad)
loss.backward()
print('反向传播之后的conv1.bias的梯度')
print(net.conv1.bias.grad)

在这里插入图片描述

3.5 优化器

在反向传播计算完所有参数的梯度后,还需要使用优化方法更新网络的权重和参数。torch.optim中实现了深度学习中绝大多数的优化方法,例如RMSProp、Adam、SGD等,便于使用。

optimizer = optim.SGD(net.parameters(),lr=0.01)
# 在训练过程中,先把梯度清零
optimizer.zero_grad()
# 计算损失
input = t.randn(1,1,32,32)
output = net(input)
target = t.arange(0,10).reshape(1,10)
target = target.float()
criterion = nn.MSELoss()
loss = criterion(output,target)
# 反向传播
loss.backward()
# 更新参数
optimizer.step()

四、一个简单神经网络实例

主要步骤如下:
(1)使用torchvision加载并预处理CIFAR-10数据集
(2)定义网络
(3)定义损失函数和优化器
(4)训练网络并更新网络参数
(5)测试网络

使用的数据集是CIFAR-10,这是一个常用的彩色图片数据集,他有10个类别:airplane、automobile、bird、cat、deer、dog、frog、horse、ship和truck。每张图片都是3×32×32,即3通道,分辨率为32×32.

代码如下:

import torchvision as tv
import torchvision.transforms as transforms
from torchvision.transforms import ToPILImage
import torch.nn as nn
import torch as t
import torch.nn.functional as F
import torch.optim as optim

# 定义对数据的预处理
transform = transforms.Compose([
    transforms.ToTensor(),      # 转为Tensor
    transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))   # 归一化
        ])
# 训练集
trainset = tv.datasets.CIFAR10(
    root='I:/pycharm-project/DL_code-pytorch/data/',
    train=True,
    download=False,
    transform=transform)

trainloader = t.utils.data.DataLoader(
    trainset,
    batch_size=4,
    shuffle=True,
    num_workers=0)

# 测试集
testset = tv.datasets.CIFAR10(
    'I:/pycharm-project/DL_code-pytorch/data/',
    train=False,
    download=False,
    transform=transform)
testloader = t.utils.data.DataLoader(
    testset,
    batch_size=4,
    shuffle=False,
    num_workers=0)

classes = ('plane','car','bird','cat','deer','dog',
           'frog','horse','ship','truck')
           
# 定义网络
class Net(nn.Module):
    def __init__(self):
        # nn.Module子类的函数必须在构造函数中执行父类的构造函数
        # 下式等价于nn.Module.__init__(self)
        super(Net,self).__init__()

        # 卷积层
        # 卷积层’1‘表示输入图片为单通道,’6‘表示输出通道数,’5‘表示卷积核为5*5
        self.conv1 = nn.Conv2d(3,6,5)
        self.conv2 = nn.Conv2d(6,16,5)

        # 全连接层
        self.fc1 = nn.Linear(16*5*5,120)
        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))
        x = F.max_pool2d(F.relu(self.conv2(x)),2)

        # reshape,’-1‘表示自适应
        x = x.view(x.size()[0],-1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

# 损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)

# 训练网络
for epoch in range(2):
    running_loss = 0.0
    for i,data in enumerate(trainloader,0):
        # 输入数据
        inputs,labels = data
        # 梯度清零
        optimizer.zero_grad()
        # 前向传播和反向传播
        outputs = net(inputs)
        loss = criterion(outputs,labels)
        loss.backward()
        # 更新参数
        optimizer.step()

        # 打印log信息
        running_loss += loss.item()
        if i%2000 ==1999:
            print('[%d,%5d] loss: %.3f'%(epoch+1,i+1,running_loss/2000))
            running_loss = 0.0
    print('Finished Training')

# 预测
correct = 0
total = 0
for data in testloader:
    images,labels = data
    outputs = net(images)
    _,predicted = t.max(outputs.data,1)
    total += labels.size(0)
    correct += (predicted == labels).sum()

    print('10000张测试集中的准确率为:%d %%' % (100*correct/total))


运算结果:
在这里插入图片描述
在这里插入图片描述
最终的准确率为55%。
程序中:Dataset对象是一个数据集,可以按下标访问,返回形如(data,label)的数据。
Dataloader是一个可迭代的对象,它将dataset返回的每一条数据样本拼接成一个batch,并提供多线程加速优化和数据打乱等操作。当程序对dataset的所有数据遍历完一遍之后,对Dataloader也完成了一次迭代。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章