机器学习的两种主要类型是监督学习和无监督学习
监督学习给出了一组训练集,给定了样本输入和其对应的输出,机器在学习后得出一个函数,使之在之后给定任意输入时,都能自主预测一个合理的输出值。
监督学习的目的有两种:回归与分类。回归是预测给定输入对应的输出值,它是连续的、无限的,例如给定房屋面积预测房屋价格;分类是预测给定输入对应的种类,它是离散的、有限的,例如给定肿瘤大小预测肿瘤是否为良性。当然,监督学习也可能是多输入多输出系统。

线性回归:
一个最佳的拟合函数fw,b(x),其对应的代价函数J(w,b)应当是(预测的结果-真实的结果)^2的均值的最小值,这说明拟合的效果足够好,足够接近真实值了。其中预测的结果-真实的结果代表了误差,平方是为了得到绝对正的误差累计,系数1/2是为了使得func不会随着训练样本的增大而急剧增大,同时方便后面梯度下降求导时把2约去使式子看起来更简洁。
这个形式的代价函数也被称为平方误差成本函数(square error cost function)。平方误差成本函数的特点是:它只存在一个局部最小值,且该局部最小值就是全局最小值。

以一个满足y=x的样本集为例,同时固定b=0。我们发现w的值对J(w)的影响如下图。

我们的目标就是得到一个w,使J(w)的值最小,在本例中即w=1。

综上,线性回归的基本内容就是:确定模型,根据代价函数得到最小值。

如果把b考虑进来,那么代价函数J(w,b)的图像应该就是一个二元函数在三维视图的图像:

同理,这个二元函数也会有一个最小值,我们的目标仍然是求解这个最小值时对应的w和b。
更进一步的,如果x是一个多变量的矩阵,那么此时的w也就是一个矩阵[w1,w2…wn]。
则此时的J(w,b)就可以写成J(w1,w2…wn,b),对应的图像也会向更高的维度拓展。
接下来我们的目标就是,如何借助算法找到这个最小值,这个算法就是著名的梯度下降法。
梯度下降法的适用目标不仅仅在线性回归中,它可以用来尝试最小化任何函数。
梯度下降法的步骤如下:

梯度下降法的目的在于尝试找到任意函数的最小值。它的基本思路是,站在某点环视四方,找到该点下降率最大的方向(即梯度方向)前进一小步,再多次重复上一步骤,如此便有可能找到一个极小值。因此,我们需要开始时为梯度下降法确定一个起点w0,b0(可以任意确定,但可能影响结果),然后让w,b沿各自的梯度方向下降从而降低J(w,b)的值,如此循环直到我们找到或接近一个极小值。为什么要说是极小值而非最小值呢?因为这是梯度下降法的一个特性,当一个函数J(w,b)存在多个极小值时,梯度下降法往往会陷入其中一个极小值,而这个极小值不能被确保是最小值,具体陷入哪个极小值由往往起点和学习率决定。

梯度下降法的梯度更新过程如下所示:
分别对当前的J(w,b)求关于w和关于b的偏导,然后用偏导值乘以一个系数α后作为减数减小当前的w和b,这个系数α也被称为学习率,它的取值是[0,1]。它反映了梯度下降的速率,即梯度对w和b更新的影响程度。而后借助下述公式更新w和b,注意:w和b需要同步更新,即先求出更新后的w,b值再统一赋值给原来的w,b,这意味着在计算更新的w,b值时用的永远都是原来的w,b。下面给出了关于同步更新的正确与错误两种形式。

为什么这个公式能帮助我们找到局部最小值呢?我们来看下面只有w的例子:

可以看到,不论是位于局部最小值的哪端,梯度下降法总能帮助我们逼近局部最小值。

学习率α对梯度下降有很大影响,当α取值过小时,会导致梯度下降过程缓慢;当α取值过大时,梯度下降可能过冲,永远无法到达最小值,甚至可能无法收敛导致发散。

对于线性回归和平方误差成本函数而言,其偏导可以写作如下形式:

其推导过程如下所示:

事实上,对于任意凸函数J(w1,w2…wn,b)而言,其局部最优解是全局最优解,因而梯度下降法很适合求解凸函数的全局最小值问题。
补充一下关于如何判断一个函数是凸函数(convex function):
 对于一元函数f(x),可以通过其二阶导数f′′(x)的符号来判断。如果函数的二阶导数总是非负,即f′′(x)≥0,则f(x)是凸函数。我们可以从几何上直观地理解一元凸函数的特点,如下图所示:即凸函数的割线在函数曲线的上方。

 对于多元函数f(X),可以通过其Hessian矩阵(Hessian矩阵是由多元函数的二阶导数组成的方阵)的正定性来判断。如果Hessian矩阵是半正定矩阵,则是f(X)凸函数。
对于非凸函数而言,梯度下降法可能会陷入其中某个极小值而非全局最小值,以下图为例:当函数进入局部最小值时,此时梯度为0,w和b将不再更新。

此外,梯度下降还分为批量梯度下降(batch gradient descent)和部分/小批量梯度下降(Mini-Batch),批量梯度下降的每一步使用所有的训练集数据,而部分梯度下降的每一步只使用部分的训练集数据(通常是顺序选取一组完全不同的部分)。批量梯度下降寻找w,b的过程会比较准确,但当样本集m过大时求和过程会非常慢,导致批量梯度下降的时间成本很高;而部分梯度下降由于只采用部分数据,因此它的每一步在接近全局最小值时可能不够可靠甚至有些嘈杂,有时甚至会背离最小值移动,但它整体仍沿着趋向全局最小值的方向运动,部分梯度下降的优势在于当训练集m很大时,它仍然能以较快的速度完成每一步迭代(例如m=1亿而我每次只取m’=1000)

我们之前谈论的x只有一个,这也被称为单元线性回归;现在我们把它拓展为多元线性回归,直观上来看,就是一个结果取决于多个变量输入,而w则是各变量对于结果的权重。
仍以房屋价格为例,此时的模型可以写作:

我们可以发现,此时的w和x均为一个由各变量组成的向量:

于是我们就可以得到用向量表示的线性回归普遍公式:

对于多变量的表达式而言,我们通常对其进行矢量化(vectorization):在python中我们利用numpy下的函数np.array和np.dot对其进行矢量化及运算,这不仅使得整个代码看起来更简洁,且由于numpy对于硬件的并行调用,整个代码的运行速度也会得到提升。

以一个不含b的多元线性回归J(w)为例,其中d表示w的偏导,学习率α取0.1:

我们可以发现,有没有进行矢量化的代码风格与效率之间相去甚远。
利用矢量化,则多元线性回归的表达式就可以改写为:

同理,我们把偏导公式在线性回归的转化加进来,记住对于wi的求解就是求出J对于各个wi的偏导,故各偏导的差别即为末尾对应的xi的差别:

事实上,解决线性回归还有一个方法被称为正规方程法(Normal Equation),他可以不通过迭代就可以直接算出w和b的值,但它对于除线性回归以外的问题而言没有推广性,且当w的规模很大时计算会非常慢,因而正规方程法往往只会出现在机器学习库中用于实现线性回归,梯度下降法仍是寻找代价函数J最小值和对应的w和b的推荐算法。

接下来介绍一些小技巧,这可以帮助你更快更好的完成梯度下降过程:
1.特征缩放Feature Scaling:
重组规模RESCALE:当你有不同的变量x时,如果这些变量的取值范围相去甚远时,可能会导致梯度下降运行缓慢。首先我们要了解一个常识,当某个变量xi的取值都很大时,其对应的权重wi一般都会偏小,这会导致梯度下降过程中较难逼近这个wi,可能会反复横跳。
例如下面这个例子:假设房屋的面积x1通常取值在300-2000,而房屋卧室的数量x2通常取值在0-5,这就会导致w1非常小,难以完成梯度逼近。

解决方法也有很多,首先介绍的就是标准化方法,它将各变量的值除以它们的最大值从而得到一组取值范围均在[0,1]之间的变量向量,对应的RESCALE图如下所示,可以看到此时的梯度下降效果较好

其次要介绍的是mean normalization均值归一化,它先得到各变量xi的均值μi,而后对每个xi减去均值μi后除以他们最大值与最小值之差,得到的结果就是均值归一化的结果,这个值位于[-1,1]之间

最后要介绍的是Z-score标准化,它先得到各变量xi的均值μi和标准差σi,而后对每个xi减去均值μi后除以σi,得到的结果就是一个均值为0,方差为1的标准分布,也会比较小且均匀。

下面给出了一些适合与无需特征缩放的变量取值:

2.观察梯度下降过程是否收敛(梯度下降是否在帮你找到J的最小值):
也介绍两种方法,一种被称为学习曲线J-k,它的纵轴是成本函数J的值,横轴是迭代次数k,如果我们发现随着k的增大,J不断减小且趋于平稳,则认为梯度下降过程收敛。

另一种方法是自动收敛测试,设定一个小量ε(比如0.001,实际上可以任意设置),当单次迭代J的变化小于这个小量时,认为梯度下降趋于收敛,但它的缺点在于如何选取ε以及结果不够直观,因而更加推荐使用学习曲线法。
当我们发现学习曲线随着迭代次数呈现往复摆动甚至快速上升的情况,这很有可能是学习率α过大导致的,此时我们应该减小学习率。

至于如何选择一个合适的学习率,可以采取以下方法:先取一个比较小的学习率,此时J的下降会非常缓慢;再取一个比较大的学习率,此时J会随着迭代往复摆动。则一个合适的学习率便介于[small,big]之间,且通常从big侧往小取可以得到一个不错的效果。

之前讲的都是线性回归,现实生活中的很多情况变量之间可能并不满足线性特征,这会导致使用线性回归无法得到一个好的效果,例如下面这组训练集:

这时候就要引入多项式回归(Polynomial regression)了,但在此之前,我们先来介绍一下特征工程(Feature Engineering)的概念,特征工程是将原特征变量进行数学变化或组合得到新特征变量的过程,特征工程的目的在于使特征方程更加符合数据集的拟合要求,而所谓的多项式回归,实际上是特征工程的一个特例,当线性回归fw,b(x)=wx+b效果不好时,我们通过对原变量x进行平方,立方,开放等特征工程操作,尽量使新方程更加符合数据集的拟合要求,而这种线性回归的特征工程结果就是多项式回归,典型结果如下:

值得注意的是,当使用多项式回归或特征工程产生次方项时,特征缩放就显得尤为重要了,因为假设原变量的取值为1-103,那么它的立方取值就要1-109了,这是不能被接受的。

