Pytorch简介

[TOC]

为什么是PyTorch? 人生苦短

几乎所有的深度学习框架都是基于计算图的,而图又可以分为静态计算图和动态计算图,静态计算图是先定义再运行(define and run),一次定义多次运行,静态图一旦创建则不能修改,如果修改了图的结构,则必须重新开始运行,而动态计算图是在运行的过程中被定义,在运行的时候构建(define by run),可以多次构建多次运行。以静态计算图为代表的深度学习框架是TensorFlow,动态计算图为代表的是PyTorch。下图则展示了PyTorch动态构建计算图的过程。

dynamic_graph

静态图在构建的时候必须把所有可能出现的情况都要包含进去,则也导致了静态图过于庞大,可能占用过高的显存,而且静态图的定义无法使用if、while、for 等常用的python语句,不得不为这些操作专门设计语法。而动态计算图则没有这个问题,它可以使用原生自带的if、while、for等条件语句,最终创建的计算图取决于你执行的条件分支。
我们来看下if语句在TensorFlow和Pytorch中两种实现方式。

  • PyTorch

    {.line-numbers}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import torch
    from torch.autograd import Variable

    N,D,H =3,4,5

    X = Variable(torch.randn(N,D))
    W1 = Variable(torch.randn(N,H))
    W1 = Variable(torch.randn(N,H))

    z = 10

    y = X.mm(W1) if z>0 else X.mm(W2)
  • TensorFlow

    {.line-numbers}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import tensorflow as tf
    import numpy as np

    N,D,H = 3,4,5
    X = tf.placeholder(tf.float32,shape=(N,D))
    W1 = tf.placeholder(tf.float32,shape=(D,H))
    W2 = tf.placeholder(tf.float32,shape=(D,H))
    z = tf.placeholder(tf.float32,shape=None)

    def f1():
    return tf.matmul(X,W1)
    def f2():
    return tf.matmul(X,W2)
    y = tf.cond(tf.less(z,0),f1,f2)
    with tf.Session() as sess:
    values = {
    X:np.random.randn(N,D),
    z:10,
    W1:np.random.randn(D,H),
    W2:np.random.randn(D,H)
    }
    y_val = sess.run(y,feed_dict=values)

