当前位置:   article > 正文

Pytorch 学习

pytorch

一、简介

PyTorch 是一个基于 Torch 的 Python 开源机器学习库(Python+Torch(深度学习框架)),由Facebook的人工智能研究小组开发。
Pytorch类似于Numpy,可以使用GPU,运行在CUDA上;内置动态图,可以定义深度学习模型,可灵活的进行训练和应用。

(1)PyTorch 基于Python的科学计算包,服务于以下两种场景:

● 作为NumPy的替代品,可以使用GPU的强大计算能力
● 提供最大的灵活性和高速的深度学习研究平台

(2)PyTorch 是一个 Python 包,提供两个高级功能:

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

二、pytorch 的基石——Tensor 张量

Tensors 与 Numpy 中的 ndarrays 类似,但是在 PyTorch中 Tensors 可以使用 GPU 进行计算。

要介绍 Tensor 这个数据类型,我觉得有必要扯一下数学。我们都知道:标量(Scalar)是只有大小,没有方向的量,如1,2,3等;向量(Vector)是有大小和方向的量,其实就是一串数字,如(1,2);矩阵(Matrix)是好几个向量拍成一排合并而成的一堆数字,如 [1,2;3,4]。

标量,向量,矩阵它们三个都是张量,标量是零维的张量,向量是一维的张量,矩阵是二维的张量。除此之外,张量还可以是四维的、五维的等等。
在这里插入图片描述
张量就是按照任意维排列的一堆数字的推广。如图所示,矩阵不过是三维张量下的一个二维切面。要找到三维张量下的一个标量,需要三个维度的坐标来定位。

1、创建 tensor

可以通过 torch.tensor() 将普通数组转化为 tensor,torch.empty() 或r and() 可以创建随机 tensor,torch.zeros()、ones() 创建数据都是 0 或 1 的 tensor。

import torch x = torch.tensor([5.5, 3])  # 创建一维张量 
print(x) 
x = torch.rand(5, 3)  # 随机生成5*3的矩阵 print(x)
  • 1
  • 2
  • 3

在这里插入图片描述

2、tensor 运算

(1) 算术操作

result = torch.empty(5, 3) 
x = torch.rand(5, 3) 
y = torch.rand(5, 3) 
torch.add(x, y, out=result) 
print(x) 
print(y)
print(result) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述

(2)索引

我们还可以使⽤类似NumPy的索引操作来访问 Tensor 的一部分,需要注意的是:索引出来的结果与原数据共享内存,也即修改⼀个,另⼀个会跟着修改

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

y = x[0,:] 
y += 1 
print(y) 
print(x) 
print(x[0,:]) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在这里插入图片描述

(3)改变形状

注意 view() 返回的新 tensor 与源 tensor 共享内存,也即更改其中的⼀个,另外⼀个也会跟着改变。(顾名思义,view仅是改变了对这个张量的观察角度)

x = torch.rand(5, 3) 
print(x) 
z = x.view(-1,5) 
print(z)
print(x) 
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

3、张量和 Numpy 的相互转换

张量和Numpy数组之间的转换十分容易。

(1)Tensor 到 Numpy,在使用 Cpu 的情况下,张量和 array 将共享他们的物理位置,改变其中一个的值,另一个也会随之变化。

a = torch.ones(5)
print(a)
"""
tensor([1., 1., 1., 1., 1.])
"""
b = a.numpy()
print(b)
"""
[1. 1. 1. 1. 1.]
"""
a.add_(1)
print(a)
print(b)
"""
tensor([2., 2., 2., 2., 2.])
[2. 2. 2. 2. 2.]
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

(2)Numpy 到 Tensor ,在使用 Cpu 的情况下,张量和 array 将共享他们的物理位置,改变其中一个的值,另一个也会随之变化。

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)
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

(3)Gpu下的转换

# let us run this cell only if CUDA is available
# We will use ``torch.device`` objects to move tensors in and out of GPU
if torch.cuda.is_available():
    device = torch.device("cuda")          // a CUDA device object
    y = torch.ones_like(x, device=device)  //  直接在GPU上创建tensor
    x = x.to(device)                       // 或者使用.to("cuda"),将tensor移入GPU
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       // 将tensor移出GPU,也能在移动时改变dtype
"""
tensor([-0.4743], device='cuda:0')
tensor([-0.4743], dtype=torch.float64)
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

4、张量的压缩与扩充

(1)张量压缩 squeeze()

squeeze()函数就是对张量的维度进行减少的操作。 首先得到一个维度为(1,2,3)的tensor(张量)
在这里插入图片描述
使用squeeze()函数将第一维去掉
在这里插入图片描述 如果
在这里插入图片描述
可以看出维度并没有变化,仍然为(1,2,3),这是因为只有维度为1时才会去掉该维度

(2)张量扩充 unsqueeze()

首先初始化一个维度为(2,3)的 a
在这里插入图片描述
在第二维增加一个维度,使其维度变为(2,1,3)
在这里插入图片描述

另外,squeeze()函数和unsqueeze()函数还有另一种写法,直接用张量类型的变量来调用这两个函数:

c = a.unsqueeze(0)
  • 1

三、自动求导机制

在pytorch中,神经网络的核心是自动微分。自动微分包会提供自动微分的操作,它是一个取决于每一轮的运行的库,你的下一次的结果会和你上一轮运行的代码有关,因此,每一轮的结果,有可能都不一样。

