前言
深度学习近两年来发展极为迅速,在计算能力大大提高的情况下,很多深度学习方向的思想都得以实现。但是,深度学习有一个令人头疼的缺点,那就是需要大量样本来进行训练才能达到较好的泛化。
虽然我们有迁移学习可以适当减少我们需要的数据量,亦或者,我们可以通过数据增强的方式来翻倍我们的数据:
图像增强方法最直接了当,但是为什么需要那么多的数据呢,从直观上理解很简单,和人相似,学习到足够多的东西才能举一反三。当然这里指的足够多的东西是一个数据集中包含多种多样的数据,包含不同情况不同形态的尽可能丰富的数据。
数据集的分布曲线应该平滑
如果你只学习到一个场景,这个场景的数据很多,很容易造成神经网络“过度学习”,也就是所谓的学的太过了,举一反三的本领太强。举个例子,在目标检测的一个简单的任务中,我们要识别一个木板(board)和参照物(reference),下图就是一个正确的识别结果:
但假如我们的数据集是这样的(冰山一角):
发现什么了?在数据集中我们的参照物往往都放置在木板的上面,只是换了摆放的位置和拍摄的角度,也就是所有的参照物下面都有一个木板。
那么,如果我们的数据集都是这样的,然后我们进行了训练,随后,取一张没有木板的光有参照物的图像(这种图像在数据集中没有或者很少)做测试:
看看发生了什么?我们训练的这个神经网络太会举一反三了,认为只要有这个参照物,参照物下面的总是木板,于是就出现了这样的情况。想要消除这种情况,数据集中木板和参照物的数据必须多元化,足够多,不能光满足一种情况下的数据量,需要多种情况多种条件下的数据。
深度学习圣经有句话:常用的隐示”先验”是平滑先验或局部不变性,这个先验表明我们学习的函数不应该在小区域内发生很大的变化。也就是说学习的函数应该是平滑的,不应该“偏科”。另外,只要学习的真实函数的峰值和谷值处有足够多的样本,那么平滑性假设和相关的无参数学习算法的效果都非常好,这也就是数据量丰富的优势。
但对于人工智能领域这些比较复杂的算法任务来说,我们想要获得更好的结果的直接途径,除了改进算法,那就是扩充你的数据集了。
关于神经网络的旋转不变形
这里有一个简单的手写数据识别网络的例子,LaNet5数字识别网络,一个拥有七层的简单的神经网络,但却包含了一整套神经网络的雏形。
LeNet手写数字识别网络中有一个对图像变化的简单测试,利用普通的数字图像训练好数据后,拿经过变换后的数据尝试进行识别,
以下分别是数字图经过平移、缩放、旋转后的识别情况,我们可以看到,在经过简单的平移(数字简单移动15px左右)、缩放和旋转(左右各40度)神经网络仍然可以进行识别,但是如果变形的太过分,识别就失效了。
为什么会出现这种情况,我们看下LaNet5的基本结构:
我们可以看到整个神经网络一共有7层(不包括输入层),其中有两个是池化层(pooling layer),LaNet-5使用的是平均池化方法,在网络中起到了降维的作用。
其实,为什么神经网络也可以识别数据集中微小的变形呢?说白了是因为pooling
操作在降维的同时起到了一定的(很微小)旋转不变性的作用。
比如在我们检测一个物体的时候,神经网络的activation层
在这个物体的边缘处发生了比较明显的变化,当这个物体稍微旋转一下,物体的边缘稍稍移动时,由于max-pooling
的缘故,这个物体的边缘依然会刺激到我们的activation层
,当然,max-pooling
的核大小是很有限的,对于一般的图像旋转,平移,max-pooling
就无法发挥作用了。
quora
上也有说法,大致意思是过滤层(卷积层)有不变性,但是全连接层只会对特定的激活层产生反应,也就是如果激活层地点发生了变化,那么全连接层就检测不到了。
底下的评论中也有人进行了测试(训练第一象限位置的数字图像,然后用第二象限的图像进行测试):
设计的网络层也很简单(Keras):
class Model(object): def __init__(self, num_classes, *input_shape): self.model = None self.input_shape = input_shape[0] self.num_classes = num_classes def create_model(self, use_fully_connected=True): self.model = Sequential() self.model.add(Conv2D(64, (3, 3), activation='relu', input_shape=self.input_shape)) self.model.add(Conv2D(32, (3, 3), activation='relu')) self.model.add(MaxPooling2D((2, 2))) if use_fully_connected: self.model.add(Flatten()) self.model.add(Dense(128, activation='relu')) self.model.add(Dense(self.num_classes, activation='softmax')) else: self.model.add(Conv2D(16, (3, 3), activation='relu')) self.model.add(Flatten()) self.model.add(Dense(self.num_classes, activation='softmax')) def compile(self): self.model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adadelta(), metrics=['accuracy']) def train(self, train_x, train_y, num_epochs, batch_size): self.model.fit(train_x, train_y, verbose=1, epochs=num_epochs, batch_size=batch_size) def test(self, test_x, test_y): return self.model.evaluate(test_x, test_y)
设计的模型中有max-pooling
层,最后的连接层是设置为全连接层或者直接去掉。就这样训练某个位置的数字图像然后用另一个位置的数字图像去测试,不论构建的神经网络有没有全连接层,测试出来的结果都很“烂”:
准确率不足10%。
也就是说,max-pooling
的作用其实是有限的,我们不应该光靠这个“特性”来偷懒,然后减少数据的收集。
其他相关的研究与看法
类似的研究还是有很多的,在Multi-scale Orderless Pooling of Deep Convolutional Activation Features作者对图像分别进行旋转、缩放、平移等各种变换操作,记录多种变换程度下测试的准确性。
可以看到,大部分的曲线都是成”拱形”的或者下降趋势,显然还是original
的情况下效果最好。
还有很多另外的研究结果,大家可以自行搜索阅读。
关于Uber最新出现的论文CoordConv
Uber最新出了一片论文,在神经网络层的卷积操作上加了一层坐标层,为的就是改进神经网络对目标位置不敏感的问题(平移不变性)。
呃,评价呢,褒贬不一,有兴趣可以看看一下的连接:
卷积神经网络「失陷」,CoordConv 来填坑
要拯救CNN的CoordConv受嘲讽,翻译个坐标还用训练?
结合大家观点总结下这个论文:实现起来很简单,几十行代码就可以。但是实际上可能并不适合用于大型的任务,因为在论文中作者只是在小型任务上进行了尝试,其他的任务还没有人尝试过,我也没有,所以不好评价。
结论
说了这么多,最简单的提升神经网络不变性的方法就是拓展我们的数据集,也就是所谓的data augmentation,在kaggle上很多的trick都是通过在数据集上下功夫就可以提高算法的分数了,这个方面还是很有学问的。
当然也有很多的在神经网络上直接实现对数据的增强,达到提高测试精度的效果,有兴趣的可以阅读下:
spatial transform network
AutoAugment: Learning Augmentation Policies from Data
参考
https://medium.com/nanonets/how-to-use-deep-learning-when-you-have-limited-data-part-2-data-augmentation-c26971dc8ced
https://blog.keras.io/building-powerful-image-classification-models-using-very-little-data.html
https://www.zhihu.com/question/30817011
Exploiting Cyclic Symmetry in Convolutional Neural Networks
http://yann.lecun.com/exdb/lenet/index.html