逻辑回归:虽然它有回归regression二字,但它实际上用于解决分类classification问题,且专用于解决二元分类,所谓二元分类,即分类的结果只有两种,通常定义为0和1。对于二元分类,线性回归不是一个好的方法,我们介绍逻辑回归:
仍以肿瘤是否为阳性举例,我们期望的回归函数应当类似于下图:

如何利用这个连续的回归函数判断离散的结果呢?思路也很直白,就是当对应x的回归函数结果<0.5时返回0,>0.5时则返回1。这种类型的回归函数有一个名字被称为Sigmoid函数,它的函数表达式为:g(z)=1/(1+e^-z),它的函数图像如下图所示:

可以看到,Sigmoid函数的一个重要特点在于不论z的取值如何,其结果总约束在[0,1]之间,
利用这个特点,我们可以把原先线性规划中的函数f(x)=[w][x]+b作为z代入到g(z)中,从而将一个无限变化的值限制在[0,1]之间,再利用四舍五入得到二元结果。需要指出的是,逻辑回归在四舍五入前的输出代表的意义就是二元结果取1的概率,而正是因此我们选择0.5作为结果变化的区分点。我们也把这个输出写作概率的形式:

由于这个预测结果仍是一个猜测值,故仍表示为hat{y}。
以Sigmoid函数作逻辑回归为例,当z=0时g(z)=0.5,而z=[w][x]+b,因而区分结果取0还是取1的关键就是[w][x]+b是小于零还是大于零。

由此我们引入决策边界(Decision boundary)的概念,所谓决策边界,就是g(z)=0.5,也就是Sigmoid函数下z=0时的各变量可能取值形成的边界线,整体的样本集被决策边界大致的一分为二,且可以认为在决策边界附近时,结果的取值是最不可信的而摇摆的。以线性回归的函数z为例,此时z=w1x1+w2x2+b=x1+x2-3,则决策边界为如下的一条直线,它将y=0和y=1明显的区分成了两部分。

当然,线性回归的直线边界有时候无法取得很好的效果,我们仍可以借助特征工程的理论来得到其他边界形式。对于多项式回归和非线性回归而言,决策边界也会变为曲线的形式。如下例中z=w1x1^2+w2x2^2+b=x1^2+x2^2-1,则决策边界为一个以原点为圆心,1为半径的圆。

同理,对于更复杂的多项式和非线性式,决策边界也会变得复杂,从而适配更加复杂的逻辑回归问题。

接下来梳理一下逻辑回归要实现的目标,作为监督学习中分类的一种,同样在给定一系列变量x和其对应的离散二元结果y作为训练集后,我们要确定w和b取何值时代价函数J最小。
借助Sigmoid函数定义的逻辑回归方程是:

这个方程的意义就像线性回归中的fw,b(x)=wx+b,是逻辑回归得到的用于拟合数据的曲线,只不过其结果还要与0.5做比较来得到最终结果究竟是取0还是取1。
同理,逻辑回归也会有它相应的成本函数,可以发现平方误差函数不适合在逻辑回归中承担成本函数的责任,因为它在逻辑回归中非凸,会出现很多局部最小值,不利于梯度下降算法。
逻辑回归的代价函数如下,它是由最大似然估计法(Maximum Likelihood)证明的,其中L(f,y)是单个样本的损失函数(Loss Function):
注意:这里的log是数学意义上的ln。

我们来分析一下这个函数,首先看看单个样本损失L的定义。它在坐标图L-f下的形状如下图所示,由于f的取值范围在[0,1]之间,所以只取[0,1]部分绘制:

          if y=1                              if y=0

可以看到,当样本结果为1时,函数fw,b(x)越接近于1损失L就越小;同理当样本结果为0时,函数fw,b(x)越接近于0损失L就越小。由于fw,b(x)即预测结果的函数,如果预测的结果越接近于实际的样本结果,那么其损失自然就越小,将所有的损失L取均值得到代价J,那么J的最小值就是样本集的各样本损失L总和的最小值,损失总和越小代表着总体的预测结果越接近于总体的真实结果,通过这种方法调整w和b使J趋向于最小值,从而得到一条最佳的预测曲线fw,b(x)。
由于y只能取0或1两个值,我们可以利用变换将L从条件式变为复合式如下:

则代价函数便可以写作:

数学上可以证明,这个代价函数J仍然是一个凸函数。要得到这个代价函数的最小值,方法仍然是梯度下降法:

偏导值的详细推导如下:

我们有一个发现,逻辑回归的梯度下降公式竟然与线性回归一模一样!这意味着在计算梯度下降时,可以使用同一套公式和代码进行迭代求解。
在进行逻辑回归的时候,观察学习曲线判断梯度下降收敛、向量化、特征缩放等方法仍然是通用的,可以借鉴线性回归的相关理论。

以上便是线性回归和逻辑回归的大致内容了。回顾一下,线性回归用于解决回归问题,而逻辑回归用于解决分类问题,二者同属于监督学习的范畴,那么两者也会出现很多类似的问题。
一个问题就是预测曲线的欠拟合(underfit,也被称为高偏差high bias)和过拟合(overfit,也被成为高方差high variance)问题:所谓欠拟合,一般是特征变量的个数或维数过低,导致特征方程(拟合曲线或是决策边界)为一条直线,难以拟合一些复杂的非线性变化;所谓过拟合,一般是特征变量的个数或维数过高,导致特征方程极力的想去完美适配样本集的所有样本,此时虽然对于样本集内的数据特征方程适配的很好,但当它预测新的例子时就变得很难推广。因此,一个合理的拟合应该既不错的适配样本集的数据,当新的例子引入时,又要能够很好的预测该例子对应的输出结果。

解决过拟合通常有以下几种方法:获取更多的样本数据,减少特征变量的个数或阶数(这也被称为特征选择Feature Selection),还有一种比较好的方法称为正则化(Regularization),对于一个高阶的特征方程产生的过拟合而言,通常是由于高次项占比过大导致的,减少特征变量的个数或阶数事实上就是将原特征方程的高次项系数置0了,事实上,我们可以将这个过程变得更温和一点,即通过减小高次项的系数,在保留高次项作用的同时又让高次项不会过分影响特征方程的形状产生过拟合,这项技术就被称为正则化。正则化实际上就是改变系数权重w1-wn的过程,而b是否要发生改变则无关紧要,故多数情况下不选择改变。
下图是采用正则化后线性回归拟合效果的改变,可以看到过拟合现象得到了很好的缓解:

正则化作为一个通用方法,也可以作用于解决欠拟合问题,增加特征变量的个数和阶数的过程可以看做是正则化增加高次项系数的结果。在之前的讨论中我们知道了,如果要防止出现欠拟合和过拟合的现象,对于高次项的系数设置应该不能过小(0)也不能过大(1000),而对于给定特征变量个数与阶数的特征方程而言,系数的设置不会过小而只会过大,且对于复杂的特征方程,难以找到影响拟合结果的重要特征,因此,实现正则化时,我们通常考虑整体缩减(也叫惩罚)所有的权重wj,这通常会使曲线变得更加平滑而不易过度拟合。
那么如何用数学语言描述正则化方法的这个惩罚过程呢?其实也很简单,那就是在代价函数J中加一项惩罚项即可,惩罚项由各权重wj的平方和乘以系数组成,有了惩罚

项,J在梯度下降过程中如果尝试增大w来降低J,那么就会受到惩罚项惩罚使J增大,从而限制算法在使J下降时不要过度增加w。其中λ是惩罚系数,它和学习率α一样由人为设置,决定了代价函数对正则化的重视程度,λ越大,代价函数就越重视正则化。
于是,正则化线性回归问题的代价函数就是下式:其中m是样本数量,n是特征变量数量

而要找到这个代价函数的最小值,其方法仍然是梯度下降,且公式基本不变,只是在求解w的偏导时,需要额外对惩罚项求偏导,因而其迭代公式如下:

同理,我们也可以得到逻辑回归问题的代价函数:

而由于线性回归和逻辑回归的代价函数梯度下降算法一样,因而正则化后的线性回归和逻辑回归的代价函数梯度下降算法也是一样的,请参考正则化线性回归的梯度下降公式。

接下来要讲的就是机器学习中最重要的知识——神经网络(也叫多层感知器)了:
神经网络最初是一个生物学的概念,一般是指大脑神经元、触点、细胞等组成的网络,用于产生意识,帮助生物思考和行动,后来人工智能受神经网络的启发,发展出了人工神经网络。
人工神经网络(Artificial Neural Networks,简写为ANNs)是一种模仿动物神经网络行为特征进行分布式并行信息处理的算法数学模型。这种网络依靠系统的复杂程度,通过调整内部大量节点之间相互连接的关系,从而达到处理信息的目的。神经网络的分支和演进算法很多种,从著名的卷积神经网络CNN,循环神经网络RNN,再到对抗神经网络GAN等等。
无论是在学习机器学习还是深度学习时,我们都会频繁接触神经网络这个词,人工智能、机器学习、深度学习、神经网络这几个概念之间的关系图如下图所示。

随着要处理数据和变量的增大,传统的人工智能学习算法(线性回归和逻辑回归)的表现将远远弱于神经网络,而在当前这个大数据时代且随着GPU的发展,神经网络正大展宏图。

神经网络模型建立在很多神经元之上,每一个神经元又是一个个学习模型。这些神经元(也叫激活单元,activation unit)采纳多个特征作为输入,并且根据本身的模型提供一个输出。事实上,神经元相比于大脑的神经元而言是一个极其简化的模型,但它确实能让神经网络发挥的足够出色。神经元的数学模型如下,可以把它理解为一个多输入单输出的函数单元。

这个函数具体的选择有很多,例如我们之前学过的sigmoid就可以作为单个神经元的函数,之后也会再介绍几种,但函数的自变量通常都是由输入变量与权重的矢量相乘[z]=[w][x]+[b]。通常,我们也把神经元的输出成为激活(activation)。
而神经网络就是由一系列不同功能/函数的神经元以一定的结构层次排列组合而成的网络。
我们把一组在功能上相同或是输入相似特征的神经元聚合在一起称为神经网络的层(layer),将接受输入的层称为输入层,输出结果的层称为输出层,中间各类转换和处理的层称为隐藏层。我们一般把输入层作为layer0,而之后的层则依序递增。上层神经元的输出(或是激活)会成为下层神经元的输入。神经网络的层数计算不包括输入值和输出值。