PyTorch 中,所有神经网络的核心是 autograd 包,它为张量上的所有操作提供了自动求导机制。Pytorch 通过 Tensor 来储存数据,我们可以把它看作一个节点,通过 Function 实现数据操作,即节点之间转换的路径。

autograd 包为张量上的所有操作提供了自动求导。

它是一个在运行时定义的框架,这意味着反向传播是根据你的代码来确定如何运行,并且每次迭代可以是不同的。

torch.Tensor 是这个包的核心类。如果设置.requires_grad 为 True,那么将会追踪所有对于该张量的操作

当完成计算后通过调用 .backward(),自动计算所有的梯度,这个张量的所有梯度将会自动积累到 .grad 属性
这里是引用

在自动梯度计算中还有另外一个重要的类 Function。Tensor 和 Function 互相连接并生成一个非循环图,它表示和存储了完整的计算历史。

每个张量都有一个 .grad_fn 属性,这个属性引用了一个创建了 Tensor 的 Function(除非这个张量是用户手动创建的,即这个张量的 grad_fn 是 None)。

import torch
x=torch.ones(2,2,requires_grad=True)
print(x) 
print(x.grad_fn) 
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述
由于x是被直接创建的,也就是说它是一个叶子节点,所以它的grad_fn属性的值为None

如果需要计算导数,你可以在 Tensor 上调用.backward()获取求得的导数用 .grad 方法

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

1、标量对张量的求导

原则上,pytorch 不支持张量对张量的求导,它只支持标量对张量的求导。

先看标量对张量求导的情况:

如下所示为一个训练过程,输入张量x,经过一系列操作之后得到输出y。
在这里插入图片描述
如下所示为实现上面过程的代码,首先通过 tensor 定义张量,通过属性 requires_grad 指定是否需要自动求导。之后执行正向操作 *w1、+b、*w2,最后求平均得到输出 y。接着通过 backward() 执行反向传播,就可以得到张量的 grad 值了。

// 定义张量
x = torch.ones(5, requires_grad=True)
w1 = torch.tensor(2.0, requires_grad=True)
w2 = torch.tensor(3.0, requires_grad=True)
b = torch.tensor(4.0, requires_grad=False)

// 执行正向操作
l1 = x * w1
l2 = l1 + b
l3 = l2 * w2
y = l3.mean()

// 反向传播
y.backward()

print(l1.data, l1.grad, l1.grad_fn)
// tensor([2., 2., 2., 2., 2.]) None <MulBackward0 object at 0x0000024D8E921BE0>
print(l2.data, l2.grad, l2.grad_fn)
// tensor([6., 6., 6., 6., 6.]) None <AddBackward0 object at 0x000001B960FC0F98>
print(y)
// tensor(18., grad_fn=<MeanBackward0>)
print(w1.grad, w2.grad)
// tensor(3.) tensor(6.)
print(x.grad)
// tensor([1.2000, 1.2000, 1.2000, 1.2000, 1.2000])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

从上面的输出可以看到 x 一开始为 [1,1,1,1,1],乘以 w1 后为 [2., 2., 2., 2., 2.],+b 之后为 [6., 6., 6., 6., 6.],再乘以 3 为 [18., 18., 18., 18., 18.],求均值得到 y 为 18。

在这里插入图片描述

Tensor 的 grad_fn 属性用于记录上一步是经过怎样的操作得到自己的,用于反向传播时进行求导。例如 l1.grad_fn 为 MulBackward0 代表其反向传播操作为乘法。

反向传播后通过 grade 属性可以获得最后输出对本张量的导数值
例如上面的操作中输出为 y,反向传播后,w2.grad 获得的就是 dy/dw2。由链式求导法则可得 dy/dw2=dy/dl3 × dl3/dw2。由于 y 由 l3 求均值得到,即 y=1/5(Σl3),所以 dy/dl3=1/5,又 l3=l2 * w2,所以 dl3/dw2=l2,所以 dy/dw2=1/5*l2=1/5(6., 6., 6., 6., 6.)=6。同理通过反向链式求导得到 w1、x 的 grad。

注意到 l1、l2 的 grade 值为 None,这是由于张量 l1、l2、l3 都是中间计算结果,它们被称为非叶张量,相对地由用户创建的 x、w1、w2 被称为叶张量(leaf tensor)。pytorch为了节约内存并不会保存中间张量的导数值,只会用grad_fn来记录是通过什么操作产生的。如果我们希望查看的话,可以通过retain_grad()来保存非叶张量的导数值

在这里插入图片描述

2、张量对张量求导的求导

默认情况下 pytorch 不允许张量对张量求导,所以在使用张量对张量求导的时候,必须要传入一个与被求导张量同形的张量,然后pytorch根据传入的张量与被求导张量作加权求和将其转化为标量
张量的梯度是一个与原张量同形的张量

首先创建一个叶子节点 x

x = torch.tensor([[1.0,2.0],[3.0,4.0]],requires_grad=True)
print(x)

// tensor([[1., 2.], [3., 4.]], requires_grad=True)
  • 1
  • 2
  • 3
  • 4

接下来计算 y=3*x

y = 3*x
print(y)

// tensor([[ 3., 6.], [ 9., 12.]], grad_fn=<MulBackward0>)
  • 1
  • 2
  • 3
  • 4

接下来我们直接用 y 求导 y.backward()。毫无意外,直接报错,这就印证了前面说过的pytorch不支持张量对张量直接求导。
我们必须构建一个与 y 同形的张量 z,把 z 作为 y.backward() 的参数求 y 对 x 的导

