## 实战路径:程序员的机器学习进阶方法 在计算机行业,关于从业人员的素质,一直都有一个朴素的认识——科班出身好过非科班,学历高的好过学历低的。大部分时候,这个看法是对的。在学校学习,有老师指点,有同学讨论,有考试压迫,有项目练手。即便不大用心的学生,几年耳濡目染下来,毕业后作为半个专业人士,还是没什么问题的。 不过,量子物理告诉我们,这个世界的本质要看 概率。所以,科班出身的同学,在技术上好过非科班出身的同学,这是大概率事件;相反,非机器学 习专业,甚至非计算机专业的同学,在这个领域做的比本专业同学更好,则就是小概率事件了。但小概率事件并非“不可能事件”,国内很多做机 器学习公司的 CTO,都不是机器学习专业的科班出身,却能够抓住这里的“小概率”,让自己华丽地转身并实现弯道超车。 他们是怎么做到的? 如果在上学的时候,我们没能嗅到机器学习领域的机会,而是选择其他领域来学习和工作……如今却打算半路出家、改行机器学习,应该怎么做,才能做到跟这些人一样好?或者,至少是足够好? 我自己痛苦转型的经历,说出来可以供大家参考一下。 我也是非科班出身,但因为工作,一直需要接触计算机视觉的一些传统算法。后来,看到 ImageNet 竞赛的结果,我意识到了深度学习在视觉领域的巨大优势,遂决定开始转型深度学习和 神经网络,走上了这条学习的不归路(笑)。 想要转型,跟上学的时候不同,因为手头正在做的工作意味着,自己需要从没有时间的情况下挤 出时间,需要把别人睡觉、打游戏的时间用来学习,而所学的又是一种颇为艰深晦涩的学问。 转型,其实很容易,需要做到的只有一件事:学习。 转型,其实很困难,因为必须做到一件事:坚持学习。 最难的不是下定决心,而是贯彻到底。所以,在开 始之前,不妨先问问自己这样几个问题: “*我真的已经想清楚,要踏足这个行业吗?*” “*我能够付出比其他人更多的辛苦汗水,在这条路上坚定地走下去吗?*” “*在遭受了痛苦甚至打击之后,我对机器学习的热爱,仍然能够维持我继续前进吗?*” 根据我掌握的数据,100个程序员里大概有30个考虑过转型,而真正付诸行动的不过10个。一个月以后仍然在坚持的仅有5个,最终能完成第一个阶段学习的,最多两三个而已。 真的这么困难吗?是的。特别是你要白天上班,晚上才能学习,独学而无友,有问题又只能自己查。而要系统地入门,又不是咬牙一天两天就能学出来,恐怕得坚持几个月才能 get 到点。 我个人的经历是这样:一开始接触时,每周一、三、五固定3天时间,每晚花两个小时去学习、看视频、翻书,周六周日则用来完成课程附带的编程作业,大概也是每天两小时左右。在这种强度下坚持了三个月,我才算是完成了入门的第一步。 也许有的人效率更高一些,也许有的人步子更慢一些,但快和慢不是关键,即使学习最慢的人,也要比一开始放弃学习的人走得更远。 所以,其实真正重要的,不是“我该学什么”,或者“我该怎么学”;而是“我是不是真的有足够的决心”,以及“我是不是能坚持到底”。 ### 上手的课程 定好决心后,我们就能看看:在学机器学习的时候,我们到底在学什么? 几乎所有人都知道人工智能这个概念;有一部分人知道“机器学习”这个概念;其中一小部分人能清楚描述“深度学习”、“机器学习”和“神经网络”的关系;很少一部分人能够正确说明“卷积”、“池化”、“CTC”这些名词的正确含义与计算/实现的方法;非常少的人能清楚地理解损失函数和反向传播的数学表达;极少极少的人能够阐述网络的一个修改(比如把卷积核改小)对 precision/recall 会产生什么影响;几乎没有人能描述上述影响到底是什么原理。 这就是目前“程序员”这个群体,对于机器学习的了解程度。 我个人的经验,适用于“很少一部分人”之外的那“很大一部分人”,也就是说,他们最多知道深度学习是什么意思,神经网络又是什么概念,却并未真正系统地学习接触过这个领域。 而我们的目标,则定位成为“非常少的人”,也就是能够理解损失函数/反向传播这些较为基础、较为底层的知识,使得自己能够设计新的算法或网络,或至少能辅助大牛去实现他们所设想的算法和神经网络。 要实现这个小目标,我们就必须掌握最基础的知识,就好像学写汉字时,不练横竖撇捺折是不现实的。 但是,作为“不明真相的广大群众”,从哪里入手好呢?线性代数?概率论?那是最糟糕的选择!它们只会让你入门之前就彻底丧失信心,其漫长而陡峭的学习曲线还会让你误以为这是一个不友好的领域。事实上,只有成为“很少一部分人”(能够正确说明“卷积”,“池化”,“CTC”这些名词的正确含义和计算/实现方法)之后,你才真正需要去复习它们。 在这之前,你所要用到的数学知识,只有以下这三点: - 懂得矩阵运算的基本计算方法,能够手动计算[3×4]×[4×3]的矩阵,并明白为什么会得到一个[3×3]的矩阵。 - 懂得导数的基本含义,明白为什么可以利用导数来计算梯度,并实现迭代优化。 - 能够计算基本的先验及后验概率。只要大学考试不是完全靠抄答案,稍微翻翻书,你就能把这点知识找回来,可能半天都用不上。 然后就可以入门了,对于所有零基础的同学,我都建议从[吴恩达的机器学习课程开始](https://www.coursera.org/learn/machine-learning)。 这是唯一的推荐,也是最好的入门教程,没有之一:因为吴恩达的英语又慢又清晰,课程字幕的翻译又到位,课程设置与课中测验及时而又合理,重点清晰、作业方便,再加上吴恩达教授深入浅出的讲解,讲解过程中不时的鼓励和调侃,都能让你更为积极地投入到机器学习的学习之中,让你扎实而快速地掌握机器学习的必备基础知识。 这门在线课程,相当于斯坦福大学 CS229的简化版,涵盖内容包括机器学习最基础的知识、概念及其实现,以及最常用的算法(例如 PCA、SVM )和模型(全连接神经网络)。学习这门课程,重要的是基础的概念与实现。作为一名具备编程基础的开发人员,在这个阶段要将自身理论同实践相结合的优势发挥出来,充分利用它所提供的编程作业,尽可能多地实践,从理论和代码两个角度去理解课程中的知识点。 学习完成后,你能了解到机器学习的一些基本名词和概念,并具备一定的算法层面的编码能力。打好理论和实践的基础,你就可以进行下一阶段的学习了,其中有两大的方向:夯实基础和选择领域。 夯实基础的意思,就是这门课的完成,并不代表自己学会了机器学习,只不过是从门外汉进了一步,一只脚踏进了门,但其实也仅仅是一些基本的了解。这个时候,你也许会觉得自己有很多的奇思妙想,却难以评估这些想法的价值和正确的可能性,这就是基础不够的缘故。 所以,在继续学习深度神经网络之前,建议结合自己所学到的知识,回头去看一遍 CS229,将传统算法整体熟悉一遍,尽可能把所有的基本概念都掌握扎实。 而选择领域,则是由于任务目标的不同,深度学习领域已在大体上分成了计算机视觉( CV )、自然语言处理( NLP )以及其他一些子领域,例如语音和更为特殊的强化学习等。在每个领域下,都有大量的研究者在投入精力钻研,发表论文和成果。考虑到个人精力的限度,建议选择一到两个方向作为主攻,跟上学术界主流的进展,其他子领域有基础的了解即可,必要时作为参考即可。 以计算机视觉为例,在完成 CS229的课程之后,可以继续学习 CS231n,作为进一步学习的材料;而自然语言处理则可以选择 CS224d。在完成了相应领域的学习后,下一步要做的就是尝试阅读最新的经典论文并试图复现它们了。 ### 编程语言与深度学习框架的选择 当然,作为开发者,想要去实现一个模型,绕不开的问题便是: 应该选择什么语言?应该选择什么框架? 对于开发人员而言,语言的选择其实不是问题。但作为入门,最为理所当然的建议则是 Python,原因也非常简单:Python 最好学。 对于机器学习的学习,使用 Python 就意味着你不必分心去学习那些复杂的数据类型约束以及转化、指针、内存管理或垃圾收集之类的“高级”(一般同时也代表着复杂)的特性,将精力集中在自己的目标上。当然,一些 Python 特有的方法(如 lambda、yield 或 reduce)以及工具(如 NumPy、pandas ),还是需要多多使用,尽快熟练。 而框架方面,从使用者的维度去划分,当前数量非常之多的机器学习框架,则可大体上分为两大阵营。 #### 学术友好型:Theano、Torch 与 Caffe 学术研究时,弄出来一个新模型、新算法、新函数是常有的事,做出新的突破也是学术研究最基本的要求。所以,这些框架通常都便于定制模型,也可深入修改内部实现。很多新成果都会在发表论文的同时,提供这些框架上的实现代码以供参考。它们在性能方面也比较出色。 其代价就是,要么是使用了困难( Caffe:C++ )或小众( Torch:Lua )的开发语言,要么是有一些古怪的缺点( Theano:编译超级慢)。 而且,这些框架似乎都没怎么考虑过“怎么提供服务”的问题。想要部署到服务器上?Caffe 已算是最简单的了,但仍要经历漫长而痛苦的摸索历程。 #### 工业友好型:Tensorflow、MXNet 与 Caffe 工业上往往更注重“把一个东西做出来,并且让它运行得良好”。所以这些框架首先就需要支持并行训练。其中 Tensorflow 和 MXNet 支持多机多卡、单机多卡、多机单卡并行,Caffe 则支持单机多卡,虽然性能还不是特别理想。 在我们的测试中,Tensorflow 的双卡并行只能达到单卡的1.5倍左右性能,卡越多,这个比例越低。Caffe 要好一些,但参数同步和梯度计算无论如何也都需要时间,所以没有哪个框架能在没有性能损失的情况下实现扩展。而多机情况下,性能损失更大,很多时候都让人感到无法接受。 相对来说,只有 Tensorflow 提供了比较好的部署机制( Serving ),并且有直接部署到移动端的方案。而 MXNet 和 Caffe 则是直接编译的方式,虽然也能实现,但是说实话,依然很麻烦。 至于缺点,除 Caffe 之外,其他两种框架对于学术界动态的跟踪都不太紧,Tensorflow 到现在都没有 PReLU 的官方实现,前不久才刚推出一系列检测( Detection )的模型。MXNet 这一点上要积极些,可是受限于较小的开发者社区,很多成果都只能等待大神们的 contribution,或是自行实现。 这样看来,难道最好的框架是 Caffe?既能兼顾学术和实现,又能兼备灵活性和性能兼备……说实话,我的确是这么认为的。但前提是你懂 C++,如果出身不是 C++ 开发人员,相信我,这门语言也不比机器学习容易多少。 所以,对于大多数有志于投身于机器学习开发(而非研究)的同学们来说,我推荐首选 Tensorflow 作为你的第一个开发框架。除了上述的优点之外,最主要的因素是它人气高。遇到任何问题,你都可以找到一群志同道合的伙伴们去咨询,或是一起研究。对于初学者而言,其重要程度不言而喻。 ### 实战上手的数据 上过课程、学好语言、装好框架之后,自然就要通过亲手编程,来把自己的模型实现出来。 但在深度学习领域,没有数据的模型就是无源之水,毫无价值。而目前流行的监督学习,要求必须有足够的带标注数据来作为训练数据。那么,从哪里能得到这样的数据以进行学习呢?答案就是公开数据集。 例如,在学习论文时,如果它提出了一个性能优异的模型或者方法,通常会附有在几个公开的标准数据集上的成绩,这些标准数据集就是可以去下载来学习和使用的资源。另外,诸如 Kaggle 和天池之类的机器学习竞赛,其比赛项目中也会提供很多数据集供学习和测试。这些就是学习阶段的主要数据来源。 以 CV 领域为例,常见的公开数据集就包括以下这些。 #### MNIST 不论选择哪本教材、哪个框架,在刚刚接触机器学习的时候,一定会接触到 MNIST。它是由 YannLeCun 所建立的手写数字库,每条数据是固定的784个字节,由28x28个灰度像素组成,大概长成这样: ![enter image description here](http://images.gitbook.cn/ea9b5d60-c5c1-11e7-8cad-b1d89e3215dc) 目标是对输入进行10-分类,从而输出每个手写数字所表示的真实数字。 因为它体积小(10M左右)、数据多(6万张训练图片)、适用范围广( NN/CNN/SVM/KNN 都可以拿来跑跑)而闻名天下,其地位相当于机器学习界的 HelloWorld。在 LeCun 的[ MNIST 官方网站](http://yann.lecun.com/exdb/mnist/)上,还贴有各种模型跑这个数据集的最好成绩,当前的最好得分是 CNN 的,约为99.7%。 由于该数据集非常之小,所以即便是在 CPU 上,也可以几秒钟就跑完 NN 的训练,或是几分钟跑完一个简单的 CNN 模型。 #### CIFAR 而打算从图像方面入手的同学,[CIFAR数据库官网](http://www.cs.toronto.edu/~kriz/cifar.html)则是一个更好的入门选项。 该数据库分为2个版本,CIFAR-10 和 CIFAR-100。 ![enter image description here](http://images.gitbook.cn/1c24f1c0-c5c2-11e7-bbbb-9b4adc537622) 顾名思义,CIFAR-10 有10个分类,每个分类有5000张训练图片和1000张测试图片,每张图片是32x32像素的3通道位图,如图2所示。 而CIFAR-100则有100个分类,每个分类变成500张训练图片与100张测试图片,但图片的大小并没有什么变化。 之所以它比 MNIST 更适合作为图片处理的入门,是因为它尽管分辨率较低,但却是三通道、真实拍摄的照片。其中有些图片的背景还略微复杂,更贴近我们真实的图片处理场景。相对而言,MNIST 的灰度输入和干净背景就显得过于简单,况且99.7%的准确率也确实难有提升的空间。 [Tensorflow给出了CIFAR的例程](https://www.tensorflow.org/tutorials/deep_cnn) [并附有代码](https://github.com/tensorflow/models/tree/fb96b71aec356e054678978875d6007ccc068e7a/tutorials/image/cifar10) #### ImageNet 和 MSCOCO ![enter image description here](http://images.gitbook.cn/2c93a3d0-c5c2-11e7-8cad-b1d89e3215dc) 至于[ImageNet](http://www.image-net.org/)和[COCO](http://mscoco.org/),则是两个工业级别的图像数据集。通常提到它们时,ImageNet 指的是 ILSVRC2012的训练集,而COCO则是COCO-2014训练集。ImageNet有大量的图片(一百多万张,分成1000个分类)和标注,大部分都是 图3 这样的。 ![enter image description here](http://images.gitbook.cn/602e16d0-c5c2-11e7-9bc4-492ab73dde1b) COCO 虽然图片数量少一些(8万多张,80个分类),但每张图片都有轮廓标记,并且附带分类标注和5句描述话语(英文)。其图片大致如 图4。 所以当我们进入实际工作的阶段,就要根据具体的需要从中选择适合自己的数据集,以作为 benchmark 或是 pretrain 数据集。 ### 实战阶段的学习用机配置 接下来,我们就需要一台机器来把框架搭建起来,以编写和运行我们的 helloAI。然而,我在很多地方都看到小伙伴们在问: *我需要什么样的配置能学机器学习?* *我需要买块 GTX1080/TITAN/Tesla 吗?* *我应该装几块显卡?一块?两块?还是四块?* 而答案也往往倾向于: “*必须得有 GPU 啊,至少1080,没有四路 Titan 你都不好意思跟人打招呼!*” 其实,并不完全是这样。如果仅仅是入门和学习,CPU 或 GPU 完全不影响你对代码和框架的学习。运行 MNIST 或 CIFAR 之类的玩具数据集,它们的差距并不大。以我的机器为例,运行自带的 CIFARdemo,i7CPU 和 GTX1080Ti 的速度分别是 770pics/s 和 2200pics/s。GPU 大概有不到三倍的性能优势。所以,差距其实也没多大。 这里还有一个小窍门,就是想用 CPU 版本的 Tensorflow,最好不要用 pip 下载的方式,而是自行编译。因为在开发机上编译时,它会自动打开所有支持的加速指令集( SSE4.1/SSE4.2/AVX/AVX2/FMA ),从而使 CPU 的运算大大加快。根据我们的测试,在打开全部加速指令集的情况下,训练速度大概会有30%的提升,而预测的速度大概能提升一倍。 当然,如果真想用一个复杂模型去处理实际的生产问题,模型的复杂度和数据量都不是 CIFAR 这样的玩具数据集可以比拟的。如果用我们的一个生产模型来运行 CIFAR 数据集,其他参数和条件完全相同,它在 i5/i7/960/GTX1080/GTX1080Ti 下的速度分别是:19/25/140/460/620(单位 pics/s,越大越好)。这里就能看出差距了,1080Ti 大概是 i7CPU 的25倍。而在模型上线使用( inference )时,GPU 也会有10-20倍的性能优势。模型越复杂,GPU 的优势越明显。 综合来看,如果仅仅是入门时期的学习,我建议先不用专门购买带 GPU 的机器;而是先用你现有的机器,使用 CPU 版本,去学习框架和一些基础。等到你对基础已经掌握得比较扎实,那么自然就会形成跑一些更复杂的模型和更“真实”的数据的想法,这时候再考虑买一块 GPU,以缩短训练时间。 在选 GPU 时,我听过一些朋友们推荐 GTX1070×2 这样的选择。理论上讲,1070的性能大概能达到1080的75%,而价格只有1080的一半,从各个方面看,似乎都是双1070更有优势。然而不要忘记,双卡的性能是不可能达到单卡的2倍的,在目前的 Tensorflow 上,大概只能达到1.5倍上下,算下来其实和1080单卡差不多。而双显卡的主板、电源与机箱散热都需要做更多的考虑,从性价比上来看,未必真的划算。 不过,如果显卡预算刚好卡在5000-6000的档位,双1070也有它的优势。比如,可以学习使用多显卡并行计算的用法,在不着急的时候可以用两块显卡同时跑两个不同的任务,合并起来就相当于有了16G的显存等等。考虑到这些因素,双1070的确是最适合入门学习的选择——如果买不起双1080/双 TITAN 的话(笑)。 如果你有打算用笔记本来作为主力学习用机,我的建议是:最好不要,除非你使用 Linux 的经验很丰富,或是不打算用 GPU 加速。很多笔记本在安装 Linux 后会出现驱动方面的问题,而且使用 GPU 加速时的高热量也会非常影响系统的稳定性。如果没有很丰富的经验,经常会在一个小问题上卡掉几个小时宝贵的学习时间。然后,要不要来试试第一个模型? 在 Tenforflow 安装完成后,我们可以用这种方式来最快地[把第一个 CIFARdemo 跑起来](gitclonehttps://github.com/tensorflow/models.gitmy_modelscdmy_models/tutorials/image/cifar10/pythoncifar10_train.py)。 OK,只需几分钟来下载数据,我们就能看到我们的第一个“图像识别模型”正在训练了。 训练过程中我们可以看到 log 中在不断地输出 loss 信息,但除了想要跟踪 loss 之外,我们还希望看到当前训练模型的识别准确率到底如何,这就不是 `cifar10_train.py` 这个脚本能够提供的了。我们还需要执行 `pythoncifar10_eval.py` 这个脚本会不断地验证最近的检查点的识别准确率。 如果使用 GPU 的话,就会发现训练脚本运行起来之后,所有的显存都已被这个进程占满;再启动验证脚本的话,就会报错一大堆的内存不足( OOM ),这是 Tensorflow 的机制决定的,它会默认占据所有显卡的所有显存,而不管自己是否真能用到那么多。 解决这个问题的办法也很简单。 首先,我们可以指定 Tensorflow 使用哪几块显卡进行训练。要做到这一点,可以在执行较本前,用命令行指定环境变量: `exportCUDA_VISIBLE_DEVICES="0,2"` 其中的“0,2”就是希望使用的GPU编号,从0开始,用逗号分隔开。 或者在代码中创建一个 GPUOption,设置 `visible_device_list=‘0,2’`,也能起到同样的效果。 然后,我们还可以限制 Tensorflow 所用的显存,使其动态增长而非一启动就占满。方法和上面的类似,在代码中创建一个 GPUOption,并设置 `allow_growth=True` 即可。 官方的 CIFAR 例程大概能达到86%的准确率,这个成绩在现在来说可以算是比较差的,最新模型的准确率通常都在97%左右,即便不经仔细调参而随意训练也能轻松达到93%左右。大家可以尝试修改 cifar10.py 中定义的模型,以得到更好的效果。最后,也是最初在经历过如此漫长、痛苦但也充满乐趣的学习和实践之后,你应该可以算是机器学习的一个业内人士了。但这并不意味这条道路已经走到了尽头,恰恰相反,在完成这一切之后,你才刚刚踏出了机器学习从业生涯的第一步。 在目前这个阶段,业内还处于算法红利期,新的算法、新的模型层出不穷,仅仅在 CV 领域,每天就有二三十篇 paper 被发布到 arXiv 上,每年的顶会顶刊收录的成果都在大幅度刷新上一年甚至上一个月的记录。 打好基础之后,跟踪论文并复现、学习和思考,这样的任务将成为你现阶段的一项日常作业,如果你已经进入或是决定进入这个行业的话。因为稍有懈怠,便要面临着被时代抛弃、跟不上节奏的情况。所以,到这一步,对于有些人来说是一个结束,而对另一些人来说,则才刚刚是开始。 这个时候,我们可以回过头来重新问问自己前面那几个问题: “*我真的已经想清楚,要踏足这个行业吗?*” “*我能够付出比其他人更多的辛苦汗水,在这条路上坚定地走下去吗?*” “*在遭受了痛苦甚至打击之后,我对机器学习的热爱,仍然能够维持我继续前进吗?*” 这条路,我在走,很多人在走,那么,你来吗? 文 / 智亮 >鲁朗软件联合创始人,Google Tensorflow 框架的 Contributor。他带领团队所开发的植物识别 App “花伴侣”,曾获得阿里巴巴2017云栖大会 API Solution 大赛一等奖,并受邀成为腾讯微信公开课北京站的九位演讲嘉宾之一。