分享

面向程序员的深度学习

 LZS2851 2020-03-25

本文系我在上周TFUG西安活动中分享话题的文字版,旨在对一段不算短的深度学习自学过程的总结,并以程序员的角度给出深度学习的简单介绍,帮助有志于成为深度学习开发工程师的小伙伴迅速把握整体大局,理清进入深度学习的道路。

整个话题主要由以下四部分组成:

  • 先从分工谈起
  • 深度学习基础概念
  • 深度学习进阶概念
  • 深度学习实战

先从分工谈起

机器学习工程师在公司中到底是一个怎样的存在?他的职责到底是什么?估计有不少开发者会有这样的疑惑。从机器学习的书中,从招工简章中,似乎都隐隐约约告诉大家:数学、算法……。然而,这个岗位中不是还有“工程师”三个字么?

直到这篇文章的出现才让一切变得清晰起来。先来看看机器学习工程师要构建的系统是什么样子: image.png

诚如图中展示的,机器学习相关功能虽然是系统的核心,但它只是整个系统的一小部分,其他的功能(如安全权限、数据导入和处理、可视化等)并没有超出大多数普通开发者需要掌握的知识范畴。并且由上图,也很自然地导出了机器学习工程师需要掌握的知识技能: image.png

从图中可以看出,“机器学习工程师”相当于“掌握了机器学习技能的工程师”:

  • 工程能力依旧是其首要掌握的技能,它需要利用这些技能在产品环境中实现模型:包括模型的训练和推理两个阶段。
  • 机器学习知识不可或缺。这一点毫无疑问,假如对机器学习一无所知,所谓的实现自然无从谈起。但就理论知识而言,并不要求他达到算法工程师或数据分析师那样的水准。两者的分工更多的是首先由后者在实验环境中把模型设计出来,然后再由机器学习工程师去实现即可。
  • 对于业务知识的理解同样也是必须。因为机器学习不是空中楼阁,它必须要解决实际问题。如果对问题域完全一抹黑,很难想象实际系统能工作得很好。这里很典型的一个例子就是对于输入数据的处理,要是缺乏业务问题的背景知识,可能连数据的含义都弄不明白,更不要说如何处理了。同样的,虽说机器学习工程师要求具备业务领域知识,但他不必成为领域专家,对于更深层次的问题,可以由后者提供专业帮助。

那么,作为普通工程师来讲,我们该如何开始去学习机器学习(或者说本文更关注的深度学习)呢?结合个人的学习过程,我建议将整个学习过程分为3个阶段。

阶段1:找感觉

这一步是最难的。

假如你还没有被书中那些数学公式和推导过程吓到,仍然坚定地要入手专研机器学习,请给自己一个大大的👍🏻!不过,在这个阶段,我建议不要把理论学习作为重点。相反地,我更建议大家先去找上一两个当前流行好用的机器学习框架,直接照着例子开始动手实验。这便是所谓的“找感觉”。

这个阶段的主要目的就是破除大多数开发者对于机器学习的神秘感和畏惧感,形成对机器学习的感性认识,进而找到自己的“感觉”。若是一开始就去专研机器学习理论,除非你天赋异禀,很容易陷入一大堆数学公式爬不出来,学了大半年却讲不清楚机器学习到底是什么,遇到实际问题也不知如何入手,其挫败感可想而知。

以上也是我自己的亲身体会。因此,为了避免上面的问题,我推荐大家一开始先不要去看理论背景很强的机器学习书籍,尤其是那些大学教材和算法类数据。而是先从一些实战类的书籍看起(比如【参考】部分的Python深度学习),以例子驱动的方式去学习。在形成对机器学习的感性认识之后,再去着手学习理论,以增强作为机器学习工程师的内涵。

同时,对于深度学习而言,为了更清楚地了解其背后的实现机理,不妨自己动手去实现一个简单地深度网络。对于这个任务,【参考】部分的深度学习入门:基于Python的理论与实现Python神经网络编程为大家提供了支持。

阶段2:找队友

一个人学习总是孤单的,假如有同伴的话,情况就不一样了。不仅可以相互扶持和鼓励,也有助于形成一种竞争和合作的良好氛围。对于那些已经找到感觉的开发者,我建议可以去Kaggle找同好,顺便练练级,赚点外快(竞赛奖金)。作为国际知名的竞赛平台,Kaggle的排名含金量非常高,并且还有什么比起在实战中提高能力更好的途径吗?

在练级的过程中,你很快就会发现自己的知识短板,此时,只需跟随你的感觉,缺啥补啥就好了。