z = torch.tensor([[1.0,0.1],[0.01,0.001]],dtype=torch.float)
y.backward(z)
print(x.grad)
  • 1
  • 2
  • 3

输出结果:tensor([[3.0000, 0.3000], [0.0300, 0.0030]]) (张量的梯度是一个与原张量同形的张量)

对于表达式 y.backward(z) (y、z为同形张量)的计算过程,实际上将 y 与 z 加权求和得到标量 m,然后用 m 对 x 求导得到结果,也就是说实际上有这样一步计算 m=torch.sum(y*z)

四、神经网络包 nn 和优化器 optm

使用 torch.nn 包来构建神经网络。nn 包依赖 autograd 包来定义模型并求导。 一个 nn.Module 包含各个层和一个 forward(input) 方法,该方法返回 output。

1、nn.Module(模组)

在 Pytorch 中编写神经网络,所有的层结构和损失结构都来自 torch.nn , 所有模型构建都是从基类 nn.Module 继承的。

2、torch.optim(优化)

(1)一阶优化算法:使用各个参数的梯度值来更新参数,最常用的是梯度下降
在这里插入图片描述
(2)二阶优化算法:使用二阶导数 ( Hessian方法 ) 来最大化或者最小化代价函数,主要基于牛顿法。下面举一个例子
在这里插入图片描述
这样,我们就得到了学习速率为0.01,动量是0.9的随机梯度下降,在优化之前 要将梯度归零,optimizer.zeros(),然后通过
loss.backward() 反向传播,自动求导得到每个参数的梯度,然后通过 optimizer.step() 进行参数的更新。

3、模型的保存和加载

在 Pytorch 中使用 torch.save 来保存模型的结构和参数,有两种方式:

(1)保存整个模型的结构信息和参数信息, 保存的对象是模型 model
(2)保存模型的参数, 保存的对象是模型的状态 model.state_dict()
save 的第一个参数是保存的对象, 第二个是保存的路径

加载模型对应两种保存方式也有两种

(1) 加载完整的模型结构和参数信息,使用 load_model = torch.load(‘model.pth’) 在网络较大的时候记载时间教程,存储空间较大
(2)加载模型参数信息,需要先导入模型的结构,然后通过 model.load_state_dict(torch.load(‘model_state.pth’)) 来导入。

4、神经网络的典型训练过程

● 定义包含一些可学习的参数(或者叫权重)神经网络模型;
● 在数据集上迭代;
● 通过神经网络处理输入;
● 计算损失(输出结果和正确值的差值大小);
● 将梯度反向传播回网络的参数;
● 更新网络的参数,主要使用如下简单的更新原则: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, 5x5 square convolution
        // kernel
        // 定义了两个卷积层
        // 第一层是输入1维的(说明是单通道,灰色的图片)图片,输出6维的的卷积层(说明用到了6个卷积核,而每个卷积核是5*5的)。
        self.conv1 = nn.Conv2d(1, 6, 5)
        // 第一层是输入1维的(说明是单通道,灰色的图片)图片,输出6维的的卷积层(说明用到了6个卷积核,而每个卷积核是5*5的)。
        self.conv2 = nn.Conv2d(6, 16, 5)
        // an affine operation: y = Wx + b
        // 定义了三个全连接层,即 fc1 与 conv2 相连,将 16 张 5*5 的卷积网络一维化,并输出 120 个节点。
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        // 将 120 个节点转化为 84 个。
        self.fc2 = nn.Linear(120, 84)
        // 将 84 个节点输出为 10 个,即有 10 个分类结果。
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        // Max pooling over a (2, 2) window
        // 用 relu 激活函数作为一个池化层,池化的窗口大小是 2*2,这个也与上文的 16*5*5 的计算结果相符(一开始我没弄懂为什么 fc1 的输入点数是 16*5*5 ,后来发现,这个例子是建立在 lenet5 上的)。
        // 这句整体的意思是,先用 conv1 卷积,然后激活,激活的窗口是 2*2 。
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        // If the size is a square you can only specify a single number
        // 作用同上,然后有个需要注意的地方是在窗口是正方形的时候,2的写法等同于(2,2)。
        // 这句整体的意思是,先用 conv2 卷积,然后激活,激活的窗口是 2*2。
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        // 这句整体的意思是,调用下面的定义好的查看特征数量的函数,将我们高维的向量转化为一维。
        x = x.view(-1, self.num_flat_features(x))
        // 用一下全连接层fc1,然后做一个激活。
        x = F.relu(self.fc1(x))
        // 用一下全连接层fc2,然后做一个激活。
        x = F.relu(self.fc2(x))
        // 用一下全连接层fc3。
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        // 承接上文的引用,这里需要注意的是,由于pytorch只接受图片集的输入方式(原文的单词是batch),所以第一个代表个数的维度被忽略。
        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(
  (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)
)
"""

// 现在我们已经构建好模型了,但是还没有开始用bp呢,如果你对前面的内容有一些印象的话,你就会想起来不需要我们自己去搭建,我们只需要用某一个属性就可以了,autograd。

// 现在我们需要来看一看我们的模型,下列语句可以帮助你看一下这个模型的一些具体情况。

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

"""
10
torch.Size([6, 1, 5, 5])
"""

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

"""
tensor([[ 0.0114,  0.0476, -0.0647,  0.0381,  0.0088, -0.1024, -0.0354,  0.0220,
         -0.0471,  0.0586]], grad_fn=<AddmmBackward>)
