百度魅族深度学习大赛初赛纪实

一、结缘 深度学习于我来说并不陌生,但我仿佛始终徘徊在外围,摸不着进去的门。最近有幸参加了百度魅族深度学习大赛,跟众多大佬学习到了不少知识。昨天初赛结束,趁着决赛前的间隙,对这段时间学到的内容做个总结。
大赛官网:http://meizu.baiducloud.top/ps/web/index.html
初赛内容:从图片中识别四则运算式,算式可能包含数字0~9、运算符+-*、括号()。并且,算式的长度固定为5或7,包含三个数字,两个运算符,0或1对括号。下面是几个样例:
百度魅族深度学习大赛初赛纪实
文章图片
(4*8)+8 百度魅族深度学习大赛初赛纪实
文章图片
(0-2)+5 百度魅族深度学习大赛初赛纪实
文章图片
2*8-7 要求参赛者给出每张图片中的算式和运算结果。
训练集共100,000张图片,并附带标签。测试集共200,000张图片,无标签,预测结果上传后计算正确率,作为初赛的排名。
了解到这个比赛的时候已经7月下旬了,当时的排名已经是清一色的0.99+的正确率,我的内心是崩溃的。作为第一次参加深度学习应用大赛的我,完全不知道这种题目该如何做,甚至无法评估它的难度。还好我当时没放弃,不然就失去了一次绝佳的接触深度学习的机会。
二、寻觅思路 【百度魅族深度学习大赛初赛纪实】像我这样毫无经验的人只能向别人请教解决问题的思路。
在图像识别领域非常擅长的同学建议使用卷积神经网络(CNN)来做,因为这本质上就是一个分类问题,把不同位置的字符分为16类(10类数字+3类运算符+2类括号+1类空白)。只不过这里并非对每张图片给出一个分类,而是给出7个分类,因为算式的最大长度为7。那就相当于输出一个长度为112的向量(112=16*7)。此时再从分类的角度看的话,似乎是把图片分为了112类,但仔细一想,其实并非如此。因为分为112类的话只需要找出其中的最大值作为分类结果就好了,而现在我们要每隔16个都找出一个最大值,最后共找出7个最大值,只有7个数都预测正确才行。我很难从理论上推出这样做会带来多大的复杂度,但直觉上这并非一个好的方案。
从另一方面考虑,算式具有上下文关系,它是从左向右读的,并非一张不可分割的图案。直接用CNN处理的话,相当于每一位都用完整的图片来训练,需要让网络懂得抛弃错误位置的形状,只关注正确位置的形状。虽然我们相信CNN能够做到这些,但未免有些过于苛刻。
幸运的是,在QQ群里遇到了杨培文大神,杨大神是唯一一个把准确率做到1.0的,稳稳占据初赛头名。杨大神不光技术过硬,还分享了许多技术博客,简直是我们这些小白的福音。我顺藤摸瓜找到了这篇《使用深度学习来破解 captcha 验证码》,这篇文章与这次初赛的贴合度可以说达到了99%。文中介绍了两种方法,第一种方法就是前文的CNN方法,第二种方法则是CNN+GRU+CTC。第二种方法非常适合这种具有序列结构的文本识别。先用CNN提取特征,再送进GRU进一步提取特征。GRU是一种特殊的循环神经网络(RNN),效果比LSTM要好一些(具体为什么我也不知道...)。最后用CTC损失函数作为优化目标,这个损失函数可以自动编解码不定长字符串的识别结果,网络的输出结果是稀疏编码,会包含很多空白,需要解码成稠密编码后才能与标签做比较。
本来嘛,四则运算式识别跟验证码识别本质上就是一样的,它们属于不定长的手写文本序列识别问题。于是,我照着那篇文章中的方法,先实现了一遍验证码识别,再把它改成运算式识别。
三、踏坑 毕竟是新手,总有踏不完的坑。仅仅是把输入从自动生成的验证码改成事先给定的图片数据集,就费了我一番苦工。首先,我发现一次性读取全部图片的话内存会爆掉(可怜了我的小笔记本)。于是我只能在用到的时候才读取。这样仍然需要使用fit_generator函数,以动态加载需要的图片。我采用随机数来自动生成训练图片序列,以保证每个epoch的训练顺序是随机的。代码修改好后,开始训练。令人失望的是,不论训练多久,loss都维持在10以上降不下去,正确率也只有10%左右。我认真地检查了好多遍代码,仍然找不到错误出在哪里。
就这样僵持了好几天,到了周日上午,索性再把验证码识别的程序拿出来跑一下,感受正常的训练流程应该是什么样的。如果说我的程序好像便秘了一样,验证码识别的程序则是一泻千里,浩浩荡荡,很快就达到99%的正确率。突然,就在一瞬间,我觉察到莫非我的输入出问题了?当我把读取到的输入图片显示出来的时候,它是这个样子的:
百度魅族深度学习大赛初赛纪实
文章图片
内心一万头草泥马路过。这个黑啊,伸手不见五指,这么恶劣的条件下能达到10%的正确率我也是服气。经过排查,发现load_image函数中对图片做了归一化,这是很正常的事情,按说不应该出错的。但是,由于输入数据设置成了X = np.zeros((batch_size, width, height, 3), dtype=np.uint8),就是最后的这个uint8,把我的像素数据全部变成了0,于是有了上面全黑的输入图片。改成float32之后,程序终于如愿以偿得飞奔起来,不一会就达到百分之九十多的正确率。
除此之外,还遇到了许多小问题,大都是由于对Python不熟悉,对Numpy、Keras的用法不熟悉导致的,所幸最后都解决了。
没想到最后能进决赛,在此需要再次感谢素不相识的杨培文大神的帮助,感谢他在深度学习领域给我们这些初学者提供了如此清晰易懂的教程,感谢他耐心解答我的问题,最后祝愿他顺利拿到比赛的冠军。
初赛数据集已上传百度网盘,下载地址: https://pan.baidu.com/s/1snfs3vB 密码: tt63

    推荐阅读