Pytorch入门教程

  记得刚开始学TensorFlow的时候,那给我折磨的呀,我一直在想这个TensorFlow官方为什么搭建个网络还要画什么静态图呢,把简单的事情弄得麻烦死了,直到这几天我开始接触Pytorch,发现Pytorch是就是不用搭建静态图的Tensorflow版本,就想在用numpy一样,并且封装了很多深度学习高级API,numpy数据和Tensor数据相互转换不用搭建会话了,只需要一个转换函数,搭建起了numpy和TensorFlow爱的桥梁。

  Pytorch自17年推出以来,一度有赶超TensorFlow的趋势,是因为Pytorch采用动态图机制,替代Numpy使用GPU的功能,搭建网络灵活。

Pytorch和TensorFlow的区别:

  • TensorFlow是基于静态计算图的,静态计算图是先定义后运行,一次定义多次运行(Tensorflow 2.0也开始使用动态计算图)
  • PyTorch是基于动态图的,是在运行的过程中被定义的,在运行的时候构建,可以多次构建多次运行

上手难度:tensorflow 1 > tensorflow 2 > pytorch

工业界:tensorflow 1>tensorflow 2 > pytorch

学术界:pytorch > tesnroflow 2 >  tesnorflow 1(已经被谷歌抛弃)

pytorch的优点

  • GPU加速
  • 自动求导
  • 常用网络层

如何表达字符串:1、one-hot,如果词库很大会造成矩阵很大,占内存。2、embeding(word2vec、glove)

pytorch的数据类型

Data type

CPU tensor

GPU tensor
torch.float32 torch.FloatTensor torch.cuda.FloatTensor
torch.floar64 torch.DoubleTensor torch.cuda.DoubleTensor
torch.int32 torch.IntTensor torch.cuda.IntTensor
torch.int64 torch.LongTensor torch.cuda.LongTensor

即便是同一个变量同时部署在CPU和GPU上面是不一样的

 

这篇文章的所有代码,请务必手敲!!!

安装

这个网址包含pytorch与cuda的对应关系

由于我的cuda是 10.0

我选择的安装命令是:

pip install torch==1.4.0 torchvision==0.5.0 -f https://download.pytorch.org/whl/torch_stable.html

pip install torch==1.4.0+cu100 torchvision==0.5.0+cu100 -f https://download.pytorch.org/whl/torch_stable.html

pip install pytorch torchvision cuda100 -c pytorch

官网给的安装命令是:

pip install torch==1.2.0 torchvision==0.4.0 -f https://download.pytorch.org/whl/torch_stable.html

我原本是:torch-1.2.0+cu92

目前我装的是(安装torchaudio时候帮我升级的):torch-1.6.0+cu101 torchaudio-0.6.0

创建张量

  torch的数据类型torch.float32、torch.floar64、torch.float16、torch.int8、torch.int16、torch.int32、torch.int64。当数据在GPU上时,数据类型需要加上cuda,例:torch.cuda.FloatTensor

tesnor.dim():求张量的阶数

tensor.shape/tensor.size():获取张量的shape

tensor.reshape()/tensor.view():修改张量的shape

tesnor.item():如果我们的张量只有一个数值,可以使用.item()获取,多用于神经网络损失值

import torch

a = torch.randn(2, 3)
print(a.type())  # torch.FloatTensor
a = a.cuda()
print(a.type())  # torch.cuda.FloatTensor

0阶\0维 张量

a = torch.tensor(1.3)
print(a)  # tensor(1.3000)
print(a.shape)  # torch.Size([])
print(a.size())  # torch.Size([])
print(len(a.shape))  # 0

1阶张量

a = torch.tensor([1.1])
b = torch.tensor([1.1, 2.2])

print(a)    # tensor([1.1000])
print(b)    # tensor([1.1000, 2.2000])
print(torch.FloatTensor(1)) # 1个随机数 
# tensor([0.])
print(torch.FloatTensor(2,2))   # 两行两列的随机数
# tensor([[ 0.0000e+00,  0.0000e+00],
#         [-1.5690e-31,  4.5909e-41]])

将numpy数据转换为torch数据

x = np.array([[1, 2], [3, 4]])
y = torch.from_numpy(x)  # 转换为 torch数据
z = y.numpy()        # 转换为 numpy 数据

2阶张量

a = torch.randn(2,3)
print(a)
# tensor([[ 0.6078, -0.0300,  0.8677],
#         [ 0.4085, -2.9589, -0.0837]])
print(a.shape)  # torch.Size([2, 3])