"""

//最后让我们清空缓存,准备下一阶段的任务。
net.zero_grad()
out.backward(torch.randn(1, 10))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92

(2)损失函数

选用nn.MSELoss来计算误差

# 这个框架是来弄明白我们现在做了什么,这个网络张什么样子。
"""
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> view -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss
"""
# 到目前为止我们学习了Tensor(张量),autograd.Function(自动微分),Parameter(参数),Module(如何定义,各个层的结构,传播过程)
# 现在我们还要学习损失函数和更新权值。

# 这一部分是来搞定损失函数
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)

# 看一看我们的各个点的结果。
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

"""
<MseLossBackward object at 0x7efbcad51a58>
<AddmmBackward object at 0x7efbcad51b38>
<AccumulateGrad object at 0x7efbcad51b38>
"""

# 重点来了,反向传播计算梯度。
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)

"""
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([ 0.0087, -0.0073,  0.0013,  0.0006, -0.0107, -0.0042])
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

(3)更新权值

在反向传播计算完所有参数的梯度后,还需要使用优化方法来更新网络的权重和参数,权值更新采用的方法是随机梯度下降法(SGD, Stochastic Gradient Descent )。
在这里插入图片描述

import torch.optim as optim

# 新建一个优化器,SGD只需要要调整的参数和学习率
optimizer = optim.SGD(net.parameters(), lr=0.01)

# 先梯度清零(与net.zero_grad()效果一样)
optimizer.zero_grad()   
output = net(input)
loss = criterion(output, target)
loss.backward()

#更新参数
optimizer.step()    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

以上步骤我们实现了网络的数据完整传播步骤。

五、pytorch 中的批训练(batch)

用 pytorch 进行批训练其实很简单,只要把数据放入 DataLoader(可以把它看成一个收纳柜,它会帮你整理好)。

1、batch_size

代表每次从所有数据中取出一小筐子数据进行训练,类似挑十框石头,每次挑一筐,此 batch_size=1。这个参数是由于深度学习中尝使用SGD(随机梯度下降)产生。batch_size 的大小取值和 GPU 内存会有关系,数值越大一次性载入数据越多,占用的 GPU 内存越多。 适当增加 batch_size
能够增加训练速度和训练精度(因为梯度下降时震动较小),过小会导致模型收敛困难。