动态计算图可以使我们任意修改前向传播,可以随时查看和修改变量的值,十分灵活,这样的特性带来的另外一个优势是调试更方便,Pytorch中报错的地方,往往就是写错代码的地方,而静态图先构建图(Graph对象,构建图的过程不报错,然后在session.run()的时候报错,这种报错很难定位到源代码中真正出错的地方,更像在操作一个黑箱。

2017年在PyTorch发布不久后,OpenAI的科学家,Tesla的AI部门主管Andrej Karpathy发了一篇Twitter调侃道:

I’ve been using PyTorch a few months now. I’ve never felt better,I have more energy. My skin is clearer. My eye sight has improved.

人生苦短,我用PyTorch.
the_real_reason.png

Tensor

Tensor 是PyTorch中一个重要的数据结构,可以认为是一高维数组。它可以是标量(一位数)、向量(一维数组)、矩阵(二维数组)以及更高维的数组。Tensor和numpy的ndarray类似,二者可以互相自由转化。但Tensor可以使用GPU进行加速运算。下面通过几个例子来看下Tensor的基本用法。

1
2
3
# 查看PyTorch的版本
import torch
torch.__version__
1
'0.4.1'
1
2
3
4
# 构建5*3的矩阵
import numpy as np
x0 = torch.Tensor(5,3)
x0
1
2
3
4
5
tensor([[-1.2792e+36,  3.0679e-41,  5.7453e-44],
[ 0.0000e+00, nan, 0.0000e+00],
[ 1.3733e-14, 6.4076e+07, 2.0706e-19],
[ 7.3909e+22, 2.4176e-12, 1.1625e+33],
[ 8.9605e-01, 1.1632e+33, 5.6003e-02]])

可以看到torch.Tensor()方法默认生成指定维度的随机浮点数,默认的类型为torch.FloatTensor

1
2
3
4
#查看tensor的大小
x0.size()
print(x0.size())
print("Tensor的行数:{r},列数:{c}".format(r=x0.size(0),c=x0.size(1)))
1
2
torch.Size([5, 3])
Tensor的行数:5,列数:3

torch.Size 是tuple对象的子类,因此它支持tuple的所有操作,如x.size()[0]

1
2
3
# 使用[0,1]均匀分布随机初始化Tensor
x1 = torch.rand(5,3)
x1
1
2
3
4
5
tensor([[0.4395, 0.7811, 0.9241],
[0.0098, 0.5738, 0.6705],
[0.9428, 0.7089, 0.1504],
[0.7572, 0.4142, 0.6234],
[0.1544, 0.4454, 0.5211]])
1
2
# 加法的三种形式
x0+x1
1
2
3
4
5
tensor([[-1.2792e+36,  7.8115e-01,  9.2413e-01],
[ 9.7851e-03, nan, 6.7051e-01],
[ 9.4281e-01, 6.4076e+07, 1.5038e-01],
[ 7.3909e+22, 4.1425e-01, 1.1625e+33],
[ 1.0504e+00, 1.1632e+33, 5.7714e-01]])
1
x0.add(x1)
1
2
3
4
5
tensor([[-1.2792e+36,  7.8115e-01,  9.2413e-01],
[ 9.7851e-03, nan, 6.7051e-01],
[ 9.4281e-01, 6.4076e+07, 1.5038e-01],
[ 7.3909e+22, 4.1425e-01, 1.1625e+33],
[ 1.0504e+00, 1.1632e+33, 5.7714e-01]])
1
2
3
# 指定加法结果的输出目标为result
result= torch.Tensor(5,3) # 预先定义变量,分配空间
torch.add(x0,x1,out=result) # 结果保存到result
1
2
3
4
5
tensor([[-1.2792e+36,  7.8115e-01,  9.2413e-01],
[ 9.7851e-03, nan, 6.7051e-01],
[ 9.4281e-01, 6.4076e+07, 1.5038e-01],
[ 7.3909e+22, 4.1425e-01, 1.1625e+33],
[ 1.0504e+00, 1.1632e+33, 5.7714e-01]])

下面我们看下另外一种写法,函数名后面带下划线_ ,这种带下划线的函数会修改Tensor本身的内容。
例如x0.add_(x1)会改变x0,而x0.add(x1)则会返回一个新的Tensor,而x0不变。

1
print(x0)
1
2
3
4
5
tensor([[-1.2792e+36,  3.0679e-41,  5.7453e-44],
[ 0.0000e+00, nan, 0.0000e+00],
[ 1.3733e-14, 6.4076e+07, 2.0706e-19],
[ 7.3909e+22, 2.4176e-12, 1.1625e+33],
[ 8.9605e-01, 1.1632e+33, 5.6003e-02]])
1
x0.add_(x1)
1
2
3
4
5
tensor([[-1.2792e+36,  7.8115e-01,  9.2413e-01],
[ 9.7851e-03, nan, 6.7051e-01],
[ 9.4281e-01, 6.4076e+07, 1.5038e-01],
[ 7.3909e+22, 4.1425e-01, 1.1625e+33],
[ 1.0504e+00, 1.1632e+33, 5.7714e-01]])
1
x0
1
2
3
4
5
tensor([[-1.2792e+36,  7.8115e-01,  9.2413e-01],
[ 9.7851e-03, nan, 6.7051e-01],
[ 9.4281e-01, 6.4076e+07, 1.5038e-01],
[ 7.3909e+22, 4.1425e-01, 1.1625e+33],
[ 1.0504e+00, 1.1632e+33, 5.7714e-01]])

Tensor和Numpy的数组之间的可以无缝转换,对于Tensor不支持的操作,我们可以先转为Numpy数组,处理后再转回Tensor。

1
2
3
# 初始化一个全为1的Tensor
x2 = torch.ones(5)
x2
1
tensor([1., 1., 1., 1., 1.])
1
2
3
#Tensor对象 转 numpy 对象
x2_np = x2.numpy()
x2_np
1
array([1., 1., 1., 1., 1.], dtype=float32)
1
x2.add_(x2)
1
tensor([2., 2., 2., 2., 2.])
1
x2_np
1
array([2., 2., 2., 2., 2.], dtype=float32)

我们可以看到x2被修改后,x2_np对象也被修改了,这是因为Tensor和numpy对象共享内存,所以他们之间的转换很快,而且几乎不会消耗什么资源。但其中一个改变了。另外一个也会被修改。

1
2
3
4
# numpy 对象转 Tensor
x3_np = np.array([[1,2,3]])
x3_0 = torch.tensor(x3_np)
x3_1 = torch.from_numpy(x3_np)

x3_0和x3_1 有什么区别?我们先对x3_0和x3_1 进行add_操作,改变对象的值,再看看x3_np 有什么变化。

1
2
x3_0.add_(1)
print(x3_0,x3_np)
1
tensor([[2, 3, 4]]) [[1 2 3]]
1
2
x3_1.add_(1)
print(x3_1,x3_np)
1
tensor([[2, 3, 4]]) [[2 3 4]]

我们发现,通过torch.tensor()将Numpy对象转成Tensor对象,二者并不共享内存数据,torch.Tensor()总是先将Numpy数据拷贝一份。而torch.form()则直接引用原先Numpy数据。
Tensor可以通过cuda方法将CPU数据转化为GPU数据,从而可以使用GPU进行加速运算。

1
tensor = tensor.cuda if torch.cuda.is_available() else tensor

autograd: 自动微分

深度学习的本质是通过反向传播求导来减小误差,而Pytorch的autograd模块实现了此功能。Tensor上的所有操作,autograd都能为他们自动提供微分,避免手动求导的复杂过程。
从pytorch 0.4 起,Variable 正式并入 Tensor, 现在还可以使用旧版本的Variable,但Variable(tensor) 其实什么也没做
要想使Tensor对象使用autograd功能,只需要设置renor.requries_grad = True

1
2
x = torch.from_numpy(np.array([[1,2,3,4]],dtype=np.float))
x.requires_grad=True
1
2
3
y = x.sum()
y.backward()# 反向传播
x.grad # 计算梯度
1
tensor([[1., 1., 1., 1.]], dtype=torch.float64)

$y = {x_1} + {x_2} + {x_3} + {x_4}$,对$x_i$分别求偏导,每个值都是1.

1
y
1
tensor(10., dtype=torch.float64, grad_fn=<SumBackward0>)
1
2
3
y = x.sum()
y.backward() # 反向传播
x.grad # 计算梯度
1
tensor([[2., 2., 2., 2.]], dtype=torch.float64)

反向传播的过程是累加的(accumulated)。这意味着每次一次反向传播,梯度都会累加之前的梯度,所有反向传播之前记得把梯度清零

1
2
3
# 以下划线结束的函数是inplace操作,会修改自身的值
x.grad.data.zero_()
x.grad
1
tensor([[0., 0., 0., 0.]], dtype=torch.float64)
1
2
y.backward()
x.grad
1
tensor([[1., 1., 1., 1.]], dtype=torch.float64)

下面这个例子可能更深刻。求$y = 4\sqrt {(x_1^4 + x_2^4)} $在x=(1,1)处的偏导数。
image2.png

1
2
3
4
x = torch.ones(2,requires_grad=True)
z=(4*x*x)
y= z.norm()
y.data.item()
1
5.656854152679443
1
2
y.backward() #反向传播
x.grad # 求导
1
tensor([5.6569, 5.6569])

我们可以看到x.grad也与我们上图计算结果一致。

神经网络(neural network)

Autograde 实现了反向传播的功能,但是直接用来实现神经网络还是稍显复杂。torh.nn 是专门为神经网络设计的模块化接口。torch.nn 构建于Autograd之上,可用来定义和运行神经网络。nn.Module是nn种最重要的类,可把它看成是一个网络的封装,包含网络各层定义以及forward方法,调用forward(input)方法,可返回前向传播的结果。下面就以最早的卷积神经网络LeNet为例,来看看如何实现nn.Module实现。LeNet的网络结果如图所示:
nn_lenet.png
LeNet-5出自论文Gradient-Based Learning Applied to Document Recognition,是一种用于手写体字符识别的非常高效的卷积神经网络。

定义网络

定义网络的时候,需要继承nn.Module,并实现它的forward方法,把网络中具有可学习参数的层放在构造函数__init__中。如果某一层不具有可学习参数(如ReLU层),则建议不建议放在构造函数中,而是在forward中使用nn.functional代替。

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
import torch.nn as nn
import torch.nn.functional as F

class LeNet(nn.Module):
def __init__(self):
super(LeNet,self).__init__()
# 卷积层1 输入通道数1(单通道),输出通道数6,卷积核大小5
self.conv1 = nn.Conv2d(in_channels=1,out_channels=6,kernel_size=5)

#卷积层2 输入通道数6(单通道),输出通道数16,卷积核大小5
self.conv2 = nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5)

