跳转至

Pytorch

导言

(本人是rookie,纯小白~

什么是 PyTorch?

PyTorch 是一个基于 Python 的科学计算包,主要定位两类人群:

  1. NumPy 的替代品,可以利用 GPU 的性能进行计算。
  2. 深度学习研究平台拥有足够的灵活性和速度

Pytorch简介

Pytorch前生Torch

Torch是一个有大量机器学习算法支持的科学计算框架,是一个与Numpy类似的张量(Tensor) 操作库,其特点是特别灵活,但因其采用了小众的编程语言是Lua,所以流行度不高,这也就有了PyTorch的出现。所以其实Torch是 PyTorch的前身,它们的底层语言相同,只是使用了不同的上层包装语言。

PyTorch是一个基于Torch的Python开源机器学习库,用于自然语言处理等应用程序。它主要由Facebookd的人工智能小组开发,不仅能够 实现强大的GPU加速,同时还支持动态神经网络,这一点是现在很多主流框架如TensorFlow都不支持的。 PyTorch提供了两个高级功能:

  • 具有强大的GPU加速的张量计算(如Numpy)
  • 包含自动求导系统的深度神经网络

优势

  • 支持GPU
  • 灵活,支持动态神经网络
    • TensorFlow和Caffe都是命令式的编程语言,而且是静态的,首先必须构建一个神经网络,然后一次又一次使用相同的结构,如果想要改变网络的结构,就必须从头开始。
    • 但是对于PyTorch,通过反向求导技术,可以让你零延迟地任意改变神经网络的行为,而且其实现速度快。正是这一灵活性是PyTorch对比TensorFlow的最大优势。
  • 底层代码易于理解
  • 命令式体验
  • 自定义扩展

劣势

对比TensorFlow,有些处于劣势,目前PyTorch

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

安装和使用

官网 选择对应cuda版本下载即可

from __future__ import print_function
import torch

数据类型和操作

Tensor(张量)

# 构造一个5x3矩阵,不初始化。基本是0,或者+-10^-4之类
x = torch.empty(5, 3)
# 构造一个随机初始化的矩阵:范围[0,1)
x = torch.rand(5, 3)
# 构造一个随机int初始化的矩阵:范围[3,10),大小2*2
torch.randint(3, 10, (2, 2))
tensor([[4, 5],
        [6, 7]])
# 构造一个矩阵全为 0,而且数据类型是 long.
x = torch.zeros(5, 3, dtype=torch.long)
# 直接使用数据 1*2维 
x = torch.tensor([5.5, 3])
# 裁取已有tensor 5*3的元素
x = x.new_ones(5, 3, dtype=torch.double)   
# 已有tensor元素全部随机化
x = torch.randn_like(x, dtype=torch.float) 
# 连接矩阵,不同维度 Concatenates 
>>> x = torch.randn(2, 3)
>>> x
tensor([[ 0.6580, -1.0969, -0.4614],
        [-0.1034, -0.5790,  0.1497]])
>>> torch.cat((x, x, x), 0)
# torch.cat([input]*100)
tensor([[ 0.6580, -1.0969, -0.4614],
        [-0.1034, -0.5790,  0.1497],
        [ 0.6580, -1.0969, -0.4614],
        [-0.1034, -0.5790,  0.1497],
        [ 0.6580, -1.0969, -0.4614],
        [-0.1034, -0.5790,  0.1497]])
# 相同大小对应位置相乘
x = torch.tensor([[5, 6], [1 / 5, 2]])
print(x)
print(torch.prod(x, 0))  # product along 0th axis
tensor([[5.0000, 6.0000],
        [0.2000, 2.0000]])
tensor([ 1., 12.])
# 转置 指定维度transpose() 和 permute()
x.t()   
# 横向纵向复制拓展
>>> x = torch.tensor([[1], [2], [3]])
>>> x.size()
torch.Size([3, 1])
>>> x.expand(3, 4)
tensor([[ 1,  1,  1,  1],
        [ 2,  2,  2,  2],
        [ 3,  3,  3,  3]])
>>> x.expand(-1, 4)   # -1 means not changing the size of that dimension
tensor([[ 1,  1,  1,  1],
        [ 2,  2,  2,  2],
        [ 3,  3,  3,  3]])
# 输出第二列的数据
print(x[:, 1])
# 维度信息 输出是一个元组,所以它支持左右的元组操作。
print(x.size())
# 改变一个 tensor 的大小或者形状
# reshape也行 https://blog.csdn.net/Flag_ing/article/details/109129752
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)  # -1位置的取值是从其他维度推断出来的
print(x.size(), y.size(), z.size()) # torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])
# 加法
z=x+y
z=torch.add(x, y)
y.add_(x)  # adds x to y

