PyTorch的基本概念

一、什么是Pytorch,为什么选择Pytroch?

1. 什么是Pytorch

pytorch是一个基于Python的科学计算软件包,针对两组受众:

  • NumPy的替代品,可以使用GPU的强大功能
  • 深入学习研究平台,提供最大的灵活性和速度

PyTorch 是一个以Python 优先的深度学习框架,不仅能够实现强大的GPU 加速,同时还支持动态神经网络,这是现在很多主流框架比如Tensorflow 等都不支持的。

PyTorch 既可以看做加入了GPU 支持的numpy,同时也可以看成一个拥有自动求导功能的强大的深度神经网络,除了Facebook 之外,它还已经被Twitter、CMU 和Salesforce 等机构采用。

2. 为什么选择pytorch

面对如此多的深度学习框架,我们为何要选择PyTorch 呢?Tensorflow 不是深度学习框架默认的老大吗,为什么不直接选择Tensorflow 而是要选择PyTorch 呢?下面分4个方面来介绍为何要使用PyTorch。

(1)掌握一个框架并不能一劳永逸,现在深度学习并没有谁拥有绝对的垄断地位,就算是Google 也没有,所以只学习Tensorflow 并不够。同时现在的研究者使用各个框架的都有,如果你要去看他们实现的代码,至少也需要了解他们使用的框架,所以多学一个框架,以备不时之需。

(2)Tensorflow 与Caffe 都是命令式的编程语言,而且是静态的,首先必须构建一个神经网络,然后一次又一次使用同样的结构,如果想要改变网络的结构,就必须从头开始。但是对于PyTorch,通过一种反向自动求导的技术,可以让你零延迟地任意改变神经网络的行为,尽管这项技术不是PyTorch 独有,但目前为止它实现是最快的,能够为你任何疯狂想法的实现获得最高的速度和最佳的灵活性,这也是PyTorch 对比Tensorflow 最大的优势。

(3)PyTorch 的设计思路是线性、直观且易于使用的,当你执行一行代码时,它会忠实地执行,并没有异步的世界观,所以当你的代码出现Bug 的时候,可以通过这些信息轻松快捷地找到出错的代码,不会让你在Debug 的时候因为错误的指向或者异步和不透明的引擎浪费太多的时间。

(4)PyTorch 的代码相对于Tensorflow 而言,更加简洁直观,同时对于Tensorflow高度工业化的很难看懂的底层代码,PyTorch 的源代码就要友好得多,更容易看懂。深入API,理解PyTorch 底层肯定是一件令人高兴的事。一个底层架构能够看懂的框架,你对其的理解会更深。

最后,我们简要总结一下PyTorch 的特点:

  • 支持GPU;
  • 动态神经网络;
  • Python 优先;
  • 命令式体验;
  • 轻松扩展。

二、Pytroch的安装

到这个网站根据硬件情况查看所需要安装的pytorch版本,然后用命令行安装
https://pytorch.org/get-started/locally/

三、配置Python环境

安装python解释器

四、准备Python管理器

安装anaconda,安装一些必要的包

五、通过命令行安装PyTorch

找到对应的pytorch安装命令,在命令行中输入进行安装

六、PyTorch基础概念

根据此网址学习pytorch基础知识
使用PYTORCH深度学习:60分钟闪电战
https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html

  1. tensor
    tensor与NumPy的ndarray类似,另外还有Tensors也可用于GPU以加速计算。
from __future__ import print_function
import torch

#构造一个未初始化的5x3矩阵:
x = torch.empty(5, 3)  

#构造一个随机初始化的矩阵:
x = torch.rand(5, 3)

#构造一个矩阵填充的零和dtype long:
x = torch.zeros(5, 3, dtype=torch.long)

#直接从数据构造张量tensor
x = torch.tensor([5.5, 3])

#或者根据现有的张量创建张量。除非用户提供新值,否则这些方法将重用输入张量的属性,例如dtype
x = x.new_ones(5, 3, dtype=torch.double)
print(x)
x = torch.randn_like(x, dtype=torch.float)
print(x)
#得到它的大小:
print(x.size())   #torch.Size 实际上是一个元组,因此它支持所有元组操作
  1. 各种操作
    (1)增加
#增加:语法1
y = torch.rand(5, 3)
print(x + y)

#增加;语法2
print(torch.add(x, y))

#增加:提供输出张量作为参数
result = torch.empty(5, 3)
torch.add(x, y, out=result)
print(result)

#增加:就地(in-place)
#add x to y
y.add(x)
print(y)

注意:任何使原位张量变形的操作都是用_后固定的。例如:x.copy_(y),x.t_(),将改变x。