# 全连接层,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):
# 卷积层-> 激活层-> 池化层
# conv2d-> relu-> pool2d
x = F.max_pool2d(input=F.relu(self.conv1(x)),kernel_size=2)
x = F.max_pool2d(input=F.relu(self.conv2(x)),kernel_size=2)

# reshape '-1' 表示自适应
x = x.view(x.size()[0],-1)
x = F.relu(self.fc1(x)) # 全连接-> 激活
x = F.relu(self.fc2(x)) # 全连接-> 激活
x = self.fc3(x) #全连接
return x
1
2
lenet = LeNet()
lenet
1
2
3
4
5
6
7
LeNet(
(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)
)

只要在nn.Module的子类中定义了forward函数,backward函数就会自动被实现(利用autograd)。在forward函数中可以使用任何tensor支持的函数,还可以使用if、for、log等任何python 支持的语法。
网络的参数可以通过lenet.parameters()返回,也可通过lenet.named_parameters()返回参数名和参数值。

1
2
for name,parameters in lenet.named_parameters():
print(name,':',parameters.size())
1
2
3
4
5
6
7
8
9
10
conv1.weight : torch.Size([6, 1, 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])
1
2
3
input = torch.randn(1,1,32,32)
out = lenet(input)
out
1
2
tensor([[-0.0417, -0.0039, -0.1051,  0.1119,  0.0675,  0.0071,  0.0435,  0.0524,
-0.0480, -0.0928]], grad_fn=<ThAddmmBackward>)
1
input
1
2
3
4
5
6
7
tensor([[[[ 0.8405, -0.9849,  0.2180,  ...,  0.1453, -1.0257,  0.1306],
[-1.0547, 1.2620, -1.1557, ..., 0.3912, -0.5614, 0.4352],
[-1.2492, 0.3228, -1.0925, ..., -0.2140, -0.4023, -1.5622],
...,
[ 0.4966, 0.4494, 0.2998, ..., 1.2110, -0.4863, -1.0164],
[ 0.8932, -1.0220, 0.0874, ..., 0.8751, 1.7069, -0.6414],
[ 1.0658, 0.6862, 0.2639, ..., 1.2042, 0.2181, 0.5975]]]])
  • 关于input