注意 任何使张量会发生变化的操作都有一个前缀 '_'。例如: x.copy_(y), x.t_(), 将会改变 x

PyTorch 自动微分

autograd 包是 PyTorch 中所有神经网络的核心。

autograd 软件包为 Tensors 上的所有操作提供自动微分。它是一个由运行定义的框架,这意味着以代码运行方式定义你的后向传播,并且每次迭代都可以不同。

TENSOR

torch.Tensor 是包的核心类。

如果将其属性 .requires_grad 设置为 True,则会开始跟踪针对 tensor 的所有操作。.requires_grad_( ... ) 会改变张量的 requires_grad 标记。输入的标记默认为 False ,如果没有提供相应的参数。

完成计算后,您可以调用 .backward() 来自动计算所有梯度。

该张量的梯度将累积到 .grad 属性中。要停止 tensor 历史记录的跟踪,您可以调用 .detach(),它将其与计算历史记录分离,并防止将来的计算被跟踪。要停止跟踪历史记录(和使用内存),您还可以将代码块使用 with torch.no_grad(): 包装起来。

在评估模型时,这是特别有用,因为模型在训练阶段具有 requires_grad = True 的可训练参数有利于调参,但在评估阶段我们不需要梯度。(???)

另一个重要的类是Function。Tensor 和 Function 互相连接并构建一个非循环图,它保存整个完整的计算过程的历史信息。

每个张量都有一个 .grad_fn 属性保存着创建了张量的 Function 的引用,(如果用户自己创建张量,则g rad_fn 是 None )。

计算导数

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

例子1

import torch
# 创建一个张量,设置 requires_grad=True 来跟踪与它相关的计算
x = torch.ones(2, 2, requires_grad=True)
# 操作张量
y = x + 2
z = y * y * 3
out = z.mean()
# 后向传播,因为输出包含了一个标量,out.backward() 等同于out.backward(torch.tensor(1.))。
out.backward()
# 打印梯度 d(out)/dx
print(x.grad)

# tensor([[4.5000, 4.5000],
#        [4.5000, 4.5000]])

原理: 最终Loss的值,网络结构(部分偏导数),当前训练的值。三者共同决定了梯度。这意味着在Batch使用时,假如将网络复制多遍(包括初始训练参数也一样),对于总的Loss来训练得到的参数是完全相同的。

例子2

y 不再是一个标量。torch.autograd 不能够直接计算整个雅可比,但是如果我们只想要雅可比向量积,只需要简单的传递向量给 backward 作为参数。(??? 雅可比向量积有什么用)

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

print(x.grad)
# tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])

神经网络的训练

定义网络

一个简单的前馈神经网络,它接收输入,让输入一个接着一个的通过一些层,最后给出输出。 通过 torch.nn 包来构建。一个 nn.Module 包括层和一个方法 forward(input) 它会返回输出(output)。

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


class Net(nn.Module):

    def __init__(self):
        # 习惯上,将包含可训练参数的结构,声明在__init__里
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 5x5 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        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)

一个模型可训练的参数可以通过调用 net.parameters() 返回:

params = list(net.parameters())
print(len(params))
print(params[0].size())  # conv1's .weight

运行一次网络

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

反向传播计算各个位置梯度

把所有参数梯度缓存器置零,用随机的梯度来反向传播

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

损失函数

一个损失函数需要一对输入:模型输出和目标,然后计算一个值来评估输出距离目标有多远。

