< Deeplearning > TF实操Game of Noise

前言

来自于2020年1月份的一篇arxiv文章.文章的主要思想是通过给CNN网络(以分类模型举例)的输入图加入噪声来使得模型更加的鲁棒.

与之前手动加入噪声不同的是,该文章采用对抗网络的思想,通过一个噪声生成器来生成噪声,并尽量使你的分类模型(判别模型)做出错误的分类.

而你的分类模型的目的是尽量能够不被加入的图片噪声干扰,依然能做做出正确的输出.最终经过数轮的迭代训练,达到使得你的分类模型能够抵抗各类噪声干扰的目的.

网络泛化与图像增强

神经网络需要落地的最大困难往往在于训练数据和实际应用场景有较大的gap.经常是你的模型在训练集上拟合的很好,甚至在你的验证集/测试集上都拟合的很好,但是一部署到实际场景往往会出现各种各样的问题,如误检太多,召回太低等等.这会让你一度觉得你的人工只能简直是人工智障.以上统称模型的泛化能力差.

当然,这里面最大的问题在你的训练\测试数据不够丰富,说就是你的训练集只是你训练素材真实分布的一个小小的sample.你在训练的时候只是用真实分布的一个sample,但你在线下应用的时候却要让模型面对整个真实分布(更加泛的sample),这个时候模型如果遇到未知的数据,往往会作出错误判断.

当然我们不太可能获得真实分布那种海量的数据.于是人们就通过各种各样的方式来提升模型的泛化能力.有人通过修改模型的结构,比如Hinton老爷子发明的胶囊网络或dropout等等.有人通过调整优化方式或增加正则项来使得模型的最优值尽量落在平滑的极值点.有人通过调整训练方式,如增加多任务,或给网络添加各种先验等等.除了这些方式之外,更多的人是通过数据增强来达到让模型增加泛化能力的目的.

最初的图像增强是通过给图像加入各种手动的扰动来达到的.如随机裁剪,色彩变换等等.但这样的扰动方式有个很大的弊端,就是你必须手动调整各个增加的参数(如裁剪大小,色彩强度等等).这是一个很主观且繁琐的事情.万一调试不好,反而会使得模型的准确率下降.

于是Google后来提出了AutoAugment,也就是自己定义了一个扰动的空间,通过强化学习的方式自动选择最优的扰动和参数.而本篇文章要说的Game of Noise,则是另外一种思路.

我们可以这样理解Game of Noise,假如说把扰动分类为两种,一种扰动是改变图像像素的位置,但是不改变图像像素的值(我们姑且成为方位扰动),如速记裁剪,图像旋转,图像flip,仿射变换等等.另外一种扰动是在图像原始的像素值增加后减去某一个值,但是不改变像素的位置(我们姑且成为像素扰动),如亮度增强,对比度增强,以及加入噪声等等,今天我们讨论的Game of Noise本质上是属于一种像素扰动.而Game of Noise可以通过对抗学习的方式去学习出最优的像素扰动,从而取代手动设置的像素扰动.

Game of Noise

Game of Noise 这篇文章通过实验发现了几个有趣的现象。比如如果给图像加入高斯扰动,一个精心选择的噪声方差可以对模型效果有很好的提升(相对于随意噪声方差来说),比如通过对抗学习的方法来给图像加入噪声可以在免去手动调参的同时给模型加入最适合的扰动。而本文主要讨论和实验的是后者,也就是通多对抗学习的方式给模型加入噪声扰动。
具体的实验方式与普通的对抗思想差异不大,由生成器生成对抗噪声,把噪声加入到训练图片上,再把加入噪声的图片送到你的判别器去判别和训练,这里的判别器就是你自己的需要扰动的模型,通常是一个已经训练好的模型,在对抗噪声图片上进行fintue.生成器的目的是尽量让加入噪声的图片让判别器误判,判别器的目的是尽量对加入噪声的图片正确区分。通过这样的对抗训练,最终让你的判别模型学习到抗未知噪声干扰的能力。

TF实现

首先我们需要定义一个生成器:

1
2
3
4
5
6
7
8
9
def noise_generator(x):

with slim.arg_scope([slim.conv2d], normalizer_fn=slim.batch_norm,
activation_fn=tf.nn.relu):
x = slim.conv2d(x, 32, (1, 1), stride=1, scope='Conv1_advnoise')
x = slim.conv2d(x, 64, (1, 1), stride=1, scope='Conv2_advnoise')
x = slim.conv2d(x, 3, (1, 1), stride=1, activation_fn=None,normalizer_fn=None,scope='Conv3_advnoise')

return x

原文中没加batch_norm,我后来实验发现加入bn还是效果会好一些,原文中前两层的channel都为20,我把channel数变大之后发现效果会好一些.
下一步生成noise图像:

1
2
3
4
5
6
7
g_noise_input = tf.random_normal(shape=(BATCH_SIZE_SEP, SIZE, SIZE, CHANNEL), mean=0.0, stddev=0.5, dtype=tf.float32)
out_noise = noise_generator(g_noise_input)
ran_sel = tf.random_uniform((BATCH_SIZE_SEP, 1, 1, 1), minval=0,maxval=2,dtype=tf.int32)
ran_sel = tf.cast(ran_sel, tf.float32)
out_noise = out_noise * ran_sel
noise_image = images + out_noise
noise_image = tf.clip_by_value(noise_image, 0.0, 255.0)

generator的输入是和输入图同样大小的高斯噪声(为了后面的相加操作).给噪声加入ran_sel随机选择参数,目的是随机选择需要扰动的图片.
获得图片的输出:

1
out_logits = model.inference(noise_image,  num_classes=LABEL_NUM)

定义生成器的loss:

1
2
3
4
5
6
reverse_label = tf.cast((1-labels), tf.float32)
loss_softmax_sep_fake = LOSS_FUN(reverse_label, out_logits, ran_sel)
tmp_noise = tf.reshape(out_noise, (BATCH_SIZE_SEP, 280*280*3))
ord_loss = tf.norm(tmp_noise, ord=2, axis=-1)
ord_loss = tf.sqrt(tf.pow(ord_loss - 2000, 2))/200
ord_loss = tf.reduce_mean(ord_loss)

按照普通Gan中生成器的训练方式,需要把label取反,然后和输出logtis求交叉熵loss(最后要乘以ran_sel).另外如果你要控制噪声的强度(防止noise的值过大),需要对噪声加上一个2范数的限制,上面的ord_loss就是起到这样的一个作用.
定义判别器的Loss:

1
loss_softmax_sep = LOSS_FUN(label, out_logits, ran_sel)

判别器的loss很简单,就是没有reverse的label和输出logits的交叉熵.
最后定义训练过程:

1
2
3
4
5
6
if step % N ==0:
sess.run(tf.variables_initializer(G_variable))
if ord_loss_out > 0.1:
_, ord_loss_out = sess.run([train_op_ord, ord_loss])
_, G_loss_out = sess.run([train_op_G, train_loss_G])
_, D_loss_out = sess.run([train_op_D, train_loss_D])

训练就是普通的Gan的训练方式,也就是G和D交替优化.这里有个需要说明的trick是,最好在一定的迭代次数之后再次初始化G模型的变量,这样做是为了方式G生成的noise过于单一.

< Deeplearning > TF实操Game of Noise

https://zhengtq.github.io/2020/03/20/advnoise/

Author

Billy

Posted on

2020-03-20

Updated on

2021-03-13

Licensed under

Comments