loader = Data.DataLoader( 	dataset = dataset,
    batch_size = batch_size, # 每批次多少图像
    shuffle = True#是否打乱数据,默认为False
    num_workers = 2,  # 用多线程读数据,默认0表示不使用多线程 ) 
  • 1
  • 2
  • 3
  • 4

2、batch_idx(iteration )

代表要进行多少次 batch_size 的迭代,十框石头一次挑一框,batch_idx 即为10。而为什么会介绍这个参数呢? 是因为在深度学习训练中,需要print出一个个阶段的精度/loss,而这个参数就是用来作为pirnt精度/loss的 index。实际上,这是一个代号参数,也可以写成iteration 、step、i或者其他自己能够清晰理解的名字。
存在等式:图像总数量 = batch_size * batch_idx。一般的,图像总数量是已知的,batch_size 是程序员自行设计的,而 batch_idx 是根据等式得出的。

for batch_idx,(inputs,labels) in enumerate(train_loader,0):
# 从0开始迭代
    print(batch_idx) 
  • 1
  • 2
  • 3

使用enumerate函数对已经载入的数据进行迭代,这是一个能够迭代式地从数据装载器中拿出数据的函数,看看能够迭代多少次。

3、epoch

把所有的训练数据全部迭代遍历一遍(单次epoch),在上面的例子是把 train_loader 的50000个数据遍历一遍,举例的话是将十框石头全部搬一遍称为一个epoch。深度学习中常常训练十个甚至百个epoch,这是根据网络最终是否收敛决定的。

六、数据的加载与预处理

要想有一个好的模型你必须有一些好的数据,并将他们转化为模型可以理解的语言,这个工作非常重要。
  
当我们需要处理图像,文本,音频或者视频数据的时候,你可以使用标准的 python 库来将这些数据就转化为 numpy array,然后再转化为 Tensor。下面列出一些相应的python库:
在这里插入图片描述
特别是对于视觉领域,我们写了一个叫做 torchvision 的包,他可以将很多知名数据的数据即涵盖在内。并且,通过 torchvision.datasets 和 torch.utils.data.DataLoader 进行数据的转化。在本里中我们将会使用 CIFAR10 数据集,它有以下各类: ‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。

PyTorch 通过 torch.utils.data 对一般常用的数据加载进行了封装,可以很容易地实现多线程数据预读和批量加载。

torchvision 是 PyTorch 中专门用来处理图像的库,torchvision 已经预先实现了常用图像数据集,包括前面使用过的 CIFAR-10,ImageNet、COCO、MNIST、LSUN 等数据集,可通过 torchvision.datasets 方便的调用。

1、Dataset

(1)Torchvision.datasets

Torchvision.datasets 可以理解为 PyTorch 团队自定义的 dataset, 这些 dataset 帮我们提前处理好了很多的图片数据集,我们拿来就可以直接使用:
在这里插入图片描述
(2)自定义 Dataset

Dataset 是一个抽象类,为了能够方便的读取,需要将要使用的数据包装为Dataset类。
自定义的 Dataset 需要继承它并且实现两个成员方法:
在这里插入图片描述
定义好数据集类之后使用 dataloader 加载数据。

2、torchvision.models

Torchvision 不仅提供了常用图片数据集,还提供了训练好的模型,可以加载之后,直接使用,或者在进行迁移学习 torchvision.models 模块的子模块中包含以下子模型结构。
在这里插入图片描述
在这里插入图片描述

3、torchvision.transforms

transforms 模块提供了一般的图像转换操作类,用作数据处理和数据增强。

data_transforms = transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(1)transforms.Compose(transforms) 方法是将多种变换组合在一起。
上述对data_transforms进行了四种变换,前两个是对PILImage进行的,分别对其进行随机大小和随机宽高比的裁剪,之后resize到指定大小224,以及对原始图像进行随机的水平翻转;第三个transforms.ToTensor() 将PILImage转变为torch.FloatTensor的数据形式;最后一个Normalize则是对tensor进行的。

(2)多种组合变换有一定的先后顺序,处理 PILImage 的变换方法(大多数方法)都需要放在 ToTensor 方法之前,而处理tensor的方法(比如Normalize方法)就要放在ToTensor方法之后。

(1)ransforms处理方法,总结有四大类:

在这里插入图片描述
在这里插入图片描述

(2)transforms中的常见函数

 torchvision.transforms.Normalize(mean, std) 
  • 1

用给定的均值和标准差分别对每个通道的数据进行正则化。具体来说,给定均值(M1,…,Mn),给定标准差(S1,…,Sn),其中n是通道数(一般是3),对每个通道进行如下操作: output[channel] = (input[channel] - mean[channel]) / std[channel]

比如:原来的tensor是三个维度的,值在[0,1]之间,经过变换之后就到了[-1,1], 计算如下:((0,1)-0.5)/0.5=(-1,1)

torchvision.transforms.ToTensor
  • 1

转换为tensor格式,这个格式可以直接输入进神经网络了
把一个取值范围是[0,255]的PIL.Image或者shape为(H,W,C)的numpy.ndarray,转换成形状为[C,H,W],取值范围是[0,1.0]的torch.FloadTensor

torchvision.transforms.ToPILImage
  • 1

将shape为(C,H,W)的Tensor或shape为(H,W,C)的numpy.ndarray转换成PIL.Image,值不变。

torchvision.transforms.CenterCrop(size)
  • 1

将给定的PIL.Image进行中心切割,得到给定的size,size可以是tuple,(target_height, target_width)。size也可以是一个Integer,在这种情况下,切出来的图片的形状是正方形。

torchvision.transforms.RandomCrop(size, padding=0)
  • 1

切割中心点的位置随机选取。size可以是tuple也可以是Integer。

torchvision.transforms.RandomHorizontalFlip
  • 1

随机水平翻转给定的PIL.Image,概率为0.5。即:一半的概率翻转,一半的概率不翻转。

torchvision.transforms.Resize(size) 
  • 1

按照比例把图像最小的一个边长放缩到size,另一边按照相同比例放缩。

torchvision.transforms.RandomSizedCrop(size, interpolation=2)
  • 1

把图像按照中心随机切割成size正方形大小的图片。
先将给定的PIL.Image随机切,然后再resize成给定的size大小。

torchvision.transforms.Pad(padding, fill=0) 
  • 1

将给定的PIL.Image的所有边用给定的pad value填充。 padding:要填充多少像素 fill:用什么值填充

4、Batch Normalization(批量归一化)

通常来说,数据标准化预处理对于浅层模型就足够有效了:处理后的任意一个特征在数据集中所有样本上的均值为0、标准差为1。标准化处理输入数据使各个特征的分布相近:这往往更容易训练出有效的模型。但对深层神经网络来说,即使输入数据已做标准化,训练中模型参数的更新依然很容易造成靠近输出层输出的剧烈变化。这种计算数值的不稳定性通常令我们难以训练出有效的深度模型。

批量归一化利用小批量上的均值和标准差,不断调整神经网络中间输出,从而使整个神经网络在各层的中间输出的数值更稳定

(1)对全连接层做归一化

批量归一化层置于全连接层中的仿射变换和激活函数之间。设全连接层的输入为 u,权重参数和偏差参数分别为 W 和 b ,激活函数为 ϕ。设批量归一化的运算符为 B 。那么,使用批量归一化的全连接层的输出为:
在这里插入图片描述

(2)对卷积层做批量归一化

对卷积层来说,批量归一化发生在卷积计算之后、应用激活函数之前。如果卷积计算输出多个通道,我们需要对这些通道的输出分别做批量归一化,且每个通道都拥有独立的拉伸和偏移参数,并均为标量。设小批量中有 m 个样本。在单个通道上,假设卷积计算输出的高和宽分别为 p 和 q。我们需要对该通道中 m × p × q 个元素同时做批量归一化。对这些元素做标准化计算时,我们使用相同的均值和方差,即该通道中 m × p × q 个元素的均值和方差。

(3)预测时的批量归一化

将训练好的模型用于预测时,我们希望模型对于任意输入都有确定的输出。因此,单个样本的输出不应取决于批量归一化所需要的随机小批量中的均值和方差。一种常用的方法是通过移动平均估算整个训练数据集的样本均值和方差,并在预测时使用它们得到确定的输出。

BN 的优点

缓解梯度消失,加速网络收敛速度。BN层可以让激活函数(非线性变化函数)的输入数据 落入比较敏感的区域,缓解了梯度消失问题。

简化调参的负担,网络更稳定。在调参时,学习率调得过大容易出现震荡与不收敛,BN层则抑制了参数微小变化随网络加深而被放大的问题,因此对于参数变化的适应能力更强,更容易调参。

防止过拟合。BN层将每一个batch的均值与方差引入到网络中,由于每个batch的这两个值都不相同,可看做为训练过程增加了随机噪音,可以起到一定的正则效果,防止过拟合

在测试时应该注意的问题:

在测试时,由于是对单个样本进行测试,没有 batch 的均值与方差,通常做法是在训练时将每一个 batch 的均值与方差都保留下来,在测试时使用所有训练样本均值与方差的平均值

PyTorch 的 BatchNorm

Pytorch 中 nn 模块定义的 BatchNorm1d 和 BatchNorm2d 类分别用于全连接层和卷积层,都需要指定输入的 num_features 参数值。

 torch.nn.BatchNorm1d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) 
  • 1