print(a.size(0))    # 2
print(a.size(1))    # 3
print(a.shape[1])   # 3

3阶

  多用于RNN、input、batch

a = torch.rand(1, 2, 3)
print(a)
# tensor([[[0.9094, 0.8469, 0.2710],
#          [0.7306, 0.3788, 0.9464]]])
print(a.shape)  # torch.Size([1, 2, 3])
print(a[0])
# tensor([[0.9094, 0.8469, 0.2710],
#         [0.7306, 0.3788, 0.9464]])

4阶

a = torch.rand(2,3,28,28)
print(a)
# tensor([[[[0.7709, 0.7338, 0.7997,  ..., 0.2025, 0.2864, 0.1989],
#           [0.8917, 0.0605, 0.4229,  ..., 0.7809, 0.9371, 0.0639],
#           [0.1946, 0.0166, 0.9767,  ..., 0.9593, 0.2626, 0.9426],
#           ...,
#           ...,
#           [0.4095, 0.3448, 0.9255,  ..., 0.6079, 0.8952, 0.0065],
#           [0.7871, 0.9928, 0.6331,  ..., 0.7503, 0.6191, 0.8684],
#           [0.5359, 0.3247, 0.8745,  ..., 0.7800, 0.8538, 0.7997]]]])

print(a.shape)  # torch.Size([2, 3, 28, 28])
print(a.dim())  # 4

将numpy数据转换为torch数据

torch.from_numpy()

a = np.array([2, 3, 3])
print(torch.from_numpy(a))
# tensor([2, 3, 3], dtype=torch.int32)

a = np.ones([2, 3])
print(torch.from_numpy(a))
# tensor([[1., 1., 1.],
#         [1., 1., 1.]], dtype=torch.float64)

直接指定tensor的数值

print(torch.tensor([2.,3.2]))
# tensor([2.0000, 3.2000])
print(torch.FloatTensor([2.,3.2]))
# tensor([2.0000, 3.2000])
print(torch.tensor([[2.,3.2],[1.,22.3]]))
# tensor([[ 2.0000,  3.2000],
#         [ 1.0000, 22.3000]])

定义未初始化张量

print(torch.empty(2,3))
# tensor([[2.5657e-05, 6.3199e-43, 2.5657e-05],
#         [6.3199e-43, 2.5855e-05, 6.3199e-43]])
print(torch.FloatTensor(2,3))
# tensor([[2.5804e-05, 6.3199e-43, 8.4078e-45],
#         [0.0000e+00, 1.4013e-45, 0.0000e+00]])
print(torch.IntTensor(2,3))
# tensor([[937482688,       451,         1],
#         [        0,         1,         0]], dtype=torch.int32)

设置tensor数据的默认类型type

print(torch.tensor([1.2,3]).type())

torch.set_default_tensor_type(torch.DoubleTensor)
print(torch.tensor([1.3,3]).type())

torch.ones(size)/zero(size)/eye(size):返回全为1/0/单位对角 张量

>>> torch.ones(2, 3)
tensor([[ 1.,  1.,  1.],
        [ 1.,  1.,  1.]])

>>> torch.ones(5)
tensor([ 1.,  1.,  1.,  1.,  1.])

torch.full(size, fill_value):返回以size大小填充fill_value的张量

torch.rand(size):返回[0, 1)之间的均匀分布 张量

torch.randn(size):均值为0,方差为1的正态分布

torch.*_like(input):返回一个和input shape一样的张量,*可以为rand、randn...

torch.randint(low=0, high, size):返回shape=size,[low, high)之间的随机整数

torch.arange():和np.arange类似用法

torch.linspace(start, end, step=1000):返回start和end之间等距steps点的一维步长张量。

torch.logspace(start, end, steps=1000, base=10.0):返回$base^{start}$和$base^{end}$之间等距steps点的一维步长张量。

torch.randperm(n):返回从0到n-1的整数的随机排列

b = torch.rand(4)
idx = torch.randperm(4)
print(b)    # tensor([0.0224, 0.7826, 0.5529, 0.2261])
print(idx)  # tensor([0, 2, 1, 3])
print(b[idx])   # tensor([0.5573, 0.6121, 0.6581, 0.1892])

索引与切片操作

a = torch.rand(4, 3, 28, 28)
print(a.shape)  # torch.Size([4, 3, 28, 28])

# 索引
print(a[0, 0].shape)  # torch.Size([28, 28])
print(a[0, 0, 2, 4])  # tensor(0.1152)