需要注意的是,torch.nn只支持mini-batches,不支持一次只输入一个样本,即一次必须是一个batch。但如果只想输入一个样本,则用 input.unsqueeze(0)将batch_size设为1。例如 nn.Conv2d 输入必须是4维的,形如$nSamples \times nChannels \times Height \times Width$。可将nSample设为1,即$1 \times nChannels \times Height \times Width$。

损失函数

nn 实现了神经网络中绝大多数的损失函数,例如nn.MSELoss用来计算均方误差,nn.CrossEntropyLoss用来计算交叉熵损失。

1
2
3
4
5
output = lenet(input)
target = torch.arange(0.0,10.0).view(1,10)
criterion = nn.MSELoss()
loss = criterion(output,target)
loss
1
tensor(28.5375, grad_fn=<MseLossBackward>)

当调用loss.backward()的时候,该图会动态生成并自动微分,也即会自动计算图中参数(Parameter)的导数。

1
2
3
4
5
6
7
 # 误差反向传播前先把网络中可学习参数的梯度置零
lenet.zero_grad()
print("误差反向传播前的conv1.bias的梯度")
print(lenet.conv1.bias.grad)
loss.backward()
print("误差反向传播后的conv2.bias的梯度")
print(lenet.conv1.bias.grad)
1
2
3
4
误差反向传播前的conv1.bias的梯度
None
误差反向传播后的conv2.bias的梯度
tensor([ 0.0069, 0.0428, 0.1053, -0.1353, -0.0520, 0.0168])