在这里插入图片描述

torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, 
						track_running_stats=True, device=None, dtype=None)
  • 1
  • 2

在这里插入图片描述

5、处理数据和训练模型的技巧

数据预处理

(1)中心化
每个特征维度都减去相应的均值来实现中心化,这样可以使数据变成0均值。
(2)标准化
在数据都变成0均值后,采用标准化的做法是数据不同的特征维度都有相同的规模。有两种常用的方法。一种是除以数据标准差,是数据的分布接近标准高斯分布。二是让每个维度特征的最大值和最小值按比例缩放到-1~1之间。
(3)PCA
在用PCA处理数据前,通常将数据中心化,并计算数据的协方差矩阵。该协方差矩阵是对称半正定的,可以通过矩阵来进行奇异值分解(SVD), 然后对数据进行去相关性,再将其投影到一个特征空间,选取较大的、主要的向量来降低数据的维度,这种方法叫做PCA,主成分分析
(4)白噪声
与PCA相似,首先将数据投影到一个特征空间,然后每个维度除以特征值来标准化这些数据。直观上就是将一个多元高斯分布转化到了一个0均值、协方差为1的多元高斯分布。白噪声的处理增强数据中的噪声,因为其增强了数据中的所有维度。
在实际处理中,中心化和标准化都特别重要,但是白噪声和PCA在卷积神经网络中几乎不使用,因为网络可以自动学些如何提取这些特征。

权重初始化

(1)全0初始化
直观但是不应采取的策略就是全0初始化。在神经网络中如果每个权重都被初始化为相同的值,那个每个神经元都会计算出相同的结果,在反向传播时计算出相同的梯度,最后权重都会有相同的更新。
(2)随机初始化
我们希望权重初始化时能够尽量靠近0,但不都是0。一般随机化策略有高斯随机化和均匀随机化,值得注意的是并不是越小的随机化产生的结果越好。
(3)稀疏初始化
将权重全部初始化为0,然后挑选一些参数赋随机值,这种方法在实际中使用较少。
(4)初始化偏置
对于偏置(bias),通常初始化为0。
(5)批标准化(Batch Normalization)
核心思想是标准化这个过程是可微的,批标准化通常应用在全连接层后面,非线性层前面。批标准化还可以理解为在网络中的每一层前面都会做数据的预处理。

防止过拟合

(1)正则化
L2正则化是正则化中比较常用的手段,其思想是对权重过大的部分进行惩罚,也就是直接在损失函数中加入权重的二范数量级。
(2)Dropout
核心思想是在训练网络的时候依概率P保留每个神经元,也就是每次训练的时候有些神经元会被置为0。

七、Pytorch 建立卷积神经网络的 API

图像在 Pytorch 中的 tensor 表示

图像是一个由像素组成的二维矩阵,每个元素是一个RGB值(R, G, B)。
图像的展现形式是一个三维数组,维度分别是高度,宽度和像素RGB值。

在 Pytorch 的处理过程的输入输出中图像的表示方法为四维,分别是N H W C: [batch, height, width, channels]。

N 表示这批图像有几张,H 表示图像在竖直方向有多少像素,W 表示水平方向像素数,C 表示通道数(例如黑白图像的通道数 C = 1,而 RGB 彩色图像的通道数 C = 3)

data_format 默认值为 “NHWC”,也可以手动设置为 “NCHW”。这个参数规定了 input Tensor 和 output Tensor 的排列方式

tf.nn.conv2d(
    input,
    filter,
    strides,
    padding,
    use_cudnn_on_gpu=True,
    data_format='NHWC',
    dilations=[1, 1, 1, 1],
    name=None
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

1、Parameters

class torch.nn.Parameter()
  • 1

Variable 的一种,常被用于模块参数(module parameter)。
Parameters 是 Variable 的子类。Paramenters 和Modules 一起使用的时候会有一些特殊的属性,即:当 Paramenters 赋值给 Module 的属性的时候,他会自动的被加到 Module 的参数列表中(即:会出现在 parameters() 迭代器中)。

2、Containers(容器)

class torch.nn.Module 
  • 1

所有网络的基类。你的模型也应该继承这个类。Modules
也可以包含其它 Modules,允许使用树结构嵌入他们。你可以将子模块赋值给模型属性。

class torch.nn.Sequential(* args)
  • 1

一个时序容器。Modules 会以他们传入的顺序被添加到容器中。当然,也可以传入一个OrderedDict。
例如

model = nn.Sequential(
          nn.Conv2d(1,20,5),
          nn.ReLU(),
          nn.Conv2d(20,64,5),
          nn.ReLU()
        )
# Example of using Sequential with OrderedDict 
model = nn.Sequential(OrderedDict([
          ('conv1', nn.Conv2d(1,20,5)),
          ('relu1', nn.ReLU()),
          ('conv2', nn.Conv2d(20,64,5)),
          ('relu2', nn.ReLU())
        ])) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
 cpu(device_id=None)
  • 1

将所有的模型参数(parameters)和 buffers 复制到 CPU。

cuda(device_id=None)
  • 1

将所有的模型参数(parameters)和 buffers 赋值 GPU

parameters(memo=None)
  • 1

返回一个 包含模型所有参数的迭代器。一般用来当作optimizer的参数。

zero_grad()
  • 1

将module中的所有模型参数的梯度设置为0.

3、卷积层

 class torch.nn.Conv1d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True) 
  • 1