一段时间之后,你已经具备了一定的理论知识和实战技巧。这时,不妨可以考虑将自己的所学和经验向社区(如自己的博客和TFUG)输出,树立自己的个人品牌并检验自己的实际掌握程度。因为“你只能讲清楚你理解的东西”,假如你发现自己下笔困难或无法清晰地向他人讲清楚“机器学习是何物”,那么很可能是哪些地方还没有完全弄明白。

阶段3:找方案

机器学习是门实践性很强的课程,不断有新的理论、方法和模型诞生。为了保持跟得上技术的发展,你还得去抽时间去搜集模型、论文和案例,好让这些能成为你未来武器库中的一部分。并且,作为开发者,为了更好地发挥你所用框架的潜力,对于框架的源码和机理需要做必要的了解,这样才有可能去进一步扩展和定制。

最后,在结束本节之前,我再来说说需要避免的学习误区:

  • 光看不练,这一点前面已经强调,不结合工具实战练习是无法掌握机器学习的。
  • 克服心理障碍,这一点非常重要。学习者千万不要被书中的数学公式吓到,随着技术的发展和时代的进步,一些平民化的工具已经出现了,典型如:Keras和TF。利用它们,实现一个深度网络并没有看上去的那么难。并且,参照上文所述,就机器学习工程师而言,数学的要求并不太高。
  • 神秘化ML类项目,这一类问题也同样需要避免。从前文可以看出,ML项目就是用ML模型解决实际问题,与一般项目没有本质区别。
  • 模型自己写,这一点对于初学者来讲非常容易忽视。站在工程角度,预训练模型本质上就是类库,只要其解决的问题和我们当前正在解决的问题相当或相似,就可以考虑直接使用。
  • 不重视数据,这一点同样也是初学者容易忽视的问题。过于强调模型的重要性,这是初学者的通病,但倘若结合机器学习项目中的模型训练过程,就不难发现数据的重要性。假如把数据比做原材料,模型比做机器设备,很显然只有好数据才能得出好结果。

深度学习基础概念

在了解深度学习的基础概念之前,先来看看典型的机器学习分类:

  • 以解决问题的类型来分
    • 回归,预测连续值,如根据历史温度数据预测未来几天的温度值。
    • 分类,预测离散值,如mnist数据集的0~9的手写识别。
  • 以学习方式来分
    • 有监督学习,先通过标记好的数据集完成训练,然后使用训练好的模型完成预测。典型如上面手写识别和预测温度值的例子。
    • 无监督学习,训练数据没有标记,模型自行发现其中的关联。典型如聚类算法,输入一组数据,自动将数据完成分组。

讲深度学习,不能不提神经网络,而且既然有“深”自然就有“浅”,下图给出了两种网络的示意图,左边为“浅层”网络,右边为“深度”网络。

image.png

就大的结构而言,它们都由:输入层、隐藏层和输出层构成。所谓深浅之别,只不过在于:浅层网络的层数不会超过3层,反之则为深度网络。这个区分是不是有些意外地令人失望😄?

根据神经网络理论,只需要输入输出两层全连接网络就可以“创造出世界”(只要不断加入神经元就好了,即胖网络),但为何又要引入“隐藏层”,并且后来又不断加深“隐藏层”的深度呢?原因就在于:

  • 计算的可行性。理论上虽然可行,但由于硬件的原因,两层神经网络不可能无限加宽,否则将导致整个计算无法进行。
  • 关联的灵活性。全连接可以视为暴力穷举的做法,而隐藏层则相当于对数据有一个二次加工的过程,利用不同的隐藏层结构和激活函数可以灵活地加工出所需的中间数据集,有助于改善数据之间的关联性。而层层递增的隐藏层其实就相当于数据不断精加工的过程,关联性一步步得到增强。

同时,“深”网络相比起“胖”网络,在计算上可以用现在的硬件得以实现。

了解完深度网络的概念,让我们再来看看深度网络的使用。整个使用过程分为两个阶段:

  • 训练阶段,这个阶段的目标就是得到一定网络模型下的权重矩阵和偏置矩阵。不妨将建模类比为“列方程组”,训练类比于“解方程组”的过程。
    1. 构建网络
    2. 训练数据数值化处理,即深度网络只能处理数值类型数据,对于文本、图像、音频等数据,需要借助一定的手段,将其转化成能被网络接受的数值表示。
    3. 前向传播计算偏差
    4. 后向传播调整权重和偏置
    5. 重复3/4直到偏差不再缩小
    6. 保存模型
  • 推理阶段
    1. 加载模型(网络 + 权重/偏置)
    2. 转化输入符合训练时的输入格式
    3. 调用模型预测结果
    4. 解释结果,深度网络预测的结果也是数值类型,对于这个类型的含义,应该由使用者来进行解释。