优化器

在反向传播计算完所有的参数的梯度后,还需要使用优化方法来更新网络的权重和参数,例如随机梯度下降法(SGD)的策略来更新:
$$w_i+1 = w_i - lr \times grad$$
代码实现如下:

1
2
3
learning_rate = 0.01
for f in lenet.parameters():
f.data.sub_(f.grad.data*learning_rate) # inplace操作

在torch.optime模块中实现了深度学习的绝大多数优化方法,例如RMSEProp、Adam、SGD等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch.optim as optim
# 新建一个参数优化器 ,指定优化的参数、学习率
optimizer = optim.SGD(lenet.parameters(),lr=0.01)

# 在训练的时候,先将梯度置零
optimizer.zero_grad() # 等价于 lenet.zero_grad()

# 计算损失
output = lenet(input)
loss = criterion(output,target)

# 误差反向传播
loss.backward()

# 更新参数
optimizer.step()

常用数据集

在深度学习中,数据加载和预处理是非常繁琐的,但PyTorch提供了一些可以极大简化和加载数据处理流程的工具。同时,对于常用的数据集,PyTorch也提供了封装好的接口供用户快速调用。这些数据集主要保存在torchvison中。

torchvision实现了常用的图像数据加载功能,例如imagenet、CIFAR10、MNIST等。以及常用的数据转换操作,这极大的方便了数据加载和预处理,并且代码具有可重用性。

小试牛刀 :CIFAR-10分类

  • CIFAR-10数据集

该数据集共有60000张彩色图像,它有10个类别: ‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。每张图片都是$3\times32\times32$,也即3-通道彩色图片,分辨率为$32\times32$。每类6000张图。这里面有50000张用于训练,构成了5个训练批,每一批10000张图;另外10000用于测试,单独构成一批。测试批的数据里,取自10类中的每一类,每一类随机取1000张。抽剩下的就随机排列组成了训练批。注意一个训练批中的各类图像并不一定数量相同,总的来看训练批,每一类都有5000张图。

![cifar.png]https://images2018.cnblogs.com/blog/1196151/201712/1196151-20171225161744462-2083152737.png)

下面我们来实现对CIFAR-10数据集的分类,步骤如下:

  1. 使用torchvision 加载并预处理CIFAR-10数据集
  2. 定义网络
  3. 定义损失函数和优化器
  4. 训练网络并更新网络参数
  5. 测试网络
1
2
3
4
import torchvision as tv
import torchvision.transforms as transforms
from torchvision.transforms import ToPILImage
show = ToPILImage() # 可以将Tensor 转为Image 方便可视化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 定于数据的预处理方法
transform = transforms.Compose([
transforms.ToTensor(),# 第一步先转化为Tensor
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)) , # 第2步 归一化
]
)