#  切片
print(a[:2].shape)  # torch.Size([2, 3, 28, 28])
print(a[:2, :2, :, :].shape)  # torch.Size([2, 2, 28, 28])
print(a[:2, -1:, :, :].shape)  # torch.Size([2, 1, 28, 28])

# ...的用法
print(a[...].shape)  # torch.Size([4, 3, 28, 28])
print(a[0, ...].shape)  # torch.Size([3, 28, 28])
print(a[:, 1, ...].shape)  # torch.Size([4, 28, 28])
print(a[..., :2].shape)  # torch.Size([4, 3, 28, 2])

掩码取值

x = torch.rand(3, 4)
# tensor([[0.0864, 0.8583, 0.9847, 0.6263],
#         [0.4546, 0.1105, 0.5902, 0.7919],
#         [0.3894, 0.8882, 0.3354, 0.1561]])

mask = x.ge(0.5)    # ge 是符号 >
# tensor([[False,  True,  True,  True],
#         [False, False,  True,  True],
#         [False,  True, False, False]])

print(torch.masked_select(x, mask))
# tensor([0.8583, 0.9847, 0.6263, 0.5902, 0.7919, 0.8882])

通过torch.take取值

src = torch.tensor([[4,3,5],[6,7,8]])
print(torch.take(src, torch.tensor([0,2,5])))
# tensor([4, 5, 8])

维度变换

torch tensor有两个维度变换方法

  • tensor.reshape()
  • tensor.view()

squeeze:去除对应维度为1的维度

unsqueeze:往对应位置索引插入一个维度

a = torch.rand(4, 1, 28, 28)
b = a.unsqueeze(0)
print(b.shape)  # torch.Size([1, 4, 1, 28, 28])

a = torch.rand(4, 1, 1, 28)
b = a.squeeze()
print(b.shape)  # torch.Size([4, 28])
b = a.squeeze(1)
print(b.shape)  # torch.Size([4, 1, 28])

tensor.expand(*size):返回具有单个尺寸扩展到更大尺寸的张量

tesnor.repeat(*size):沿指定尺寸重复张量

x = torch.tensor([[1], [2], [3]])
print(x.size())  # torch.Size([3, 1])
print(x.expand(3, 4))
# tensor([[ 1,  1,  1,  1],
#         [ 2,  2,  2,  2],
#         [ 3,  3,  3,  3]])
print(x.expand(-1, 4))  # -1表示不改变维度的大小
# tensor([[ 1,  1,  1,  1],
#         [ 2,  2,  2,  2],
#         [ 3,  3,  3,  3]])

b = torch.tensor([1, 2, 3])  # torch.Size([1, 3])
print(b.repeat(4, 2).shape)  # torch.Size([4, 6])
print(b.repeat(4, 2, 1).size())  # torch.Size([4, 2, 3])

tensor.transpose():调换张量指定维度的顺序

tensor.permute():将张量按指定顺序排列

b = torch.rand(4, 3, 28, 32)

print(b.transpose(1, 3).shape)  # torch.Size([4, 32, 28, 3])
print(b.permute(0, 2, 3, 1).shape)  # torch.Size([4, 28, 32, 3])

数学运算

加法

a = torch.rand(3,4)
b = torch.rand(4)   # dim=1
# 加法
print(a+b)
print(torch.add(a,b))
# 减法
print(a-b)
print(torch.sub(a,b))

# 乘法
a = torch.rand(2,3)
b = torch.rand(2,3)   # dim=1
print(a*b)  # 对应元素相乘

a = torch.rand(2,3)
b = torch.rand(3,2)   # dim=1
# 矩阵相乘
print(torch.mm(a,b))    # shape=(2,2)
print(torch.matmul(a,b))    # shape=(2,2)
print(a@b)  # shape=(2,2)

平方

a = torch.full([2,2], 2)    # 创建一个shape=[2,2]值为2的数组
print(a.pow(2))
print(a**2)

平方根

a = torch.full([2,2], 4)    # 创建一个shape=[2,2]值为2的数组
print(a.sqrt())    # 平方根
print(a**(0.5))

e的指数冥:torch.exp()

取对数:torch.log()

tensor.floor():向下取整

tensor.ceil() :向上取整

tensor.round():四舍五入

tensor.trunc():取整数值

tensor.frac():取小数值

tensor.clamp(min,max):不足最小值的变成最小值,大于最大值的变成最大值

torch.mean():求均值

torch.sum():求和

torch.max\torch.min:求最大最小值

torch.prod(input, dtype=None) :返回input中所有元素的乘积

torch.argmin(input)\torch.argmax(input):返回input张量中所有元素的最小值\最大值的索引

