一般,深度学习的教材或者是视频,作者都会通过 MNIST 这个数据集,讲解深度学习的效果,但这个数据集太小了,而且是单色图片,随便弄些模型就可以取得比较好的结果,但如果我们不满足于此,想要训练一个神经网络来对彩色图像进行分类,可以不可以呢?
当然可以的,但是没有想象的容易。
我最开始亲自设置神经网络去训练时,训练准确度还不到 30%,并且不能收敛。后来逐步运用自己对于深度学习的理解去不断调整网络的结构,同时采用有效的优化手段,让自己的模型可以攀升到 50%,然后又提高到 70%,最后达到训练时 99% 的准确度,测试时达到 85% 的准确度,所以这个模型也见证了我自己深度学习过程的能力成长。也是我分享这篇文章的用意。
那么,神经网络的训练一般要进行哪些步骤呢?
上图说明了一般有监督学习的训练过程。
- 加载数据集,并做预处理。
- 预处理后的数据分为 feature 和 label 两部分,feature 送到模型里面,label 被当做 ground-truth。
- model 接收 feature 作为 input,并通过一系列运算,向外输出 predict。
- 通过以 predict 和 predict 为变量,建立一个损失函数 Loss,Loss 的函数值是为了表示 predict 与 ground-truth 之间的差距。
- 建立 Optimizer 优化器,优化的目标就是 Loss 函数,让它的取值尽可能最小,loss 越小代表 Model 预测的准确率越高。
- Optimizer 优化过程中,Model 根据规则改变自身参数的权重,这是个反复循环和持续的过程,直到 loss 值趋于稳定,不能在取得更小值。
注意:本篇文章,用 PyTorch 进行开发,其实其它深度学习框架如 TensorFlow 也能轻易实现。
由于是基于 PyTorch 代码说明,所以我假定读者对于 PyTorch 这个深度学习框架具备基本的了解。
1.加载数据集
数据集的加载,我们可以自行编写代码,但如果是基于学习的目的的话,那么把经历放在编写这个步骤的代码上面会让人十分崩溃与无聊。
好在,pytorch 提供了非常方便的包 torchvision.
torchvison 提供了 dataloader 去加载常见的 MNIST、CIFAR-10、ImageNet 等数据集,也提供了 transform 去对图像进行变换、正则化和可视化。
核心包:torchvision.datasets、torch.utils.data.DataLoader
在本文中,我们的目的是用 pytorch 创建基于 CIFAR-10 数据集的图像分类器。
CIFAR-10 有 50000 张训练图片,10000 张测试图片,总共 10 个类别,分别是 ‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’
CIFAR-10 图片的尺寸是 32x32x3,所以它是比较小的彩色图片。
如何加载呢?
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
import torch.optim as optim
import time
import os
transform = transforms.Compose(
[
transforms.RandomHorizontalFlip(),
transforms.RandomGrayscale(),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
transform1 = 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=100,
shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform1)
testloader = torch.utils.data.DataLoader(testset, batch_size=50,
shuffle=False, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
transform 在上面的代码的作用主要是用来对数据进行预处理。
transforms.RandomHorizontalFlip(),
transforms.RandomGrayscale(),
这两句代码主要是用来做数据增强的,为了防止训练出现过拟合,通常在小型数据集上,通过随机翻转图片,随机调整图片的亮度,来达到增加训练时数据集的容量。
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
数据集加载时,默认的图片格式是 numpy,所以通过 transforms 转换成 Tensor。
然后,再对输入图片进行标准化。
但是,测试的时候,并不需要对数据进行增强,所以它们的转换还是有点不同。
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
torchvision.datasets.CIFAR10 就指定了 CIFAR-10 这个数据集,这个模块定义了它如何去下载数据集,及如何从本地加载现成的数据。
root 指定了数据集存放的位置,train 指定是否是训练数据集。
trainloader = torch.utils.data.DataLoader(trainset, batch_size=100,
shuffle=True, num_workers=2)
数据集需要配合 DataLoader 使用,DataLoader 从数据集中不断提取数据然后送往 Model 进行训练和预测。
在这里,指定了 batch size 为 100,也就是 mini-batch 单批次图片的数量为 100.
shuffle = True 表明提取数据时,随机打乱顺序,因为我们都是基于随机梯度下降的方式进行训练优化,但测试的时候因为不需要更新参数,所以就无须打乱顺序了。
num_workers = 2 指定了工作线程的数量。
运行代码后,会自动下载数据集,并存放在当前目录下的 data 文件中。
Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data/cifar-10-python.tar.gz
Files already downloaded and verified
2. 定义神经网络
我们可以再看看文章开始的那张图,Model 是核心部分,而神经网络就是一个 Model。
而在本篇博文中,我创建的神经网络层次还比较深,是 VGG 的翻版,目的是想让测试的准确率更加高一点。但我没有完全参照 VGG-16,因为我的 GPU 是 GTX1080i,当时在 Tensorflow 上实验时爆出了内存不够的问题,所以我在 VGG-16 的基础上减少了卷积核的数量。
VGG 的最大特征是大量采用 3x3 尺寸的卷积核,更详细的信息可以参考我的博文。【深度学习】经典神经网络 VGG 论文解读
input (32x32x3) color image |
---|
Conv1 3x3,64 |
Conv2 3x3,64 |
Maxpool 2x2,strides=2 |
Batch Normalization |
Relu |
– |
Conv3 3x3,128 |
Conv4 3x3,128 |
Maxpool 2x2,strides=2 |
Batch Normalization |
Relu |
– |
Conv5 3x3,128 |
Conv6 3x3,128 |
Conv7 1x1,128 |
Maxpool 2x2,strides=2 |
Batch Normalization |
Relu |
– |
Conv8 3x3,256 |
Conv9 3x3,256 |
Conv10 1x1,256 |
Maxpool 2x2,strides=2 |
Batch Normalization |
Relu |
– |
Conv11 3x3,512 |
Conv12 3x3,512 |
Conv13 1x1,512 |
Maxpool 2x2,strides=2 |
Batch Normalization |
Relu |
– |
FC14 (8192,1024) |
Dropout |
Relu |
FC15 (1024,1024) |
Dropout |
Relu |
FC16 (1024,10) |
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
self.conv1 = nn.Conv2d(3,64,3,padding=1)
self.conv2 = nn.Conv2d(64,64,3,padding=1)
self.pool1 = nn.MaxPool2d(2, 2)
self.bn1 = nn.BatchNorm2d(64)
self.relu1 = nn.ReLU()
self.conv3 = nn.Conv2d(64,128,3,padding=1)
self.conv4 = nn.Conv2d(128, 128, 3,padding=1)
self.pool2 = nn.MaxPool2d(2, 2, padding=1)
self.bn2 = nn.BatchNorm2d(128)
self.relu2 = nn.ReLU()
self.conv5 = nn.Conv2d(128,128, 3,padding=1)
self.conv6 = nn.Conv2d(128, 128, 3,padding=1)
self.conv7 = nn.Conv2d(128, 128, 1,padding=1)
self.pool3 = nn.MaxPool2d(2, 2, padding=1)
self.bn3 = nn.BatchNorm2d(128)
self.relu3 = nn.ReLU()
self