#切片,输出x的第一列
print(x[:, 1])

(2)调整大小
如果要调整张量/重塑张量,可以使用torch.view

#调整大小:如果要调整张量/重塑张量,可以使用torch.view:
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8) #大小-1是从其他维度推断出来的
print(x.size(), y.size(), z.size())

#如果你有一个元素张量,用于.item()获取值作为Python数字
x = torch.randn(1)
print(x)
print(x.item())
  1. NumPy Bridge
    将Torch Tensor转换为NumPy阵列(反之亦然)是一件轻而易举的事。
    Torch Tensor和NumPy阵列将共享其底层内存位置(如果Torch Tensor在CPU上),更改一个将改变另一个。
    (1)将Torch Tensor转换为NumPy数组
#将Torch Tensor转换为NumPy数组
a = torch.ones(5)
print(a)

b = a.numpy()
print(b)

#输出结果
tensor([1., 1., 1., 1., 1.])
[1. 1. 1. 1. 1.]

#了解numpy数组的值如何变化
a.add_(1)
print(a)
print(b)

#输出
tensor([2., 2., 2., 2., 2.])
[2. 2. 2. 2. 2.]

(2)将NumPy数组转换为Torch Tensor

#将NumPy数组转换为Torch Tensor
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
print(b)

#输出
[2. 2. 2. 2. 2.]
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)

除了CharTensor之外,CPU上的所有Tensors都支持转换为NumPy并返回。

  1. AUTOGRAD:AUTOMATIC DIFFERENTIATION(自动微分)
    autograd包中是PyTorch中所有神经网络的核心。

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

    torch.Tensor是包的核心类。如果将其属性设置 .requires_grad为True,则会开始跟踪其上的所有操作。完成计算后,您可以调用**.backward()并自动计算所有渐变。该张量的梯度将累积到.grad**属性中。

    要阻止tensor跟踪历史记录,您可以调用**.detach()**它将其从计算历史记录中分离出来,并防止将来的计算被跟踪。

    要防止跟踪历史记录(和使用内存),您还可以将代码块包装在其中。这在评估模型时尤其有用,因为模型可能具有可训练的参数 ,但我们不需要梯度。
    with torch.no_grad():requires_grad=True

    还有一个类对于autograd实现非常重要 - a Function

    Tensor与Function互相连接并构建一个非循环图,它编码完整的计算历史。每个张量都有一个
    .grad_fn属性,该属性引用Function已创建的属性Tensor(除了用户创建的张量 - grad_fn is None

    如果你想计算导数,你可以调用 .backward() a Tensor。如果Tensor是标量(即它包含一个元素数据),则不需要指定任何参数backward(),但是如果它有更多元素,则需要指定一个gradient 匹配形状的张量的参数。

#自动微分
import torch

#创建一个张量并设置requires_grad=True为跟踪计算
x = torch.ones(2, 2, requires_grad=True)
print(x)


#做一个张量操作:
y = x + 2
print(y)

#y是作为一个操作的结果创建的,所以它有一个grad_fn
print(y.grad_fn)

#做更多的操作 y
z =  y * y * 3
out = z.mean()
print(z, out)

#.requires_grad_( ... ):requires_grad 就地改变现有的Tensor标志。False如果没有给出,输入标志默认为False
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad = True
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
#输出
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)
<AddBackward0 object at 0x000001FBB78C96A0>
tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
False
True
<SumBackward0 object at 0x000001FBB78C9A90>
  1. Gradients
    因为out包含单个标量,out.backward()相当于out.backward(torch.tensor(1.))
#求导
out.backward()

#打印导数 d(out)/ dx
print(x.grad)


#
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
    y = y * 2
print(y)


v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)

print(x.grad)

print()
print()

#还可以.requires_grad=True通过包装代码块来 停止在Tensors上跟踪历史记录的autogradwith torch.no_grad():
print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2).requires_grad)
#输出
tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])
tensor([ -100.9193,   255.7541, -1418.0422], grad_fn=<MulBackward0>)
tensor([5.1200e+01, 5.1200e+02, 5.1200e-02])


True
True
False

七、通用代码实现流程(实现一个深度学习的代码流程)

  1. 神经网络

可以使用torch.nn包构造神经网络

现在你已经看到了autograd,nn取决于 autograd定义模型并区分它们。一个nn.Module包含层,和一种方法forward(input),它返回output

神经网络的典型训练程序如下:

  • 定义具有一些可学习参数(或权重)的神经网络
  • 迭代输入数据集
  • 通过网络处理输入
  • 计算损失(输出距离正确多远)
  • 将gradient传播回网络参数
  • 通常使用简单的更新规则更新网络权重: weight = weight - learning_rate * gradient