torch.where(condition, x, y):如果符合条件返回x,如果不符合条件返回y

torch.gather(input, dim, index):沿dim指定的轴收集值

  • input:输入tensor
  • dim:索引所沿的轴
  • index:要收集的元素的索引
t = torch.tensor([[1,2],[3,4]])
torch.gather(t, 1, torch.tensor([[0,0],[1,0]]))
# tensor([[ 1,  1],
#         [ 4,  3]])

autograd:自动求导

autograd 包为张量上的所有操作提供了自动求导机制。它是一个在运行时定义(define-by-run)的框架,这意味着反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的.

让我们用一些简单的例子来看看吧。

张量

  如果torch.Tesnor 的属性 .requires_grad 设置为True,那么autograd 会追踪对于该张量的所有操作。当完成计算后可以通过调用 .backward(),来自动计算所有的梯度。这个 torch.Tensor 张量的所有梯度将会自动累加到 .grad属性上。

  如果要阻止一个张量被跟踪历史,可以调用 .detach() 方法将其与计算历史分离,并阻止它未来的计算记录被跟踪。

为了防止跟踪历史记录(和使用内存),可以将代码块包装在 with torch.no_grad(): 中。在评估模型时特别有用,因为模型可能具有 requires_grad = True 的可训练的参数,但是我们不需要在此过程中对他们进行梯度计算。

 

Tensor 和 Function 互相连接生成了一个无圈图(acyclic graph),它编码了完整的计算历史。每个张量都有一个 .grad_fn 属性,该属性引用了创建 Tensor 自身的Function(除非这个张量是用户手动创建的,即这个张量的 grad_fn 是 None )。

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

有一部分有点难,需要多看几遍。

https://pytorch.apachecn.org/docs/1.2/beginner/blitz/autograd_tutorial.html


 

 

如果设置torch.tensor_1(requires_grad=True),那么会追踪所有对该张量tensor_1的所有操作。

import torch

# 创建一个张量并设置 requires_grad=True 用来追踪他的计算历史
x = torch.ones(2, 2, requires_grad=True)
print(x)
# tensor([[1., 1.],
#         [1., 1.]], requires_grad=True)

当Tensor完成一个计算过程,每个张量都会自动生成一个.grad_fn属性

# 对张量进行计算操作,grad_fn已经被自动生成了。
y = x + 2
print(y)
# tensor([[3., 3.],
#         [3., 3.]], grad_fn=<AddBackward>)
print(y.grad_fn)
# <AddBackward object at 0x00000232535FD860>

# 对y进行一个乘法操作
z = y * y * 3
out = z.mean()

print(z)
# tensor([[27., 27.],
#         [27., 27.]], grad_fn=<MulBackward>) 
print(out)
# tensor(27., grad_fn=<MeanBackward1>)

.requires_grad_(...) 可以改变张量的requires_grad属性。 

import torch

a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)      # 默认是requires_grad = False
a.requires_grad_(True)
print(a.requires_grad)      # True
b = (a * a).sum()
print(b.grad_fn)        # <SumBackward0 object at 0x000002325360B438>

梯度

回顾到上面

import torch

# 创建一个张量并设置 requires_grad=True 用来追踪他的计算历史
x = torch.ones(2, 2, requires_grad=True)
print(x)
# tensor([[1., 1.],
#         [1., 1.]], requires_grad=True)


# 对张量进行计算操作,grad_fn已经被自动生成了。
y = x + 2
print(y)
# tensor([[3., 3.],
#         [3., 3.]], grad_fn=<AddBackward>)
print(y.grad_fn)
# <AddBackward object at 0x00000232535FD860>

# 对y进行一个乘法操作
z = y * y * 3
out = z.mean()

print(z)
# tensor([[27., 27.],
#         [27., 27.]], grad_fn=<MulBackward>)
print(out)
# tensor(27., grad_fn=<MeanBackward1>)

让我们来反向传播,运行 out.backward() ,等于out.backward(torch.tensor(1.))

对out进行反向传播,$out = \frac{1}{4}\sum_i z_i$,其中$z_i = 3(x_i+2)^2$,因为方向传播中torch.tensor=1(out.backward中的参数)因此$z_i\bigr\rvert_{x_i=1} = 27$

对于梯度$\frac{\partial out}{\partial x_i} = \frac{3}{2}(x_i+2)$,把$x_i=1$代入$\frac{\partial out}{\partial x_i}\bigr\rvert_{x_i=1} = \frac{9}{2} = 4.5$