我们以一个判断一件T恤是否会成为爆款的神经网络为例来看看其基本组成,图中的各神经元函数均为sigmoid函数,至于为什么这样选之后会介绍:
可以看到,作为一件T恤,决定它是否会成为爆款的直观输入可能包含价格、运输成本、市场营销和材质四个方面,而输出则是它成为爆款的概率。我们发现,输入与输出之间并没有直接联系,因而我们可以引入中间变量可负担能力,知名度和质量来作为一层隐藏层。可负担能力接收价格和运输成本经过sigmoid函数输出可负担能力造就爆款的概率,知名度接收市场营销经过sigmoid函数输出知名度造就爆款的概率,质量接收价格和材质经过sigmoid函数输出质量造就爆款的概率,而后这三个激活再作为输出层的输入经过sigmoid函数得到综合造就爆款的概率。
如果我们把左边的输入遮住,只看可负担能力,知名度和质量就可以发现,其实这个神经网络的右边部分就是这三个变量的逻辑回归。其实这个神经网络就像是逻辑回归,只不过我们把逻辑回归中的输入向量x变成了中间层的a,我们可以把a看成由x进化而来的更为高级的特征值。由于梯度下降会将a是变得越来越厉害而精确,这些更高级的特征值远比仅仅将x次方(做特征工程)厉害,也能更好的预测新数据。这就是神经网络相比于逻辑回归和线性回归的优势。神经网络的本质仍然是根据输入预测输出,而中间层产生的各变量(也就是可负担能力,知名度和质量),事实上完全由神经网络自身进行分析与处理,不劳我们费心。

在实际的神经网络中,由于我们一般难以判断隐藏层的各神经元需要的输入具体与哪个原始输入变量有关,故事实上我们会让神经元与每个原始输入量挂钩,而将具体的取舍判断交由神经网络自身决定,这也是为什么每层的如下图所示:

进一步地,写作向量形式如下:

将神经网络各层的输入输出矢量化后,我们引入更多的表示概念:

把layer l的输入记为矢量a[l-1],输出记为矢量a[l],输入的权重矢量记为w[l],偏差矢量记为b[l],每层神经元的函数记为g。如果要表示矢量里的某个值,即一层中某个神经元的输入输出结果,则用下标表示该神经元位置,上标表示该神经元所在的层,即aj[l]等。则任意某个神经元的函数表达即为下式:

接下来我们来介绍一下神经网络前向传播(Forward Propagation)的概念: 所谓神经网络的前向传播,即从神经网络的第一层开始正向一层一层进行计算,直到最后一层。利用输入x依次得到a[1],a[2]一直到输出a[k],这通常是为了利用输入计算神经网络的预测结果和手动构建神经网络。对于一般的神经网络而言,每层神经元的数量通常是逐层递减的。

如何在代码中构建神经网络呢?我们将按照普通->矢量->函数的方法循序渐进的介绍:
普通:如下图所示,假设神经元函数均为sigmoid函数,为了搞清楚前向传播的流程,我们这里直接给定每层每个神经元的[w]和[b]而不通过学习得到,同时我们给定输入层变量[x],从而得到该神经元的[z]和激活[a],最后将该层所有神经元的[a]组合得到该层的[a],将这个[a]作为下层的输入重复这个过程。

矢量化:在之前的学习中我们知道了,利用矢量运算能大大加快代码的运行速度,在普通的代码中,我们处理的内容都是一维向量,而事实上,我们可以把每一层的参数都组合成一个矩阵形式,如下图的[W],A和[B],其中[W]的每列都表示该层一个神经元的权重[w],[B]的每列都表示该层一个神经元的偏差[b],则该层所有的激活输入[Z]=[AT][W]+[B],其中矩阵乘法在Python中使用matmul实现,该层所有的激活就是[A]=g([Z])。利用矢量化,原来复杂的代码就变得既简洁又高效了。

函数:以上两个构建方法只是为了熟悉神经网络的构成,同时了解矢量化的优点。实际构建神经网络时,我们不可能一开始就知道[W]和[B]的具体取值,而是需要通过学习得到。事实上,PyTorch和TensorFlow已经为我们提供了一系列成熟的机器学习所需函数,真正使用时只需要调用它们提供的函数就可以了。得到一个神经网络的基本过程有以下三步:
1.构建架构:Dense规定了一层神经元的数量和该层神经元使用的激活函数(一般同一层的神经元都使用同一种激活函数),Sequential规定了哪些层会被顺序连接成一张神经网络。
2.定义代价函数和损失函数: 将得到的神经网络model利用方法compile(…)定义损失函数,格式是loss=损失函数,例如loss=BinaryCrossentropy()表示使用逻辑回归的损失函数(二元交叉熵),或是loss=MeanSquaredError()表示使用线性回归的损失函数(平方误差函数)。损失函数针对的目标即神经网络前向传播得到的输出y与输入x之间的关系式。
3.确定样本集:将输入x和输出y以np.array的形式赋值
4.训练神经网络:利用函数fit(x,y,epochs)来训练,其中x是输入,y是输出,通过训练可以确定神经网络各层的[W],[B]参数,而神经网络就是通过其各层各神经元的w和b来构建起一个庞大的预测模型的,可选参数epochs=K限制最大迭代次数为K次。初始化[W],[B]参数的方法一般是令[B]=0,[W]为一个很小的随机值(如0.01),而一般的训练或学习方法依然是对代价函数使用梯度下降法(事实上,是采用梯度下降法的变体Adam算法),而计算梯度下降法中的偏导项使用了神经网络反向传播算法(Back Propagation),如何在神经网络中使用梯度下降法(Adam算法)将会马上说明。
5.进行模型预测:训练完毕后,当传入新的输入x_new时,就可以使用方法predict得到该输入的预测输出。

我们对比逻辑回归和神经网络,事实上两者都有经历根据输入计算输出的建模过程,确定损失函数和代价函数的过程,以及确定参数w和b使损失最小的过程。

到目前为止,我们一直在隐藏层和输出层的所有节点中使用sigmoid激活函数。事实上,我们还有很多其他激活函数可用,这些激活函数适合不同的使用场合。
三大主流的激活函数如下,分别是线性激活函数,sigmoid激活函数和ReLU激活函数:

1.线性激活函数:a=g(z)=z,线性激活函数也可以看作是不使用任何激活函数
2.sigmoid激活函数:g(z)=1/(1+e^-z),值介于(0,1)之间
3.ReLU激活函数:g(z)=max(0,z),值介于(0,+∞)之间
在后面我们还会学到一个叫softmax的激活函数,它通常用于多元分类问题。
那么如何进行激活函数的选择呢?我们分别对输出层和隐藏层探讨:
输出层选择:

输出层的激活函数选择其实非常有规则性,由于输出结果与其输入之间必然蕴含着一个规划问题。于是当输出y是一个二元分类问题时,输出层的激活函数显然要选择sigmoid激活函数;当输出y可以取正或取负时(股票的涨跌),输出层的激活函数显然要选择线性激活函数;当输出y只能取正数时,输出层的激活函数显然要选择ReLU激活函数。
隐藏层选择:
事实证明,隐藏层的激活函数几乎都会选择ReLU激活函数,为什么呢?
对比ReLU和sigmoid激活函数,首先ReLU的函数形式比sigmoid更简单,这意味着计算会更快;此外,由于sigmoid在首尾斜率几乎为0,这会导致代价函数J在多处比较平,这意味着有很多偏导很小的段,这会导致在使用梯度下降法寻找最小值时,会迭代很多次而下降的非常慢。因此,ReLU比sigmoid激活函数更适合做隐藏层的激活函数。

而由于线性激活函数表示不使用任何激活函数,故如果我们对所有的神经元都使用线性激活函数,整个大型的神经网络就会变成一个普通的线性回归问题。以下面这个例子说明,可以看到,输出a[2]和输入x之间就是一个线性关系a[2]=[w][x]+[b]。这就破坏了构建神经网络的全部目的,这也是为什么激活函数对神经网络这么重要的原因。

故综上,在隐藏层我们通常选择ReLU激活函数,少部分选择sigmoid激活函数,而几乎不在隐藏层中使用线性激活函数。
于是我们如果要构建一个二元分类问题的三层神经网络,一个典型的构架如下程序所示:

多元分类问题:即现在的输出y可以取多个离散的值,例如识别手写数字0-9。
多元分类是二元分类逻辑回归的推广,输出层使用Softmax函数作为激活函数。
Softmax激活函数的公式为如下:

也就是:

可以看到它是把要计算概率对应的e的z次方除以所有e的z次方得到结果。
易得有结果:a1+a2+…+an=1,符合概率分布。
以四元分类为例,此时的输出[a]=[a1,a2,a3,a4]分别是y=1,2,3,4的概率:

Softmax对应的损失函数是(log对应数学里的ln):

由于aj表示了预测y=j的概率,故当y的真实值就是j时,就可以使用-log(aj)表示预测的损失,当aj越大时预测认为y=j的概率就越大,而当y真的为j时,损失就会越小;当aj越小时预测认为y=j的概率就较小,而结果说明y真的为j,那么损失就会较大。这就是Softmax如此规定损失函数的原因。
这个损失函数在TensorFlow里称为SparseCategoricalCrossentropy,于是构架代码如下:

为了提高精度,实际构建代码时我们可以使能可选参数from_logits,这个参数的功能在于它将输出层的激活函数与损失函数合二为一了,假设输出层的输入为z,输出为a,则原来的流程是利用激活函数得到a=g(z),再对a求损失函数。利用from_logits=true,此时TensorFlow会直接把z作为损失函数的输入,没有了中间变量a运算会变得更精确,如下图所示:

于是此时的代码框架如下所示,注意由于激活函数与损失函数合二为一了,因此输出层的激活函数直接用线性激活函数起传递作用即可,代码会自行根据使用的损失函数判断输出层会使用什么函数激活z从而代入损失函数中。此外,由于此时的输出值本质上是z而非a,因此之后在预测得到输出后还应该对其做一次输出层激活(例如下图中的softmax函数)

之前介绍了多元分类问题(Multi-Class Classification),这里再介绍多输出分类问题(Multi-Label Classification)。多输出分类问题解决针对同一输入而有多个输出的情况,例如给出一张图片,我们要判断里面是否有小轿车,是否有公交车,是否有行人。注意:这三个是非判断相互独立,也就是说这张图片里可能同时有小轿车、公交车和行人。针对这个问题,我们当然可以分为三个独立的二元分类神经网络分别判断,但多输出神经网络给了我们一个集成的机会,此时我们只需将输出层变为三个神经元即可,输出层输出的是一个含有三个特征的矢量。而后对输出层的每个神经元都使用sigmoid函数激活,其效果就等同于三个独立的二元分类神经网络了,而效率显然比三个独立的二元分类神经网络高。

对应于多输出分类问题的代价函数为(L为损失函数,m为样本数,以四输出分类问题为例):