请注意,推理阶段的模型来源可以有两种:

  • 方式1:自己造轮子。即自己建模型,自己训练,自己使用。
  • 方式2:使用现成的预训练模型。还记得前面说的吗,模型本质上与类库没有什么区别,只要预训练模型要解决的问题和手头待解决问题属于一类问题,就可以考虑直接使用现成模型。

下图展示了作为神经网络基本构成的神经元的组织结构:

image.png

其中:

  • x和y分别表示输入和输出。
  • W和b分别表示网络的权重和偏置,也就是所谓的“知识”。
  • f代表激活函数,它决定了神经元的输出,这也是为何它被称为“激活函数”的原因。
    • 如图所示,输出Y = f((sum(x*w) + b),

然而,单个神经元并不能翻起多大风浪,必须将神经元按层组织,形成网络之后才有实际意义。对于网络中的层而言:

  • 层 = 一组神经元,可视为单个神经元的矩阵化
    • 输入(1 x m)、输出(1 x n)、W(m x n)、偏置(1 x n)
    • 输出 = 激活函数(输入 * 权重) + 偏置
  • 每个层可被视为数据的加工处理函数,其输出为中间数据集,并作为下一层的输入。
    • 层的组合形成了数据处理流水线,即网络结构。
  • 对于层的激活函数
    • 隐藏层常用:sigmod、relu、tanh等
    • 输出层根据问题类型决定:
      • 回归问题(任意值),无
      • 回归问题(0~1),sigmod
      • 分类问题(n选1),softmax
      • 分类问题(2选1),sigmod

组建好了网络,自然需要对它进行训练。就训练过程而言,整个流程如下:

image.png

其中:

  • 损失函数,评判预测和实际的差距,不同类型的问题使用不同类型的损失函数,典型的有:
    • 回归问题:MSE
    • 分类问题:Cross Entropy
  • 优化器,调整权重和偏置,常见SGD。

前面也说过,深度网络对于输入数据的要求是数值性,然而现实世界中的数据则不止于此,因此就必然涉及到对于输入数据预处理的问题。常见的预处理有:

  • 标准化和归一化
    • 避免量纲影响。比如有的输入量是10000,有的又是0.1,这样的数据直接进入网络训练往往效果不如人意。通过一定手段,将输入量统一到一个量纲之后,既简化了计算,同时也避免了量纲问题带来的训练效果不佳。
    • 简化计算
  • 缺失值
    • 众数(数据集中最常见的值)或均值
  • 字符标签
    • one-hot编码,即有多少相异标签就引入多少个特征,处理时将相应特征对应的列标记为1,如下图。可能有朋友会问,为什么不简单地用数字(如1、2、3)来指代不同的分类呢?原因很简单:这样会带来隐含的关联关系。假设 red = 1,yellow = 2,这就隐含着 yellow > red,这显然是荒谬的。而采用one-hot编码则能有效避免这种问题。

image.png

  • 文本数据:先token化,再转换成数值向量。
  • 图像数据
    • 预处理,如灰度化
    • 动态生成图片,扩充数据集,如扭曲、颠倒等
  • 正则化,防止过拟合,关于过拟合的问题,参见下文。

此外,关于训练,你还需要了解下面的常见术语,有时候你会在一些书籍和代码中看到它们:

  • 1 pass,一条数据处理完毕
  • 1 iteration,一批样本数据中所有数据处理完毕
  • 1 epoch,所有批次所有样本数据处理完毕

同时,在训练模型时,你需要考虑:

  • batch大小,它决定了权重更新时机,越小,则更新越频繁
  • epoch大小,它决定了训练次数,随着一轮一轮的训练,你会发现预测的差异在不断变小。最后,每一epoch之间差异改善并不大,这时就可以考虑停止训练了。

在实际过程中,常常将训练数据集划分为3部分:

  • 训练集,训练数据
  • 验证集,训练过程中验证训练效果没有下降
  • 测试集,训练完毕后检验训练效果

验证集存在的价值在于提早发现问题,终止无用的训练。因为训练本身是一个耗时耗力的过程,若训练了半天发现效果很差,那么还不如提早结束。利用验证集,可以很早就发现过拟合现象。

这三类数据集的比例通常如下(训练集、验证集、测试集):

  • 60:20:20
  • 70:10:20
  • 超大数据集:95:2:3

为了最大化利用这些来之不易的数据的价值,在实际部署之前,往往会将整个训练数据集合为一体,输入模型,完成对模型上线前的最终训练。

在本节最后,看看深度模型的部署方式。还记得吗,在本文中一直强调“模型即类库”。那么在部署方式上,其实也非常类似:

  • 方式1:API。如:Rest 框架 + 模型
  • 方式2:嵌入应用。如:MVC 框架 + 模型

在实际过程中需要注意:

  • 输入数据需符合模型预期
  • 模型输出需应用自己解释。这一点很显然,因为模型的输出是一个数值类型,对于它的具体含义解释必然是跟应用相关的。
  • 训练和使用可以是不同框架,如在DL4J环境中使用TF训练的模型

深度学习进阶概念

上一节中提到,将层视为不同的数据处理器,不同类型的层完成不同类型的工作。Keras框架中典型的层有:

  • Convolution,抽取局部特征
  • Pooling,跟Convolution配合使用,典型MaxPooling、AvgPooling等
  • DropOut,随机去掉一定比例神经元
  • Dense,全连接
  • Embedding,词向量转换
  • Recurrent,处理序列数据,如文本和时序
  • Merge,合并多个输入层数据

因此,构建网络的过程就是使用不同层进行组合的过程,本质上等同于使用编程语句构建程序。同时需要注意,一般情况下,输出层都为Dense。这里可以理解为,通过隐藏层不断加强数据的关联性和缩小范围之后,最终通过暴力穷举解决问题。

常见的网络结构如下,具体介绍请参见【参考】中的MIT Deep Learning Basics: Introduction and Overview with TensorFlow,限于篇幅这里就点到为止。

image.png

前文已经出现了“过拟合”一词,现在我们来讨论一下关于拟合的两个概念:欠拟合和过拟合。

  • 欠拟合,可视为网络学习能力低下,无法从训练数据学到任何东西。导致它出现的典型原因主要是网络结构存在缺陷,比如“小网络,大数据”。修复它的办法也很直接,改进网络,比如:
    • 增加每层神经元
    • 增加网络层级
  • 过拟合,可类比于“死记硬背”,对训练数据几乎能达到100%,对测试数据,则表现不如人意。典型原因“大网络、小数据”。主要的改进对策:
    • 数据正则化:L1和L2,本质上是通过对成本函数引入惩罚机制来限制网络的自由度。
    • DropOut,在训练过程中随机丢弃层中一定比例的神经元,不让它们参与计算。

如果将网络类比为“内存”就很容易理解上面的两个概念。前一种相当于内存不足以容纳全部数据,从而无法学习;后者则相当于内存远超数据量,导致对现有数据通过死记硬背的方式就能蒙混过关。一般在实际工作中,欠拟合很容易发现,因此这里也不做过多强调。过拟合,则是从业者经常与之斗争的“顽疾”。这也是为何要引入“验证集”,提前发现过拟合现象,及时终止训练,避免浪费时间。

另一个需要了解的进阶概念就是“鞍点”,如下图:

image.png

有一定高数背景的读者应该对于通过“求导求极值”的做法并不陌生,深度学习中的优化方法本质上仍是如此。由上图可见,在鞍点附近,导数(斜率)几乎为0。这样造成的负面后果就是:

  • 学习性能低下,迟迟无法收敛
  • 无法获得理想结果,这里只是得到了局部的优化值,因此鞍点本身又有“局部极值”的叫法。

为了应对这种情况,一般采用:

  • 使用有动量的优化算法,如Adagrad、RMSProp。套用一般深度学习书中将优化过程比喻成“小球”沿梯度方向滚动的说法,所谓动量可视为让小球具有一定的加速度,快速通过鞍点的过程。
  • 自适应学习率,因为学习率决定了权重调整的大小

最后一个需要掌握的进阶概念就是“超参数”,它本质上相当于模型的“元数据”,无法通过训练过程得到。典型的超参数有:

  • 神经元个数
  • 网络层数
  • 激活函数
  • 优化器
  • 学习率
  • Epoch数
  • Batch大小

并且超参数的调优过程是一个不断实验的体力活,一般的做法有:人工、Grid Search、随机优化、贝叶斯优化。随着技术的发展,目前也有类似AutoML的的端到端解决方案出现,相信未来会更美好。

深度学习实战

关于深度学习用到的框架和工具,以及实验环境的搭建,请参见我写的这篇文章这里就不再赘述。下面的代码来自【参考】部分Python深度学习一书,大家可以对于使用Keras编写深度网络有一个感性认识:

from keras import layers
from keras import models

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

简单吧,😄。

总结

讲到这里,我希望这次分享能够消除大家对于深度学习的神秘感和恐慌,真正动手去做做练习,在实践中成长。最后总结一下:

  • 深度学习入门并没有想象中那么困难
  • 借助Keras等工具可以快速实现网络模型
  • 可以看出,深度网络的调参是个脏活累活。但AutoML和AutoKeras这类工具的出现,意味着自动调参时代即将到来。
  • 建议学习方法:Make Your Hands Dirty

参考

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多