一维卷积层,输入的尺度是(N, C_in,L),输出尺度( N,C_out,L_out)。
在这里插入图片描述
在这里插入图片描述

class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True) 
  • 1

二维卷积层, 输入的尺度是(N, C_in,H,W),输出尺度(N,C_out,H_out,W_out)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class torch.nn.Conv3d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
  • 1

三维卷积层, 输入的尺度是(N, C_in,D,H,W),输出尺度(N,C_out,D_out,H_out,W_out)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、池化层

class torch.nn.MaxPool1d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
  • 1

对于输入信号的输入通道,提供1维最大池化(max pooling)操作。 输入大小是(N,C,L),输出大小是(N,C,L_out)。
在这里插入图片描述

 class torch.nn.MaxPool2d(kernel_size, stride=None,padding=0, dilation=1, return_indices=False, ceil_mode=False)
  • 1

对于输入信号的输入通道,提供2维最大池化(max pooling)操作
输入大小是(N,C,H,W),输出大小是(N,C,H_out,W_out),池化窗口大小(kH,kW)
在这里插入图片描述

 class torch.nn.MaxPool3d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False) 
  • 1

对于输入信号的输入通道,提供3维最大池化(max pooling)操作
输入大小是(N,C,D,H,W),输出大小是(N,C,D,H_out,W_out),池化窗口大小(kD,kH,kW)
在这里插入图片描述

5、Non-Linear Activations

class torch.nn.ReLU(inplace=False) 
  • 1

对输入运用修正线性单元函数 R e L U ( x ) = m a x ( 0 , x ) {ReLU}(x)= max(0, x) ReLU(x)=max(0,x)
参数: inplace-选择是否进行覆盖运算

6、Linear layers

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

 class torch.nn.Linear(in_features, out_features, bias=True)
  • 1

对输入数据做线性变换:y=Ax+b
在这里插入图片描述

6、Dropout layers

 class torch.nn.Dropout2d(p=0.5, inplace=False) 
  • 1

随机将输入张量中整个通道设置为0,也就是在正向传播过程中随机失活一部分神经元。对于每次前向调用,被置0的通道都是随机的。 通常输入来自Conv2d模块。
在这里插入图片描述

7、Loss functions

criterion = LossCriterion() #构造函数有自己的参数
 loss = criterion(x, y) #调用标准时也有参数 
  • 1
  • 2

x :网络的输出值;
y :真实值

8、torch.optim

torch.optim 是一个实现了各种优化算法的库。大部分常用的方法得到支持,并且接口具备足够的通用性,使得未来能够集成更加复杂的方法。
为了使用 torch.optim,你需要构建一个 optimizer 对象。这个对象能够保持当前参数状态并基于计算得到的梯度进行参数更新。
为了构建一个 Optimizer,你需要给它一个包含了需要优化的参数(必须都是Variable对象)的 iterable。然后,你可以设置 optimizer 的参数选项,比如学习率,权重衰减,等等。

 optimizer = optim.SGD(model.parameters(), lr = 0.01, momentum=0.9) 
 optimizer = optim.Adam([var1, var2], lr = 0.0001) 
  • 1
  • 2

所有的optimizer都实现了step()方法,这个方法会更新所有的参数。一旦梯度被如backward()之类的函数计算好后,我们就可以调用这个函数。

 for input, target in dataset:
    optimizer.zero_grad()
    output = model(input)
    loss = loss_fn(output, target)
    loss.backward()
    optimizer.step() 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

一些优化算法例如Conjugate Gradient和LBFGS需要重复多次计算函数,因此你需要传入一个闭包去允许它们重新计算你的模型。这个闭包应当清空梯度, 计算损失,然后返回。

 for input, target in dataset:
    def closure():
        optimizer.zero_grad()
        output = model(input)
        loss = loss_fn(output, target)
        loss.backward()
        return loss
    optimizer.step(closure) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

9、torch.cuda

该包增加了对CUDA张量类型的支持,实现了与CPU张量相同的功能,但使用GPU进行计算。
它是懒惰的初始化,所以你可以随时导入它,并使用is_available()来确定系统是否支持CUDA。

10、torch.utils.data

class torch.utils.data.Dataset 
  • 1

表示Dataset的抽象类。
所有其他数据集都应该进行子类化。所有子类应该override__len__和__getitem__,前者提供了数据集的大小,后者支持整数索引,范围从0到len(self)。

 class torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0, collate_fn=<functiondefault_collate>, pin_memory=False, drop_last=False)
  • 1

数据加载器。组合数据集和采样器,并在数据集上提供单进程或多进程迭代器。
在这里插入图片描述

11、torch.nn.init

pytorch 在 torch.nn.init 中提供了常用的初始化方法函数。

(1)Xavier 系列

Xavier 系列的公式推导是从“方差一致性”出发,初始化的分布有均匀分布和正态分布两种:
均匀分布