print(out)  # tensor(27., grad_fn=<MeanBackward1>)

print("*"*50)
out.backward()
# 打印梯度
print(x.grad)
# tensor([[4.5000, 4.5000],
#         [4.5000, 4.5000]])

对吃栗子找到规律,才能看懂

import torch

x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y)  # tensor([-920.6895, -115.7301, -867.6995], grad_fn=<MulBackward>)
gradients = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)

# 把gradients代入y的反向传播中
y.backward(gradients)   

# 计算梯度
print(x.grad)   # tensor([ 51.2000, 512.0000,   0.0512])

为了防止跟踪历史记录,可以将代码块包装在with torch.no_grad():中。 在评估模型时特别有用,因为模型的可训练参数的属性可能具有requires_grad = True,但是我们不需要梯度计算。

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

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

神经网络

神经网络是基于自动梯度 (autograd)来定义一些模型。一个 nn.Module 包括各个层和一个 forward(input) 方法,它会返回output。

神经网络训练过程包括以下几点:

  1. 定义一个包含可训练参数的神经网络
  2. 迭代整个输入
  3. 通过神经网络处理输入
  4. 计算损失(loss)
  5. 反向传播梯度到神经网络的参数
  6. 更新网络的参数,典型的用一个简单的更新方法:weight weight learning_rate *gradient

定义网络

  我们先来定义一个网络,处理输入,调用backword

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


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # 输入图像channel:1,输出channel:6; 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)
        x = x.view(-1, self.num_flat_features(x))   # reshape 成二维,方便做全连接操作
        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:]  # 除去 batch 维度的其他维度
        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))

我们只需要定义 forward 函数,backward函数会在使用autograd时自动被定义,backward函数用来计算导数。可以在 forward 函数中使用任何针对张量的操作和计算。

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

params = list(net.parameters())
print(len(params))       # 10
print(params[0].size())  # 第一个卷积层的权重 torch.Size([6, 1, 3, 3])

 让我们随机生成一个 shape=(1, 1, 32, ,32) 的数据输入模型

# 输入shape格式:[batch_size, nChannels, Height, Width]
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out.shape)    # torch.Size([1, 10])
print(out)
# tensor([[ 0.0227, -0.0823,  0.1665,  0.2153,  0.0732,  0.0471,  0.0019,  0.0664,
#          -0.0576,  0.0238]], grad_fn=<AddmmBackward>)

清零所有参数的梯度缓存,然后进行随机梯度的反向传播:

net.zero_grad()     # 把所有参数梯度缓存器置零
out.backward(torch.randn(1, 10))    # 用随机的梯度来反向传播

损失函数

  我们这里计算均方误差 $loss=nn.MSELoss(模型预测值-目标)$

output = net(input)             # torch.Size([1, 10])
target = torch.randn(10)        # 生成一个随机数据作为target
target = target.reshape(1,-1)   # [1, 10]
mse_loss = nn.MSELoss()
loss_value = mse_loss(output, target)
print(loss_value)   # tensor(0.5513, grad_fn=<MseLossBackward>)

现在,如果使用loss.grad_fn属性跟踪反向传播过程,会看到计算图如下:

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

 

所以,当我们调用loss.backward(),整张图开始关于loss微分,图中所有设置了requires_grad=True的张量的.grad属性累积着梯度张量。

为了说明这一点,让我们向后跟踪几步:

print(loss.grad_fn)  # MSELoss
# <MseLossBackward object at 0x7fab77615278>
print(loss.grad_fn.next_functions[0][0])  # Linear
# <AddmmBackward object at 0x7fab77615940>
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU
# <AccumulateGrad object at 0x7fab77615940>

反向传播

  为了实现损失函数的梯度反向传播,我们需要使用 loss.backward() 来反向传播权重。我们需要清零现有的梯度,否则梯度将会与已有的梯度累加。

现在,我们将调用loss.backward(),并查看conv1层的偏置(bias)在反向传播前后的梯度。

net.zero_grad()     # 清零所有参数的梯度
print('反向传播之前的 conv1.bias.grad 梯度')
print(net.conv1.bias.grad)
# tensor([0., 0., 0., 0., 0., 0.])

loss.backward()

print('反向传播之后的 conv1.bias.grad 梯度')
print(net.conv1.bias.grad)
# tensor([-0.0118,  0.0125, -0.0085, -0.0225,  0.0125,  0.0235])

 

 

更新权重

最简单的更新规则是随机梯度下降法(SGD):

weight = weight - learning_rate * gradient

我们先简单的的使用python代码来实现一下:

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

