一个JS程序员对机器学习的概念简单手记

659 阅读16分钟

为什么要学习机器学习,我认为有以下重要的三点:

  • 可缩短我们的编程时间,比如可以通过机器学习学习垃圾话样本,进行更快速更精准的垃圾话的检测
  • 普通编程方法难以解决的问题,比如用户潜在喜好和用户行为的预测
  • 更重要的是扩宽我们程序员的思维逻辑,对于适用的方向能够提出这方面的构思

从前JS程序员要学习机器学习,总是困难的,很多时候被算法和复杂的逻辑所困住,但现在问题得到很大的缓解,我们可以用tensorflow.js(训练和部署机器语言模型的JavaScript库)提供的库和用更好的方式来更简单的实现机器学习能力。

本文将主要讲解机器学习的一些主要概念。更偏重适用tensorflow.js的实战的入门级教程,请点击这里(前三节强烈推荐)

基本的机器学习的流程一般是怎么样的

在学习机器学习前我认为首先要明确以下几点

  • 标签 一般来说标签就是预测目标的结果,也可以是预测的正确值。
  • 特征 一般是指提提供训练的样本,而样本分以下几类:
    • 有标签的样本(带正确答案的样本,大部分都是使用有标签的样本进行训练)
    • 无标签的样本(不带正常答案的样本)
  • 模型 指的的是预测和训练样本的工具。你可以形象的理解为婴儿的大脑。
  • 损失函数 用于计算标签和模型预测值的差值。
  • 优化器 用于将损失函数计算的差值向正确方向优化的步伐
  • 学习速度 一般代表优化器的优化的步伐大小,过大则容易偏离正确值,过小则要更多运算才能到达正确值。就好比你要到马路中间要走5米,然而一次走500米和一次走5厘米,都很难到达马路中间。

好了,知道了以上几种概念,那么我们来以一张图的方式来展示,机器到底是如何进行学习的。

basic.png

那么从图中很容易了解到,我们是将特征输入到模型中计算出预测值,将标签进行通过损失函数计算出误差值,再交给优化器优化,参数更新后,模型再重复这一个过程,就构成了基本的机器学习的流程。

过拟合

为什么要讲过拟合?什么是过拟合?

防止过拟合是分类器的一个核心任务。而比如人脸识别,实际上就是一个分类器,比如将图片的风景,动物脸,人脸中的人脸的特征进行归类,将欧美人脸,亚太人脸,非洲人脸进行归类,甚至可以将某个特定的人的脸单独归为一类。所以它在机器学习里面也有举足轻重的地位。

那什么是过拟合呢?举个例子,我们在日常生活中见到的羊都是白色的,那有一天看到了除了颜色是黑色其他特征和我们日常见到的白羊都是一样的,那我们是不是就会认为这不是一只羊,人当然不会因为颜色就断定这不是一只羊,而机器却会说我见过的羊都是白色的,所以不可能有黑色的羊,所以这不是一只羊。过拟合官方的解释是为了得到一致假设而使假设变得过度严格。

那么过拟合要如何解决呢?这是一个机器学习重要的能力:泛化。

那么如何保证泛化,经验告诉我们有3个要点:

  • 从分布中抽取独立同分布(iid)样本
  • 分布是平稳的不会随时间变化而变化
  • 始终从同一分布抽取样本

但样本的使用也同样重要,一般我们会将样本分为 训练集,验证集,测试集。

各自用途是什么?为什么需要分3个集?

用途是什么,这个问题很简单:

  • 训练集 用于训练模型
  • 验证集 用于评估模型,即在训练每轮后验证训练的准确性,并帮助矫正参数
  • 测试集 用于测试模型,验证模型的准确性

为什么需要分3个集?可能大家会觉得为什么要三个集,直接用测试集评估和测试模型不就好了。

那我们做一个假设。如果我们用测试集评估模型,然后调整参数的话如下图:

error-train.png