torch.nn.init.xavier_uniform_(tensor, gain=1)
  • 1

在这里插入图片描述
正态分布

torch.nn.init.xavier_normal_(tensor, gain=1)
  • 1

在这里插入图片描述

(2)kaiming 系列

均匀分布

torch.nn.init.kaiming_uniform_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu') 
  • 1

在这里插入图片描述
正态分布

torch.nn.init.kaiming_normal_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu') 
  • 1

在这里插入图片描述

12、torch.nn.init.constant_

 torch.nn.init.constant_(tensor, val) 
  • 1

用值val填充向量。

13、torch.flatten

pytorch 读取数据类型为 tensor,pytorch读取数据类型的通道顺序为:NCHW
在这里插入图片描述

java torch.flatten(input, start_dim=0, end_dim=-1)
在这里插入图片描述
torch.flatten 作用是改变张量的维度和维数,从指定的维度开始将后面维度的维数全部展成一个维度,新的维数就是被展开的所有维度的维数的乘积
例如 x 是一个 size 为 [4,5,6] 的 tensor.flatten(x, 0, 1)(即将第 0 维 4 与第 一 维 5 相乘,4*5=20,第二维不变= 6)的结果是一个 size 为 [20,6] 的 tensor。
demo:

t = torch.tensor([[[1, 2, 2, 1],
                   [3, 4, 4, 3],
                   [1, 2, 3, 4]],
                  [[5, 6, 6, 5],
                   [7, 8, 8, 7],
                   [5, 6, 7, 8]]]) print(t, t.shape)
# tensor([[[1, 2, 2, 1],
#         [3, 4, 4, 3],
#         [1, 2, 3, 4]],

#        [[5, 6, 6, 5],
#         [7, 8, 8, 7],
#         [5, 6, 7, 8]]])
# torch.Size([2, 3, 4])
# 我们可以看到,最外层的方括号内含两个元素,因此 shape 的第一个值是 2;类似地,第二层方括号里面含三个元素,shape 的第二个值就是 3;最内层方括号里含四个元素,shape 的第二个值就是 4。

x = torch.flatten(t, start_dim=1) print(x, x.shape)

y = torch.flatten(t, start_dim=0, end_dim=1) print(y, y.shape)

# tensor([[1, 2, 2, 1, 3, 4, 4, 3, 1, 2, 3, 4],
#        [5, 6, 6, 5, 7, 8, 8, 7, 5, 6, 7, 8]]) 
#        torch.Size([2, 12])
        
# tensor([[1, 2, 2, 1],
#        [3, 4, 4, 3],
#        [1, 2, 3, 4],
#        [5, 6, 6, 5],
#        [7, 8, 8, 7],
#        [5, 6, 7, 8]]) 
# torch.Size([6, 4])
# 可以看到,当 start_dim = 1 而 end_dim = −1 时,它把第 1 个维度到最后一个维度全部推平合并了。而当 start_dim = 0 而 end_dim = 1 时,它把第 0 个维度到第 1 个维度全部推平合并了。 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

八、卷积神经网络参数的设定

在构建和训练卷积神经网络的过程中,有非常多的参数需要设置,其中一部分是神经网络本身的参数,另一部分只与训练有关的参数。

1、与神经网络相关的主要参数如下

● 卷积层的卷积核大小、卷积核个数
● 激活函数的种类
● 池化方法的种类
● 网络的层结构(卷积层的个数和全连接层的个数)
● Dropout 的概率
● 有无预处理
● 有无归一化

2、与训练有关的参数如下

● Mini-Batch的大小
● 学习率
● 迭代次数
● 有无预训练

3、参数的影响力对比

通常在构建和训练神经网络的时候,我们需要选择这些参数的最优组合,但是因为参数的数量过多,往往我们只能根据经验来选择。但是实际上有的参数对神经网络影响比较小,有的影响比较大。我们可以优先调整影响比较大的参数,再调整影响比较小的参数。假设我们有一个如下图所示的神经网络,应用 CIFAR-10 数据集进行训练,观察各个参数的影响。
在这里插入图片描述
参数的比较结果如下图所示:
在这里插入图片描述
从图中可以看出,卷积层的卷积核个数、激活函数的种类和输入图像的预处理对模型存在较大的影响,应该首先确定这些重要参数。其他的参数影响相对较小,只需要在设定好重要参数之后进行微调就可以。但是要注意,对于不同的数据集和问题,重要的参数情况可能有变化,需要根据实际问题和研究,这个例子只是参考作用。

九、训练分类器

● 首先装载数据,并将其统一化;
● 定义CNN;
● 定义损失函数;
● 训练神经网络;
● 测试网络;

1、CPU 版本

import torch
import torch.optim as optim
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')

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

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

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
        criterion = nn.CrossEntropyLoss()
        optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
        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')

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

outputs = net(images)

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

print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
                              for j in range(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))

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]))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141

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

inputs, labels = inputs.to(device), labels.to(device)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
import torch.optim as optim
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')

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

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

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)



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
        inputs, labels = inputs.to(device), labels.to(device)
        # zero the parameter gradients
        criterion = nn.CrossEntropyLoss()
        optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
        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')

dataiter = iter(testloader)
images, labels = dataiter.next()
images, labels = inputs.to(device), labels.to(device)

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

outputs = net(images)

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

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

correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        images, labels = inputs.to(device), labels.to(device)
        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
        images, labels = inputs.to(device), labels.to(device)
        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]))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/391472
推荐阅读
相关标签
  

闽ICP备14008679号