多输出分类问题适用于不同输出之间可以共用低层级特征的神经网络,例如共用图像识别。

在神经网络的早期实现中,人们仍然使用梯度下降法来最小化成本函数,但随着时代的发展,人们发现了一种被称为Adam(自适应矩估计Adaptive Moment Estimation)的优化算法来最小化成本函数,而其效果会比梯度下降法快很多,而因此成为训练神经网络的优异算法。
Adam算法通过随着优化进行自动改变学习率α入手来优化梯度下降法:

Adam算法发现,当wj和b在优化过程中始终沿着近似同一方向改变时,这意味着我们走的每一小步都朝着相似的方向迈出,这可能是α比较小的结果,既然如此,我们把学习率α变大就可以更快的达成目的了;反过来,当wj和b可能在优化过程中在谷底附近左右振荡无法收敛时,可能是α比较大,既然如此,我们把学习率α变小就可以完成收敛了。这就是Adam算法自适应改变学习率α的基本思路,实际算法比较复杂,在这不作说明。由于Adam算法的自适应性,我们可以预见,对于不同的wj和b,Adam算法给出的学习率α也就不是同一个值了,也就是α并不是一个全局的单一值而是随j变化的αj。
接下来给出Adam算法的代码实现,在compile方法里,除了指出损失函数loss外,还要给出优化算法optimizer并规定为Adam(learning_rate=…),其中learning_rate是学习率的初值。

我们之前应用的神经网络层都是一种被称为密集层(Dense Layer)的网络层,密集层中的每个神经元都能得到它上一层所有的激活作为本层的输入,但事实上,网络层还有很多其他类型。卷积层是另一种比较常用的网络层,相比于密集层可以得到上一层的所有激活作为输入,卷积层的每个神经元只能上一层的部分激活作为输入,换句话说,卷积层的每个神经元只能看到上一个网络层的一部分,这会导致神经网络具有更快的运行速度且只需更少的训练数据。
下图可视化了一个卷积层,其中每个神经元只能看到上个网络层中对应色块的数据。

由卷积层组成的神经网络被称为卷积神经网络,以下图为例,我们给出一百个输入数据传入第一个有9个神经元的卷积层,其中每个神经元只看得到20个数据,而后第一层输出一个9列的列向量到第二个有3个神经元的卷积层,其中每个神经元只看得到5个数据,而后第二层输出一个3列的列向量到输出层做sigmoid函数得到输出。

卷积层相比密集层而言多了一个架构选择,即每个神经元窗口开的大小。
接下来我们看看如何评估你建立的模型的好坏。对于二维和三维模型,可以直接通过绘图来直观的判断模型效果,但对于更高维度的系统而言,我们就必须提出一个更加数学化的公式来定量的判断模型的好坏。为了在应用模型预测新输入前判断模型的好坏,我们可以把原训练集拆分为训练集(Training Set)和测试集(Test Set)两部分,比例一般为70%-30%。其中训练集专门用来训练神经网络得到[w],[b],测试集专门用来测试得到的神经网络在预测新输入时的效果。

于是此时计算代价函数J(w,b)时,其求和项便是训练集总数mtrain。
对于线性回归而言,其代价函数如下:
同时我们定义测试集误差Jtest(w,b)和训练集误差Jtrain(w,b)分别代表训练的神经网络对新输入的适应程度和对训练样本的适应程度,在线性回归下的公式分别如下,均为对应集合的预测值减去实际值的平方均值:

同理,我们也可以得到逻辑回归的代价函数,以及其测试集误差和训练集误差如下:

一般而言,由训练集得到的模型,其训练集误差Jtrain(w,b)会比未来输入的预测结果误差小。而未来输入的预测结果误差通常可以由测试集误差Jtest(w,b)来表示,因为Jtest(w,b)不参与模型训练,因而其值可以用来当作未来输入进行预测。
更进一步地,如果我们想要系统自动选择一个好的模型来进行机器学习,例如我们在拟合点的时候,到底应该选择几阶多项式呢?一个选择是根据不同阶的多项式利用训练集拟合,并查看测试集误差Jtest(w,b),选择最小的测试集误差对应阶数的多项式认为是最好的模型。但这样子我们就失去了测试集误差测试模型的作用,事实上,我们不应该让测试集参与任何形式的模型建立以确保测试集的完全中立。而要实现这个效果,我们可以将样本总集额外再多分一个部分,变为训练集、测试集和交叉验证测试集(cross-validation set)三部分,比例一般为(60%-20%-20%)。其中训练集专门用来训练神经网络得到[w],[b],交叉验证集专门用来决定用什么模型来进行机器学习,测试集专门用来测试得到的神经网络在预测新输入时的效果。
此时除了定义测试集误差Jtest(w,b)和训练集误差Jtrain(w,b)外,还要定义交叉验证误差Jcv(w,b)

把待选模型按照训练集均训练一遍,取之中交叉验证误差最小的认为是最好的模型,采用此模型下训练集的训练结果作为最后的应用模型,并使用测试集来观测其对新输入的适应程度。
这里的模型是一个宽泛概念,既可以用于选择拟合多项式,又可以在待选的神经网络中选择最佳的一个等。

利用测试集误差Jtest(w,b),训练集误差Jtrain(w,b)和交叉验证误差Jcv(w,b),我们可以判断一个系统是否存在过拟合和欠拟合的问题,对于欠拟合的系统,训练集误差Jtrain(w,b)和交叉验证误差Jcv(w,b)都会非常高;对于过拟合的系统,训练集误差Jtrain(w,b)会非常低,但交叉验证误差Jcv(w,b)会非常高;对于良好的系统,训练集误差Jtrain(w,b)和交叉验证误差Jcv(w,b)都会非常低。

随着拟合多项式阶数的提高,训练集误差Jtrain(w,b)和交叉验证误差Jcv(w,b)会呈现以下趋势,在Jcv(w,b)取最小值认为系统有最佳效果:

同理,交叉验证误差Jcv(w,b)也可以被用于判断正则化项系数λ的选择,其基本流程仍然是把参数λ的待选集合按照训练集均训练一遍,取之中交叉验证误差最小的认为是最好的模型,采用此模型下训练集的训练结果作为最后的应用模型,并使用测试集来观测其对新输入的适应程度。随着λ的提高,训练集误差Jtrain(w,b)和交叉验证误差Jcv(w,b)会呈现以下趋势,因为λ的增大会导致w的减小,从而从过拟合问题过渡到欠拟合问题,这会导致Jtrain(w,b)的增大,以及Jcv(w,b)会先减小后增大,这代表着拟合效果会先变好再变差(过拟合->良好->欠拟合),在Jcv(w,b)取最小值认为系统有最佳效果:

那么如何判断测试集误差Jtest(w,b),训练集误差Jtrain(w,b)和交叉验证误差Jcv(w,b)是高还是低,我们通常会制定一个用于性能评估的基准,例如人类完成该任务的能力,或是同类算法的表现,或是根据经验猜测。则此时的高低是基于基准的相对值,如下图所示:

判断一个系统性能是否良好也可以使用学习曲线,学习曲线是训练集误差Jtrain(w,b)和交叉验证误差Jcv(w,b)关于训练集规模mtrain的函数。
一个良好的模型,其学习曲线呈现如下趋势:随着m的增大训练集误差Jtrain(w,b)会上升,交叉验证误差Jcv(w,b)会下降,且两者会随着m的增大趋近于比较基准。

欠拟合:如果学习曲线随着m的增大训练集误差Jtrain(w,b)上升不大,交叉验证误差Jcv(w,b)下降不大,且两者都远高于比较基准,则此时算法可能存在欠拟合问题。因为欠拟合时可以调整的变量有限,故训练集的增大并不能让结果更好的拟合数据。

过拟合:如果学习曲线随着m的增大训练集误差Jtrain(w,b)会上升,交叉验证误差Jcv(w,b)会下降,且训练集误差Jtrain(w,b)远低于比较基准,交叉验证误差Jcv(w,b)远高于比较基准,则此时算法可能存在过拟合问题。因为过拟合会导致训练集误差很低,但对于其余数据适配度不高。同时可以发现,随着m的增大,过拟合有所缓解,这是因为训练集样本比较大时可以纠正算法过于执着于拟合经过几个点的情况。

学习曲线需要不断的选取训练集子集进行绘制,因而存在运行速率慢的特点。

修正过拟合可以采取以下方法: 修正欠拟合可以采取以下方法:
1.给予算法更多的训练集数据 1.增加特征的维度和数量
2.降低特征的维度和数量 2.尝试降低正则化系数λ
3.尝试提高正则化系数λ

故训练一个算法或神经网络的一般步骤如下,当训练集误差Jtrain(w,b)较大时,系统可能存在欠拟合问题,于是可以扩大神经网络解决欠拟合问题;当交叉验证误差Jcv(w,b)较大时,系统可能存在过拟合问题,于是可以给予算法更多的训练集数据解决过拟合问题。

事实证明,一个良好正则化的大型神经网络,其效果一般都会比小型神经网络效果好,故不必过于担心扩大神经网络可能导致的过拟合问题。
用代码为神经网络层添加正则化项的方法是在Dense中增加一项kernel_regularizer=λ即可。

机器学习的开发迭代满足以下逻辑闭环:Loop(选择模型和数据,训练模型,利用性能指标的判断方法诊断模型性能)

当训练出的模型效果不佳时,此时可以启用误差分析。所谓误差分析,即人类在系统判断错误的样本中抽取一部分来观察出错的原因,并取其中占比较大的优先解决。我们可以为样本集添加大量由最大原因导致的同类错误样本来集中训练神经网络在这方面的缺陷。误差分析适合该任务人类擅长时,可以作为指导方向的参考,避免花了大量精力解决了一个可能不是那么重要或者占比不是那么大的出错原因。

接下来我们来看看如何在有限的样本集中添加数据的方法:
对于视觉识别或语音识别等神经网络,我们可以对图片进行旋转,放缩,扭曲,添加噪声等来对一个示例提出更多具有类似标签的新示例。同理我们也可以对语音识别的原始语音添加背景噪声来提出新示例。