# 训练集
trainset = tv.datasets.CIFAR10(
root="/home/fltsettlement/zenwan/jupyternote/data/",
train=True,
download=False,
transform=transform
)
trainloader = torch.utils.data.DataLoader(
trainset,
batch_size =4,
shuffle = True,
num_workers = 2
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 测试集
testset = tv.datasets.CIFAR10(
root="/home/fltsettlement/zenwan/jupyternote/data/",
train=False,
download=False,
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')

DataSet 是一个数据集,可以按照下标访问,返回形式如(data,label)的数据。

1
2
3
4
(data,label) = trainset[100]
print(classes[label])
# (data+1)/2 是为了还原归一化后的数据
show((data+1)/2).resize((100,100))

DataLoader是一个可迭代的对象,它将dataset返回的每一条数据拼接成一个batch,并提供多线程加速优化和数据混洗等操作。当程序对DataSet的所有数据遍历完一遍后,相应的DataLoader 也就完成了一遍迭代。

1
2
3
4
dataiter = iter(trainloader)
images,labels = dataiter.next() # 返回1batch,共4张图片
print(" ".join([classes[labels[j]] for j in range(4)]))
show(tv.utils.make_grid((images+1)/2)).resize((100*4,100))

重新定义网络

拷贝之前定义的LeNet网络结构,修改第一个卷积层的输入通道数为3,因为CIFAR-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
import torch.nn as nn
import torch.nn.functional as F

class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(3, 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(x.size()[0], -1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x


lenet = LeNet()
print(lenet)
1
2
3
4
5
6
7
LeNet(
(conv1): Conv2d(3, 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)
)

定义损失函数(loss function)和优化器(optimizer)

损失函数,又叫目标函数,是编译一个神经网络模型必须的两个参数之一,另外一个必不可少的是优化器。
pytorch的优化器都在torch.optim模块中。
常用的optimizer有SGD、Adam、Adadelta、Adagrad、Adamax等。
我们这里选择SGD(stochastic gradient descent),即随机梯度下降法,参数lr为学习率,momentum为动量因子,parameters为需要优化的对象。

1
2
criterion = nn.CrossEntropyLoss() # 交叉信息熵损失函数
optimizer = optim.SGD(lenet.parameters(), lr=0.001, momentum=0.9)

训练网络

训练网络的步骤都是类似的,不断执行下面流程:

  • 输入数据
  • 前向传播+反向传播
  • 更新参数
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
from tensorboardX import SummaryWriter
writer = SummaryWriter('./logs/chapter2')
_inputs, _labels = iter(trainloader).__next__()
writer.add_graph(lenet,input_to_model=_inputs)

torch.set_num_threads(8)
niter=0
for epoch in range(5):
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# 输入数据
inputs, labels = data
if torch.cuda.is_available():
inputs = inputs.cuda()
labels = labels.cuda()

# 梯度清零
optimizer.zero_grad()

# forward + backward
outputs = lenet(inputs)
loss = criterion(outputs, labels.long())
loss.backward()

# 更新参数
optimizer.step()

# 打印log信息
# loss 是一个scalar,需要使用loss.item()来获取数值,不能使用loss[0]
running_loss += loss.item()
writer.add_scalar('Train/Loss', loss.item(), niter)
niter+=1
if i % 1000 == 0: # 每500个batch打印一下训练状态
print('[%d, %5d] loss: %.3f' % (epoch , i , running_loss / 2000))
running_loss = 0.0
correct = 0
total = 0
for testdata in testloader:
test_imgs,test_labels = testdata
test_outpus = lenet(test_imgs)
_, predicted = torch.max(test_outpus,1)
total += test_labels.size(0)
correct += (predicted.data.numpy()==test_labels.data.numpy()).sum()
writer.add_scalar('Test/Accu', correct / total, niter)
print("acc",correct*1.0 / total)
print('Finished Training')
writer.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[0,     0] loss: 0.001
acc 0.1
[0, 1000] loss: 1.149
acc 0.1551
[0, 12000] loss: 0.736
acc 0.4635
[1, 12000] loss: 0.659
acc 0.5231
[2, 0] loss: 0.001
acc 0.5343
[2, 12000] loss: 0.608
acc 0.5579
[3, 0] loss: 0.001
acc 0.5717
[3, 12000] loss: 0.571
acc 0.5737
[4, 0] loss: 0.000
acc 0.5701
[4, 12000] loss: 0.527
acc 0.6094
Finished Training

经过简单的5轮训练,准确率达0.6,比随机猜测1/10的概率要大很大,看来我们的网络的确学习到了有用信息。