那么会不会出现之前所说过拟合的问题呢?答案是会的。即为了得到一致假设而使假设变得过度严格,请仔细思考这句话。

而正确的方式应该是:

right-train.png

使用这样的流程就不会产生因为测试数据加入训练,导致通过了最终的测试数据中。

处理源数据的技巧

在训练模型之前我们需要处理大量的源数据同时转换为我们模型可以使用的数据,那么源数据的处理技巧就至关重要了。

在将原始数据处理成特征的这个过程,我们叫特征工程。

那么我们在做特征工程时有什么技巧:

  • 字符串可以使用独热编码
  • 过滤非合理数据,比如库中极少的数据
  • 筛选不随时间变化的特征
  • 使用分箱技巧处理非线性性特征

当然我们在处理这类数据时,也应保持以下几点以更容易暴露有问题的数据:

  • 将数据可视化呈现
  • 不断地对数据进行调试
  • 对数据进行监控

讲了数据处理,我们讲讲扩展数据集的方法,那么什么是扩展数据集呢?比如我们的数据集不是很充足,比如只有10个,显然是不足以训练模型的数据集,目前最主流的方法是可以通过它的单一特征来实现扩展,比如都在某个城市出现过,这种是线性的扩展方式。但是 而对数据的处理中很多特征,不是通过简单的单一线性就能划分的特征,比如在某个城市出现过的且短期存在的特征,这个就是非线性特征,我们需要短期存在和某个城市出现两个特征一起查询数据,这样的过程叫做特征交叉,即将两个数据交叉后转换为线性的方法。目前的事实也证明通过通过线性扩展数据是最高效的方法。(个人觉得说特征交叉就高大上一点,而两个条件相交查询就不专业了)

如何让预测结果更加正常和正确评估模型

在之前说过我们的模型训练需要通过损失函数计算损失,那么我们如何降低更多的损失呢?就比如我们训练集的损失度通过不断的学习越来越低,而测试集在训练集损失最低的时刻并不能是最低点,甚至说训练集样本损失度更高的时候,测试集损失度反而更低,我们应该如何解决这个问题。让机器学习更好泛化到新样本中呢?

解决方案:

  • 早停法,可以简单理解为用测试集测试的损失度最低点时就停止训练的模型
  • 正则化

第一种方案在实际的操作中比较难控制,那么我们今天主要来讲讲主要使用的L2(岭回归)正则化,即以下几点。

  • 定义复杂度(模型)= 权重的平方和
  • 使用适当的权重
  • 对于线性模型:首选比较平缓的斜率
  • 贝叶斯先验概率:
    • 权重应该以 0 为中心
    • 权重应该呈正态分布

除L2正则化还存在L0正则化,L0正则化主要是解决:

  • 稀疏特征组合造成内存消耗过大
  • 噪点过多的问题

那么L0,和L2的区别是什么呢?即将权重设为0,处理稀疏特征组合数据。L10属于非凸优化问题。

那么L0可能过于暴力,会导致NP-hard的问题,但我们还有一个折中的方案L1,即只对权重绝对值之和进行惩罚。同时L1属于凸优化问题。

通常我们可以使用逻辑回归结合一起使用

例如在求解概率的问题上,我们可以使用线性逻辑回归来快速求解问题,在非线性的情况,我们可以使用上节提到的特征交叉的方法来转换成线性再进行线性逻辑回归来快速求解问题,同时引入早停法和正则化来防止过拟合。

例如之前说过人脸识别实际上是一个分类问题,我们就可以使用逻辑回归来判断分类概率,同时设置阈值来判断是否加入该分类,有时在这方面准确率产生也会产生误导性,如:

  • 在不同类型的问题需要不同的解决方案时
  • 分类不平衡,即正类别或负类别极其罕见时

解决分类不平衡,我觉得我们需要先了解下真正例和假正例,官方说的很好,我直接把这个狼来了的故事搬过来。

  • 真正例 我们正确地提醒了狼的出现!我们拯救了小镇。
  • 假正例 错误:我们错误地提醒了狼的出现。所有人都对我们非常生气。
  • 假负例 确实有一头狼出现了,但我们没有发现它。狼吃光了我们所有的鸡。
  • 假负例 没有狼出现,也没有提醒。大家都相安无事。

