NLP

Pytorch

Pytorch基本介绍

Posted by 新宇 on September 1, 2020

一、Pytorch基本介绍

1. 什么是Pytorch

  • Pytorch是一个基于Numpy的科学计算包, 向它的使用者提供了两大功能.
  • 作为Numpy的替代者, 向用户提供使用GPU强大功能的能力.
  • 做为一款深度学习的平台, 向用户提供最大的灵活性和速度.

2. Pytorch的基本元素操作

from __future__ import print_function
import torch

# 创建一个没有初始化的矩阵:

x = torch.empty(5, 3)
print(x)

# 创建一个有初始化的矩阵:

x = torch.rand(5, 3)
print(x)

#  创建一个全零矩阵并可指定数据元素的类型为long

x = torch.zeros(5, 3, dtype=torch.long)
print(x)

# 直接通过数据创建张量

x = torch.tensor([2.5, 3.5])
print(x)

# 通过已有的一个张量创建相同尺寸的新张量
# 利用news_methods方法得到一个张量

x = x.new_ones(5, 3, dtype=torch.double)
print(x)

# 利用randn_like方法得到相同张量尺寸的一个新张量, 并且采用随机初始化来对其赋值

y = torch.randn_like(x, dtype=torch.float)
print(y)

# 得到张量的尺寸

print(x.size())

3. Pytorch基本运算操作

# 加法操作

y = torch.rand(5, 3)
print(x + y)

# 第二种方式

print(torch.add(x, y))

# 第三种方式
# 提前设定一个空的张量

result = torch.empty(5, 3)
# 将空的张量作为加法的结果存储张量

torch.add(x, y, out=result)
print(result)

# 第四种方式:原地置换

y.add_(x)
print(y)

# 用类似于Numpy的方式对张量进行操作

print(x[:, 1])

# 改变张量的形状: torch.view()

x = torch.randn(4, 4)
# tensor.view()操作需要保证数据元素的总数量不变

y = x.view(16)
# -1代表自动匹配个数

z = x.view(-1, 8)
print(x.size(), y.size(), z.size())

# 如果张量中只有一个元素, 可以用.item()将值取出, 作为一个python number

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

4. Torch Tensor和Numpy array之间的相互转换

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

a.add_(1)
print(a)
print(b)

# 将Numpy array转换为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)

使用GPU运行

# 如果服务器上已经安装了GPU和CUDA

if torch.cuda.is_available():
    # 定义一个设备对象, 这里指定成CUDA, 即使用GPU

    device = torch.device("cuda")
    # 直接在GPU上创建一个Tensor

    y = torch.ones_like(x, device=device)
    # 将在CPU上面的x张量移动到GPU上面

    x = x.to(device)
    # x和y都在GPU上面, 才能支持加法运算

    z = x + y
    # 此处的张量z在GPU上面

    print(z)
    # 也可以将z转移到CPU上面, 并同时指定张量元素的数据类型

    print(z.to("cpu", torch.double))

二、Pytorch中的autograd

1. 关于torch.Tensor

  • torch.Tensor是整个package中的核心类, 如果将属性.requires_grad设置为True, 它将追踪在这个类上定义的所有操作. 当代码要进行反向传播的时候, 直接调用.backward()就可以自动计算所有的梯度. 在这个Tensor上的所有梯度将被累加进属性.grad中.
  • 如果想终止一个Tensor在计算图中的追踪回溯, 只需要执行.detach()就可以将该Tensor从计算图中撤下, 在未来的回溯计算中也不会再计算该Tensor.
  • 除了.detach(), 如果想终止对计算图的回溯, 也就是不再进行方向传播求导数的过程, 也可以采用代码块的方式with torch.no_grad():, 这种方式非常适用于对模型进行预测的时候, 因为预测阶段不再需要对梯度进行计算.

2. 关于torch.Function

  • Function类是和Tensor类同等重要的一个核心类, 它和Tensor共同构建了一个完整的类, 每一个Tensor拥有一个.grad_fn属性, 代表引用了哪个具体的Function创建了该Tensor.
  • 如果某个张量Tensor是用户自定义的, 则其对应的grad_fn is None.

3. 代码

x1 = torch.ones(3, 3)
print(x1)

x = torch.ones(2, 2, requires_grad=True)
print(x)

y = x + 2
print(y)

print(x.grad_fn)
print(y.grad_fn)

z = y * y * 3
out = z.mean()
print(z, out)

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)

4. 关于梯度Gradient

out.backward()
print(x.grad)

print(x.requires_grad)
print((x ** 2).requires_grad)