方式在pytorch中官方有一个优化器包torch.optim,包含了不同的优化器:SGD、Nesterov-SGD、Adam、RMSProp等,使用方法如下

import torch.optim as optim

optimizer = optim.SGD(net.parameters(), lr=0.01)    # 创建 SGD 优化器

optimizer.zero_grad()   # 清零梯度缓存
output = net(input)
loss = criterion(output, target)    # 损失函数
loss.backward()     # 损失函数的梯度反向传播
optimizer.step()    # 更新参数

图像分类器

torch有一个叫做totchvision 的包,支持加载类似Imagenet,CIFAR10,MNIST 等公共数据集的数据加载模块 torchvision.datasets

支持加载图像数据数据转换模块 torch.utils.data.DataLoader。

本节我们使用CIFAR10数据集,它包含十个类别:‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。CIFAR-10 中的图像尺寸为33232,也就是RGB的3层颜色通道,每层通道内的尺寸为32*32。

训练一个图像分类器

pytorch创建了一个包torchvision,其中包含了针对Imagenet、CIFAR10、MNIST等常用数据集的数据加载器(data loaders),还有对图片数据变形的操作。

这一节我们将训练一个cifar10的图像分类器,步骤如下:

  1. 使用torchvision加载并且归一化CIFAR10的训练和测试数据集
  2. 定义一个卷积神经网络
  3. 定义一个损失函数
  4. 在训练样本数据上训练网络
  5. 在测试样本数据上测试网络

1.加载并标准化CIFAR10

  使用torchvision加载CIFAR10,torchvision 数据集的输出是范围在[0,1]之间的 PILImage,我们将他们转换成归一化范围为[-1, 1]之间的张量 Tensors。

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)
# 下载测试数据集
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
# 训练集加载器
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=0)
# 测试集加载器
testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=0)

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

现在让我们可视化部分训练数据

import matplotlib.pyplot as plt
import numpy as np


def imshow(img):
    img = img / 2 + 0.5  # 去标准化
    npimg = img.numpy()  # 转换成numpy数据
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()

# 随机获取图片
dataiter = iter(trainloader)
images, labels = dataiter.next()

# 显示图片
imshow(torchvision.utils.make_grid(images))
# 打印图片标签
print("".join("%6s" % classes[labels[j]] for j in range(4)))
#   frog truck  bird   cat

2.定义卷积神经网络

将上一节定义的神经网络拿过来,并将其修改成输入为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()

 3.定义损失函数和优化器

  我们使用分类的交叉熵损失函数和随机梯度下降SGD(使用momentum)。

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):
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data   # 获取数据
        optimizer.zero_grad()   # 清零参数的梯度

        # --- forward + backward + optimize ---  #
        outputs = net(inputs)   # 前向传播
        loss = criterion(outputs, labels)   # 计算损失
        loss.backward()     # 损失梯度反向传播
        optimizer.step()    # 参数更新

        running_loss += loss.item()
        # 每2000个小批量打印一次
        if i % 2000 == 1999:
            print("[%d, %5d] loss: %.3f" % (epoch + 1, i+1, running_loss/2000))
            running_loss = 0.0
print("训练完成")
# [1,  2000] loss: 2.182
# [1,  4000] loss: 1.819
# [1,  6000] loss: 1.648
# [1,  8000] loss: 1.569
# [1, 10000] loss: 1.511
# [1, 12000] loss: 1.473
# [2,  2000] loss: 1.414
# [2,  4000] loss: 1.365
# [2,  6000] loss: 1.358
# [2,  8000] loss: 1.322
# [2, 10000] loss: 1.298
# [2, 12000] loss: 1.282
# 训练完成

5.使用测试数据测试网络

  接来下对比神经网络输出的标签和正确样本(ground-truth),并检测网络的预测精度,如果预测是正确的,我们将样本添加到正确预测的列表中。

第一步,我们先显示测试集中的图像

# 测试数据集
dataiter = iter(testloader)
images, labels = dataiter.next()

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

# 预测值
outputs = net(images)
_, predicted = torch.max(outputs, 1)
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))
# Predicted:    dog  ship  ship plane

预测对了两个,让我们看看网络在整个数据集上的表现。

correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data   # 测试集图片和标签
        # images.shape torch.Size([4, 3, 32, 32])
        # labels.shape torch.Size([4])
        outputs = net(images)   # 预测标签 torch.Size([4, 10])
        # .data 返回和outputs的相同数据tensor, 但不会加入到x的计算历史里
        _, predicted = torch.max(outputs.data, dim=1)   # 返回最大值和最大值的索引
        total += labels.size(0) # 计算一共有多少个测试数据集
        # .item()得到一个元素张量里面的元素值
        correct += (predicted == labels).sum().item()   # 预测正确的数量