有一些不同的损失函数在 nn 包中。一个简单的损失函数就是 nn.MSELoss ,这计算了均方误差。

可以调用包,也可以自己设计。

output = net(input)
target = torch.randn(10)  # 随便一个目标
target = target.view(1, -1)  # make it the same shape as output
criterion = nn.MSELoss()

loss = criterion(output, target)

使用loss反向传播更新梯度

查看梯度记录的地方

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

当我们调用 loss.backward(),整个图都会微分,而且所有的在图中的requires_grad=True 的张量将会让他们的 grad 张量累计梯度。

为了实现反向传播损失,我们所有需要做的事情仅仅是使用 loss.backward()。你需要清空现存的梯度,要不然将会和现存(上一轮)的梯度累计到一起。

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

查看某处梯度

print(net.conv1.bias.grad)

使用梯度和各种方法优化器更新参数

最简单的更新规则就是随机梯度下降。

weight = weight - learning_rate * gradient

我们可以使用 python 来实现这个规则:

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

上面是一次训练

一般是按照一次多少batch训练,训练10次等.

或者考虑loss 稳定后结束,一般不使用loss小于某个值(因为不知道loss阈值是多少)

或许可以考虑K折交叉检验法(k-fold cross validation)

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

测试单个任务

分类任务,取最高的

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

测试总误差

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

各种初学者问题

In-place 正确性检查

所有的Variable都会记录用在他们身上的 in-place operations。如果pytorch检测到variable在一个Function中已经被保存用来backward,但是之后它又被in-place operations修改。当这种情况发生时,在backward的时候,pytorch就会报错。这种机制保证了,如果你用了in-place operations,但是在backward过程中没有报错,那么梯度的计算就是正确的。

对于不需要自动微分

=不需要计算梯度=手动计算值的

使用 someTensor.detach() 来更新

相关知识

欠拟合和过拟合判断

  1. 训练集和测试集都不好——欠拟合
  2. 训练集好,测试集不好——过拟合

多通道

一般是任务特征很多维度时,拓展描述参数用的。

比如:图像一般包含三个通道/三种原色(红色、绿色和蓝色)。 实际上,图像不是二维张量,而是一个由高度、宽度和颜色组成的三维张量。所以第三维通过通道表示。

https://zh.d2l.ai/chapter_convolutional-neural-networks/channels.html

多通道举例说明

self.conv1 = nn.Conv2d(1, 6, 5) # 输入通道1,输出通道6,卷积核 5*5
\[ 28=32-5+1 \]

初始1通道变6通道,意味着对初始的A数据,有6个初始值不同的5*5卷积核操作,产生6张图。需要参数6*5*5.

初始6通道变16通道,相当于将6通道变1通道,重复16次。6通道变1通道,通过6张图与由6个5*5卷积核组成的卷积核组作用,生成6张图,然后简单相加,变成1张。需要总参数16*6*5*5*5。相当于下图某些数据变成6和16:

BatchSize

https://blog.csdn.net/qq_34886403/article/details/82558399

  1. Batch Size定义:一次训练所选取的样本数。
  2. 由于矩阵操作,增加batch/行号。每行经过同一个网络,引起的就是输出行号增加。只需要对每行单独计算出来的误差进行sum或者mean得到一个误差值,就可以反向传播,训练参数。
  3. 简单来说就是平均了一个batch数据的影响,不会出现离谱的波动,方向比较准确。
  4. Batch Size的大小影响模型的优化程度和速度。同时其直接影响到GPU内存的使用情况,假如你GPU内存不大,该数值最好设置小一点。
  5. 没有Batch Size,梯度准确,只适用于小样本数据库
  6. Batch Size增大,梯度变准确。但是单个epoch的迭代次数减少了,参数的调整也慢了,假如要达到相同的识别精度,需要更多的epoch。
  7. Batch Size再增大,梯度已经非常准确,再增加Batch Size也没有用
  8. 虽然Batch Size增大,一遍的总次数变少,单步计算量增加。但是由于GPU并行操作,单步时间不会增加太多。

BatchNorm

