kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

目录

概要

问题来源

前置步骤:

数据流程

原csv数据的读取与分割

csv数据转图片和tfrecord的存取:

tfrecord接生产队列供模型训练:

神经网络定义:

1.lenet5和alexnet变形:

滑动平均(EMA):

训练与优化过程:

学习率:

关于batch_size:

训练次数:

Dropout:

BN:

网络宽度调整:

网络层数调整:

Dropout:

其他可持续做的优化:

调试过程及其他问题:

 

关于类型问题:

TFRecord:

在restore模型时遇到的问题:

测试准确率时内存溢出:

线程问题:

打印graph

关于lrn归一化

其他操作失误:

吐槽:


 

 

概要

问题来源

kaggle的一个表情识别的训练集。

https://www.kaggle.com/c/challenges-in-representation-learning-facial-expression-recognition-challenge/data

 

前置步骤:

数据流程

全流程的数据操作,三部分。

原csv数据的读取与分割

csv读取解析存储等。

csv数据转图片和tfrecord的存取:

这块细节比较多,好好调一下。

tfrecord接生产队列供模型训练:

续程协调器,生产者队列,直接从队列动态拿数据

 

神经网络定义:

1.lenet5和alexnet变形:

本文主要围绕这个结构,增加卷积层数和全连接层数(因为48*48,比mnist大的多,lenet-5是针对mnist的24*24的),有lrn、dropout、Regularization等。tensorboard打印结构图见下方。

2.cliquenet,别人写的,也能处理fer2013,暂不熟悉,不是典型的DenseNet。

3.关于VGG,跑题了,VGG的问题是他训练的图集不一样,而且定位不一样,他是识别动物,这是表情,细致度也不一样。只是微调的话效果应该不好,计算量又大,有机会再测了。

滑动平均(EMA):

加“惯性”,让weights和biases变动更平缓,避免个别batch造成的过大波动,加速收敛,也能保证测试结果比较稳定。

EMA又叫影子变量,影子之所以叫影子,是因为他会跟在变量的后边变化。

注意事项:如果想用影子变量做预测,注意你恢复的变量是不是影子变量。ema.variables_to_restore()是用来干这个的,指定一个映射,用影子变量替代原变量。

一些调试笔记:

https://github.com/huqinwei/tensorflow_demo/EMA.ipynb

https://github.com/huqinwei/tensorflow_demo/model_save_restore_practice.ipynb

https://github.com/huqinwei/DL_projects/fer2013_kaggle/my_model/ema_restore_practice.ipynb

训练与优化过程:

学习率:

如果算力有限,初期学习率可以适当高一点,方便观察,及时调整。

后期学习率当然可以低一点,个人觉得分界线在过拟合吧,如果一个模型能学到过拟合的水平(本例是准确率70+),学习率就不用往高去调了,有算力的话,还可以再低一些。

 

关于batch_size:

暂时用虚拟机,没法GPU,第一次BATCH小了,一直到结束,只有34(中间也跳了一次40,但是因为batch小,没说服力)

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

把batch改为128:

第一次就36了,训练1000次以后在五十上下:

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

 

 

训练次数:

前边设置1000次训练太少了,提升训练次数:

两万step的时候,已经接近1了,因为网上冠军也没这么高,所以这是过拟合了,再用测试集对比一下

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

改了网络的变量命名,第二次训练又死机了,卡在6000次的时候,先以6000次为基准做个对照:

训练集的mini-batch准确率如下:

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

测试集的准确率如下:

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

过拟合明显,所以打算先从dropout入手改善

Dropout:

初始设定是三层分别0.95,改成三层都是0.8,总的是0.512,接近单层dropout=0.5的水准。

同样的6500步,明显准确率下降了,基本不到90%,比之前略低

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

测试集:62%左右,比之前的不到60略高

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

因为训练的时候能达到八九十,仍然太高,有下降空间,把dropout降到0.6*0.6*0.6,再试一次

在两万多次相对充分的训练后,准确率也就是60

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

接下来不急着调dropout,先把BN加上,理论上,BN影响也不至于太大。

BN:

第一个版本用了一下lrn,lrn是**和pooling之后使用,后边的版本替换成BN,BN在**前使用。

实测替换BN后在同样的训练次数下准确率好于之前的lrn,虽然relu正轴斜率是1,相比sigmoid淡化了一部分BN的效果,但是整体来说BN还是很有效的。

 