print('该网络对10000张测试图像的精度: %d %%' % (100 * correct / total))
# 该网络对10000张测试图像的精度: 55 %

正确率有55%,看来网络学到了东西。那么哪些是表现好的类呢?哪些是表现的差的类呢?

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.shape torch.Size([4, 3, 32, 32])
        # labels.shape torch.Size([4])
        outputs = net(images)       # 预测标签 torch.Size([4, 10])
        _, predicted = torch.max(outputs, 1)
        c = (predicted == labels).squeeze()     # 去掉维数为1的的维度
        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]))
# Accuracy of plane : 70 %
# Accuracy of   car : 70 %
# Accuracy of  bird : 28 %
# Accuracy of   cat : 25 %
# Accuracy of  deer : 37 %
# Accuracy of   dog : 60 %
# Accuracy of  frog : 66 %
# Accuracy of horse : 62 %
# Accuracy of  ship : 69 %
# Accuracy of truck : 61 %

在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

然后这些方法将递归遍历所有模块,并将它们的参数和缓冲区转换为CUDA张量:

net.to(device)

请记住,我们不得不将输入和目标在每一步都送入GPU:

inputs, labels = inputs.to(device), labels.to(device)

 数据并行处理

  在这一章节中我们教大家使用DataParallel来使用多GPU

我们把模型放入GPU中

device = torch.device("cuda: 0")    # 实例化cuda设备
model.to(device)

将张量复制到GPU设备上:

mytensor = my_tensor.to(device)

PyTorch 默认只会使用一个 GPU。因此我们可以使用 DataParallel 让模型在多个GPU上并行运行

model = nn.DataParallel(model)

输入和参数

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader

input_size = 5
output_size = 2
batch_size = 30
data_size = 100

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

虚拟数据集

  制造一个随机的数据集,只需实现__getitem__

class RandomDataset(Dataset):
    def __init__(self, size, length):
        self.len = length
        self.data = torch.randn(length, size)

    def __getitem__(self, index):
        return self.data[index]

    def __len__(self):
        return self.len

rand_loader = DataLoader(dataset=RandomDataset(input_size, data_size),
            batch_size=batch_size, shuffle=True)

简单模型

  作为演示,我们的模型只接受一个输入,执行一个线性操作,然后得到结果。然而,你能在任何模型(CNN,RNN,Capsule Net等)上使用DataParallel

class Model(nn.Module):
    def __init__(self, input_size, output_size):
        super(Model, self).__init__()
        self.fc = nn.Linear(input_size, output_size)

    def forward(self, input):
        output = self.fc(input)
        print("In Model: input size", input.size(), "output size", output.size())
        return output

创建模型和数据并行

我们先要检查模型是否有多个GPU,如果有我们再使用nn.DataParallel,然后我们可以把模型放在GPU上model.to(device)

model = Model(input_size, output_size)  # 模型实例化
if torch.cuda.device_count() >= 1:
    print("我们有", torch.cuda.device_count(), "个GPUs!")
    model = nn.DataParallel(model)  # 设置数据并行

model.to(device)    # 将模型放到cuda上面

运行模型,现在我们可以看到输入和输出张量的大小了

for data in rand_loader:
    input = data.to(device)     # 将数据放到cuda上面
    output = model(input)
    print("Outside: input size", input.size(), "output_size", output.size())
    print("---------------------------------------------------------")

输出