Batch Normalization是将各层的输入进行归一化,使训练过程更快、更稳定的一种技术。在实践中,它是一个额外的层,我们通常添加在计算(卷积)层之后,在非线性(激活函数)之前。也有更先进的,比如layernorm。

BN层只是效果会变好,因为感受到了细节。不是有batch一定有BN层的意思。

各种不同的Loss

交叉熵和加权交叉熵

多用于多分类任务,预测值是每一类各自的概率。label为特定的类别 torch.nn.NLLLOSS通常不被独立当作损失函数,而需要和softmax、log等运算组合当作损失函数。

torch.nn.CrossEntropyLoss相当于softmax + log + nllloss。

预测的概率大于1明显不符合预期,可以使用softmax归一,取log后是交叉熵,取负号是为了符合loss越小,预测概率越大。

# 4类权重是 1, 10, 100, 100 一般是与样本占比成反比
criterion = nn.CrossEntropyLoss(weight=torch.from_numpy(np.array([1,10,100,100])).float() ,reduction='sum')
* size_average(该参数不建议使用,后续版本可能被废弃),该参数指定loss是否在一个Batch内平均,即是否除以N。默认为True * reduce (该参数不建议使用,后续版本可能会废弃),首先说明该参数与size_average冲突,当该参数指定为False时size_average不生效,该参数默认为True。reduce为False时,对batch内的每个样本单独计算loss,loss的返回值Shape为[N],每一个数对应一个样本的loss。reduce为True时,根据size_average决定对N个样本的loss进行求和还是平均,此时返回的loss是一个数。 * reduction 该参数在新版本中是为了取代size_average和reduce参数的。 * 它共有三种选项'mean','sum'和'none'。 * 'mean'为默认情况,表明对N个样本的loss进行求平均之后返回(相当于reduce=True,size_average=True); * 'sum'指对n个样本的loss求和(相当于reduce=True,size_average=False); * 'none'表示直接返回n分样本的loss(相当于reduce=False)

Focal Loss

相对于加权交叉熵不仅权重不需要计算,自动通过概率算,而且gamma=2按照平方缩小了,大样本的影响。

“蓝”线代表交叉熵损失。X轴即“预测为真实标签的概率”(为简单起见,将其称为pt)。举例来说,假设模型预测某物是自行车的概率为0.6,而它确实是自行车, 在这种情况下的pt为0.6。

Y轴是给定pt后Focal loss和CE的loss的值。

从图像中可以看出,当模型预测为真实标签的概率为0.6左右时,交叉熵损失仍在0.5左右。因此,为了在训练过程中减少损失,我们的模型将必须以更高的概率来预测到真实标签。换句话说,交叉熵损失要求模型对自己的预测非常有信心。但这也同样会给模型表现带来负面影响。

深度学习模型会变得过度自信, 因此模型的泛化能力会下降.

当使用γ> 1的Focal Loss可以减少“分类得好的样本”或者说“模型预测正确概率大”的样本的训练损失,而对于“难以分类的示例”,比如预测概率小于0.5的,则不会减小太多损失。因此,在数据类别不平衡的情况下,会让模型的注意力放在稀少的类别上,因为这些类别的样本见过的少,比较难分。

https://cloud.tencent.com/developer/article/1669261

https://blog.csdn.net/qq_34914551/article/details/105393989

https://ptorch.com/news/253.html

Pytorch.nn常用函数

torch.nn.Linear

\[ y=x*A^T+b \]

设置网络中的全连接层的,需要注意在二维图像处理的任务中,全连接层的输入与输出一般都设置为二维张量,形状通常为[batch_size, size],不同于卷积层要求输入输出是四维张量。

in_features指的是输入的二维张量的大小,即输入的[batch_size, size]中的size。

out_features指的是输出的二维张量的大小,即输出的二维张量的形状为[batch_size,output_size],当然,它也代表了该全连接层的神经元个数。

torch.nn.ReLU()

\[ ReLU(x)=(x)^+=max(0,x) \]

torch.nn.Sigmoid