具体BN两种写法和详细计算过程以及原理,注意事项和相关笔记链接在底部

 

维持0.6*0.6*0.6的dropout,改为在relu之前插入BN层,6500步观察结果,过拟合减轻,测试集准确率61+,有所上升:

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

看趋势,持续训练还可以获得提升——至少是训练集上的提升:

batch翻倍为256(还是不够大),BN改为0.5*0.5*0.5,训练集90+,测试集还是60出头

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

但是如果测试的时候将BN层的参数改为training=False,准确率就没有那么高了,只有十几。这是很违反直觉的!!但是好像又很符合BN的预期!!

说明BN的滑动训练的还是不够充分,BN的调试放到后边问题集里。

 

目前的模型效果:

基础学习率0.0005,BATCH=256

ema decay=0.99,dropout=0.6,0.6,0.6,37700步,训练正确率90-,测试正确率50%左右。

还没有达到加BN前的60左右,但是比加BN而迭代不充分时的15%或者25%要强多了,所以还需要继续迭代。目前的主要干扰是不充分训练的BN。

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

 

 

其他可持续做的优化:

网络结构调整:可适当加深,宽度已经足够宽,看是否有必要收窄一些,因为太宽就容易过拟合,过拟合就要dropout。

数据方面:网上还有一个fer2013 plus的git,数据不变,只是label改成了10类,原来是7类。这个号称能提升识别结果,乍一看简单,就是换个label数据,但是也有问题,我把这个数据集当10个label来看,进行训练,最终的预测呢?怎么转成7个?所以得研究一下那个git的说明,好好看一下。

私有和公有验证集的问题:训练集是自动训练调参用的,先不论。(公有)验证集是人工调参使用的集合,测试集(私有验证集)等于提交后的成绩,用来二次修正。目前两者区别不大,等到后期差别大的时候再做调整,减少过拟合,比如调整dropout。有时间的话,还可以使用交叉验证等方法。

网络结构:替换cliquenet等其他网络结构。

 

最后,做几个图像的预测看一看。用png,处理图片,丢进网络。

 

 

工程都放在github了,调试放到ipynb

https://github.com/huqinwei/DL_projects

https://github.com/huqinwei/DL_projects/kaggle_face_expression_recognition_challenge 

https://github.com/huqinwei/DL_projects/tree/master/fer2013_kaggle

 

调试过程及其他问题:

中间遇到了很多细节问题需要调试。

 

各种滑动平均,包括BN,保存与使用:

无论是普通weights的滑动平均ema,还是BN自带的隐藏滑动平均,保存和恢复这里都是个坑点。

尤其BN,BN是部分训练,部分滑动。很明显测试和预测时都应该用滑动的这部分,而这部分是不能train的,不包含在trainable。所以保存时也要注意。

很多坑都是不可感知的,不动脑不调试就漏掉了,但是好在tensorflow做了防呆设计,至少能保证你确实保存了变量,不然bn层是根本加载不出来的,会报错(话说回来,就算防呆不能运行,也白训练了不是~):

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

说起来简单,但是自己抠清楚还是花了些时间的。如果不想抠,也不要紧,TF有很多机制,可能简单的常规操作(复制正确的模板的前提下)不会碰到问题,但是最好理解并掌握。

其他都好说,但是有一个错误是没有预防机制的,就是不更新滑动平均,一定要记得更新滑动平均!!

预测的时候,一定要把BN层改回False,如果保持同样的网络设置,就不对了。(其实在一定训练条件下,BN的training=False,反而看起来准确率更低)

https://github.com/huqinwei/tensorflow_demo/blob/master/batch_normalization_use_TF.ipynb

https://blog.****.net/huqinweI987/article/details/88071425

 

BN有两种写法,有一种黑盒直接使用,第二种需要自定义变量,并区分训练和测试过程维护滑动变量。学习研究用第二种,模型图方便用第一种。

因为用了第一种,没有显式的去声明变量,但是不代表没有,把模型resotre并打印出来。每个BN层包括四个变量,所有数据的滑动平均mean和variance,经过mean和variance处理后,缩放系数gamma和平移系数beta,他们未经训练的时候应该是刚好让数据保持不变,训练之后的效果是让不同的输入对应的输出都在一个相同的分布。

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

EMA更新了moving_mean和moving_variance

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

 