我们有 1 个GPUs!
In Model: input size torch.Size([30, 5]) output size torch.Size([30, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
---------------------------------------------------------
In Model: input size torch.Size([30, 5]) output size torch.Size([30, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
---------------------------------------------------------
In Model: input size torch.Size([30, 5]) output size torch.Size([30, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
---------------------------------------------------------
In Model: input size torch.Size([10, 5]) output size torch.Size([10, 2])
Outside: input size torch.Size([10, 5]) output_size torch.Size([10, 2])
---------------------------------------------------------

  当我们对30个输入和输出进行批处理时,我们和期望的一样得到30个输入和30个输出,但是若有多个GPU,会得到如下的结果。

如果我们有2个GPU我们可以看到以下结果

我们有 2 个GPUs!
In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
---------------------------------------------------------
In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
---------------------------------------------------------
In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
---------------------------------------------------------
In Model: input size torch.Size([5, 5]) output size torch.Size([5, 2])
In Model: input size torch.Size([5, 5]) output size torch.Size([5, 2])
Outside: input size torch.Size([10, 5]) output_size torch.Size([10, 2])
---------------------------------------------------------

DataParallel自动的划分数据,并将作业发送到多个GPU上的多个模型。DataParallel会在每个模型完成作业后,收集与合并结果然后返回给你。

TensorBoard可视化

我通过from torch.utils.tensorboard import SummaryWriter导入tensorboard有问题,因此我选择通过tesnorboardX。

from tensorboardX import SummaryWriter

创建事件对象:writer = SummaryWriter(logdir)

写入图片数据:writer.add_image(tag, img_tensor, global_step=None)

写入标量数据:writer.add_scalar(tag=, scalar_value, global_step=None)

关闭事件对象:writer.close()

在事件文件夹 ./events 中打开cmd,输入

tensorboard --logdir=runs
# 或者
tensorboard --logdir "./"

然后在浏览器中输入

https://localhost:6006/ 即可显示。

 

保存和加载模型

torch.save:保存模型,序列化对象保存到磁盘,常见的PyTorch约定是使用.pt或 .pth文件扩展名保存模型。

torch.load:加载模型,目标文件反序列化到内存中

torch.nn.Module.load_state_dict:使用反序列化的state_dict 加载模型的参数字典 

state_dict:python字典,包括具有可学习参数的层、每层的参数张量、优化器以及优化器超参数

  为了充分了解state_dict,我们看下面例子:

import torch.nn as nn
import torch.nn.functional as F
from torch import optim


class TheModelClass(nn.Module):
    def __init__(self):
        super(TheModelClass, 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


model = TheModelClass() # 初始化模型
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)   # 初始化optimizer

print("Model的state_dict:")
for param_tensor in model.state_dict():
    print(param_tensor, "\t", model.state_dict()[param_tensor].size())

print("Optimizer的state_dict:")
for var_name in optimizer.state_dict():
    print(var_name, "\t", optimizer.state_dict()[var_name])
    
# Model的state_dict:
# conv1.weight      torch.Size([6, 3, 5, 5])
# conv1.bias      torch.Size([6])
# conv2.weight      torch.Size([16, 6, 5, 5])
# conv2.bias      torch.Size([16])
# fc1.weight      torch.Size([120, 400])
# fc1.bias      torch.Size([120])
# fc2.weight      torch.Size([84, 120])
# fc2.bias      torch.Size([84])
# fc3.weight      torch.Size([10, 84])
# fc3.bias      torch.Size([10])
# Optimizer的state_dict:
# state      {}
# param_groups      [{'lr': 0.001, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [3251954079208, 3251954079280, 3251954079352, 3251954079424, 3251954079496, 3251954079568, 3251954079640, 3251954079712, 3251954079784, 3251954079856]}]

保存

torch.save(model.state_dict(), PATH)  # 保存模型的参数
torch.save(model, PATH)  # 保存整个模型

加载

model.load_state_dict(torch.load(PATH))  # 加载模型的参数
model = torch.load(PATH)  # 加载整个模型

继续训练

保存

checkpoint = {
    'epoch': epoch,
    'model_state_dict': model.state_dict(), # 模型参数
    'optimizer_state_dict': optimizer.state_dict(), # 优化器参数
    'loss': loss,
    ...
    }

PATH = './checkpoint/ckpt_best_%s.pth' %(str(epoch))    # path中要包含.pth
torch.save(checkpoint, PATH)

加载

model = TheModelClass(*args, **kwargs)
optimizer = TheOptimizerClass(*args, **kwargs)

checkpoint = torch.load(PATH)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']

model.eval()
#
model.train()

 

参考

Github一个Pytorch从入门到精通比较好的教程

PyTorch模型训练实用教程

[知乎]PyTorch实现断点继续训练

简单易上手的PyTorch中文文档:https://github.com/fendouai/pytorch1.0-cn

【文档】

知乎pytorch搜索页

【视频】

如何成为Pytorch大神

  1. 学好深度学习的基础知识
  2. 学习PyTorch官方tutorial
  3. 学习GitHub以及各种博客上的教程(别人创建好的list)
  4. 阅读documentation,使用论坛 https://discuss.pytorch.org/
  5. 跑通以及学习开源PyTorch项目
  6. 阅读深度学习模型paper,学习别人的模型实现
  7. 通过阅读paper,自己实现模型
  8. 自己创造模型(也可以写paper)

 

posted @ 2019-10-08 21:12  凌逆战  阅读(7424)  评论(0编辑  收藏