【23】基于全卷积网络的图像分类

需要解决的问题

在智慧芽的官网上,GB 专利的历史数据(上世纪七八十年代)的摘要附图中有大量错误抠取的图片,客户体验较差。因此需要尽快清除这些脏数据。以下是示例图片(本地数据集截图)。

从官网直接批量拉取的摘要附图:

ds-all

其中可以看到有大量非摘要附图的图片,包括文字,空白,logo,信息表格。以下是三张脏数据的示例图:

ds-dirty-1

ds-dirty-2

ds-dirty-3

这是需要保留的正确的摘要附图:

ds-clean

难点

如果使用传统统计学方法,例如 PCA,kNN,小波变换之类的算法进行筛选,并非不可能。但是这两类图片之间的差异比较难以量化的描述,需要算法编写者精通统计学和各种算法才可能完成。

所以我选择了一条相对简单的方法,也就是交给机器学习。炼丹还是要比正儿八经写算法简单的多了。

模型介绍

机器学习框架我采用了 Pytorch。废话不多说,直接上模型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
model = nn.Sequential(
    nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=0, dilation=1),
    nn.BatchNorm2d(16),
    nn.ReLU(),
    nn.Conv2d(16, 16, kernel_size=3, stride=1, padding=0, dilation=2),
    nn.AvgPool2d(2),
    nn.BatchNorm2d(16),
    nn.ReLU(),
    nn.Conv2d(16, 16, kernel_size=3, stride=1, padding=0, dilation=4),
    nn.AvgPool2d(2),
    nn.BatchNorm2d(16),
    nn.ReLU(),
    nn.Conv2d(16, 16, kernel_size=3, stride=1, padding=0, dilation=8),
    nn.AvgPool2d(2),
    nn.BatchNorm2d(16),
    nn.ReLU(),
    nn.Conv2d(16, 2, kernel_size=3, stride=1, padding=0, dilation=1),
    nn.BatchNorm2d(2),
    nn.ReLU(),
    nn.AdaptiveAvgPool2d(1),
    View(),
)

在送入网络之前,所有图片都会被 resize 到 200×200 的大小,并且转换为灰度图片(单通道)。网络最后的 View() 层是用来做 view 操作的,把输出张量调整成一个向量。

一些 (xjb) 分析

Normalization

标准化可以说是我在自己折腾这些图片分类的时候最早碰到的一大难题了。在 Pytorch 中,标准化的函数形式是 torchvision.transforms.Normalize(mean, std, inplace=False) 。显然,其中需要我填写两个参数,均值和标准差。最早我并不知道他们的意义何在,所以参照各种博客和教程的说法使用了 mean=[0.5], std=[0.5] 的参数使值域映射到 $[-1, 1]$ 的范围内。结果训练效果奇差。

于是我自己摸索这个标准化参数,最后发现使用 mean=[1], std=[0.5] 的时候反而效果非常好。

查阅文档以后,我才知道这个函数的实际作用是

$input[channel] = \frac{(input[channel] - mean[channel])}{std[channel]}$

Normalization 的正确做法应当是使得标准化后的数据均值落在 0 附近,至于标准差,对于单通道的黑白图片并不重要,彩色图片的话需要控制标准差使得多通道标准化到同一区间。原本图像的每个像素点的取值是 $[0, 1]$,按照 [1], [0.5] 的参数,其实是把值域映射到了 $[-2, 0]$ 的区间,为什么反而效果好?因为这个分类任务中使用的图片非常特殊,都是从 pdf 文档中截出来的图。这类数据集的特征就是空白区域非常多,换句话说整个数据集的均值非常靠近 1,实际在 0.95 左右。所以使用 [0.5], [0.5] 的参数后,均值依旧远离 0,而 [1], [0.5] 反倒是把均值拉到了 0 附近。

所以教训就是,在每次训练的时候都应该根据实际数据集的分布,去调整这两个参数。(所以为啥 Pytorch 不加个自动标准化的 feature 呢…)

Dilated Convolution

在模型里我用了我个人很喜欢的空洞卷积。最早是在谷歌的 WaveNet 里看来的,觉得非常神奇。在空洞卷积中,按照指数增长空洞率,就可以随层数指数增长感受野。而在普通的卷积网络中,感受野随层数是线性增长的。

在这个分类任务中,图像的特征差别还是非常大的,至少我们可以从缩略图一眼辨认出这张图是不是脏数据。所以对于神经网络来说,获取大尺度的感受野以及多尺度的感受野对正确分类非常重要。然而为了控制参数数量避免难以训练和过拟合的问题,无限制加深层数是不行的,那么空洞卷积正好就派上了用场。虽然这样设置空洞参数(1,2,4,8)显然会引起 gridding 也就是棋盘效应,不过最终效果非常棒,所以我猜测足够明显的特征下,就算丢失一部分局部信息也并不影响结果。

Global Average Pooling

传统 CNN 总是会在最后加几个全连接层计算最后的输出。但是问题在于,全连接层的参数数量非常可怕。例如经典的 VGG16 网络,最后的全连接层的参数数量占到了总数量的将近 90%。这带来的问题就是难以训练和易于过拟合。所以我就用了之前看来的骚操作,全局池化。

在最后一个卷积层,我把 feature map 的数量降到目标预测类别的数量,也就是 2 个。在经过 BatchNorm 和 ReLu 后,直接把整个 feature map 取平均得到一个数。这两个数经过 Softmax 以后,就是这个两个对应类别的预测置信度。这样一来,整个全连接网络都被省掉了,不仅容易训练,还不容易过拟合,性能还好,可以说超级好用了。

结果与性能

Tensorboard 可视化训练记录

loss

acc

可以看到,拟合的速度非常快,最终 acc 达到了 99% 以上。而且 train acc 和 validation acc 几乎完全一致,没有过拟合。(就是学习率调的太高了点)

测试预测

在本地对 10 张随机样本做测试预测。

test-ds

结果:

test-result

可见结果非常准确。

性能测试

我本地配置是 i5-4590 (4C4T, 3.3-3.7GHz),8 GB DDR3,无 GPU,操作系统是 Windows 10 1903。

perf

在一颗五年前的中端 cpu 上,性能达到了每张仅需 0.023 秒,可以说是非常优秀的了。

最终效果

最后放一张官网的截图,现在已经基本没有脏数据啦。

sample


本文阅读量
本站访客量