但是有一个要注意的事项,如果你只是为神经网络添加一些完全随机而无意义的样本,这并不会让神经网络表现得更好,即利用这种方法添加样本时必须基于原有样本。
事实上,现代的神经网络反而更看重训练数据而非算法和模型了。
迁移学习:对于数据很难获得、变换或添加而没有那么多数据的神经网络,迁移学习可以帮助你使用来自不同任务的数据来完善你的神经网络。例如我想识别手写数字0-9,但我没那么多样本,但我有一组很大的关于猫、狗、汽车、人等的样本集,比如说100万张具有1000个不同类别图像的样本集,此时我可以直接使用这个样本集先训练一个分辨这1000个种类的神经网络得到一组[w],[b]数据,而后将这个神经网络整体迁移到我的训练集中,而只需改变神经网络的输出层(从1000个输出变为10个输出),则非输出层的[w]和[b]的初值就是已训练好的神经网络非输出层的参数,而输出层的[w]和[b]需要用自己的训练集重新训练。对于极小的训练集而言,可以保持非输出层参数不变而只训练输出层参数;对于稍微大一点的训练集而言,可以训练所有参数,其中非输出层参数的初值为已训练好的神经网络非输出层的参数。

于是迁移学习的步骤一般为:先利用大型训练集进行神经网络的预训练,再使用自己的训练集对参数进行微调,微调方法无异于梯度下降法或Adam算法。
迁移学习的原理在于,对于输入类型相同的样本集来说,每一层神经网络(尤其是底层)做的事情会被训练的几近相同,例如图像识别中的第一层往往会识别物体的边,第二层识别物体的角,第三层识别曲线和基本图形,而这些不论在数字还是物体中都是非常通用的图像特征,正是因此我们可以使用图片来预训练神经网络用于识别数字,而只需要进行部分微调就能实现一个很好的效果。不过这也告诉了我们,迁移学习的输入种类必须相同,例如图像识别要用图片集,语音识别要用音频集,但两者不能混用,这是因为只有相同类别的神经网络才具有相似性和可转移性。

接下来介绍一下精确率和召回率(Precision and Recall)的概念,对于存在极为罕见情况或二元可能差别极大的二元分类而言(例如监测患者是否患有某种罕见病),对于这样的系统而言,判断系统的好坏就不能用一个简单的“准确率”来概括了,事实上,如果我们把所有输入的患者都标记为没病,则系统的“准确率”依旧很高,但这不能说明这是一个好的预测系统。
对于这种系统而言,我们一般将其分为四种类型,把hat{y}=1且y=1的称为True Positive类(即正确预测阳性),把hat{y}=1但y=0的称为False Positive类(即错误预测阳性),把hat{y}=0但y=1的称为False Negative类(即错误预测阴性),把hat{y}=0且y=0的称为True Negative类(即正确预测阴性),同时我们假设y=1为罕见类。以阳性为例,预测结果的精确率被定义为正确预测阳性的数量/预测阳性的总量,预测结果的召回率被定义为正确预测阳性的数量/实际阳性的总量。一个良好的模型应该同时具有不错的精确率和召回率,而像我们之前说的那个系统,虽然预测y=0时具有良好的精确率和召回率,但y=1时由于正确预测阳性的数量=0,故精确率和召回率都为0(定义0/0=0),模型自身的问题便暴露无遗了。

事实说明,精确率和召回率两者不能兼得,又或者说两者侧重于系统的不同方面。仍拿罕见病作为例子,如果我们希望具有十足把握时系统才预测y=1,此时我们可以将逻辑回归的阈值从0.5上调到0.8甚至0.9,即只有系统认为有80%-90%概率该患者患有罕见病时才预测其有罕见病,这种情形一般出现在治疗需要花大量金钱且保守治疗效果不差时,此时系统的精确率会提高,因为预测阳性的条件更为苛刻了,但系统的召回率会降低,因为会有很多系统认为概率不那么高但实际为阳性的病例被忽略掉了。反之,如果我们希望系统宁可杀错,不可放过的话,此时我们可以将逻辑回归的阈值从0.5下调到0.3或0.2,即只要系统认为有20%-30%概率该患者患有罕见病时就预测其有罕见病,这种情形一般出现在如果患病则可能危及生命时,此时系统的精确率会下降,因为会预测错误很多实际上没患病的患者,但系统的召回率会提高,因为预测阳性的条件比较宽松,大部分患病的患者都会被检测出来。
事实上,精确率和召回率之间的关系随阈值的改变呈现如下趋势,阈值较高必然带来预测率的提高和召回率的下降,阈值较低必然带来预测率的下降和召回率的提高,而要想确定这个阈值究竟取何值,既可以手动根据精确率-召回率曲线结合实际需求观察,也可以自动确定:

如果想要自动确定阈值,一般是取精确率和召回率的调和平均数(F1 score)最高者。记精确率为P,召回率为R,则F1 score的公式如下图所示,调和平均数会避免P和R之中某个值过小,即对应精确率或召回率过小的系统,这样的系统不是一个好系统。

决策树(Division Tree):接下来我们将学习另一种高级机器学习算法称为决策树,同样我们也要给定一组包含输入与输出的样本集,而决策树的各个非叶节点就是样本集的每种输入变量,这个输入变量既可以是离散的,也可以是连续的,决策树根据这个输入变量的某种区别将整个样本集分为左右两个子树,而这两个子树的根节点就是该输入变量,再对每一个子树也做如上递归,以此类推,直到满足终止条件(关于终止条件会在之后讨论)时,将仍在一起的一群样本归为一类放到叶子结点,并成为未来可能输入的预测输出。
我们以一个给出动物的耳型、面部和是否有胡须判断该动物是否为猫的决策树为例,由样本集训练得到的决策树如下所示,未来预测新输入是否为猫时也按照下述决策树的步骤一步步从根节点走到叶子结点,直到判断完成:

决策树学习算法:在所有可能的决策树里,尝试选择一个在训练集中表现良好,且具有推广性的最优决策树。
那么如何得到一棵最优决策树呢?
我们先来引入纯度(Purity)和熵(Entropy)的概念:纯度是一个子集同属某种类型(叶子结点)的程度,熵是一个子集分属不同类型的程度,可以认为纯度和熵是一对相反的概念。一棵最佳决策树的每一次节点分裂,不是任取某个特征就进行分裂,应当是选取能将所有的样本分割成两部分熵较小,纯度较大的样本子集的特征进行分裂。而当一个节点中的所有样本同属某种类型时(纯度为1,熵为0)时,我们就找到一个叶子结点,于是停止分裂这一节点。由于实现一个节点中的所有样本同属某种类型比较困难,当分裂会决策树超出最大深度(规定最大深度主要是为了防止决策树过拟合,即设置的特征辨别过多),或是每次分裂导致纯度的提高或熵的下降低于阈值(即将饱和),或是一个节点中样本的数量小于阈值时(即将饱和),我们也会停止分裂。
每个节点纯度的计算公式是:p=同属同一叶子结点的样本数量/总的样本数量

每个节点熵的计算公式由熵函数(Entropy Function)H(p)定义:

由于熵包含了纯度的概念,故通常我们就用熵来替代纯度。从熵函数可以看出,当一个节点的样本中由给定特征分出的两个子集分别各占一半时(p1=p2=0.5),熵的结果最大为1;当一个节点的样本中由给定特征分类只有一种结果时(p1=1,p2=0/p1=0,p2=1),熵的结果最小为0。使用Log2来定义熵函数主要是为了使当使p1=p2=0.5时,熵的结果为1,定义0log(0)=0。
有了熵函数,我们就定量的知晓了拆分特征时最大程度减小熵,最大化纯度的方法。每次拆分特征时熵的减小量称为信息增益(Information Gain)。我们还是以判断一个动物是否为猫为例,在确定其根节点特征时,我们将三个特征分别求解出对应的熵函数(注意:由于每次拆分时左右两个子集的数量会有所区别,在分别计算左右两部分子集的熵函数要乘以权重来得到最终的熵函数)并与拆分前的熵函数进行比较得到信息增益,信息增益最大的拆分规则即为最理想的拆分规则,每次都是最理想的拆分得到的结果也就一定是最优决策树。

信息增益的计算公式如下所示:即根节点的熵与左右子节点熵的加权平均值的差值

确定了最佳的拆分特征后,具体将训练数据发送到左分支或右分支,则取决于该实例的该特征的值。
于是,得到最优决策树的过程如下所示:
1.开始时所有的样本均在根节点
2.对所有可能的特征计算信息增益,选取信息增益最大的特征为实际拆分特征
3.根据实际拆分特征拆分样本集为左右两个样本子集
4.对左右子树重复过程2(一般先左再右),直到满足终止条件则设置一个叶子结点。
可以看到得到最优决策树是一个递归过程。如果左右子树在选择可能的特征时选择与父节点一样的特征,会发现H(p1root)=1,wleft=1,H(wleft)=1,故而信息增益为0,因此新的分类必然不可能是与父节点一样的特征。但是同一父节点的左右子节点之间是可以取用相同的特征的。
上面判断一个动物是否为猫的例子中,我们把所有的特征都定义为只能取两个值的特征,那么如果一个特征可以取多个离散值时应该怎么处理呢?我们引入独热编码(one-hot encoding)的概念,所谓独热编码,即如果一个特征可以取k个值时,就把它变为k个只能取两个值的二元变量。例如Ear Shape可以分为Pointy,Oval和Floppy三种时,我们就把Ear Shape变为是否是Pointy ears,是否是Floppy ears,是否是Oval Ears三个变量,这三个变量都是二元变量,且同时只有一个可以取1,其他都是0。则此时又可以用一般的决策树算法了,只是变量的数量有所增加。

此外,如果特征变量是连续的话,决策树又该如何确定该变量的节点呢?例如我们给定动物的体重,如何判断该动物是不是猫呢?对于二元特征而言,我们容易把它分为左右两个子树,而对于连续特征而言,分为两类的思想也是理所应当的,即将小于某个阈值的分为一类,而将大于该阈值的分为另一类,那么关键就在于如何确定这个阈值了。其方法是:我们先画出是否为猫与重量关系的图表,而后选择某个值作为阈值将样本分为两个子集,仍然按照离散的规则计算对应的信息增益即可。而确定所谓“某个值”的方法是取相邻两个连续特征的中点作为分界线做一次阈值尝试,故n个样本点需要做n-1次阈值尝试,最后取信息增益最高时对应的值作为实际阈值即可。

进一步地,之前的例子都是判断一个动物是否为猫,即输出的结果是离散的,那我们可不可以用决策树判断连续的结果呢?答案是可以的。此时输入的变量仍然是离散的,而输出结果是连续的。我们把这种用于预测回归问题的决策树称为回归树(Regression Tree)。在回归树中,我们预测的结果是一个连续变量的可能取值。例如,我们给定动物的耳型、面部和是否有胡须判断该动物的体重,此时分类的衡量标准便由熵变为了方差。具体地,我们对所有可能的特征计算分类后方差的加权平均数并与分类前的方差作差值得到方差增量,同理选择方差增量最高的作为实际特征并重复循环。

