Archive for July 10th, 2018

在Google Colab上完成Coursera的作业

Tuesday, July 10th, 2018

最近开始学国立高等经济学院的机器学习课程,和之前 Andrew Ng 的课程不同的是,这个课程大量用编程而不是视频来带动教学。而且这些程序用了行业通用的数据库,并且从零开始训练,而不是只训练一个epoch就加载由老师训练好的模型进行验证。

因此,这个课程的机器学习过程,在CPU上经常需要数小时完成,更别谈用Coursera自己在线的那个服务了(不太稳定)。

Google为数据分析及机器学习学生准备了一个平台:Colab。这个平台最棒的地方是完全免费的提供了一个GPU供使用,但限制在12个小时内。
但是这个平台并不能直接用来跑Coursera的课程。虽然两个平台用的都是Jupyter notebook格式的文件,但Goolge限制了对文件系统和目录的访问,最直接的表现就是,Colab一次只能运行一个notebook,运行下一个的时候,未必是同一个环境。

那么有没有hack的办法呢?我成功了。

TL;DR: 你需要把所有需要的Notebook合并成一个,并且用Google的API把一些依赖库上传到环境中。

以第一个专项课程第四周的第一个作业为例。

通过分析,完成这个作业需要用到两个notebook,其中一个用来下载测试数据,另一个才是课程代码。
课程还需要数个*.py的库,因此,我做了一个初始化环境的notebook
使用时,将这个初始化脚本下载资源作业本身的notebook用nbmerge命令合并成一个notebook并传到自己的Google Drive。(这个命令没有的话用pip安装)
然后,因为文件目录结构发生了变化,download_utils.py 里面的文件位置需要改变,简单来说就是去掉所有的“../“。(链接的这份我已经改好了)
用初始化脚本第一行代码上传requirements文件和所有依赖的py库。注意,这个requirements文件我在官方的基础上做了修改,删除了tensorflow的版本要求,因为直接用Colab环境的版本适配GPU最方便。
完成以上工作以后,顺着notebook安装依赖库,下载训练数据,然后就可以开始写作业了。

但这个方案还是有些不完美的地方:

  • notebook里面的图片会显示不出来,因为根本就没上传。你可以从官方Github手动下载过去,我则是直接在Coursera上面打印一份PDF对照看。
  • 下载和训练的进度会显示不出来。这个我不知道是什么问题,网上搜不到多少类似案例。我用PyCharm跑notebook也有这个问题,但直接在浏览器中则不会。我倒是不纠结这个问题,因为Google的网速和GPU的训练速度快啊。

比起用CPU训练几个小时,现在几分钟就可以出结果,实在是太棒了!

Keras 2.1.3+ 的一个改变带来的问题

Tuesday, July 10th, 2018

今天被一个奇怪的事情折腾了一天:课程作业无法得到应有的结果,本应得到高于90%的准确率的模型,训练出来只有40%。
幸好在讨论区找到了答案:Keras在2.1.3版后,对BatchNormalization这个层的功能做了修改。但是这个修改并没有体现在Keras自己的文档中, 只在GitHub的问题区有零星的讨论。

不少人对这个改变有不同的看法,主要两种观点及其验证可以见以下三篇文章:1 2 3

总结来说,其实在2.1.2及之前,BN这个层是有bug的,这个bug就是:如果该层被设为trainable=False,实际在训练中,这个层还是会更新mean和variance的值,并且这些参数会更新到测试阶段使用。

到了2.1.3及以后,这个bug被消灭了,但是对很多做transfer learning的人来说,却有新的问题出现了。因为做transfer learning时,一般会导入Inception、ResNet等已经训练好的模型作为底层模型。这些底层模型的训练集和我们用来训练的数据肯定是有区别的,因此会导致BN这一层的mean和variance不正确。

如果是训练和测试的时候都一样错就罢了,起码模型的结果是一致的。Keras另一个参数trainning的默认值决定了模型在训练的时候,是以mini-batch的mean和variance数据来训练的,但并不会更新到模型中,因此测试用的mean和variance仍然是导入的模型的原始数据。

上面这种设计带来的结果就是,在某些情况下,同样的模型同样的数据,训练和测试的loss或者acc会差很远。因为是同样的数据,所以根本不是over-fitting的问题(虽然看起来很像)。这也正是我遇到的问题。

针对这个问题,解决办法有两个思路:

  1. 彻底锁死BN层的参数,即使训练阶段也不更新,可以通过设置training=False来实现。但是我初步测试了一下这个方法的训练效果不好。
  2. 把BN层设为可训练。我初步测试了一下跟2.1.2版的训练效果差不多。