我们可以把真正例,假正例,假负例,假负例组成不同的指标

  • 精确率:(真正例次数)/(所有正类别预测次数)
  • 召回率:(真正例次数)/(所有实际正类别数)

精确率和召回率往往处于此消彼长的状态,精确率和召回率是模型的重要评估指标,但往往我们无法直接得出精确率和召回率,所以可以使用AUC(ROC曲线下方的面积大小)来解决这个问题,即随机挑选一个正样本以及一个负样本,当前的分类算法根据计算得到的Score值将这个正样本排在负样本前面的概率就是AUC值,而这个AUC值越高,模型就越好。

还有一个重要评估的指标是预测偏差,即我们所有预测项的总和和观察项的总和比较下的结果。但偏差为0也并不能说明模型就完美,但预测偏差是个很好的checklist项目。

深度神经网络

在处理源数据的技巧的小节上说到,对于非线性特征可以使用特征交叉的方法来转换成线性特征来解决,但对于十分复杂的特征值,比如说真正例和假正例很接近,该如何处理呢?对的,可以使用深度神经网络来解决这个问题。

对于线性问题,我们的层级大概只有两层,即输入层和输出层

但对于非线性问题,我们往往需要增加一些层,通常通过ReLU(线性整流函数)和BP(反向传播算法)来实现。

对于ReLU可以计算是否线性,一般来说大于0则为线性问题

对于BP来说,常见的方法有SGD(随机梯度下降法),算法由主要由两个阶段组成:激励传播与权重更新。

第1阶段:激励传播

每次迭代中的传播环节包含两步:

  • (前向传播阶段)将训练输入送入网络以获得激励响应;
  • (反向传播阶段)将激励响应同训练输入对应的目标输出求差,从而获得隐藏层和输出层的响应误差。

第2阶段:权重更新

对于每个突触上的权重,按照以下步骤进行更新:

  • 将输入激励和响应误差相乘,从而获得权重的梯度;
  • 将这个梯度乘上一个比例并取反后加到权重上。 这个比例(百分比)将会影响到训练过程的速度和效果,因此成为“训练因子”。梯度的方向指明了误差扩大的方向,因此在更新权重的时候需要对其取反,从而减小权重引起的误差。

第 1 和第 2 阶段可以反复循环迭代,直到网络对输入的响应达到满意的预定的目标范围为止。

注意事项:

  • 梯度很重要
    • 如果它是可微的,则我们才能够对其进行学习
  • 梯度可能会消失
    • 每个额外的层都会依次降低信噪比
    • ReLu 在这里很有用
  • 梯度可能会分解(比如妈妈里面出现男,其实这在中国很常见)
    • 学习速率在这里很重要
    • 批标准化(实用按钮)可以提供帮助
  • ReLu 层可能会消失
    • 保持冷静,并降低您的学习速率

如何解决该问题,标准化特征值很重要

  • 特征具有合理的范围
    • 大致以 0 为中心,[-1, 1] 的范围通常效果比较好
    • 有助于梯度下降法收敛;避免 NaN 陷阱
    • 避免离群值也会有帮助
  • 可以使用一些标准方法:
    • 线性缩放
    • 为最大值和最小值设定硬性上下限(截断)
    • 对数缩放

其次在深度神经网络里面还有一个很有用的技巧,丢弃

  • 丢弃:另一种正则化形式,对神经网络很有用
  • 工作原理是,在一个梯度步长中随机“丢弃”网络的单元
    • 有一个可用于集成学习此处的模型的连接
  • 丢弃得越多,正则化效果就越强
    • 0.0(一点都不丢弃) = 无丢弃正则化,得到原来的复杂模型
    • 1.0 (全部丢弃) = 丢弃所有内容!学不到任何规律,得到特别简单且无用的模型
    • 中间值更有用(在这个位置进行丢弃,则是在这个位置应用了有效的正则化)