\[ Sigmoid(x)=σ(x)= \frac{1}{1+exp(−x)} \]
  1. torch.nn.Sigmoid()
  2. 是一个类。在定义模型的初始化方法中使用,需要在_init__中定义,然后再使用。
  3. torch.nn.functional.sigmoid():
  4. 可以直接在forward()里使用。eg.A=F.sigmoid(x)

torch.cat

cat是concatnate的意思:拼接,联系在一起。

C = torch.cat( (A,B),0 )  #按维数0拼接(竖着拼)
C = torch.cat( (A,B),1 )  #按维数1拼接(横着拼)

torch.nn.BatchNorm2d

num_features – C from an expected input of size (N, C, H, W)

torch.nn.BatchNorm1d

Input: (N, C) or (N, C, L), where NN is the batch size, C is the number of features or channels, and L is the sequence length

Output: (N, C) or (N, C, L) (same shape as input)

Softmax函数和Sigmoid函数的区别

https://zhuanlan.zhihu.com/p/356976844

保存与读取

Save on GPU, Load on GPU Save:

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

Load:

device = torch.device("cuda")
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.to(device)
# Make sure to call input = input.to(device) on any input tensors that you feed to the model
model.eval()

Remember that you must call model.eval() to set dropout and batch normalization layers to evaluation mode before running inference. Failing to do this will yield inconsistent inference results.

误差的表示

训练参数怎么保存和读取

怎么表示数据

怎么反向梯度法训练

怎么使用GPU,怎么多GPU

在GPU上训练 就像你怎么把一个张量转移到GPU上一样,你要将神经网络转到GPU上。 如果CUDA可以用,让我们首先定义下我们的设备为第一个可见的cuda设备。

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

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

print(device) # cuda:0
net=Net()
net.to(device)
outputs = net(inputs)
input = torch.randn(1, 1, 32, 32)
inputs, labels = inputs.to(device), labels.to(device)
out = net(input)

多GPU

如果你想要来看到大规模加速,使用你的所有GPU,请查看:数据并行性(https://pytorch.org/tutorials/beginner/blitz/data_parallel_tutorial.html)。PyTorch 60 分钟入门教程:数据并行处理

http://pytorchchina.com/2018/12/11/optional-data-parallelism/

可视化

网络结构可视化

自动 https://stackoverflow.com/questions/52468956/how-do-i-visualize-a-net-in-pytorch

或者手动drawio

误差实时可视化TensorBoard

https://www.cnblogs.com/sddai/p/14516691.html

原理: 通过读取保存的log文件来可视化数据

标量可视化

记录数据,默认在当前目录下一个名为'runs/'的文件夹中。

from torch.utils.tensorboard import SummaryWriter

# 写log的东西
log_writer = SummaryWriter('./path/to/log')
# 第一个参数是名称,第二个参数是y值,第三个参数是x值。
log_writer.add_scalar('Loss/train', float(loss), epoch)

运行 tensorboard --logdir=runs/ --port 8123 在某端口打开,比如 https://127.0.0.1:6006

网络结构可视化

在tensorboard的基础上使用tensorboardX

from tensorboardX import SummaryWriter

with SummaryWriter(comment='LeNet') as w:
    w.add_graph(net, (net_input, ))

PR曲线

什么是PR曲线

log_writer.add_pr_curve("pr_curve", label_batch, predict, epoch)

x,y轴分别是recall和precision。应该有可能有矛盾的数据,或者网络分不开,对于不同的阈值,可以划分出PR图。

与ROC曲线左上凸不同的是,PR曲线是右上凸效果越好。

怎么分布式并行

需要进一步的研究学习

暂无

遇到的问题

  1. 矩阵或者向量的使用
  2. optimizer.step() # Does the update会自动循环吗?什么误差什么时候训练完毕呢?

开题缘由、总结、反思、吐槽~~

社会计算实验二,关于Meetup数据的预测性问题的解决

参考文献

https://pytorch-cn.readthedocs.io/zh/latest/

https://www.pytorch123.com/

https://zh.d2l.ai/chapter_convolutional-neural-networks/channels.html

Exploring the Impact of Dynamic Mutual Influence on Social Event Participation