但是使用非训练模式的BN,结果总是不太满意,不知道是训练不充足还是什么其他原因,更新是肯定更新了,被EMA给更新了,受dropout影响?改成dropout 1.0 测试

ema的惯性方面,应该没问题,batch_normalization默认momentum=0.99。理论上是没问题,实际上效果不好,0.99还是太大了,可能要训练很久才能真正有效,不然测试的时候准确率会很难看。0.5就明显高了。

调试ema和bn,一些实际情况对比(因为算力原因,没有充分训练,仅供观察):

基础学习率0.0005

ema decay=0.99,dropout=0.5,0.5,0.5,1000~10000步,训练正确率60~90,测试正确率15%左右

ema decay=0.99,dropout=1.0,1000~10000步,训练正确率60~90,测试正确率15%左右

ema decay=0.99,dropout=0.6,0.6,0.6,37700步,训练正确率90-,测试正确率50%左右

学习率0.001

ema decay=0.5,dropout=1.0,1000~2000步,训练正确率60+,测试正确率25%左右

ema decay=0.5,dropout=1.0,3600步,训练正确率80+,测试正确率15%左右

由此可见,正确使用的前提下,加BN层,momentum=0.99,需要很多学习才能达到效果!

 

 

关于类型问题:

使用PIL(Pillow)的Image.fromarray转换fer2013数据集报错

fer2013,通过pandas的read_csv得到的是这个类型    '<i8'

找不到typestr的key:((1, 1), '<i8')

普通的,如果使用Image.open()打开一张图,得到的是    '|u1',是可以在typemap中找到的。

这个类型其实就是描述内存存储的,大小端、数据类型、字节数。

PIL支持的就只有这些类型:

Image._fromarray_typemap is : {((1, 1), '|u1'): ('L', 'L'), ((1, 1), '|i1'): ('I', 'I;8'), ((1, 1), '<u2'): ('I', 'I;16'), ((1, 1), '>u2'): ('I', 'I;16B'), ((1, 1), '<i2'): ('I', 'I;16S'), ((1, 1), '>i2'): ('I', 'I;16BS'), ((1, 1), '<u4'): ('I', 'I;32'), ((1, 1), '>u4'): ('I', 'I;32B'), ((1, 1), '<i4'): ('I', 'I'), ((1, 1), '>i4'): ('I', 'I;32BS'), ((1, 1), '<f4'): ('F', 'F'), ((1, 1), '>f4'): ('F', 'F;32BF'), ((1, 1), '<f8'): ('F', 'F;64F'), ((1, 1), '>f8'): ('F', 'F;64BF'), ((1, 1, 2), '|u1'): ('LA', 'LA'), ((1, 1, 3), '|u1'): ('RGB', 'RGB'), ((1, 1, 4), '|u1'): ('RGBA', 'RGBA')}

 

python3.6,Pillow更新到最新,没解决问题
    PILLOW_VERSION = '4.2.1'

(现在也想明白了,和环境版本关系不大,人家库就这样设计的,既然Image存的是图像,像素值也不需要64位整型)

 

array的来源就是split的字符串,也不该有什么绑定的属性自动传入array了(最开始以为和编码格式之类的有关系,所以跑偏了)

因为是array带的属性,所以去看np.array的说明,这个应该就是array的数据类型,默认情况下,应该按满足最低需求的来准备。写的是dtype=int,可能因为系统是64的,就算成64,自己主动指定dtype=np.int32(float64也支持),解决问题。。但是我要用到的样本的数值上限,只是255,所以类型随便用。后边灰度转换直接变8位。

 

其他要注意的,bytes确实是按8位存数据,并且只支持8位数据,转bytes存储前,灰度转换,数据已经不大于255.

 

TFRecord:

其他都是小问题,这个最重要,因为数据给不出来,训练过程根本无法进行,不搞清楚数据的每一步形式的话很不容易发现问题,所以把tfrecord的各种操作好好巩固一下。

数据方面容易有坑,有好几个可能的问题,类型不匹配,根本读不出去,不能训练;bytes的转化过程中把数据都丢掉,不过本例不算是个问题,因为转灰度了,灰度255以上都会强制255,最后只要decode的时候确保使用uint8就行了。

不管有没有灰度转换,留意,decode要和tobytes的源头类型一样,因为这个bytes是字节序列,和C语言的内存操作一个道理。

 