多类别神经网络

在之前的章节里面说到,逻辑回归很适合一些是或者不是的问题,它可以很方便给某样东西的是是或不是加上概率,比如是否是是垃圾邮件还是非垃圾邮件。

但在多类别中,我们是怎么处理的呢,即如何处理狗是哺乳动物?还是植物?还是其他什么?这种多分支选择。

那么我们在一对多类别中,我们可以:

  • 为每个可能的类别创建唯一输出
  • 分别对“我的类别”与“所有其他类别”信号进行训练
  • 可以在深度网络中执行,也可以借助单独的模型执行

那我们有没有更好,更方便的方法呢?对的,我们可以使用SoftMax进行多类别划分。SoftMax使得:

  • 添加了附加限制:要求所有一对多节点的输出总和为 1.0(100%,这就是逻辑回归的多类别升级版)
  • 附加限制有助于快速训练收敛
  • 另外,允许输出解析为概率

我们可以使用两种方式让SoftMax进行计算

  • 全部数据计算 即暴力破解;针对所有类别进行计算
  • 采样数据计算 即针对所有正类别标签进行计算,但仅针对负类别标签的随机样本进行计算。

那我们应该如何使用标签给多类别分类呢?

多类别单一标签分类:

  • 一个样本可能只是一个类别的成员。
  • 类别互斥这一限制是有用的结构。
  • 有助于在损失中对此进行编码。
  • 将一个 softmax 损失用于所有可能的类别。

多类别多标签分类:

  • 一个样本可能是多个类别的成员。
  • 无需对类别成员资格设定额外的限制。
  • 将一个逻辑回归损失用于每个可能的类别。

相似性神经网络

这个在商品推荐和广告推荐比较常用。通常我们需要在多维度分析用户来推荐对应商品:

  • 比如用户对商品的兴趣可大致从N个方面分析
  • 每部商品都变成一个点,在不同的维度中,这个点的高度都是不一样的
  • 那么我们可从数据中嵌套学习,用户在不同维度的兴趣点

在深度网络中学习嵌套能做什么?

  • 无需单独的训练过程,也就是说,嵌套层只是隐藏层,每个维度一个单元
  • 监督式信息针对所需任务调整学到的嵌套,即根据用户上一个商品调整,下一个商品出现
  • 隐藏单元直观地发现整理多维空间中的各项,才能最大限度地优化最终目标

我们可以使用输入表示法来显示用户用户兴趣的相同特征

  • 每个样本是用户兴趣的相同的特征的稀疏矢量
  • 使矩阵样本的密集表示法表示

比如

x 双肩包 沙发 冰箱 单肩包
x x v x x
x x x v x
用户 v x x v

那么用户的矩阵样本可表示为(1,0,0,1),但这种方法从空间和时间上来说,这种表示法并不高效,因为每个看过的商品都是一行,我这里只例举了双肩包,沙发,冰箱,单肩包。而正常来说,用户看的远超这些。

我们如何选择嵌套维度个数,请根据以下三条判断:

  • 嵌套维度的个数越多,越能准确地表示输入值之间的关系
  • 不过,维度个数越多,过拟合的可能性就越高,训练速度也会越慢
  • 经验法则

嵌套能做什么?

  • 嵌套会以相似内容彼此靠近的方式将各项内容(如影片、文字等)映射到低维实矢量
  • 嵌套也可应用于密集数据(如音频),以创建有意义的相似度指标
  • 联合嵌套多种类型的数据(如文字、图片、音频等),以定义这些数据之间的相似度

结语

为什么要学习这些概念,我认为概念讲解往往比代码讲解更容易理解一样东西,代码也仅仅是概念的一种实现,概念能更好帮助我们在后续的阶段更好的学习,欢迎查看tensorflow.js的实战的入门级教程学习更多实战内容。