(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))
        # If the size is a square you can only specify a single number
        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
        return num_features


net = Net()
print(net)

(2)损失函数

损失函数采用(输出,目标)输入对,并计算估计输出距目标的距离的值。

nn包下有几种不同的 损失函数。一个简单的损失是:nn.MSELoss它计算输入和目标之间的均方误差。

例如:

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)

(3)反向传播
要反向传播,我们所要做的就是loss.backward()。您需要清除现有渐变,否则渐变将累积到现有渐变。

现在我们call loss.backward(),看一下conv1在向后之前和之后的偏差梯度。

net.zero_grad()     # zeroes the gradient buffers of all parameters

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)

loss.backward()

print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)

(4)更新权重
实践中使用的最简单的更新规则是随机梯度下降(SGD):
weight = weight - learning_rate * gradient

learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

但是,当使用神经网络时,希望使用各种不同的更新规则,例如SGD,Nesterov-SGD,Adam,RMSProp等。为了实现这一点,torch.optim实现了所有这些方法。使用它非常简单:

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()    # Does the update
  1. 数据的处理

通常,当您必须处理图像,文本,音频或视频数据时,您可以使用标准的python包将数据加载到numpy数组中。然后你可以将这个数组转换成一个torch.*Tensor

  • 对于图像,Pillow,OpenCV等软件包很有用
  • 对于音频,包括scipy和librosa
  • 对于文本,无论是原始Python还是基于Cython的加载,还是NLTK和SpaCy都很有用

特别是对于视觉,我们创建了一个名为的包 torchvision,其中包含用于常见数据集的数据加载器,如Imagenet,CIFAR10,MNIST等,以及用于图像的数据转换器,即 torchvision.datasets和torch.utils.data.DataLoader。

这提供了极大的便利并避免编写样板代码。

  1. 例子:训练图像分类器

我们将按顺序执行以下步骤:

  • 使用加载和标准化CIFAR10训练和测试数据集 torchvision
  • 定义卷积神经网络
  • 定义损失函数
  • 在训练数据上训练网络
  • 在测试数据上测试网络

(1)加载和标准化CIFAR10

import torch
import torchvision
import torchvision.transforms as transforms

torchvision数据集的输出是范围[0,1]的PILImage图像。我们将它们转换为归一化范围的张量[-1,1]

transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

显示一些训练图像

import matplotlib.pyplot as plt
import numpy as np

# functions to show an image


def imshow(img):
    img = img / 2 + 0.5     # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()


# get some random training images
dataiter = iter(trainloader)
images, labels = dataiter.next()

# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

(2)定义卷积神经网络
从神经网络部分复制神经网络并修改它以获取3通道图像(而不是定义的1通道图像)

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(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        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 = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x


net = Net()

(3)定义Loss函数和优化器
让我们使用分类交叉熵损失和SGD动量

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

(4)训练网络

for epoch in range(2):  # loop over the dataset multiple times

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # get the inputs; data is a list of [inputs, labels]
        inputs, labels = data

        # zero the parameter gradients
        optimizer.zero_grad()

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        # print statistics
        running_loss += loss.item()
        if i % 2000 == 1999:    # print every 2000 mini-batches
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0

print('Finished Training')

(5)在测试数据上测试网络
1)第一步,让我们从测试集中显示一个图像以熟悉

dataiter = iter(testloader)
images, labels = dataiter.next()

# print images
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

2)现在让我们看看神经网络认为上面这些例子是什么

outputs = net(images)

3)输出是10分类的能量。一个分类的能量越高,网络认为图像是特定分类的越多。那么,让我们得到最高能量的指数

_, predicted = torch.max(outputs, 1)

print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
                              for j in range(4)))

4)让我们看看网络如何在整个数据集上执行

correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total))

5)表现良好的分类,以及表现不佳的分类

class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs, 1)
        c = (predicted == labels).squeeze()
        for i in range(4):
            label = labels[i]
            class_correct[label] += c[i].item()
            class_total[label] += 1


for i in range(10):
    print('Accuracy of %5s : %2d %%' % (
        classes[i], 100 * class_correct[i] / class_total[i]))

6)在GPU上进行训练

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# Assuming that we are on a CUDA machine, this should print a CUDA device:

print(device)

net.to(device)

#将每一步的输入和目标发送到GPU
inputs, labels = data[0].to(device), data[1].to(device)

八、参考教程

CSDN博主「博文视点」https://blog.csdn.net/broadview2006/article/details/80133047
使用PYTORCH深度学习:60分钟闪电战
https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html