当只有一个决策树时,决策对样本的敏感度会非常高,样本集的轻微变化就可能导致决策树的剧烈变化。为了降低算法对样本的敏感度,通常我们会选择同时使用多个决策树做预测,并根据多数原则给出最终判断。把多个决策树构成的决策算法称为随机森林(random forest)。如果要生成多个决策树,光有一个样本集是不够的,我们要做的是利用原样本集得到多个样本集,且新训练集需要与原训练集类似而又有不同,采取的方法有以下几种:
1.有放回抽样:将含有n个样本的原样本集进行n次有放回抽样,把得到的结果组成一个新的样本集,这样就能保证新训练集与原训练集类似而又有不同,而后对新训练集也重复做决策树学习算法得到一棵新的决策树,重复这一过程B次便可以得到一片有B棵决策树的随机森林。
2.选择部分特征:有放回抽样得到随机森林的方法可能会导致每棵决策树在根节点及其附近类似或相同,不利于森林的多样性。选择部分特征的方法指出,在选择每个节点的特征时,只允许算法选择特征总量n的一部分特征k作为可选特征,一般选取k=sqrt(n)。
3.刻意学习:是有放回抽样的进阶版本,它把上一个决策树没有做好的地方在构建下一个决策树时更加关注。刻意学习的方法指出,当按照有放回抽样的思路得到一棵新的决策树后,把该决策树与原训练集对比,查看该决策树对原训练集的预测,在下一次抽样时,不完全随机的抽取训练集数据,而更有可能去抽取上一次决策树预测错误的样本。这个算法也被称为Boost算法,而Boost算法里最为出色的是一种被称为XGBoost(Extreme Gradient Boosting)的算法,在代码里调用XGBoost的函数如下,XFBClassifier()解决分类决策树,XFBRegressor()解决回归树问题:

最后对比一下决策树和神经网络各自的优缺点,以作出正确的选择:
决策树:适合作用于有结构的数据(表格),不适合作用于无结构的数据(例如图片、文本和音视频);学习时间通常比神经网络快。
神经网络:既适合作用于有结构的数据,也适合作用于无结构的数据;学习时间通常比决策树慢;可以使用迁移学习进行预训练;可以串联构建更大型的神经网络

接下来我们就要学习无监督学习的相关知识了:

与监督学习不同,无监督学习给出的训练集中仅仅给定了样本输入,而没有给出其对应的输出,机器本身需要利用算法找到这些数据的某种结构以预测未来输入的结果。
聚类(clustering):获取没有标签的数据并尝试将它们自动分组到不同集群中。最常用的聚类算法是一种被称为K-means的聚类算法,它采用集群中心的思想完成聚类。首先,我们根据规定的集群数k随机在工作空间初始化k个集群中心记为μ1-μk,将数据总数记为m,每个数据记为x(i)(i=1 to m),每个数据x(i)离得最近的集群中心对应的序号记为c(i),每个数据x(i)离得最近的集群中心记为μc(i)。K-means算法重复如下步骤:遍历每个数据,得到每个x(i)对应的c(i);遍历每个集群中心,取属于对应集群中心的所有数据的均值更新集群中心。直到某次重复后,集群中心的值不再发生改变,即算法收敛,则此时同属于一个集群中心的所有数据便被归为同一类。
整个算法的思想是非常合乎逻辑的,我们从理论上验证一下,K-means的代价函数被称为失真函数(Distortion Function),其公式如下,即每个数据到其对应的集群中心距离的平方均值:

事实证明,K-means聚类算法既适合用于分离良好的集群的聚类,又适合用于分离不是很好的集群的聚类。例如要根据人的身高和体重分配衣服的尺码SML,此时虽然数据是比较集中的,但K-means算法仍能较为清晰地分离数据组成聚类。

现在的问题是,我们如何进行集群中心的随机初始化呢?事实上,任意的随机初始化可能会出现平均值为0的集群中心,此时的解决办法一般式消除该集群中心,或者重新初始化该中心。避免这个问题的更好方法是在初始化集群中心时,只选取已知训练集中的训练点作为初始集群中心。

当然,在初始化时还会碰到的问题是算法可能会陷入局部最优情况:

为了解决这个问题,我们可以重复K-means算法多次,每次都重新在训练集中随机选择训练点作为初始集群中心运行K-means算法得到最优解,重复次数一般在50-1000之间。最后选择一个代价函数J结果最低的作为真正的全局最优解。此时得到的结果一般就是最佳的了。

最后一个问题,我们在做聚类问题时应该如何选择K的值呢?一种方法被称为肘法(Elbow Method),一般而言,随着K的增长代价函数J会逐步减小,画出J-K曲线后选取曲线的肘部(函数二阶导最大的地方)对应的K作为聚类的数量,此时的K被认为是最佳的K。

但是,这种方法在肘部不明显的函数里将会难以使用;且事实上,我们选取聚类数量K的时候通常是带有目的的,例如我们在选择T恤尺寸分类时通常在一开始就确定了是要分三类(SML)还是五类(SMLXLXXL),故我们其实可以直接进行K值的选择而不需要采用肘法。

异常检测(Anomaly Detection):异常检测算法查看未标记的正常数据集,而学会检测异常数据并发出危险信号,异常检测常常用于制造缺陷和金融欺诈等问题。要实现这个功能需要使用密度估计(Density Estimation),一般而言,异常检测中正常的样本会占绝大部分,于是我们判断一个样本是否异常的方法是设定一个阈值ε,当测试样本xtest的出现概率p小于ε时,则认为该样本有异常,否则则认为该样本正常。

由于现实生活的大部分变量符合正态分布:

于是异常检测算法的基本流程如下:
1.选择n个可能导致异常样本的特征xi
2.计算n维正态分布的所有期望和方差参数:

3.得到n维正态分布公式后,给定新的输入样例,判断p(x)与ε的大小关系:

上述p(x)公式成立的前提条件是各特征之间相互独立,但实际上各个特征可能相关,但事实证明,就算各个特征不独立该算法也能正常工作。而且由于p(x)是各特征变量的相乘,故当一个特征过小就会导致整体结果偏小,这也说明异常检测在任何维度检测到异常就会被鉴定为整体异常,这也符合异常检测的基本要求。

在异常检测中,为了选择合适的ε以及评估系统的性能,我们也会采取训练集、交叉验证集、测试集分立的方法。而且通常,我们会对异常检测的样本集进行正常或异常的标记,这可以提高异常检测的效果,尽管这有点像监督学习,但它实际上只是为了方便测试结果。例如我们有一个飞机引擎的样本集,其中有10000个被标记为正常的和20个被标记为异常的数据,此时我们一般不在训练集中使用异常数据(这就是为什么它并不是监督学习),而在交叉验证集和测试集判断系统性能时加入异常数据来看看系统检测异常的效果。例如在训练集中放入6000个正常样本,在交叉验证集和测试集中各放入2000个正常样本和10个异常样本。
此时选择ε的值就比较直白了,我们只需要在交叉验证集中选择把所有10个异常样本均排除在外的ε即可。当然,实际应用时我们会像罕见病的那个例子一样,综合精确率和召回率来得到一个最合理的ε值。

对比异常检测和二元监督学习,异常检测通常具有极少的阳性例子和大量的阴性例子,而监督学习的二元分布通常较为平均;此外,由于异常检测基于正常数据,因而它可以处理与原来出现的异常类型或原因完全不同的异常,凡是任何偏离正常数据的结果都会被标记为异常,而不论它是如何产生的,而监督学习检测二元分布时是基于已有的二元训练集的,因而它只能处理在训练集里见到过或近似的异常。例如金融诈骗花样百出,故采用异常检测;而垃圾邮件通常都带有推销的字样,故采用二元监督学习。

在异常检测中选择合适的监测特征也是很重要的,有的监测特征可能并不符合正态分布,此时再使用正态分布概率预测时误差就会比较大。当然,对于这种特征,我们有办法能让他变得更加符合正态分布,常见的方法是对原变量取对数log(x+c)或取小于1的幂x1/n。如果想要定量判断一个分布与正态分布的相似程度,可以查阅相关资料。

如何判断是否要为异常检测提供新的检测特征呢?一个常用的规则是如果异常和正常的样本在当前数量的特征下混在了一起无法区分,此时就要添加额外的检测特征,如下图将一维特征扩展为二维特征:

还有一种方法是根据已有特征来扩展新特征,例如在维护网站时,已有的特征是用户的CPU负载和网络流量,一般CPU负载和网络流量成正比,也许一个用户的CPU负载和网络流量都处在正常范围内,但CPU负载比网络流量相差很大,这仍然是不正常的。此时可添加新特征为CPU负载/网络流量,以此检测这种问题的出现。

推荐系统(Recommendation System):推荐系统也是一个非常受关注的机器学习算法,现在你每在一个网站上浏览数据,网站都会向你推荐他们认为你可能想要关注或感兴趣的内容,实现这个功能就需要借助的机器学习算法就是推荐系统。
我们以一个根据用户对已看过电影的评分来推荐他们未来可能喜欢的电影的推荐系统为例:

在这个例子中,我们用nu表示用户的数量,nm表示电影的数量,r(i,j)表示用户j是否对电影i进行了评价,m(j)表示用户j评分电影的数量,y(i,j)表示用户j对电影i的评级(只有在r(i,j)=1时被定义)。而推荐系统的目标就在于,当给出用户对于不同电影的评价后,推荐系统要查看用户未评分(没有看过)的电影,并尝试预测用户如何评价这些电影,从而向用户推荐他们更有可能评价为5星的电影。要实现这个功能的算法比较复杂,我们循序渐进的来介绍。
我们先假设我们额外知道一些描述电影类型的特征,例如我们知道对应电影隶属于浪漫片和动作片的程度,同时我们用n来表示描述电影类型的特征个数,x(i)来表示电影i对应的特征向量x,则预测用户j对电影i的评分可以使用特征方程:hat{y(i,j)}=wx+b来实现,这个公式其实就是线性回归的公式,其中wb都是用户j的预测参数矢量,w矢量里的每个值对应了用户对每个电影的喜好程度,b是偏差量,和线性回归一样由梯度下降法得到w和b的最优值。

和线性回归一样,当我们得到了特征方程后,下一步就是写出代价函数了,对于已知描述电影类型特征的情况,每个用户j的代价函数和线性回归代价函数一致,只是需要注意的是,求和符号的下标是用户j已经预测的电影集合(相当于已知的训练集),wx+b-y(i,j)即为用户j对电影i的预测值与实际值的差,我们对其做平方均值并加上正则化项即为下式:

但事实证明,我们把分母中的m(j)项去掉对于得到代价函数的最小值没有任何影响,故用户j的代价函数可以简化为:

进一步地,我们可以得到所有用户总和的代价函数:

利用这个代价函数求梯度下降,我们就可以得到每个用户的预测参数矢量w-w和b(1)-b(nu)了。举个例子,对于用户1,我们利用样本集(即用户1对电影1,2,4,5的评分)优化代价函数得到了w(1)=[5,0],b(1)=0,则我们可以预测用户1对电影3的评分为w(1)x(3),b(1)=4.95接近于5分,故算法会向用户1推荐电影3。可以看到,此时的样本集即不同用户对不同电影的评分,而预测则是那些用户对还没有打分的电影的可能评分,从而决定是否向对应用户推荐对应电影。
同样的,我们再假设我们知道用户的参数矢量wb,而学习目标是得到每个电影的特征矢量x

则此时代价函数的目标就应该是x,则代价函数应该是预测值与实际值的差的平方均值并加上x的正则化项,且此时由于主体是电影的特征向量,故求和的目标是那些为电影i打分的用户j,即为下式:

进一步地,我们可以得到所有电影总和的代价函数:

利用这个代价函数求梯度下降,我们就可以得到每个电影的特征向量x。举个例子,我们假设w(1)=[5,0],b(1)=0,w(2)=[5,0],b(2)=0,w(3)=[0,5],b(3)=0,w(4)=[0,5],b(1)=0,则我们可以预测电影1的特征向量,即w(1)x(1)=5,w(2)x(1)=5,w(3)x(1)=0,w(1)x(1)=0,结合这四个式子可得x(1)=[1,0]可以看到,此时的样本集即不同用户对不同电影的评分,而预测则是电影的特征向量。
综上,当我们给出电影的特征向量和评分表后,算法可以预测用户的参数矢量wb;反过来,当我们给出用户的参数矢量和评分表后,算法可以预测电影的特征向量x

但是实际上,我们可能既不知道描述电影的特征向量,又不知道用户的参数矢量,也就是说w,x,b都是我们需要去预测的量。这时候就要使用协同过滤算法(Collaborative Filtering Algorithm)来解决问题了:协同过滤算法指出,既然已知x学习w,b的代价函数和已知w,b学习x的代价函数都知道了,那么当w,b,x都未知时,只需要将三者都当作优化量,而代价函数则是两者优化函数之和。注意求和项合并后即为所有已评分的项目(i,j):r(i,j)=1。

要求解这个代价函数的最小值,应用的方法仍然是梯度下降,只不过由于此时x(i)也是优化目标之一,因而求偏导时还要对x(i)求偏导

最后我们便可以利用协同过滤算法得到w,b,x的最优值。协同过滤算法通过分析多个用户的合作评价(对应打分表),可以让我们了解各变量的特征向量取值(对应一部电影可能是什么类型的),反过来,利用特征向量,我们可以得到用户的参数矢量(对应用户对不同类型电影的喜好程度),从而预测用户对于还未评价的变量会做出如何反应(对应用户对还没看过的电影可能打出的分数),从而决定是否向用户推荐该变量(对应是否要推荐这部电影给目标用户)。
此外,推荐系统除了有像电影评分这种0-5星的多变量/连续系统,还有一些二进制标签(Binary Labels),例如对一个视频点赞或不点赞,收藏或不收藏等。
我们用数字1表示用户看到某个项目后参与了该项目(例如刷到视频后点赞投币收藏),用数字0表示用户看到某个项目后没有参与该项目(例如刷到后就划走了),用?表述用户还没看到过该项目(例如还没刷到这个视频)。则此时的样本集应该长这样:

正如之前从线性回归走向逻辑回归,我们预测用户对二进制标签的结果也应该是0或1的二元值,也就是预测用户取1的概率,这点和逻辑回归如出一辙。故类似的,在预测概率时,我们也像线性回归走向逻辑回归时一样,为wx+b外套上一个g(z)=sigmoid函数即可。

于是此时的预测函数就是关于w,b,x的三元sigmoid函数:

对应单个变量的损失函数是逻辑回归中的二元交叉熵函数:

而代价函数则是单个变量损失函数之和:

现在有一个问题摆在眼前,如果存在一个用户他没有评价过任何电影,事实上这也是很常见的现象,那我们向他推荐电影时应该遵循什么原则呢?(如下图的Eve)

事实说明,如果一个用户未评价任何电影的话,系统对该用户的预测参数矢量[w]和[b]应该均为0,那么由[w][x]+[b]则系统预测用户对任何电影的评分都将是0,也就不会推荐任何电影给这位用户了,这显然是不合理的。为了避免这个问题,我们通常会对样本集进行均值归一化(Mean Normalization),我们将样本集抽象为矩阵形式,行代表不同用户对同一电影的打分,列代表同一用户对不同电影的打分,此时若对每个元素减去其对应行评分的均值μ,我们便可以得到一个每行元素之和均为0的矩阵,这就是均值归一化的结果。

由于此时减去了评分均值μ,因而在计算最后分数时还要加上μ,故公式即为wx+b+μi。如此这般,对于已评价用户而言其分数并不会发生变化,而对于没有评价过任何电影的用户而言,由于[w]和[b]均为0,则系统的推荐便取决于其他人评分的均值μ,这比预测用户对任何电影的评分都是0合理不少。
同理,如果一部电影没有任何用户评价,对应矩阵行全是?,则此时我们对列取均值μ(未评价过的用户不计入在内)重复上述步骤。则此时对于没有人评分的电影,系统认为用户对该电影的评分为每个用户评价其他电影的均值,也显得比较合理。
这类问题也被称为冷启动问题(Cold Start Problem),协同过滤算法本身并不适合解决冷启动问题,我们必须对它进行均值归一化后才能得到一个比较合理的结果。
协同过滤算法在TensorFlow下的实现如下:
关键步骤在于TensorFlow的自动微分(Auto Diff)过程求解偏导项,重点公式为:
tf.Variable(init),tf.GradientTape()和tape.gradient(y,x)

同理,多变量偏导的过程也类似:

推荐系统还有一个作用是,那就是寻找相关特征推荐。例如我们在视频网站上看一部电影,其下方的推荐栏总是会推荐很多类似的电影。它的实现也很简单,只要我们在协同过滤算法中算出[w],[b]和[x]后,寻找其他电影的[x(l)]与本电影[x(i)]类似的即可。类似在数学上的定义即为欧几里得范数之最小,即:

综上我们也可以看出,协同过滤算法存在一个问题就是,它只能得到一组特征向量[x],却没办法说明这个特征向量的具体意义,基于比较的推荐也只能做到特征向量的近似。在实际的推荐系统中,我们往往可以得到用户和对象的某些信息来判断是否值得推荐,例如用户是中国人而对象是一部国风电影,那么推荐系统便可以根据这些已知的标签去进行推荐,而协同过滤算法本身不具备这种觉察性,这便引出我们接下来要讲的基于内容的过滤算法(Content-based Filtering Algorithm)。协同过滤算法基于用户的评分表来推荐你可能给高分的电影,而基于内容的过滤算法基于用户特征与电影特征的匹配程度推荐你可能喜欢的电影。

仍以用户与电影的例子说明,在基于内容的过滤算法中,我们通常知道关于用户和电影的一系列标签,我们把用户j的标签向量记为xu(j)(例如年龄、性别、国家、看过的电影与评分等),把电影i的标签向量记为xm(i)(例如上映时间、类型、平均得分等)。此时预测用户j与电影i匹配程度的函数可以参照wx+b,只不过此时我们通常把b定义为0,把wx分别记为vu(j)和vm(i),其中vu(j)和vm(i)由xu(j)和xm(i)计算而来。由于vu(j)和vm(i)需要执行点乘,因而xu(j)和xm(i)向量长度可能不同,但vu(j)和vm(i)的长度必须相同。故判断电影和用户是否匹配即判断vu(j)和vm(i)的点乘值是否够大,例如vu(j)可以是用户对不同类型电影的喜好程度,vm(i)可以是一部电影分属不同类型的比例程度,两者通过点乘便可以寻找良好匹配。
那么如何通过xu(j)和xm(i)得到vu(j)和vm(i)的值呢?
我们仍然可以使用神经网络,通过输入xu和xm得到输出vu和vm,再对vu和vm做点乘得到预测结果,需要注意的是用户神经网络和电影神经网络是共用一个神经网络而不是分开训练的,输出层的结果是vu和vm的点乘。

对应的代价函数也就是预测值vu和vm的点乘减去真实值的平方误差和正则化项

同理,也考科一利用基于内容的过滤算法来寻找相关特征推荐。只需要将对应的欧几里得范数公式更改为:

基于内容的过滤算法在TensorFlow下的实现如下:

主成分分析(Principal Component Analysis):
为了可视化特征数量较多的系统,我们会使用一种称为主成分分析的无监督学习算法,简称PCA。PCA可以把大量的特征(例如50个)简化为2到3个,从而可以绘制于可视图表中。基于这个特性,它偶尔也会用于简化样本集或训练过程中。
PCA算法缩减特征数量的关键在于寻找一个能最大化每个样本所有特征的轴。
以二维样本简化为一维样本为例,要找到一个能最大化每个样本所有特征的轴,也就是要找到一个各样本点在该轴上的投影方差最大的轴。根据这个要求,我们便可以利用算法得到结果了,这个轴也被称为主成分轴。下图是两种主轴的选择:可以看到第一张图中主轴的选择导致各投影的方差不大,故不是一个能明显凸出各样本区别的选择,而第二张图中主轴的选择就显得合理多了(图中黑线是最优选择,蓝线是当前选择,X是而样本点,·是投影点):

当我们得到主轴上的投影点时,也可以近似的估计原始数据各特征的值,方法是对主轴上的点对各个初始轴做投影,注意其结果只是一个近似值,因为在做主轴投影点时部分特征值被忽略了。
以上都是将所有特征缩减到1个特征的情况,如果要缩减到2个特征,可以做一个与主轴垂直的轴作为第二特征轴;如果要缩减到3个特征,可以做一个与主轴和第二特征轴都垂直的轴作为第三特征轴。

此外,需要说明的是,PCA与线性回归并不是一个概念,具体区别在于差值和投影的区别,且PCA要得到最大方差,而线性回归要得到最小差值,具体可以参考下图的对比:

PCA在TensorFlow中的代码实现如下:
PCA函数确定要缩减为几个变量,fit函数根据原特征变量计算主轴,explained_variance_ratio打印每个轴分别体现了多大比例的原特征变量,transform得到将原特征变量的值投影到主轴(如果有的话:第二特征轴、第三特征轴)后的结果,inverse_transform得到将主轴值投影回原初始轴的原始数据近似真实值,以二维样本简化为一维样本为例:

强化学习(Reinforcement Learning):强化学习的目的在于,我们通过给予激励和惩罚希望系统学会要做什么而不是让我们教他具体怎么去做。例如做无人机和机器狗时,如果要让我们根据其当前状态随时教育它应该怎么做,这是不可能的,因为现实的工况过于复杂。反之,我们通过激励函数来对这个系统做出的正确行为给予奖励,而对这个系统做出的错误行为给予惩罚,从而教会系统在每个状态都依据最大奖励的行为运行,这被证明能用于优化系统。

以一个简单的离散系统为例,一个火星车在任意时刻都处于如下六种状态的一种,假设初始时刻它位于状态4,我们希望它前往状态1或状态6去观测地表,但时间只允许它前往状态1或状态6之一,且状态1比状态6更远但更值得勘察,那么我们应该怎么设置激励函数来让火星车根据激励来运动?假设我们在状态1上设置激励为100,在状态6上设置激励为40,同时我们设置前往下一个状态的时间成本为0.9。于是,我们从状态4前往状态1的激励便是0+0.90+0.90.90+0.90.90.9100=72.9;同理,我们从状态4前往状态6的激励便是0+0.90+0.90.9*40=32.4,则系统便在激励函数的作用下选择前往状态1。

同理,如果我们把时间成本改为0.5,则系统在不同初始状态前往不同终点的激励便是如下:

故系统在不同初始状态选择前往的目标和对应的激励也不同:

根据这个例子,我们引出强化学习的一些概念:
1.状态(Status):系统当前各状态变量的集合,一般把当前状态定义为s,下一状态定义为s’。
2.行动(Action):系统从当前状态前往下一个状态可以采取的动作,一般把当前状态的行动定义为a,下一状态的行动定义为a’。
3.决策(Policy):系统在当前状态的行为,是s到a的映射,也用函数π(s)=a表示。
4.激励(Reward):系统处于当前状态可以获得或增加的分数,也用函数R(s)表示。
5.折扣因子(Discount Factor):系统前往下一状态的惩罚,其现实意义是时间成本,取值为0-1,事实说明,折扣因子对正激励和负激励同时有效:对于正激励而言,我们总是希望早点得到;对于负激励而言,我们总是希望晚点被惩罚。
6.返回值(Return):系统完成整个过程后得到的总分,由奖励和折扣因子决定。其公式有一般式和递推式两种形式。一般式的形式即为:Return=R1+γR2+γ2R3+…+γn-1Rn,其中R1为起点,Rn为中点,其余为中途路径点。递推式会在之后说明。
对于上面这个例子,状态1-6是状态,向左走和向右走是行动,在状态2下向右走是决策,状态1和状态6的分数是激励R(1)=100/R(6)=40/R(else)=0,时间成本0.9/0.5是折扣因子,72.9和32.4是返回值。
而强化学习的目标就是:在每个状态s下,找到一个决策π,告诉系统在该状态下做出行为a前往下一状态s’,从而使返回值return最大。事实上,这种过程也被称为马尔可夫决策过程(Markov Decision Process),马尔可夫决策过程的特点是系统未来的输出只取决于当前的状态和行为,而与过去的状态与行为无关。

接下来介绍状态-动作价值函数(State-Action Value Function/Q-Function),一般记作Q(s,a),它的值由当前的状态和要采取的行动决定。它的定义是,如果在当前状态s下采取行动a,则我们可得到的最佳最终返回值Return是多少。以火星车为例,我们给出了在每个状态下向左走或向右走的最佳返回值,也就是Q(s,a)的值。以γ=0.5,Q(2,Right)为例,如果火星车要前往状态1,则路径为2->3->2->1,值为12.5;如果火星车要前往状态2,则路径为2->3->4->5->6,值为2.5,故Q(2,Right)=12.5。

总结一下,如果我们得知了每个状态采取每个行动的Q(s,a),这就为我们提供了一种在任意状态得到最佳返回值的方法,也就是在任意状态都能找到最佳策略和行为的方法。
返回值的递推式被称为贝尔曼方程(Bellman Equation),他可以用来计算状态-动作价值函数。
贝尔曼方程的格式为:

也就是说在当前状态s下采取行动a得到的最佳最终返回值等于当前状态s下的激励加上折扣因子乘以下一状态s’下采取最佳行动a’得到的最佳最终返回值。
例如Q(2,Right)=R(2)+0.5max(a’){Q(3,a’)}=R(2)+0.5max(Left){Q(3,Left)}=0+0.525=12.5
Q(4,Left)=R(4)+0.5
max(Left){Q(3,Left)}=R(4)+0.5max(Left){Q(3,Left)}=0+0.525=12.5

在某些情况下,当我们采取行动时,结果并不总是完全可以确定的。例如我们要求火星车往左走,它可能会因为车轮打滑等因素反而往右走,在这种随机性环境下的强化学习被称为随机强化学习问题(Stochastic Reinforcement Learning)。对于这类问题,我们感兴趣的应该是采取每一行为后的预期结果的最大值,也就是期望的最大值,因为具体会得到的值是不确定的。
例如s=3,a=Left,此时得到的s’可能为2也可能为4,只是2的可能会更大而已。
于是此时的贝尔曼方程修改为:

之前谈论的火星车的例子,它实际上还是一个离散概念。现实世界中的应用往往是连续的状态空间,则此时的状态s应该是状态变量的合集,激励应该是在每一时刻动作的回馈。且对于连续状态空间系统而言,计算返回值时使用一般式是不现实的,只能使用贝尔曼方程计算,但关键在于我们怎么得到Q函数呢?这些疑问我们在之后会阐述。
我们以一个登月器的例子说明,我们定义它的状态变量为位置、角度、速度、角速度、左脚着地、右脚着地的集合,定义它每一时刻的激励如下:例如平稳着陆加分,坠毁减分,打开引擎减分(节能)等。

现在来解答怎么得到Q函数的疑惑,事实上,关键思想是我们需要训练一个神经网络去逼近最佳的状态动作值函数Q,而这反过来又可以让系统自行选择一个好的行动。在搭建神经网络时,输入一般是状态变量,而输出则是在该状态下可采取的所有行为,我们对这些所有可采取的行为进行比较,选择其中Q最大的作为真实的输出,也就是该状态下最优的行为。

现在的问题是我们怎么得到关于x,y的训练集从而训练这个神经网络。我们把目光投向贝尔曼方程,观察贝尔曼方程可知,当前的状态s和行为a组合在一起成为一个输入x,而R(s)+γmax(a’)Q(s’,a’)就是一个输出y。我们在每个时刻都可以观测到以下量:当前的状态s,当前的行为a,当前可以获得的激励R(s),下一时刻的状态s’。利用这个元组(s,a,R(s),s’),我们就可以完全已知这个时刻的输入x=[s,a]和输出y=R(s)+γmax(a’)Q(s’,a’)(其中Q就是一个关于a’的函数,max(a’)Q(s’,a’)求解这个函数的最大值,也是一个已知量)关系。如果我们随机在一个状态s下随机采取一个动作a,重复一万次,就可以得到一个含有一万个样本的样本集了,这个样本集中x是当前的状态和采取的行为,y是获得的分数,训练目标是得到一个好的Q使Q(s,a)=Q(x)=fw,b(x)≈y。利用这个样本集训练神经网络,就可以让它学会在什么状态采取什么行为才能得到一个好的激励,从而得到一个不错的运行结果了。
这个算法也被称为DQN(Deep Q-Network)算法,现在让我们总结一下整个流程:
1.构建神经网络,其中输入是状态s,输出是Q(s,all possible action for s)
2.随机初始化Q函数,此时Q函数的结果可能不是最优的
3.重复多次的随机在一个状态s下随机采取一个动作a,根据贝尔曼方程得到样本集[x,y]
4.利用得到的样本集训练神经网络得到新的Q函数(实际上是修改神经网络的w,b值),从而使得Q(s,a)=Q(x)的值更加接近于输出y。
5.更新Q函数并重复步骤4-5直到Q(x)≈y

上述算法存在一个小问题在于,当我们在构建样本集时,如果我们在某个状态任意的选取动作a,这可能会导致算法难以收敛,因为随机选取得到的a通常是一个坏动作。于是一个自然的选择是,尽管此时的Q不是最优的Q,但我们仍然去选择能使Q(s,a)最大的a来填充样本,也就是尽力而为。事实证明,这么做能给系统一个选择的参照,但可能会陷入局部最优,例如一个动作在当前的Q下收益高就一直去尝试这个动作,而不去考虑其他动作了。但由于这个Q并不是最佳的Q,这么做一定会使算法误入歧途。解决的办法也很自然,就是选取一个概率ε,算法有(1-ε)的可能性选择能使Q(s,a)最大的a来填充样本,也有ε的可能性任意选择a完成动作,这么做的好处在于既给了算法一个优化方向,又能让算法可以尝试不同的可能性,这种算法改进也称为ε-贪婪策略(Epsilon-greedy policy)。
此外,ε的取值往往也不是一成不变的。在算法的开始,由于Q的随机性,我们更应该鼓励算法尝试多种的可能,故一开始的ε会给的比较大;随着算法的进行,Q会越来越趋向于合理化,此时我们就要逐步减小ε的取值,来让算法跟着最优路径去走,以此得到良好的样本。

在强化学习中,还有两个优化方法可以使用:
小批量(Mini-Batches),就像在监督学习中当样本集过大时,我们改批量梯度下降为部分梯度下降一样,在训练Q的样本集过大时,我们也可以采用选取部分训练集训练的方法来加快速度,例如我们可以把上面总量为10000的样本集每次使用其中1000个来训练神经网络。
软更新(Soft Updates):之前神经网络的每次迭代,我们都会将Q更新为神经网络新得到的Q,如果这个新的Q偶然比原来的Q效果还要差,会导致神经网络的倒退,从而产生较大的噪声,使收敛不够可靠。软更新的方法可以避免我们只是因为一次不幸的调整式神经网络变得更糟。它的实现方法是,相比于每次都更新Q为一个与过去的Q完全无关的函数,我们可以选择一个系数k,使Q的更新既参考过去的Q又参考新得到的Q,也就是Q=kQnew+(1-k)Qlast
对应于神经网络参数即w=kwnew+(1-k)wlast,b=kbnew+(1-k)blast,k通常会取到0.01。利用软更新,可以有效的减少神经网络训练过程中的振荡和转向,从而实现更加可靠的收敛。