在restore模型时遇到的问题:

restore主要用于恢复global_step和变量,断点续训;还有在测试阶段提取出EMA变量用于测试。

先说重点:要指定var_list,每个变量都要有自己的名字!!!如果给模型改过变量名,之前的model就不能直接用了(想用的话需要找出对应关系,倒是可以改名映射)!

 

细说,restore会遇到两个问题:找不到对应的变量;形状不对。

前者是现在定义的变量比model存的变量多了(指定var_list=ema.variables_to_restore(),能减少一部分错误,按理说在ema范围内变量足够就行了,新定义的无关变量不会去强行匹配)

后者的话,主要可能是因为没给所有变量都命名,比如我之前所有的bias都叫Variable,然后自动重命名,这样万一新定义的模型,哪怕顺序不一样,就可能出错。

我的一个报错是:两个变量错位了,一个想要64给0,一个想要0给64。(又或者是512和128不匹配)

InvalidArgumentError: Assign requires shapes of both tensors to match. lhs shape= [64] rhs shape= []
	 [[Node: save/Assign = Assign[T=DT_FLOAT, _class=["loc:@Variable"], use_locking=true, validate_shape=true, _device="/job:localhost/replica:0/task:0/device:CPU:0"](Variable, save/RestoreV2)]]
     
InvalidArgumentError (see above for traceback): Assign requires shapes of both tensors to match. lhs shape= [64] rhs shape= []

关于模型的save/restore,最烦的问题就是变量的自动重命名,这一点主要体现在使用ipynb调试,如果每次都单独开一个进程,且保证没有多余动作——比如不小心多调用一次网络定义——问题应该不大。或者,用get

最好所有变量都命名,第一个是血泪,好不容易扔一晚上的模型,用不了了。第二个是规范的能用的。

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

 

restore基本练习:https://github.com/huqinwei/tensorflow_demo/save_model_practice.ipynb

调试笔记:

https://github.com/huqinwei/DL_demos/kaggle_face_expression_recognition_challenge/my_practice/ema_restore_practice.ipynb

 

测试准确率时内存溢出:

一次把测试集三千多数据都读出来,我的7G虚拟机爆了

tensorflow.python.framework.errors_impl.ResourceExhaustedError: OOM when allocating tensor with shape[3589,48,48,64]

解决方法,分批次测试,取平均值。如下:把数据改小,加个循环和平均。因为取数据用的shuffle,加上不能整除的零散部分也向上取整了,所以数据有微小变动:

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

 

训练一半改学习率会发生什么?而这个模型用的是动态学习率

ckpt加载只有global_step,,是用基数和global_step计算得到,故而学习率可以随意更改,按相同的步数折算就好了。

一个测试:

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

 

线程问题:

生产者和reader的定义一定要早于线程协调器启动!!!

因为封装起来了,容易看走眼,因为tf这个线程协调器相对来说有点透明,有时候出了问题不便于观察,害的我把一套数据流程都调了一遍,最后发现就是函数封装导致执行初始化晚了。。

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

函数封装有生产者的定义

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

 

 

 

打印graph

下图是前往传播结构,bias_add支持同名,如果不自己改名,图不是一条线,是断的,通过BiasAdd衔接的。

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

这是不自定义名的效果,会形成一个数组,图形会断掉,也能通过红色标记的断点观察,不过不直观。

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

给bias_add单独命名,保证图形连续

除了bias_add,multiply、matmul都是如此,统统改完。

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

 

 

改完所有重名操作,无EMA情况下的前向+反向网络结构,无EMA的图更清晰一些。

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录


有ema的情形,加了一堆判断。图形也不完整,有重名

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

而且这个操作是隐藏了的,这个对整体结构图影响不大,应该是一个原地闭路,现在打印成开路了。

在tensorboard可以手动解开,就是看着更不舒服了。。。

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

kaggle fer2013表情识别挑战:训练、调优、调试与踩坑记录

 

 

 

其他操作失误:

使用ipynb调试时,file句柄没有及时close掉,最后把train的tfRecord写坏了,而这个0字节的tfrecord,在这种生产者模式下是不报错的,本来也不报错,只是负责读数据,没数据了就不读了,没毛病。。。

 

吐槽:

拿着别人的稳妥能用的数据和工程跑一遍和自己踩坑调通整个工程并且修改调优完全是两个世界啊。