# 关于自动求导的属性设置: 可以通过设置.requires_grad=True来执行自动求导, 也可以通过代码块的限制来停止自动求导.

with torch.no_grad():
    print((x ** 2).requires_grad)

# 可以通过.detach()获得一个新的Tensor, 拥有相同的内容但不需要自动求导.

print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())

三、Pytorch初步应用

1. 构建神经网络的流程

  • 构建神经网络的典型流程:
  • 定义一个拥有可学习参数的神经网络
  • 遍历训练数据集
  • 处理输入数据使其流经神经网络
  • 计算损失值
  • 将网络参数的梯度进行反向传播
  • 以一定的规则更新网络的权重

2. Pytorch实现的神经网络

# 导入若干工具包

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, 输出通道维度=6, 卷积核大小3*3

        self.conv1 = nn.Conv2d(1, 6, 3)
        # 定义第二层卷积神经网络, 输入通道维度=6, 输出通道维度=16, 卷积核大小3*3

        self.conv2 = nn.Conv2d(6, 16, 3)
        # 定义三层全连接网络

        self.fc1 = nn.Linear(16 * 6 * 6, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # 在(2, 2)的池化窗口下执行最大池化操作

        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        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, 除了第0个维度上的batch_size

        size = x.size()[1:]
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net)

params = list(net.parameters())
print(len(params))
print(params[0].size())

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

# 有了输出张量后, 就可以执行梯度归零和反向传播的操作了.

net.zero_grad()
out.backward(torch.randn(1, 10))

3. 损失函数

  • 损失函数的输入是一个输入的pair: (output, target), 然后计算出一个数值来评估output和target之间的差距大小.
  • 在torch.nn中有若干不同的损失函数可供使用, 比如nn.MSELoss就是通过计算均方差损失来评估输入和目标值之间的差距.
output = net(input)
target = torch.randn(10)

# 改变target的形状为二维张量, 为了和output匹配

target = target.view(1, -1)
criterion = nn.MSELoss()

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

关于方向传播的链条: 如果我们跟踪loss反向传播的方向, 使用.grad_fn属性打印, 将可以看到一张完整的计算图如下:

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

当调用loss.backward()时, 整张计算图将对loss进行自动求导, 所有属性requires_grad=True的Tensors都将参与梯度求导的运算, 并将梯度累加到Tensors中的.grad属性中.

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

4. 反向传播

# Pytorch中执行梯度清零的代码

net.zero_grad()

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

# Pytorch中执行反向传播的代码

loss.backward()

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

5. 更新网络参数

更新参数最简单的算法就是SGD(随机梯度下降). 具体的算法公式表达式为: weight = weight - learning_rate * gradient

# 首先用传统的Python代码来实现SGD如下

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

# 然后使用Pytorch官方推荐的标准代码如下
# 首先导入优化器的包, optim中包含若干常用的优化算法, 比如SGD, Adam等
import torch.optim as optim

# 通过optim创建优化器对象

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

# 将优化器执行梯度清零的操作

optimizer.zero_grad()

output = net(input)
loss = criterion(output, target)

# 对损失值执行反向传播的操作

loss.backward()
# 参数的更新通过一行标准代码来执行

optimizer.step()

四、使用Pytorch构建一个分类器

1. 下载CIFAR10数据集

import torch
import torchvision
import torchvision.transforms as transforms

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')

2. 展示训练集的图片

# 导入画图包和numpy

import matplotlib.pyplot as plt
import numpy as np

# 构建展示图片的函数

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


# 从数据迭代器中读取一张图片

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

# 展示图片

imshow(torchvision.utils.make_grid(images))
# 打印标签label

print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

3. 定义卷积神经网络

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()

4. 定义损失函数

import torch.optim as optim

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

5. 在训练集上训练模型

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

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # data中包含输入图像张量inputs, 标签张量labels

        inputs, labels = data

        # 首先将优化器梯度归零

        optimizer.zero_grad()

        # 输入图像张量进网络, 得到输出张量outputs

        outputs = net(inputs)

        # 利用网络的输出outputs和标签labels计算损失值

        loss = criterion(outputs, labels)

        # 反向传播+参数更新, 是标准代码的标准流程

        loss.backward()
        optimizer.step()

        # 打印轮次和损失值

        running_loss += loss.item()
        if (i + 1) % 2000 == 0:
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0

print('Finished Training')

# 保存模型
# 首先设定模型的保存路径

PATH = './cifar_net.pth'
# 保存模型的状态字典

torch.save(net.state_dict(), PATH)

6. 在测试集上测试模型

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

# 打印原始图片

imshow(torchvision.utils.make_grid(images))
# 打印真实的标签

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

# 加载模型并对测试图片进行预测

# 首先实例化模型的类对象

net = Net()
# 加载训练阶段保存好的模型的状态字典

net.load_state_dict(torch.load(PATH))

# 利用模型对图片进行预测

outputs = net(images)

# 共有10个类别, 采用模型计算出的概率最大的作为预测的类别

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

# 打印预测标签的结果

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

7. 模型评估

# 在全部测试集上的表现

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))

# 在每个类别上的表现

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]))

8. 在GPU上训练模型

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

print(device)

# 将模型转移到GPU上

net.to(device)

# 将输入的图片张量和标签张量转移到GPU上

inputs, labels = data[0].to(device), data[1].to(device)

五、NLP所需环境

通过 pip3 freeze >requirements.tx 导出

absl-py==0.8.1
alabaster==0.7.12
anaconda-client==1.7.2
anaconda-navigator==1.9.7
anaconda-project==0.8.3
asn1crypto==1.0.1
astor==0.8.0
astroid==2.3.1
astropy==3.2.2
astunparse==1.6.3
atomicwrites==1.3.0
attrs==19.2.0
Babel==2.7.0
backcall==0.1.0
backports.functools-lru-cache==1.5
backports.os==0.1.1
backports.shutil-get-terminal-size==1.0.0
backports.tempfile==1.0
backports.weakref==1.0.post1
beautifulsoup4==4.8.0
bert-for-tf2==0.12.7
bitarray==1.0.1
bkcharts==0.2
bleach==3.1.0
blinker==1.4
bokeh==1.3.4
boto==2.49.0
boto3==1.10.29
botocore==1.13.29
bottle==0.12.18
Bottleneck==1.2.1
cachetools==3.1.1
certifi==2019.9.11
cffi==1.12.3
chardet==3.0.4
Click==7.0
cloudpickle==1.2.2
clyent==1.2.2
colorama==0.4.1
conda==4.7.12
conda-build==3.18.9
conda-package-handling==1.6.0
conda-verify==3.4.2
contextlib2==0.6.0
cryptography==2.7
cycler==0.10.0
Cython==0.29.13
cytoolz==0.10.0
dask==2.5.2
decorator==4.4.0
defusedxml==0.6.0
dill==0.3.1.1
distributed==2.5.2
docutils==0.15.2
entrypoints==0.3
et-xmlfile==1.0.1
fastcache==1.1.0
fasttext==0.9.1
filelock==3.0.12
Flask==1.1.1
fsspec==0.5.2
future==0.17.1
gast==0.3.3
gevent==1.4.0
glob2==0.7
gmpy2==2.0.8
google-auth==1.7.1
google-auth-oauthlib==0.4.1
google-pasta==0.1.8
googleapis-common-protos==1.51.0
googletrans==2.4.0
greenlet==0.4.15
grpcio==1.25.0
gunicorn==20.0.4
h5py==2.10.0
hanlp==2.0.0a10
HeapDict==1.0.1
html5lib==1.0.1
htmlmin==0.1.12
idna==2.8
imageio==2.6.0
imagesize==1.1.0
importlib-metadata==0.23
ipykernel==5.1.2
ipython==7.8.0
ipython-genutils==0.2.0
ipywidgets==7.5.1
isort==4.3.21
itsdangerous==1.1.0
jdcal==1.4.1
jedi==0.15.1
jeepney==0.4.1
jieba==0.40
Jinja2==2.10.3
jmespath==0.9.4
joblib==0.13.2
jsmin==2.2.2
json5==0.8.5
jsonschema==3.0.2
jupyter==1.0.0
jupyter-client==5.3.3
jupyter-console==6.0.0
jupyter-core==4.5.0
jupyterlab==1.1.4
jupyterlab-server==1.0.6
Keras==2.3.1
Keras-Applications==1.0.8
Keras-Preprocessing==1.1.0
keyring==18.0.0
kiwisolver==1.1.0
lazy-object-proxy==1.4.2
libarchive-c==2.8
lief==0.9.0
livereload==2.6.1
llvmlite==0.29.0
locket==0.2.0
lxml==4.4.1
Markdown==3.1.1
MarkupSafe==1.1.1
matplotlib==3.1.1
mccabe==0.6.1
mistune==0.8.4
mkdocs==1.0.4
mkdocs-material==4.6.0
mkdocs-minify-plugin==0.2.1
mkl-fft==1.0.14
mkl-random==1.1.0
mkl-service==2.3.0
mock==3.0.5
more-itertools==7.2.0
mpmath==1.1.0
msgpack==0.6.1
multipledispatch==0.6.0
navigator-updater==0.2.1
nbconvert==5.6.0
nbformat==4.4.0
neo4j==1.7.6
neo4j-driver==1.7.6
neobolt==1.7.15
neotime==1.7.4
networkx==2.3
nltk==3.4.5
nose==1.3.7
notebook==6.0.1
numba==0.45.1
numexpr==2.7.0
numpy==1.17.2
numpydoc==0.9.1
oauthlib==3.1.0
olefile==0.46
openpyxl==3.0.0
opt-einsum==3.1.0
packaging==19.2
pandas==0.25.3
pandocfilters==1.4.2
params-flow==0.7.4
parso==0.5.1
partd==1.0.0
path.py==12.0.1
pathlib2==2.3.5
patsy==0.5.1
pep562==1.0
pep8==1.7.1
pexpect==4.7.0
pickleshare==0.7.5
Pillow==6.2.0
pkginfo==1.5.0.1
pluggy==0.13.0
ply==3.11
prometheus-client==0.7.1
promise==2.3
prompt-toolkit==2.0.10
protobuf==3.11.0
psutil==5.6.3
ptyprocess==0.6.0
py==1.8.0
py-params==0.8.2
py2neo==4.3.0
pyasn1==0.4.8
pyasn1-modules==0.2.7
pybind11==2.4.3
pycodestyle==2.5.0
pycosat==0.6.3
pycparser==2.19
pycrypto==2.6.1
pycurl==7.43.0.3
pyflakes==2.1.1
Pygments==2.3.1
pylint==2.4.2
pymdown-extensions==6.2.1
pyodbc==4.0.27
pyOpenSSL==19.0.0
pyparsing==2.4.2
pyrsistent==0.15.4
PySocks==1.7.1
pytest==5.2.1
pytest-arraydiff==0.3
pytest-astropy==0.5.0
pytest-doctestplus==0.4.0
pytest-openfiles==0.4.0
pytest-remotedata==0.3.2
python-dateutil==2.8.0
pytz==2019.3
PyWavelets==1.0.3
PyYAML==5.1.2
pyzmq==18.1.0
QtAwesome==0.6.0
qtconsole==4.5.5
QtPy==1.9.0
redis==3.3.11
regex==2019.11.1
requests==2.22.0
requests-oauthlib==1.3.0
rope==0.14.0
rsa==4.0
ruamel-yaml==0.15.46
s3transfer==0.2.1
sacremoses==0.0.35
scikit-image==0.15.0
scikit-learn==0.19.2
scipy==1.4.1
seaborn==0.9.0
SecretStorage==3.1.1
selenium==3.141.0
Send2Trash==1.5.0
sentencepiece==0.1.85
sentry-sdk==0.10.2
seqeval==0.0.12
simplegeneric==0.8.1
singledispatch==3.4.0.3
six==1.12.0
snowballstemmer==2.0.0
sortedcollections==1.1.2
sortedcontainers==2.1.0
soupsieve==1.9.3
Sphinx==2.2.0
sphinxcontrib-applehelp==1.0.1
sphinxcontrib-devhelp==1.0.1
sphinxcontrib-htmlhelp==1.0.2
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.2
sphinxcontrib-serializinghtml==1.1.3
sphinxcontrib-websupport==1.1.2
spyder==3.3.6
spyder-kernels==0.5.2
SQLAlchemy==1.3.9
statsmodels==0.10.1
sympy==1.4
tables==3.5.2
tblib==1.4.0
tensorboard==2.2.2
tensorboard-plugin-wit==1.7.0
tensorboardX==1.9
tensorflow==2.2.0
tensorflow-datasets==2.0.0
tensorflow-estimator==2.2.0
tensorflow-hub==0.8.0
tensorflow-metadata==0.21.1
termcolor==1.1.0
terminado==0.8.2
testpath==0.4.2
toolz==0.10.0
torch==1.3.1
torchtext==0.4.0
torchvision==0.4.2
tornado==6.0.3
tqdm==4.36.1
traitlets==4.3.3
transformers==2.3.0
unicodecsv==0.14.1
urllib3==1.24.2
wcwidth==0.1.7
webencodings==0.5.1
Werkzeug==0.16.0
WeRoBot==1.9.0
widgetsnbextension==3.5.1
wrapt==1.11.2
wurlitzer==1.0.3
xlrd==1.2.0
XlsxWriter==1.2.1
xlwt==1.3.0
xmltodict==0.12.0
zict==1.0.0
zipp==0.6.0