Statistical Learning Method


一、统计学习方法概论

1.1 统计学习

  • 特点

    • 统计学习以计算机及网络为平台,是建立在计算机及网络之上的:
    • 统计学习以数据为研究对象,是数据驱动的学科;
    • 统计学习的目的是对数据进行预测与分析;
    • 统计学习以方法为中心,统计学习方法构建模型并应用模型进行预测与分析;
    • 统计学习是概率论、统计学、信息论、计算理论、最优化理论及计算机科学等多个领域的交叉学科,并且在发展中逐步形成独自的理论体系与方法论
  • 对象:数据

  • 目的:预测与分析

  • 方法

    • 统计学习的方法是基于数据构建统计模型从而对数据进行预测与分析。统计学习由监督学习( supervised learning)、非监督学习( unsupervised learning)、半监督学习(semi- supervised learning)和强化学习( reinforcement learning)等组成
    • 三要素:模型、策略、算法
  • 实现统计学习方法的步骤如下

    • (1)得到一个有限的训练数据集合;
    • (2)确定包含所有可能的模型的假设空间,即学习模型的集合;
    • (3)确定模型选择的准则,即学习的策略;
    • (4)实现求解最优模型的算法,即学习的算法
    • (5)通过学习方法选择最优模型;
    • (6)利用学习的最优模型对新数据进行预测或分析
  • 研究:统计学习方法( statistical learning method)、统计学习理论( statistical learning theory)及统计学习应用( application of statistical learning)

  • 重要性

    • 统计学习是处理海量数据的有效方法。我们处于一个信息爆炸的时代,海量数据的处理与利用是人们必然的需求,现实中的数据不但规模大,而且常常具有不确定性,统计学习往往是处理这类数据最强有力的工具
    • 统计学习是计算机智能化的有效手段,智能化是计算机发展的必然趋势,也是计算机技术研究与开发的主要目标,近几十年来,人工智能等领域的研究表明,利用统计学习模仿人类智能的方法,虽有一定的局限性,但仍然是实现这目标的最有效手段,
    • 统计学习是计算机科学发展的一个重要组成部分。可以认为计算机科学由三维组成:系统、计算、信息统计学习主要属于信息这一维,并在其中起着核心作用。

1.2 监督学习

监督学习( supervised learning)的任务是学习一个模型,使模型能够对任意给定的输入,对其相应的输出做出一个好的预测(注意,这里的输入、输出是指某个系统的输入与输出,与学习的输入与输出不同)。计算机的基本操作就是给定一个输入产生一个输出。

1.2.1 基本概念

  • 输入空间、特征空间与输出空间

    • 在监督学习中,将输入与输出所有可能取值的集合分别称为输入空间(input space)与输出空间(output space).输入与输出空间可以是有限元素的集合,也可以是整个欧氏空间。输入空间与输出空间可以是同一个空间,也可以是不同的空间;但通常输出空间远远小于输入空间。

    • 每个具体的输入是一个实例(Instance),通常由特征向量(feature vector)表示,这时,所有特征向量存在的空间称为特征空间(feature space).特征空间的每一维对应于一个特征,有时假设输入空间与特征空间为相同的空间,对它们不予区分;有时假设输入空间与特征空间为不同的空间,将实例从输入空间映射到特征空间。模型实际上都是定义在特征空间上的。

    • 除特别声明外,本书中向量均为列向量,输入实例$x$的特征向量记作:
      $$
      x=\left(x^{(1)}, x^{(2)}, \cdots, x^{(i)}, \cdots, x^{(n)}\right)^{\mathrm{T} }
      $$

    • 监督学习从训练数据( training data)集合中学习模型,对测试数据( test data)进行预测,训练数据由输入(或特征向量)与输出对组成,训练集通常表示为:
      $$
      T=\left { \left(x_{1}, y_{1} \right), \left(x_{2}, y_{2} \right), \cdots, \left(x_{N}, y_{N} \right) \right }
      $$

  • 联合概率分布

    • 监督学习假设输入与输出的随机变量X和Y遵循联合概率分布$P(X,Y)$,$P(X,Y)$表示分布函数,或分布密度函数,注意,在学习过程中,假定这一联合概率分布存在,但对学习系统来说,联合概率分布的具体定义是未知的,训练数据与测试数据被看作是依联合概率分布P(X,)独立同分布产生的,统计学习假设数据存在一定的统计规律,X和Y具有联合概率分布的假设就是监督学习关于数据的基本假设
  • 假设空间

    • 监督学习的目的在于学习一个由输入到输出的映射,这一映射由模型来表示。换句话说,学习的目的就在于找到最好的这样的模型。模型属于由输入空间到输出空间的映射的集合,这个集合就是假设空间(hypothesis space).假设空间的确定意味着学习范围的确定

    • 监督学习的模型可以是概率模型或非概率模型,由条件概率分布$P(Y|X)$或决策函数(decision function)$Y=f(X)$表示,随具体学习方法而定。对具体的输入进行相应的输出预测时,写作$P(y | x)$或$y=f(x)$

1.2.2 问题的形式化

监督学习分为学习和预测两个过程:

监督学习问题

1.3 统计学习三要素

方法=模型+策略+算法

1.3.1 模型

  • 统计学习首要考虑的问题是学习什么样的模型。在监督学习过程中,模型就是所要学习的条件概率分布或决策函数。模型的假设空间( hypothesis space)包含所有可能的条件概率分布或决策函数。例如,假设决策函数是输入变量的线性函数,那么模型的假设空间就是所有这些线性函数构成的函数集合。假设空间中的模型一般有无穷多个
  • 假设空间用$\mathcal{F}$表示,假设空间可以定义为决策函数的集合:

$$
\mathcal{F}={f \mid Y=f(X)}
$$

  • 这时$\mathcal{F}$通常是由一个参数向量决定的函数族:

$$
\mathcal{F}=\left {f \mid Y=f_{ \theta}(X), \theta \in \mathbf{R}^{n} \right }
$$

  • $\mathbf{R}^{n}$为参数空间

  • 假设空间也可以定义为条件概率的集合:

$$
\mathcal{F}={P \mid P(Y \mid X)}
$$

  • 条件概率分布族:

$$
\mathcal{F}= \left {P \mid P_{ \theta}(Y \mid X), \theta \in \mathbf{R}^{n} \right }
$$

1.3.2 策略

有了模型的假设空间,统计学习接着需要考虑的是按照什么样的准则学习或选择最优的模型。统计学习的目标在于从假设空间中选取最优模型

  • 损失函数与风险函数

    • 监督学习问题是在假设空间$\mathcal{F}$中选取模型$f$作为决策函数,对于给定的输入$X$,由$f(X)$给出相应的输出$Y$,这个输出的预测值$f(X)$与真实值$Y$可能一致也可能不一致,用一个损失函数(loss function)或代价函数(cost function)来度量预测错误的程度。损失函数是$f(X)$和$Y$的非负实值函数,记作$L(Y,f(X))$

    • 统计学习常用的损失函数有以下几种

      • 0-1 损失函数(0-1 loss function)

      $$
      L(Y, f(X))=\left { \begin{array}{ll}
      1, & Y \neq f(X) \
      0, & Y=f(X)
      \end {array} \right.
      $$

      • 平方损失函数(quadratic loss function)
        $$
        L(Y, f(X))=(Y-f(X))^{2}
        $$

      • 绝对损失函数(absolute loss function)
        $$
        L(Y, f(X))=|Y-f(X)|
        $$

      • 对数损失函数(logarithmic loss function)或对数似然损失函数(log-likelihood loss function)
        $$
        L(Y, P(Y \mid X))=-\log P(Y \mid X)
        $$

    • 损失函数值越小,模型就越好。由于模型的输入、输出$(X,Y)$是随机变量,遵循联合分布$P(X,Y)$,所以损失函数的期望是
      $$
      R_{\exp }(f)=E_{P}[L(Y, f(X))]=\int_{\chi \times y} L(y, f(x)) P(x, y) \mathrm{d} x \mathrm{~d} y
      $$

    • 这是理论上模型$f(X)$关于联合分布$P(X,Y)$的平均意义下的损失,称为风险函数(risk function)或期望损失(expected loss)

    • 训练数据集的平均损失称为经验风险(empirical risk)或经验损失(expected loss):
      $$
      R_{\mathrm{emp} }(f)=\frac{1}{N} \sum_{i=1}^{N} L\left(y_{i}, f\left(x_{i}\right)\right)
      $$

经验风险最小化与结构风险最小化

  • 经验风险最小化(empirical risk minimization, ERM)的策略认为,经验风险最小的模型是最优的模型。根据这一策略,按照经验风险最小化求最优模型就是求解最优化问题:

$$
\min {f \in \mathcal{F} } \frac{1}{N} \sum{i=1}^{N} L \left(y_{i}, f \left(x_{i} \right) \right)
$$

  • 考虑到过拟合现象,结构风险最小化(structural risk minimization, SRM)是为了防止过拟合而提出来的策略。结构风险最小化等价于正则化(regularization).结构风险在经验风险上加上表示模型复杂度的正则化项(regularizer)或罚项(penalty term).在假设空间、损失函数以及训练数据集确定的情况下,结构风险的定义是:

$$
R_{\mathrm{sm} }(f)=\frac{1}{N} \sum_{i=1}^{N} L\left(y_{i}, f\left(x_{i}\right)\right)+\lambda J(f)
$$

  • 结构风险最小化的策略认为结构风险最小的模型是最优的模型。所以求最优模型,就是求解最优化问题:

$$
\min {f \in \mathcal{F} } \frac{1}{N} \sum _{i=1}^{N} L \left(y{i}, f \left(x_{i} \right) \right)+ \lambda J(f)
$$

1.3.3 算法

算法是指学习模型的具体计算方法,统计学习基于训练数据集,根据学习策略,从假设空间中选择最优模型,最后需要考虑用什么样的计算方法求解最优模型

1.4 模型评估与模型选择

1.4.1 训练误差与测试误差

当损失函数给定时,基于损失函数的模型的训练误差(training error)和模型的测试误差(test error)就自然成为学习方法评估的标准。

  • 假设学习到的模型是$Y=\hat{f}(X)$

  • 训练误差是模型$Y=(x)$关于训练数据集的平均损失:
    $$
    R_{\text {emp } }(\hat{f})=\frac{1}{N} \sum_{i=1}^{N} L\left(y_{i}, \hat{f}\left(x_{i}\right)\right)
    $$

  • 测试误差:
    $$
    e_{\text {test } }=\frac{1}{N^{\prime} } \sum_{i=1}^{N^{\prime} } L\left(y_{i}, \hat{f}\left(x_{i}\right)\right)
    $$

1.4.2 过拟合与模型选择

当假设空间含有不同复杂度(例如,不同的参数个数)的模型时,就要面临模型选择(model selection)的问题,我们希望选择或学习一个合适的模型。如果在假设空间中存在“真”模型,那么所选择的模型应该逼近真模型。具体地,所选择的模型要与真模型的参数个数相同,所选择的模型的参数向量与真模型的参数向量相近

如果一味追求提高对训练数据的预测能力,所选模型的复杂度则往往会比真模型更高。这种现象称为过拟合(over- fitting).过拟合是指学习时选择的模型所包含的参数过多,以致于出现这一模型对已知数据预测得很好,但对未知数据预测得很差的现象。可以说模型选择旨在避免过拟合并提高模型的预测能力

M次多项式函数拟合问题

训练误差和测试误差与模型复杂度的关系

1.5 正则化与交叉验证

1.5.1 正则化

模型选择的典型方法是正则化(regularization).正则化是结构风险最小化策略的实现,是在经验风险上加一个正则化项(regularizer)或罚项(penalty term),正则化项一般是模型复杂度的单调递增函数,模型越复杂,正则化值就越大。

  • 正则化一般具有如下形式:
    $$
    \min {f \in \mathcal{F} } \frac{1} {N} \sum _{i=1}^{N} L \left(y{i}, f \left(x_{i} \right) \right)+ \lambda J(f)
    $$

  • L2范数
    $$
    L(w)=\frac{1}{N} \sum_{i=1}^{N}\left(f\left(x_{i} ; w\right)-y_{i}\right)^{2}+\frac{\lambda}{2}|w|^{2}
    $$

  • L1范式
    $$
    L(w)=\frac{1}{N} \sum_{i=1}^{N}\left(f\left(x_{i} ; w\right)-y_{i}\right)^{2}+\lambda|w|_{1}
    $$

正则化符合奥卡姆剃刀(Occams razor)原理,奥卡姆剃刀原理应用于模型选择时变为以下想法:在所有可能选择的模型中,能够很好地解释已知数据并且十分简单才是最好的模型,也就是应该选择的模型。从贝叶斯估计的角度来看,正则化项对应于模型的先验概率,可以假设复杂的模型有较大的先验概率,简单的模型有较小的先验概率

1.5.2 交叉验证

如果给定的样本数据充足,进行模型选择的一种简单方法是随机地将数据集切分成三部分,分别为训练集(training set)、验证集(validation set)和测试集(test set).训练集用来训练模型,验证集用于模型的选择,而测试集用于最终对学习方法的评估。在学习到的不同复杂度的模型中,选择对验证集有最小预测误差的模型。由于验证集有足够多的数据,用它对模型进行选择也是有效的

  • 简单交叉验证:首先随机地将已给数据分为两部分,一部分作为训练集,另一部分作为测试集(例如,70%的数据为训练集,30%的数据为测试集):然后用训练集在各种条件下(例如,不同的参数个数)训练模型,从而得到不同的模型;在测试集上评价各个模型的测试误差,选出测试误差最小的模型。

  • S折交又验证:应用最多的是S折交又验证(S- fold cross validation),方法如下:首先随机地将已给数据切分为S个互不相交的大小相同的子集;然后利用S-1个子集的数据训练模型,利用余下的子集测试模型;将这一过程对可能的S种选择重复进行;最后选出S次评测中平均测试误差最小的模型

  • S折交叉验证的特殊情形是S=N,称为留一交叉验证( leave- one-out cross validation),往往在数据缺乏的情况下使用。这里,N是给定数据集的容量

1.6 泛化能力

1.6.1 泛化误差

首先给出泛化误差的定义。如果学到的模型是$\hat{f}$,那么用这个模型对未知数据预测的误差即为泛化误差(generalization error)
$$
R_{\exp }(\hat{f})=E_{P}[L(Y, \hat{f}(X))]=\int_{\chi \times y} L(y, \hat{f}(x)) P(x, y) \mathrm{d} x \mathrm{~d} y
$$

1.6.2 泛化误差上界

学习方法的泛化能力分析往往是通过研究泛化误差的概率上界进行的,简称为泛化误差上界(generalization error bound).具体来说,就是通过比较两种学习方法的泛化误差上界的大小来比较它们的优劣。泛化误差上界通常具有以下性质:它是样本容量的函数,当样本容量增加时,泛化上界趋于0:它是假设空间容量(capacity)的函数,假设空间容量越大,模型就越难学,泛化误差上界就越大

定理1.1(泛化误差上界)对二类分类问题,当假设空间是有限个函数的集合$\mathcal {F}=\left {f_{1}, f_{2}, \cdots, f_{d} \right }$时,对任意一个函数$f∈F$,至少以概率$1-\delta$,以下不等式成立:
$$
R(f) \leqslant \hat{R}(f)+\varepsilon(d, N, \delta)
$$
其中:
$$
\varepsilon(d, N, \delta)=\sqrt { \frac{1}{2 N } \left( \log d+ \log \frac{1} { \delta} \right) }
$$

1.7 生成模型与判别模型

监督学习的任务就是学习一个模型,应用这一模型,对给定的输入预测相应的输出。这个模型的一般形式为决策函数:
$$
Y = f ( X )
$$
监督学习方法又可以分为生成方法(generative approach)和判别方法(discriminative approach).所学到的模型分别称为生成模型(generative model)和判别模型(discriminative model).

生成方法由数据学习联合概率分布$P(X,Y)$,然后求出条件概率分布$P(Y|X)$作为预测的模型,即生成模型:
$$
P ( Y | X) = \frac { P ( X , Y ) } { P ( X ) }
$$

1.8 分类问题

分类是监督学习的一个核心问题,在监督学习中,当输出变量Y取有限个离散值时,预测问题便成为分类问题,这时,输入变量x可以是离散的,也可以是连续的,监督学习从数据中学习一个分类模型或分类决策函数,称为分类器(classifier)分类器对新的输入进行输出的预测(prediction),称为分类(classification),可能的输出称为类(class).分类的类别为多个时,称为多类分类问题。

分类问题

评价分类器性能的指标一般是分类准确率(accuracy),其定义是:对于给定的测试数据集,分类器正确分类的样本数与总样本数之比。也就是损失函数是0-1损失时测试数据集上的准确率

  • 对于二类分类问题常用的评价指标是精确率(precision)与召回率(recall).通常以关注的类为正类,其他类为负类,分类器在测试数据集上的预测或正确或不正确,4种情况出现的总数分别记作:

    • TP:将正类预测为正类数

    • FN:将正类预测为负类数

    • FP:将负类预测为正类数

    • TN:将负类预测为负类数

  • 精确率:

$$
P = \frac { T P } { T P + F P }
$$

  • 召回率:

$$
R = \frac { T P } { T P + F N }
$$

  • $F _ { 1 }$值,是精确率和召回率的调和均值,即

$$
\frac { 2 } { F _ { 1 } } = \frac { 1 } { P } + \frac { 1 } { R }
$$

$$
F _ { 1 } = \frac { 2 T P } { 2 T P + F P + F N }
$$

1.9 标注问题

标注(tagging)也是一个监督学习问题,可以认为标注问题是分类问题的一个推广,标注问题又是更复杂的结构预测(structure prediction)问题的简单形式,标注问题的输入是一个观测序列,输出是一个标记序列或状态序列,标注问题的目标在于学习一个模型,使它能够对观测序列给出标记序列作为预测。注意,可能的标记个数是有限的,但其组合所成的标记序列的个数是依序列长度呈指数级增长的。

标注问题

  • 评价标注模型的指标与评价分类模型的指标一样,常用的有标注准确率、精确率和召回率。其定义与分类模型相同
  • 标注常用的统计学习方法有:隐马尔可夫模型、条件随机场

1.10 回归问题

回归(regression)是监督学习的另一个重要问题,回归用于预测输入变量(自变量)和输出变量(因变量)之间的关系,特别是当输入变量的值发生变化时,输出变量的值随之发生的变化。回归模型正是表示从输入变量到输出变量之间映射的函数。回归问题的学习等价于函数拟合:选择一条函数曲线使其很好地拟合已知数据且很好地预测未知数据

回归问题

  • 回归向题按照输入变量的个数,分为一元回归和多元回归:按照输入变量和输出变量之间关系的类型即模型的类型,分为线性回归和非线性回归。
  • 回归学习最常用的损失函数是平方损失函数,在此情况下,回归问题可以由著名的最小二乘法(least squares)求解,

习题

习题1.1

  说明伯努利模型的极大似然估计以及贝叶斯估计中的统计学习方法三要素。伯努利模型是定义在取值为0与1的随机变量上的概率分布。假设观测到伯努利模型$n$次独立的数据生成结果,其中$k$次的结果为1,这时可以用极大似然估计或贝叶斯估计来估计结果为1的概率。

解答:

解答思路:

  1. 写出伯努利模型;
  2. 写出伯努利模型的极大似然估计以及贝叶斯估计中的统计学习方法三要素;
  3. 根据伯努利模型的极大似然估计,估计结果为1的概率;
  4. 根据伯努利模型的贝叶斯估计,估计结果为1的概率。

解答步骤:

第1步:伯努利模型
  根据题意:伯努利模型是定义在取值为0与1的随机变量上的概率分布。
  对于随机变量$X$,则有:
$$
P(X=1)=p \ P(X=0)=1-p
$$

其中,$p$为随机变量$X$取值为1的概率,$1-p$则为取0的概率。
  由于随机变量$X$只有0和1两个值,$X$的概率分布函数,即伯努利模型可写为:
$$
P_p(X=x)=p^x (1-p)^{(1-x)}, \quad 0 \leqslant p \leqslant 1
$$

  则伯努利模型的假设空间为:
$$
\mathcal{F}={P|P_p(X)=p^x(1-p)^{(1-x)}, p\in [0,1] }
$$

第2步:伯努利模型的极大似然估计以及贝叶斯估计中的统计学习方法三要素
(1)极大似然估计
  模型:伯努利模型
  策略:经验风险最小化。极大似然估计,等价于当模型是条件概率分布、损失函数是对数损失函数时的经验风险最小化。
  算法:极大化似然:$\displaystyle \mathop{\arg\max} \limits_{p} L(p|X)= \mathop{\arg\max} \limits_{p} P(X|p)$

(2)贝叶斯估计
  模型:伯努利模型
  策略:结构风险最小化。贝叶斯估计中的最大后验概率估计,等价于当模型是条件概率分布、损失函数是对数损失函数、模型复杂度由模型的先验概率表示时的结构风险最小化。
  算法:最大化后验概率:$\displaystyle \mathop{\arg\max} \limits_{p} \pi (p|X)= \displaystyle \mathop{\arg\max} \limits_{p} \frac{P(X|p)\pi(p)}{\int P(X|p)\pi(p)dp}$

第3步:伯努利模型的极大似然估计

  极大似然估计的一般步骤:
  参考Wiki:https://en.wikipedia.org/wiki/Maximum_likelihood_estimation

  1. 写出随机变量的概率分布函数;
  2. 写出似然函数;
  3. 对似然函数取对数,得到对数似然函数,并进行化简;
  4. 对参数进行求导,并令导数等于0;
  5. 求解似然函数方程,得到参数的值。

  对于伯努利模型$n$次独立的数据生成结果,其中$k$次的结果为1,可得似然函数为:
$$
\begin{aligned} L(p|X) &= P(X|p) \
&= \prod_{i=1}^{n} P(x^{(i)}|p) \
&=p^k (1-p)^{n-k}
\end{aligned}
$$

  对似然函数取对数,得到对数似然函数为:
$$
\begin{aligned} \log L(p|X) &= \log p^k (1-p)^{n-k} \
&= \log(p^k) + \log\left( (1-p)^{n-k} \right) \
&= k\log p + (n-k)\log (1-p)
\end{aligned}
$$

  求解参数$p$:
$$
\begin{aligned}
\hat{p} &= \mathop{\arg\max} \limits_{p} L(p|X) \
&= \mathop{\arg\max} \limits_{p} \left[ k\log p + (n-k)\log (1-p) \right]
\end{aligned}
$$

  对参数$p$求导,并求解导数为0时的$p$值:
$$
\begin{aligned}
\frac{\partial \log L(p)}{\partial p} &= \frac{k}{p} - \frac{n-k}{1-p} \
&= \frac{k(1-p) - p(n-k)}{p(1-p)} \
&= \frac{k-np}{p(1-p)}
\end{aligned}
$$

  令$\displaystyle \frac{\partial \log L(p)}{\partial p} = 0$,从上式可得,$k-np=0$,即$\displaystyle p=\frac{k}{n}$
  所以$\displaystyle P(X=1)=\frac{k}{n}$

第4步:伯努利模型的贝叶斯估计

解法一:求最大后验估计

  贝叶斯估计(最大后验估计)的一般步骤:
  参考Wiki:https://en.wikipedia.org/wiki/Maximum_a_posteriori_estimation

  1. 确定参数$\theta$的先验概率$p(\theta)$
  2. 根据样本集$D={ x_1, x_2, \ldots, x_n }$,计算似然函数$P(D|\theta)$:$\displaystyle P(D|\theta)=\prod_{i=1}^n P(x_i|D)$
  3. 利用贝叶斯公式,写出后验概率最大化公式:

$$
\mathop{\arg\max} \limits_{\theta} P(\theta|D)=\mathop{\arg\max} \limits_{\theta} \frac{P(D|\theta)P(\theta)}{\displaystyle \int \limits_\Theta P(D|\theta) P(\theta) d \theta} = \mathop{\arg\max} \limits_{\theta} P(D|\theta)P(\theta)
$$

  1. 利用求导,得到后验概率最大时的参数取值

  对于伯努利模型的参数$p$,根据贝叶斯估计,该参数也是随机变量。
  假设$p$的先验分布$\pi(p)$为均匀分布,则最大后验概率估计等价于极大似然估计。
  一般在贝叶斯估计中,如果后验分布与先验分布属于同一分布簇(共轭分布),则称此先验分布为似然函数的共轭先验。

  参考极大似然估计和贝叶斯估计

选取共轭先验有如下好处,例如:
(1)符合直观,先验分布和后验分布应该是相同形式的;
(2)可以给出后验分布的解析形式;
(3)可以形成一个先验链,即现在的后验分布可以作为下一次计算的先验分布,如果形式相同,就可以形成一个链条。

  伯努利分布的先验分布为Beta分布,则此处假设先验分布$\pi(p)$为Beta分布。

补充知识:Beta分布
来源维基百科:https://zh.wikipedia.org/wiki/%CE%92%E5%88%86%E5%B8%83
  Beta 分布(Beta distribution),是指一组定义在${\displaystyle (0,1)}$区间的连续概率分布,亦称Β分布。有两个参数$\alpha, \beta>0$。
概率密度函数:$\displaystyle f(x; \alpha, \beta)= \frac{1}{ {\rm B}(\alpha, \beta)}x^{(\alpha-1)}(1-x)^{\beta-1}$
其中${\rm B}(\alpha, \beta)$是Beta函数,亦称Β函数。$\displaystyle {\rm B}(\alpha, \beta) =\int _{0}^{1} x^{\alpha-1}(1-x)^{\beta-1}dx$
随机变量$X$服从参数为$\alpha, \beta$的Beta分布记作:$X \sim {\rm Be}(\alpha, \beta)$
期望:$\displaystyle {\rm E}(X) = \frac{\alpha}{\alpha+\beta}$
与均匀分布关系:当$\alpha=1, \beta=1$时,Beta分布就是一个均匀分布

  $p$的先验分布为:
$$
\displaystyle \pi (p) = \frac{1}{B(\alpha, \beta)} p^{(\alpha-1)} (1-p)^{\beta-1}
$$
  似然函数与第3步相同:
$$
\begin{aligned} L(p|X) &= P(X|p) \
&= \prod_{i=1}^{n} P(x^{(i)}|p) \
&=p^k (1-p)^{n-k}
\end{aligned}
$$
  最大化后验概率,求解参数$p$:
$$
\begin{aligned}
\hat{p} &= \mathop{\arg\max} \limits_{p} \frac{P(X|p)\pi(p)}{\displaystyle \int P(X|p)\pi(p)dp} \
&= \mathop{\arg\max} \limits_{p} P(X|p)\pi(p) \
&= \mathop{\arg\max} \limits_{p} p^k (1-p)^{n-k} \frac{1}{B(\alpha, \beta)} p^{(\alpha-1)} (1-p)^{\beta-1} \
&= \mathop{\arg\max} \limits_{p} \frac{1}{B(\alpha, \beta)} p^{k+\alpha-1} (1-p)^{n-k+\beta-1}
\end{aligned}
$$
  令$\displaystyle g(p) = \frac{1}{B(\alpha, \beta)} p^{k+\alpha-1} (1-p)^{n-k+\beta-1}$,对函数$g(p)$先取对数,再对$p$求导,得
$$ \frac{\partial \log g(p)}{\partial p} = \frac{1}{B(\alpha, \beta)} \left( \frac{k+\alpha-1}{p} - \frac{n-k+\beta-1}{1-p} \right)$$

  令上式等于0,得$\displaystyle \hat{p} = \frac{k+\alpha-1}{n+\alpha+\beta-2}$,其中$\alpha, \beta$为beta分布的参数。

  所以最大后验概率估计得到$\displaystyle P(X=1)=\frac{k+\alpha-1}{n+\alpha+\beta-2}$

解法二:求后验概率分布的期望

  后验概率分布的期望求解
  参考Wiki(中文):https://zh.wikipedia.org/wiki/%E6%9C%80%E5%A4%A7%E5%90%8E%E9%AA%8C%E6%A6%82%E7%8E%87
  参考Wiki(英文):https://en.wikipedia.org/wiki/Bayes_estimator

  贝叶斯估计中的最大后验概率估计,得到的是模型参数$\theta$这个随机变量的后验分布的众数,通常被认为是点估计。而贝叶斯方法的特点是使用分布来总结数据和得出推论,因此贝叶斯方法倾向于得到后验均值或中值,以及可信区间。
  贝叶斯估计,利用后验分布的期望(均值)作为参数的估计值的方法,前两步与最大后验概率估计相同,第3、4步如下:

  1. 利用贝叶斯公式,求$\theta$的后验概率:$\displaystyle P(\theta|D)=\frac{P(D|\theta)P(\theta)}{\displaystyle \int \limits_\Theta P(D|\theta) P(\theta) d \theta}$
  2. 计算后验概率分布参数$\theta$的期望,并求出贝叶斯估计值:$\displaystyle \hat{\theta}=\int \limits_{\Theta} \theta \cdot P(\theta|D) d \theta$

  已知似然函数和参数$p$的先验分布,参数$p$的后验分布为:
$$
\begin{aligned}
P(p|X) &= \frac{P(X|p)\pi(p)}{\displaystyle \int P(X|p)\pi(p)dp} \
&=\frac{\displaystyle \frac{1}{B(\alpha, \beta)} p^{k+\alpha-1} (1-p)^{n-k+\beta-1} }{\displaystyle \int \frac{1}{B(\alpha, \beta)} p^{k+\alpha-1} (1-p)^{n-k+\beta-1} dp} \
&=\frac{ p^{k+\alpha-1} (1-p)^{n-k+\beta-1} }{\displaystyle \int p^{k+\alpha-1} (1-p)^{n-k+\beta-1} dp} \
&=\frac{1}{B(k+\alpha, n-k+\beta)} p^{k+\alpha-1} (1-p)^{n-k+\beta-1} \
&\sim \text{Be}(k+\alpha, n-k+\beta) \
\end{aligned}
$$
  后验概率分布的期望:
$$
\begin{aligned}
E_p(p|X)&=E_p({\rm Be}(k+\alpha, n-k+\beta)) \
&=\frac{k+\alpha}{(k+\alpha)+(n-k+\beta)} \
&=\frac{k+\alpha}{n+\alpha+\beta}
\end{aligned}
$$
  则以参数的后验概率分布的期望作为贝叶斯估计的参数值:
$$
\displaystyle \hat{p}=\frac{k+\alpha}{n+\alpha+\beta}
$$
  所以贝叶斯估计得到$\displaystyle P(X=1)=\frac{k+\alpha}{n+\alpha+\beta}$

习题1.2

  通过经验风险最小化推导极大似然估计。证明模型是条件概率分布,当损失函数是对数损失函数时,经验风险最小化等价于极大似然估计。

解答:

解答思路:

  1. 根据经验风险最小化定义,写出目标函数;
  2. 根据对数损失函数,对目标函数进行整理;
  3. 根据似然函数定义和极大似然估计的一般步骤(计算时需要取对数),可得到结论。

解答步骤:
  假设模型的条件概率分布是$P_{\theta}(Y|X)$,样本集$D={(x_1,y_1),(x_2,y_2),\ldots,(x_N,y_N)}$,根据书中第17页公式(1.12),对数损失函数为:
$$
L(Y,P(Y|X)) = -\log P(Y|X)
$$
  根据书中第18页公式(1.15),按照经验风险最小化求最优模型就是求解最优化问题:
$$
\min \limits_{f \in \mathcal{F} } \frac{1}{N} \sum_{i=1}^N L(y_i, f(x_i))
$$
  结合上述两个式子,可得经验风险最小化函数:
$$
\begin{aligned}
\mathop{\arg\min} \limits_{f \in \mathcal{F} } \frac{1}{N} \sum_{i=1}^N L(y_i, f(x_i)) &= \mathop{\arg\min} \limits_{f \in \mathcal{F} } \frac{1}{N} \sum_D [-\log P(Y|X)] \
&= \mathop{\arg\max} \limits_{f \in \mathcal{F} } \frac{1}{N} \sum_D \log P(Y|X) \
&= \mathop{\arg\max} \limits_{f \in \mathcal{F} } \frac{1}{N} \log \prod_D P(Y|X) \
&= \frac{1}{N} \mathop{\arg\max} \limits_{f \in \mathcal{F} } \log \prod_D P(Y|X)
\end{aligned}
$$
  根据似然函数定义:$\displaystyle L(\theta)=\prod_D P_{\theta}(Y|X)$,以及极大似然估计的一般步骤,可得:
$$
\mathop{\arg\min} \limits_{f \in \mathcal{F} } \frac{1}{N} \sum_{i=1}^N L(y_i, f(x_i)) = \frac{1}{N} \mathop{\arg\max} \limits_{f \in \mathcal{F} } \log L(\theta)
$$
  即经验风险最小化等价于极大似然估计,得证。

二、感知器

感知机(perceptron)是二类分类的线性分类模型,其输入为实例的特征向量,输出为实例的类别,取+1和-1二值。感知机对应于输入空间(特征空间)中将实例划分为正负两类的分离超平面,属于判别模型。感知机学习旨在求出将训练数据进行线性划分的分离超平面,为此,导入基于误分类的损失函数,利用梯度下降法对损失函数进行极小化,求得感知机模型。感知机学习算法具有简单而易于实现的优点,分为原始形式和对偶形式感知机预测是用学习得到的感知机模型对新的输入实例进行分类,感知机1957年由 Rosenblatt提出,是神经网络与支持向量机的基础。

2.1 感知器模型

定义2.1(感知机)假设输入空间(特征空间)是$X \leq R ^ { n }$,输出空间是$Y = { + 1 , - 1 }$.输入$x∈X$表示实例的特征向量,对应于输入空间(特征空间)的点;输出$y∈Y$表示实例的类别。由输入空间到输出空间的如下函数
$$
f ( x ) = \operatorname { sign } ( w \cdot x + b )
$$
称为感知机、其中,w和b为感知机模型参数,$w∈R ^ { n }$叫作权值( weight)或权值向量( weight vector),$b∈R$叫作偏置(bias),$w \cdot x$表示w和x的内积。sign是符号函数,即:
$$
\operatorname { sign } ( x ) = { \begin{array} { ll } { + 1 , } & { x \geq 0 } \ { - 1 , } & { x \lt 0 } \end{array}
$$
感知器模型

2.2 感知器学习策略

2.2.1 数据集的线性可分性

定义2.2(数据集的线性可分性)给定一个数据集:
$$
T = {(x _ { 1 } , y _ { 1 }) , (y _ { 2 } , y _ { 2 }) , \cdots , ( x _ { N } , y _ { N } ) }
$$
如果存在某个超平面S: $ w \cdot x + b $,能够将数据集的正实例点和负实例点完全正确地划分到超平面的两侧,则称数据集T为线性可分数据集(linearly separable data set);否则,称数据集T线性不可分

2.2.2 感知器学习策略

感知机$\operatorname { sign } ( w \cdot x + b )$学习的损失函数定义为:
$$
L(w, b)=-\sum_{x_{i} \in M} y_{i}\left(w \cdot x_{i}+b\right)
$$
根据误差距离推导出来的

2.3 感知器学习算法

2.3.1 感知器学习算法原始形式

损失函数最优化形式求解:
$$
\min {w,b}L(w, b)=- \sum{x_{i} \in M} y_{i} \left(w \cdot x_{i}+b \right)
$$
算法2.1(感知机学习算法的原始形式)

2.3.2 算法的收敛性

定理2.1 (Novikoff)

2.3.3 感知机学习算法的对偶形式

算法2.2(感知机学习算法的对偶形式)

习题

习题2.1

  Minsky 与 Papert 指出:感知机因为是线性模型,所以不能表示复杂的函数,如异或 (XOR)。验证感知机为什么不能表示异或。

解答:

解答思路:

  1. 列出异或函数(XOR)的输入和输出;
  2. 使用图例法证明异或问题是线性不可分的;
  3. 使用反证法证明感知机无法表示异或。

解题步骤:

第1步:异或函数(XOR)的输入和输出

  对于异或函数(XOR),全部的输入与对应的输出如下:

$x_1$ $x_2$ $y=x_1\oplus x_2$
0 0 -1
0 1 1
1 0 1
1 1 -1

第2步:使用图例法证明异或问题是线性不可分的

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# 使用Dataframe表示异或的输入与输出数据
x1 = [0, 0, 1, 1]
x2 = [0, 1, 0, 1]
y = [-1, 1, 1, -1]
x1 = np.array(x1)
x2 = np.array(x2)
y = np.array(y)
data = np.c_[x1, x2, y]
data = pd.DataFrame(data, index=None, columns=['x1', 'x2', 'y'])
data.head()
x1 x2 y
0 0 0 -1
1 0 1 1
2 1 0 1
3 1 1 -1
# 获取正类别(y=1)的数据
positive = data.loc[data['y'] == 1]
# 获取负类别(y=-1)的数据
negative = data.loc[data['y'] == -1]

# 绘制数据图
# 绘制坐标轴
plt.xlim(-0.5, 1.5)
plt.ylim(-0.5, 1.5)
plt.xticks([-0.5, 0, 1, 1.5])
plt.yticks([-0.5, 0, 1, 1.5])
# 添加坐标轴文字
plt.xlabel("x1")
plt.ylabel("x2")
# 绘制正、负样本点
plt.plot(positive['x1'], positive['x2'], "ro")
plt.plot(negative['x1'], negative['x2'], "bx")
# 添加图示
plt.legend(['Positive', 'Negative'])
plt.show()

  从上图可以看出,无法使用一条直线将两类样本分开,所以异或问题是线性不可分的

from sklearn.linear_model import Perceptron
import numpy as np

# 构造异或问题的训练数据集
X_train = np.array([[1, 1], [1, 0], [0, 1], [0, 0]])
y = np.array([-1, 1, 1, -1])

# 使用sklearn的Perceptron类构建感知机模型
perceptron_model = Perceptron()
# 进行模型训练
perceptron_model.fit(X_train, y)

# 打印模型参数
print("感知机模型的参数:w=", perceptron_model.coef_[
      0], "b=", perceptron_model.intercept_[0])
感知机模型的参数:w= [0. 0.] b= 0.0

  上述使用sklearn的Perceptron类构建感知机模型,从模型的参数上观察,感知机模型无法表示异或。

第3步:使用反证法证明感知机无法表示异或

  根据书中第35页感知机模型的定义:

定义2.1(感知机) 假设输入空间(特征空间)是$\mathcal{X} \subseteq R^n$,输出空间是$\mathcal{y}={+1,-1}$。输入$x \in \mathcal{X}$表示实例的特征向量,对应于输入空间(特征空间)的点;输出$y \in \mathcal{Y}$表示实例的类别。由输入空间到输出空间的如下函数:

$$
f(x)=\text{sign}(w \cdot x + b)
$$

称为感知机。其中,$w$和$b$为感知机模型参数,$w \in R^n$叫做权值或权值向量,$b \in R$叫做偏置,$w \cdot x$表示$w$和$x$的内积。sign是符号函数,即

$$
\text{sign}(x)=\left { \begin{array}{ll}
+1, \quad x \geqslant 0 \
-1, \quad x < 0
\end{array}\right.
$$

  假设感知机模型可以表示异或问题,即满足异或函数(XOR)输入与输出的情况(见第1步)。假设$x$向量只有两个维度$x_1$,$x_2$:

  1. 根据$x_1=0, x_2=0, f(x)=-1$,则$w \cdot x +b < 0$,可得$b < 0$;
  2. 根据$x_1=0, x_2=1, f(x)=1$,则$w_2 + b > 0$,结合$b < 0$,可得$w_2 > -b > 0$;
  3. 根据$x_1=1, x_2=0, f(x)=1$,则$w_1 + b > 0$,结合$b < 0$,可得$w_1 > -b > 0$;
  4. 根据$x_1=1, x_2=1$,并结合$w_1 + b > 0$、$w_2 > 0$,则$w_1 + w_2 + b > 0$,可得$f(x)=1$,与异或条件中的$f(x)=-1$矛盾。

  所以假设不成立,原命题成立,即感知机模型不能表示异或。

习题2.2 ⭐⭐⭐

  模仿例题 2.1,构建从训练数据求解感知机模型的例子。

解答:

解答思路:
  按照书中第38~39页感知机学习算法2.1,编写代码并绘制分离超平面

算法2.1(感知机学习算法的原始形式)
输入:训练数据集$T={(x_1,y_1),(x_2,y_2),\ldots,(x_N,y_N)}$,其中$x_i \in \mathcal{X} = R^n$,$y_i \in \mathcal{Y} = {-1, +1}$,$i=1,2,\ldots,N$;学习率$\eta (0 < \eta \leqslant 1)$;
输出:$w,b$;感知机模型$f(x)=\text{sign}(w \cdot x + b)$
(1)选取初值$w_0, b_0$;
(2)在训练集中选取数据$(x_i,y_i)$;
(3)如果$y_i(w \cdot x_i + b) \leqslant 0$,

$$
\begin{array}{ll}
w \leftarrow w + \eta y_i x_i \
b \leftarrow b + \eta y_i
\end{array}
$$

(4)转至(2),直至训练集中没有误分类点。

解题步骤:

import numpy as np
from matplotlib import pyplot as plt
%matplotlib tk


class Perceptron:
    def __init__(self, X, Y, lr=0.001, plot=True):
        """
        初始化感知机
        :param X: 特征向量
        :param Y: 类别
        :param lr: 学习率
        :param plot: 是否绘制图形
        """
        self.X = X
        self.Y = Y
        self.lr = lr
        self.plot = plot
        if plot:
            self.__model_plot = self._ModelPlot(self.X, self.Y)
            self.__model_plot.open_in()

    def fit(self):
        # (1)初始化weight, b
        weight = np.zeros(self.X.shape[1])
        b = 0
        # 训练次数
        train_counts = 0
        # 分类错误标识
        mistake_flag = True
        while mistake_flag:
            # 开始前,将mistake_flag设置为False,用于判断本次循环是否有分类错误
            mistake_flag = False
            # (2)从训练集中选取x,y
            for index in range(self.X.shape[0]):
                if self.plot:
                    self.__model_plot.plot(weight, b, train_counts)
                # 损失函数
                loss = self.Y[index] * (weight @ self.X[index] + b)
                # (3)如果损失函数小于0,则该点是误分类点
                if loss <= 0:
                    # 更新weight, b
                    weight += self.lr * self.Y[index] * self.X[index]
                    b += self.lr * self.Y[index]
                    # 训练次数加1
                    train_counts += 1
                    print("Epoch {}, weight = {}, b = {}, formula: {}".format(
                        train_counts, weight, b, self.__model_plot.formula(weight, b)))
                    # 本次循环有误分类点(即分类错误),置为True
                    mistake_flag = True
                    break
        if self.plot:
            self.__model_plot.close()
        # (4)直至训练集中没有误分类点
        return weight, b

    class _ModelPlot:
        def __init__(self, X, Y):
            self.X = X
            self.Y = Y

        @staticmethod
        def open_in():
            # 打开交互模式,用于展示动态交互图
            plt.ion()

        @staticmethod
        def close():
            # 关闭交互模式,并显示最终的图形
            plt.ioff()
            plt.show()

        def plot(self, weight, b, epoch):
            plt.cla()
            # x轴表示x1
            plt.xlim(0, np.max(self.X.T[0]) + 1)
            # y轴表示x2
            plt.ylim(0, np.max(self.X.T[1]) + 1)
            # 画出散点图,并添加图示
            scatter = plt.scatter(self.X.T[0], self.X.T[1], c=self.Y)
            plt.legend(*scatter.legend_elements())
            if True in list(weight == 0):
                plt.plot(0, 0)
            else:
                x1 = -b / weight[0]
                x2 = -b / weight[1]
                # 画出分离超平面
                plt.plot([x1, 0], [0, x2])
                # 绘制公式
                text = self.formula(weight, b)
                plt.text(0.3, x2 - 0.1, text)
            plt.title('Epoch %d' % epoch)
            plt.pause(0.01)

        @staticmethod
        def formula(weight, b):
            text = 'x1 ' if weight[0] == 1 else '%d*x1 ' % weight[0]
            text += '+ x2 ' if weight[1] == 1 else (
                '+ %d*x2 ' % weight[1] if weight[1] > 0 else '- %d*x2 ' % -weight[1])
            text += '= 0' if b == 0 else ('+ %d = 0' %
                                          b if b > 0 else '- %d = 0' % -b)
            return text
X = np.array([[3, 3], [4, 3], [1, 1]])
Y = np.array([1, 1, -1])
model = Perceptron(X, Y, lr=1)
weight, b = model.fit()
Epoch 1, weight = [3. 3.], b = 1, formula: 3*x1 + 3*x2 + 1 = 0
Epoch 2, weight = [2. 2.], b = 0, formula: 2*x1 + 2*x2 = 0
Epoch 3, weight = [1. 1.], b = -1, formula: x1 + x2 - 1 = 0
Epoch 4, weight = [0. 0.], b = -2, formula: 0*x1 - 0*x2 - 2 = 0
Epoch 5, weight = [3. 3.], b = -1, formula: 3*x1 + 3*x2 - 1 = 0
Epoch 6, weight = [2. 2.], b = -2, formula: 2*x1 + 2*x2 - 2 = 0
Epoch 7, weight = [1. 1.], b = -3, formula: x1 + x2 - 3 = 0

习题2.3

  证明以下定理:样本集线性可分的充分必要条件是正实例点所构成的凸壳与负实例点所构成的凸壳互不相交。

解答:

解答思路:

  1. 写出凸壳和线性可分的定义
  2. 证明必要性:线性可分$\Rightarrow$凸壳不相交
  3. 证明充分性:凸壳不相交$\Rightarrow$线性可分

第1步:凸壳与线性可分的定义

  1. 根据书中第47页脚注1的凸壳定义如下:

设集合$S \subset R^n$,是由$R^n$中的$k$个点所组成的集合,即$S={x_1,x_2,\cdots, x_k}$。定义$S$的凸壳$\text{conv}(S)$为:

$$
\text{conv}(S) = \left{ x = \sum_{i=1}^k \lambda_i x_i \Big| \sum_{i=1}^k \lambda_i=1,\lambda_i \geqslant 0, i=1,2,\cdots, k \right}
$$

  1. 根据书中第36页的线性可分定义如下:

给定一个数据集

$$
T={(x_1,y_1), (x_2,y_2), \cdots, (x_n,y_n)}
$$

其中$x_i \in \mathcal{X}=R_n, y_i \in \mathcal{Y} = {+1, -1}, i=1,2,\cdots, n$,如果存在某个超平面$S$

$$
w \cdot x + b = 0
$$

能够将数据集的正实例点和负实例点完全正确划分到超平面的两侧,即对所有$y_i=+1$的实例$i$,有$w \cdot x_i + b > 0$,对$y_i = -1$的实例$i$,有$w \cdot x_i + b < 0$,则称数据集$T$为线性可分数据集,否则称数据集$T$线性不可分。

第2步:证明必要性:线性可分$\Rightarrow$凸壳不相交

证明思路(反证法):

  1. 假设原命题不成立:样本集线性可分,正实例点所构成的凸壳与负实例点所构成的凸壳相交
  2. 条件推理
  3. 发现矛盾,得出原命题成立

证明步骤:

  1. 假设原命题不成立:
    设数据集$T$中的正例点集为$S_+$,$S_+$的凸壳为$\text{conv}(S_+)$,负实例点集为$S_-$,$S_-$的凸壳为$\text{conv}(S_-)$。
    假设样本集线性可分,正实例点所构成的凸壳与负实例点所构成的凸壳相交,即存在某个元素$s$,同时满足$s \in \text{conv}(S_+)$和$s \in \text{conv}(S_-)$。

  2. 条件推理:
    若数据集$T$是线性可分的,根据线性可分的定义,则存在一个超平面能够将$S_+$和$S_-$完全分离:

$$
w \cdot x + b = 0
$$

对于所有的正例点$x_i$,有
$$
w \cdot x_i + b = \varepsilon_i > 0, \quad i = 1,2,\cdots,|S_+|
$$
根据凸壳的定义,对于$\text{conv}(S_+)$中的元素$s_+$,有
$$
\begin{aligned}
w \cdot s_+ + b &= w \cdot (\sum_{i=1}^{|S_+|} \lambda_i x_i) + b \
&= (\sum_{i=1}^{|S_+|} \lambda_i(\varepsilon_i - b)) + b \
&= \sum_{i=1}^{|S_+|} \lambda_i \varepsilon_i - (b\sum_{i=1}^{|S_+|} \lambda_i) + b \quad (\because \sum_{i=1}^{|S_+|} \lambda_i = 1) \
& = \sum_{i=1}^{|S_+|} \lambda_i \varepsilon_i
\end{aligned}
$$
因此$\displaystyle w \cdot s_+ + b = \sum_{i=1}^{|S_+|} \lambda_i \varepsilon_i > 0$。
同理对于$S_-$中的元素$s_-$,有$\displaystyle w \cdot s_- + b = \sum_{i=1}^{|S_-|} \lambda_i \varepsilon_i < 0$

  1. 找出矛盾,得出原命题成立:
    根据条件推理,当$s \in \text{conv}(S_+)$有$\displaystyle w \cdot s + b = \sum_{i=1}^{|S_+|} \lambda_i \varepsilon_i > 0$,当$s \in \text{conv}(S_-)$有$\displaystyle w \cdot s + b = \sum_{i=1}^{|S_-|} \lambda_i \varepsilon_i < 0$,既$s$不可能同时满足若$\displaystyle s \in \text{conv}(S_+)$和$s \in \text{conv}(S_-)$,这与假设命题矛盾。

因此,原命题成立,当样本线性可分时,$\text{conv}(S_+)$ 和$\text{conv}(S_-)$必不相交。必要性得证。

第3步:证明充分性:凸壳不相交$\Rightarrow$线性可分

证明思路:

  1. 根据凸壳不相交,找到一个超平面
  2. 证明这个超平面可将两个互不相交的凸壳分隔开(反证法)
  3. 上述超平面可以将凸壳分隔开,则样本集满足线性可分

证明步骤:

  1. 根据凸壳不相交,找到一个超平面:
    设数据集$T$中的正例点集为$S_+$,$S_+$的凸壳为$\text{conv}(S_+)$,负实例点集为$S_-$,$S_-$的凸壳为$\text{conv}(S_-)$,且$\text{conv}(S_+)$与$\text{conv}(S_-)$不相交。
    定义两个点$x_1,x_2$的距离为

$$
\text{dist}(x_1,x_2) = |x_1 - x_2|_2
$$

定义$\text{conv}(S_+)$、$\text{conv}(S_-)$的距离是,分别处于两个凸壳集合中的点的距离最小值:
$$
\text {dist}( \text{conv}(S_+), \text{conv}(S_-)) = \min |s_+ - s_-|2 \quad s+ \in \text{conv}(S_+), s_- \in \text{conv}(S_-)
$$
记最小值点分别为$x_+, x_-$,即:
$$
\text{dist}( \text{conv}(S_+), \text{conv}(S_-)) = \text{dist}(x_+, x_-) \quad x_+ \in \text{conv}(S_+), x_- \in \text{conv}(S_-)
$$
定义以$(x_+, x_-)$为法线,且过两点中点的超平面为$f(x|w,b)=0$,则参数为:
$$
\displaystyle f(x|w,b)=(x_+-x_-)^T(x - \frac{x_+ + x_-}{2})\
\left { \begin{array}{ll}
w = (x_+ - x_-)^T \
\displaystyle b = - \frac{1}{2}({|x_+|2}^2 - {|x-|_2}^2)
\end{array} \right .
$$

  1. 证明这个超平面可将两个互不相交的凸壳分隔开(反证法)
    若某个超平面可将两个互不相交的凸壳分隔开,则$f(x)≥0, x\in \text{conv}(S_+)$且$f(x)≤0, x\in \text{conv}(S_-)$。

$$
\begin{aligned}
\displaystyle f(x)&=(x_+-x_-)^T(x - \frac{x_+ + x_-}{2}) \
&=(x_+-x_-)^T(x + x_+ - x_+ - \frac{x_+ + x_-}{2}) \
&=(x_+-x_-)^T(x - x_+ + \frac{x_+ - x_-}{2}) \
&=(x_+-x_-)^T(x - x_+) + \frac{ {|x_+ - x_-|_2}^2}{2} \
\end{aligned}
$$

假设原命题不成立:当$x\in \text{conv}(S_+)$时,假设$f(x)<0$,则有:
$$
(x_+-x_-)^T(x - x_+) < 0
$$
设点$u=x_++t(x-x_+), t\in [0,1]$,即$u$在$x_+$和$x$的线段上。根据凸壳定义,$u \in \text{conv}(S_+)$。则$u$和$x_-$距离的平方为:
$$
\begin{aligned}
\displaystyle g(t)&={|u-x_-|2}^2 \
&={|x
++t(x-x_+)-x_-|2}^2 \
\end{aligned}
$$
求解$u$和$x
-$距离的最小值,对上式求导:
$$
\begin{aligned}
\displaystyle g’(t)&=2(x_++t(x-x_+)-x_-)(x-x_+) \
&=2(x_+-x_-)^T(x-x_+)+t{|x-x_+|2}^2 \
\end{aligned}
$$
根据假设,在$t=0$时,得$g’(t)<0$。在当$t$足够接近于0时(导函数在0处的极限值为负,则存在邻域函数递减),即$g(t)<g(0)$。
$\therefore$ 存在一点$u$,使得它到$x
-$的距离,比定义的凸壳距离$ \text{dist}(x_+,x_-)$还小。产生矛盾。
故原命题成立,即$ f(x)≥0, x\in \text{conv}(S_+)$。同理,可证$ f(x)≤0, x\in \text{conv}(S_-)$。则可以找到一个超平面将两个互不相交的凸壳分隔开。

  1. 上述超平面可以将凸壳分隔开,则样本集满足线性可分
      根据凸壳定义,数据集$T$中正例点$s_+ \in \text{conv}(S_+)$,负例点$s_- \in \text{conv}(S_-)$。上述超平面可以将正例点集$S_+$和负例点集$S_-$两个凸壳分隔开,则可以使样本集线性可分。充分性得证。

三、k近邻法

k近邻法(k-nearest neighbor,k-NN)是一种基本分类与回归方法。k近邻法的输入为实例的特征向量,对应于特征空间的点;输出为实例的类别,可以取多类,k近邻法假设给定一个训练数据集,其中的实例类别已定。分类时,对新的实例,根据其k个最近邻的训练实例的类别,通过多数表决等方式进行预测。因此,k近邻法不具有显式的学习过程。k近邻法实际上利用训练数据集对特征向量空间进行划分,并作为其分类的”模型”k值的选择、距离度量及分类决策规则是k近邻法的三个基本要素。k近邻法1968年由 Cover和Hart提出。

3.1 k近邻算法

算法3.1 (k近邻法)

3.2 k近邻模型

k近邻法使用的模型实际上对应于对特征空间的划分。模型由三个基本要素——距离度量、k值的选择和分类决策规则决定。

3.2.1 模型

k近邻法中,当训练集、距离度量(如欧氏距离)、k值及分类决策规则(如多数表决)确定后,对于任何一个新的输入实例,它所属的类唯一地确定。这相当于根据上述要素将特征空间划分为一些子空间,确定子空间里的每个点所属的类,这一事实从最近邻算法中可以看得很清楚。

k近邻法模型对应特征空间的一个划分

3.2.2 距离度量

特征空间中两个实例点的距离是两个实例点相似程度的反映。k近邻模型的特征空间一般是n维实数向量空间。使用的距离是欧氏距离,但也可以是其他距离。

常见的距离计算公式:

3.2.3 k值选择

k值的选择会对k近邻法的结果产生重大影响。

如果选择较小的k值,就相当于用较小的邻域中的训练实例进行预测,“学习”的近似误差(approximation error)会减小,只有与输入实例较近的(相似的)训练实例才会对预测结果起作用。但缺点是“学习”的估计误差(estimation error)会增大,预测结果会对近邻的实例点非常敏感。如果邻近的实例点恰巧是噪声,预测就会出错。换句话说,k值的减小就意味着整体模型变得复杂,容易发生过拟合。

如果选择较大的k值,就相当于用较大邻域中的训练实例进行预测,其优点是可以减少学习的估计误差,但缺点是学习的近似误差会增大。这时与输入实例较远的(不相似的)训练实例也会对预测起作用,使预测发生错误。k值的增大就意味着整体的模型变得简单。

如果k=N,那么无论输入实例是什么,都将简单地预测它属于在训练实例中最多的类。这时,模型过于简单,完全忽略训练实例中的大量有用信息,是不可取的。

在应用中,k值一般取一个比较小的数值,通常采用交叉验证法来选取最优的k值。

3.2.4 分类决策规则

k近邻法中的分类决策规则往往是多数表决,即由输入实例的k个邻近的训练实例中的多数类决定输入实例的类。

3.3 k近邻法的实现:kd树

实现k近邻法时,主要考虑的问题是如何对训练数据进行快速k近邻搜索。这点在特征空间的维数大及训练数据容量大时尤其必要。

k近邻法最简单的实现方法是线性扫描(linear scan),这时要计算输入实例与每一个训练实例的距离。当训练集很大时,计算非常耗时,这种方法是不可行的。

3.3.1 构造kd树

kd树是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构,kd树是二叉树,表示对k维空间的一个划分(partition).构造树相当于不断地用垂直于坐标轴的超平面将k维空间切分,构成一系列的k维超矩形区域。树的每个结点对应于一个k维超矩形区域。

构造树的方法如下:构造根结点,使根结点对应于k维空间中包含所有实例点的超矩形区域;通过下面的递归方法,不断地对k维空间进行切分,生成子结点。在超矩形区域(结点)上选择一个坐标轴和在此坐标轴上的一个切分点,确定一个超平面,这个超平面通过选定的切分点并垂直于选定的坐标轴,将当前超矩形区域切分为左右两个子区域(子结点);这时,实例被分到两个子区域。这个过程直到子区域内没有实例时终止(终止时的结点为叶结点),在此过程中,将实例保存在相应的结点上。

通常,依次选择坐标轴对空间切分,选择训练实例点在选定坐标轴上的中位数(median)为切分点,这样得到的d树是平衡的。注意,平衡的d树搜索时的效率未必是最优的。

算法3.2(构造平衡kd树)

3.3.2 搜索kd树

kd树示例

算法3.3(用kd树的最近邻搜索)

实例:

  • 注意KNN (监督学习)与K-means (无监督学习)区别

  • KNN:

    • 算法:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。
    • k近邻模型的三个基本要素:
      1. k值的选择:k值的选择会对结果产生重大影响。较小的k值可以减少近似误差,但是会增加估计误差;较大的k值可以减小估计误差,但是会增加近似误差。一般而言,通常采用交叉验证法来选取最优的k值。
      2. 距离度量:距离反映了特征空间中两个实例的相似程度。可以采用欧氏距离、曼哈顿距离等。
      3. 分类决策规则:往往采用多数表决。
  • k-means:

    • 算法:

      1. 从n个数据中随机选择 k 个对象作为初始聚类中心;
      2. 根据每个聚类对象的均值(中心对象),计算每个数据点与这些中心对象的距离;并根据最小距离准则,重新对数据进行划分;
      3. 重新计算每个有变化的聚类簇的均值,选择与均值距离最小的数据作为中心对象;
      4. 循环步骤2和3,直到每个聚类簇不再发生变化为止。
    • k-means方法的基本要素:

      1. k值的选择:也就是类别的确定,与K近邻中k值的确定方法类似。

      2. 距离度量:可以采用欧氏距离、曼哈顿距离等。

习题

习题3.1

  参照图3.1,在二维空间中给出实例点,画出$k$为1和2时的$k$近邻法构成的空间划分,并对其进行比较,体会$k$值选择与模型复杂度及预测准确率的关系。

解答:

解答思路:

  1. 参照图3.1,使用已给的实例点,采用sklearn的KNeighborsClassifier分类器,对k=1和2时的模型进行训练
  2. 使用matplotlib的contourf和scatter,画出k为1和2时的k近邻法构成的空间划分
  3. 根据模型得到的预测结果,计算预测准确率,并设置图形标题
  4. 根据程序生成的图,比较k为1和2时,k值选择与模型复杂度、预测准确率的关系

解答步骤:

第1、2、3步:使用已给的实例点,对$k$为1和2时的k近邻模型进行训练,并绘制空间划分

from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier
import numpy as np
%matplotlib inline

data = np.array([[5, 12, 1],
                 [6, 21, 0],
                 [14, 5, 0],
                 [16, 10, 0],
                 [13, 19, 0],
                 [13, 32, 1],
                 [17, 27, 1],
                 [18, 24, 1],
                 [20, 20, 0],
                 [23, 14, 1],
                 [23, 25, 1],
                 [23, 31, 1],
                 [26, 8, 0],
                 [30, 17, 1],
                 [30, 26, 1],
                 [34, 8, 0],
                 [34, 19, 1],
                 [37, 28, 1]])
# 得到特征向量
X_train = data[:, 0:2]
# 得到类别向量
y_train = data[:, 2]

#(1)使用已给的实例点,采用sklearn的KNeighborsClassifier分类器,
# 对k=1和2时的模型进行训练
# 分别构造k=1和k=2的k近邻模型
models = (KNeighborsClassifier(n_neighbors=1, n_jobs=-1),
          KNeighborsClassifier(n_neighbors=2, n_jobs=-1))
# 模型训练
models = (clf.fit(X_train, y_train) for clf in models)
# 设置图形标题
titles = ('K Neighbors with k=1',
          'K Neighbors with k=2')

# 设置图形的大小和图间距
fig = plt.figure(figsize=(15, 5))
plt.subplots_adjust(wspace=0.4, hspace=0.4)

# 分别获取第1个和第2个特征向量
X0, X1 = X_train[:, 0], X_train[:, 1]

# 得到坐标轴的最小值和最大值
x_min, x_max = X0.min() - 1, X0.max() + 1
y_min, y_max = X1.min() - 1, X1.max() + 1

# 构造网格点坐标矩阵
# 设置0.2的目的是生成更多的网格点,数值越小,划分空间之间的分隔线越清晰
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.2),
                     np.arange(y_min, y_max, 0.2))

for clf, title, ax in zip(models, titles, fig.subplots(1, 2).flatten()):
    # (2)使用matplotlib的contourf和scatter,画出k为1和2时的k近邻法构成的空间划分
    # 对所有网格点进行预测
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    # 设置颜色列表
    colors = ('red', 'green', 'lightgreen', 'gray', 'cyan')
    # 根据类别数生成颜色
    cmap = ListedColormap(colors[:len(np.unique(Z))])
    # 绘制分隔线,contourf函数用于绘制等高线,alpha表示颜色的透明度,一般设置成0.5
    ax.contourf(xx, yy, Z, cmap=cmap, alpha=0.5)

    # 绘制样本点
    ax.scatter(X0, X1, c=y_train, s=50, edgecolors='k', cmap=cmap, alpha=0.5)

    # (3)根据模型得到的预测结果,计算预测准确率,并设置图形标题
    # 计算预测准确率
    acc = clf.score(X_train, y_train)
    # 设置标题
    ax.set_title(title + ' (Accuracy: %d%%)' % (acc * 100))

plt.show()

png

补充知识:
np.meshgrid方法:用于构造网格点坐标矩阵,可参考https://blog.csdn.net/lllxxq141592654/article/details/81532855

第4步:比较$k$为1和2时,k值选择与模型复杂度、预测准确率的关系

  1. $k$值选择与模型复杂度的关系
      根据书中第52页(3.2.3节:$k$值的选择)

      如果选择较小的$k$值,就相当于用较小的邻域中的训练实例进行预测,“学习”的近似误差会减小,只有与输入实例较近的(相似的)训练实例才会对预测结果起作用。$k$值的减小就意味着整体模型变得复杂,容易发生过拟合。
      如果选择较大的$k$值,就相当于用较大邻域中的训练实例进行预测。$k$值的增大就意味着整体的模型变得简单。

      综上所属,$k$值越大,模型复杂度越低,模型越简单,容易发生欠拟合。反之,$k$值越小,模型越复杂,容易发生过拟合。

  1. $k$值选择与预测准确率的关系
      从图中观察到,当$k=1$时,模型易产生过拟合,当$k=2$时准确率仅有88%,故在过拟合发生前,$k$值越大,预测准确率越低,也反映模型泛化能力越差,模型简单。反之,$k$值越小,预测准确率越高,模型具有更好的泛化能力,模型复杂。

习题3.2

  利用例题3.2构造的$kd$树求点$x=(3,4.5)^T$的最近邻点。

解答:

解答思路:

方法一:

  1. 使用sklearn的KDTree类,结合例题3.2构建平衡$kd$树,配置相关参数(构建平衡树kd树算法,见书中第54页算法3.2内容);
  2. 使用tree.query方法,查找(3, 4.5)的最近邻点(搜索kd树算法,见书中第55页第3.3.2节内容);
  3. 根据第3步返回的参数,得到最近邻点。

方法二:
  根据书中第56页算法3.3用$kd$树的最近邻搜索方法,查找(3, 4.5)的最近邻点

解答步骤:

方法一:

import numpy as np
from sklearn.neighbors import KDTree

# 构造例题3.2的数据集
train_data = np.array([[2, 3],
                       [5, 4],
                       [9, 6],
                       [4, 7],
                       [8, 1],
                       [7, 2]])
# (1)使用sklearn的KDTree类,构建平衡kd树
# 设置leaf_size为2,表示平衡树
tree = KDTree(train_data, leaf_size=2)

# (2)使用tree.query方法,设置k=1,查找(3, 4.5)的最近邻点
# dist表示与最近邻点的距离,ind表示最近邻点在train_data的位置
dist, ind = tree.query(np.array([[3, 4.5]]), k=1)
node_index = ind[0]

# (3)得到最近邻点
x1 = train_data[node_index][0][0]
x2 = train_data[node_index][0][1]
print("x点(3,4.5)的最近邻点是({0}, {1})".format(x1, x2))
x点(3,4.5)的最近邻点是(2, 3)

可得到点$x=(3,4.5)^T$的最近邻点是$(2,3)^T$

方法二:

  1. 首先找到点$x=(3,4.5)^T$所在领域的叶节点$x_1=(4,7)^T$,则最近邻点一定在以$x$为圆心,$x$到$x_1$距离为半径的圆内;
  2. 找到$x_1$的父节点$x_2=(5,4)^T$,$x_2$的另一子节点为$x_3=(2,3)^T$,此时$x_3$在圆内,故$x_3$为最新的最近邻点,并形成以$x$为圆心,以$x$到$x_3$距离为半径的圆;
  3. 继续探索$x_2$的父节点$x_4=(7,2)^T$,$x_4$的另一个子节点$(9,6)$对应的区域不与圆相交,故不存在最近邻点,所以最近邻点为$x_3=(2,3)^T$。

可得到点$x=(3,4.5)^T$的最近邻点是$(2,3)^T$

习题3.3 ⭐⭐⭐

  参照算法3.3,写出输出为$x$的$k$近邻的算法。

解答:

解答思路:

  1. 参考书中第56页算法3.3(用$kd$树的最近邻搜索),写出输出为$x$的$k$近邻算法;
  2. 根据算法步骤,写出算法代码,并用习题3.2的解进行验证。

解答步骤:

第1步:用$kd$树的$k$邻近搜索算法

根据书中第56页算法3.3(用$kd$树的最近邻搜索)

输入:已构造的kd树;目标点$x$;
输出:$x$的k近邻
(1)在$kd$树中找出包含目标点$x$的叶结点:从根结点出发,递归地向下访问树。若目标点$x$当前维的坐标小于切分点的坐标,则移动到左子结点,否则移动到右子结点,直到子结点为叶结点为止;
(2)如果“当前$k$近邻点集”元素数量小于$k$或者叶节点距离小于“当前$k$近邻点集”中最远点距离,那么将叶节点插入“当前k近邻点集”;
(3)递归地向上回退,在每个结点进行以下操作:
  (a)如果“当前$k$近邻点集”元素数量小于$k$或者当前节点距离小于“当前$k$近邻点集”中最远点距离,那么将该节点插入“当前$k$近邻点集”。
  (b)检查另一子结点对应的区域是否与以目标点为球心、以目标点与“当前$k$近邻点集”中最远点间的距离为半径的超球体相交。
  如果相交,可能在另一个子结点对应的区域内存在距目标点更近的点,移动到另一个子结点,接着,递归地进行近邻搜索;
  如果不相交,向上回退;
(4)当回退到根结点时,搜索结束,最后的“当前$k$近邻点集”即为$x$的近邻点。

第2步:根据算法步骤,写出算法代码,并用习题3.2的解进行验证

import json


class Node:
    """节点类"""

    def __init__(self, value, index, left_child, right_child):
        self.value = value.tolist()
        self.index = index
        self.left_child = left_child
        self.right_child = right_child

    def __repr__(self):
        return json.dumps(self, indent=3, default=lambda obj: obj.__dict__, ensure_ascii=False, allow_nan=False)
class KDTree:
    """kd tree类"""

    def __init__(self, data):
        # 数据集
        self.data = np.asarray(data)
        # kd树
        self.kd_tree = None
        # 创建平衡kd树
        self._create_kd_tree(data)

    def _split_sub_tree(self, data, depth=0):
        # 算法3.2第3步:直到子区域没有实例存在时停止
        if len(data) == 0:
            return None
        # 算法3.2第2步:选择切分坐标轴, 从0开始(书中是从1开始)
        l = depth % data.shape[1]
        # 对数据进行排序
        data = data[data[:, l].argsort()]
        # 算法3.2第1步:将所有实例坐标的中位数作为切分点
        median_index = data.shape[0] // 2
        # 获取结点在数据集中的位置
        node_index = [i for i, v in enumerate(
            self.data) if list(v) == list(data[median_index])]
        return Node(
            # 本结点
            value=data[median_index],
            # 本结点在数据集中的位置
            index=node_index[0],
            # 左子结点
            left_child=self._split_sub_tree(data[:median_index], depth + 1),
            # 右子结点
            right_child=self._split_sub_tree(
                data[median_index + 1:], depth + 1)
        )

    def _create_kd_tree(self, X):
        self.kd_tree = self._split_sub_tree(X)

    def query(self, data, k=1):
        data = np.asarray(data)
        hits = self._search(data, self.kd_tree, k=k, k_neighbor_sets=list())
        dd = np.array([hit[0] for hit in hits])
        ii = np.array([hit[1] for hit in hits])
        return dd, ii

    def __repr__(self):
        return str(self.kd_tree)

    @staticmethod
    def _cal_node_distance(node1, node2):
        """计算两个结点之间的距离"""
        return np.sqrt(np.sum(np.square(node1 - node2)))

    def _search(self, point, tree=None, k=1, k_neighbor_sets=None, depth=0):
        if k_neighbor_sets is None:
            k_neighbor_sets = []
        if tree is None:
            return k_neighbor_sets

        # (1)找到包含目标点x的叶结点
        if tree.left_child is None and tree.right_child is None:
            # 更新当前k近邻点集
            return self._update_k_neighbor_sets(k_neighbor_sets, k, tree, point)

        # 递归地向下访问kd树
        if point[0][depth % k] < tree.value[depth % k]:
            direct = 'left'
            next_branch = tree.left_child
        else:
            direct = 'right'
            next_branch = tree.right_child
        if next_branch is not None:
            # (3)(a) 判断当前结点,并更新当前k近邻点集
            k_neighbor_sets = self._update_k_neighbor_sets(
                k_neighbor_sets, k, next_branch, point)
            # (3)(b)检查另一子结点对应的区域是否相交
            if direct == 'left':
                node_distance = self._cal_node_distance(
                    point, tree.right_child.value)
                if k_neighbor_sets[0][0] > node_distance:
                    # 如果相交,递归地进行近邻搜索
                    return self._search(point, tree=tree.right_child, k=k, depth=depth + 1,
                                        k_neighbor_sets=k_neighbor_sets)
            else:
                node_distance = self._cal_node_distance(
                    point, tree.left_child.value)
                if k_neighbor_sets[0][0] > node_distance:
                    return self._search(point, tree=tree.left_child, k=k, depth=depth + 1,
                                        k_neighbor_sets=k_neighbor_sets)

        return self._search(point, tree=next_branch, k=k, depth=depth + 1, k_neighbor_sets=k_neighbor_sets)

    def _update_k_neighbor_sets(self, best, k, tree, point):
        # 计算目标点与当前结点的距离
        node_distance = self._cal_node_distance(point, tree.value)
        if len(best) == 0:
            best.append((node_distance, tree.index, tree.value))
        elif len(best) < k:
            # 如果“当前k近邻点集”元素数量小于k
            self._insert_k_neighbor_sets(best, tree, node_distance)
        else:
            # 叶节点距离小于“当前 𝑘 近邻点集”中最远点距离
            if best[0][0] > node_distance:
                best = best[1:]
                self._insert_k_neighbor_sets(best, tree, node_distance)
        return best

    @staticmethod
    def _insert_k_neighbor_sets(best, tree, node_distance):
        """将距离最远的结点排在前面"""
        n = len(best)
        for i, item in enumerate(best):
            if item[0] < node_distance:
                # 将距离最远的结点插入到前面
                best.insert(i, (node_distance, tree.index, tree.value))
                break
        if len(best) == n:
            best.append((node_distance, tree.index, tree.value))
# 打印信息
def print_k_neighbor_sets(k, ii, dd):
    if k == 1:
        text = "x点的最近邻点是"
    else:
        text = "x点的%d个近邻点是" % k

    for i, index in enumerate(ii):
        res = X_train[index]
        if i == 0:
            text += str(tuple(res))
        else:
            text += ", " + str(tuple(res))

    if k == 1:
        text += ",距离是"
    else:
        text += ",距离分别是"
    for i, dist in enumerate(dd):
        if i == 0:
            text += "%.4f" % dist
        else:
            text += ", %.4f" % dist

    print(text)
import numpy as np

X_train = np.array([[2, 3],
                    [5, 4],
                    [9, 6],
                    [4, 7],
                    [8, 1],
                    [7, 2]])
kd_tree = KDTree(X_train)
# 设置k值
k = 1
# 查找邻近的结点
dists, indices = kd_tree.query(np.array([[3, 4.5]]), k=k)
# 打印邻近结点
print_k_neighbor_sets(k, indices, dists)
x点的最近邻点是(2, 3),距离是1.8028
# 打印kd树
kd_tree
{
   "value": [
      7,
      2
   ],
   "index": 5,
   "left_child": {
      "value": [
         5,
         4
      ],
      "index": 1,
      "left_child": {
         "value": [
            2,
            3
         ],
         "index": 0,
         "left_child": null,
         "right_child": null
      },
      "right_child": {
         "value": [
            4,
            7
         ],
         "index": 3,
         "left_child": null,
         "right_child": null
      }
   },
   "right_child": {
      "value": [
         9,
         6
      ],
      "index": 2,
      "left_child": {
         "value": [
            8,
            1
         ],
         "index": 4,
         "left_child": null,
         "right_child": null
      },
      "right_child": null
   }
}

  上述打印的平衡kd树和书中第55页的图3.4 kd树示例一致。

更换数据集,使用更高维度的数据,并设置$k=3$

import numpy as np

X_train = np.array([[2, 3, 4],
                    [5, 4, 4],
                    [9, 6, 4],
                    [4, 7, 4],
                    [8, 1, 4],
                    [7, 2, 4]])
kd_tree = KDTree(X_train)
# 设置k值
k = 3
# 查找邻近的结点
dists, indices = kd_tree.query(np.array([[3, 4.5, 4]]), k=k)
# 打印邻近结点
print_k_neighbor_sets(k, indices, dists)
x点的3个近邻点是(4, 7, 4), (5, 4, 4), (2, 3, 4),距离分别是2.6926, 2.0616, 1.8028

四、朴素贝叶斯法

朴素贝叶斯( naive Bayes)法是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练数据集,首先基于特征条件独立假设学习输入输出的联合概率分布:然后基于此模型,对给定的输入x,利用贝叶斯定理求出后验概率最大的输出y.朴素贝叶斯法实现简单,学习与预测的效率都很高,是一种常用的方法。

4.1 朴素贝叶斯法的想学习与分类

4.1.1 基本方法

X是定义在输入空间上的随机向量,Y是定义在输出空间上的随机变量。$P(X,Y)$ 是X和Y的联合概率分布。训练数据集由$P(X,Y)$ 独立同分布产生:
$$
T=\left { \left(x_{1}, y_{1} \right), \left(x_{2}, y_{2} \right), \cdots, \left(x_{N}, y_{N} \right) \right }
$$
朴素贝叶斯法通过训练数据集学习联合概率分布$P(X,Y)$.具体地,学习以下先验概率分布及条件概率分布,先验概率分布:
$$
P\left(Y=c_{k}\right), \quad k=1,2, \cdots, K
$$
条件概率分布
$$
P\left(X=x \mid Y=c_{k}\right)=P\left(X^{(1)}=x^{(1)}, \cdots, X^{(n)}=x^{(n)} \mid Y=c_{k}\right), \quad k=1,2, \cdots, K
$$
于是学习到联合概率分布$P(X,Y)$

朴素贝叶斯法对条件概率分布作了条件独立性的假设。由于这是一个较强的假设,朴素贝叶斯法也由此得名。具体地,条件独立性假设是
$$
\begin{aligned}
P\left(X=x \mid Y=c_{k}\right) &=P\left(X^{(1)}=x^{(1)}, \cdots, X^{(n)}=x^{(n)} \mid Y=c_{k}\right) \
&=\prod_{j=1}^{n} P\left(X^{(j)}=x^{(j)} \mid Y=c_{k}\right)
\end{aligned}
$$
朴素贝叶斯法实际上学习到生成数据的机制,所以属于生成模型。条件独立假设等于是说用于分类的特征在类确定的条件下都是条件独立的。这一假设使朴素贝叶斯法变得简单,但有时会牺牲一定的分类准确率。
$$
y=\arg \max {c{k}} P\left(Y=c_{k}\right) \prod_{j} P\left(X^{(j)}=x^{(j)} \mid Y=c_{k}\right)
$$

4.1.2 后验概率最大化的含义

根据期望风险最小化准则就得到了后验概率最大化准则:
$$
f(x)=\arg \max {c{k}} P\left(c_{k} \mid X=x\right)
$$

4.2 朴素贝叶斯法的参数估计

4.2.1 极大似然估计

4.2.2 学习与分类算法

算法4.1 朴素贝叶斯算法(naive Bayes algorithm)

示例:

4.2.3 贝叶斯估计

用极大似然估计可能会出现所要估计的概率值为0的情况,这时会影响到后验概率的计算结果,使分类产生偏差。解决这一问题的方法是采用贝叶斯估计。具体地,条件概率的贝叶斯估计是:

习题

习题4.1

  用极大似然估计法推出朴素贝叶斯法中的概率估计公式(4.8)及公式 (4.9)。

解答:

解答思路:

  1. 极大似然估计的一般步骤(详见习题1.1第3步)
  2. 证明公式4.8:根据输出空间$\mathcal{Y}$的随机变量$Y$满足独立同分布,列出似然函数,求解概率$P(Y=c_k)$的值;
  3. 证明公式4.9:证明同公式4.8。

解答步骤:

第1步:极大似然估计的一般步骤

参考Wiki:https://en.wikipedia.org/wiki/Maximum_likelihood_estimation

  1. 写出随机变量的概率分布函数;
  2. 写出似然函数;
  3. 对似然函数取对数,得到对数似然函数,并进行化简;
  4. 对参数进行求导,并令导数等于0;
  5. 求解似然函数方程,得到参数的值。

第2步:证明公式(4.8)
$$
\displaystyle P(Y=c_k) = \frac{\displaystyle \sum_{i=1}^N I(y_i=c_k)}{N}, \quad k=1,2,\ldots,K
$$

  根据书中第59页,朴素贝叶斯法的基本方法:

  设输入空间$\mathcal{X} \subseteq R^n$为$n$维向量的集合,输出空间为类标记集合$\mathcal{Y}={c_1,c_2,\ldots,c_k}$。输入为特征向量$x \in \mathcal{X}$,输出为类标记$y \in \mathcal{Y}$。$X$是定义在输入空间$\mathcal{X}$上的随机向量,$Y$是定义在输出空间$\mathcal{Y}$上的随机变量。$P(X,Y)$是$X$和$Y$的联合概率分布。训练数据集

$$
T={(x_1,y_1),(x_2,y_2),\ldots,(x_N,y_N)}
$$

由$P(X,Y)$独立同分布产生。

  根据上述定义,$Y={y_1,y_2,\ldots,y_N}$满足独立同分布,假设$P(Y=c_k)$概率为$p$,其中$c_k$在随机变量$Y$中出现的次数$\displaystyle m=\sum_{i=1}^NI(y_i=c_k)$,可得似然函数为:
$$
\begin{aligned} L(p|Y) &= f(Y|p) \
&= C_N^m p^m (1-p)^{N-m}
\end{aligned}
$$
对似然函数取对数,得到对数似然函数为:
$$
\begin{aligned} \displaystyle \log L(p|Y) &= \log C_N^m p^m (1-p)^{N-m} \
&= C_N^m \left[\log(p^m) + \log\left( (1-p)^{N-m} \right)\right] \
&= C_N^m \left[ m\log p + (N-m)\log (1-p)\right]
\end{aligned}
$$
求解参数$p$:
$$
\begin{aligned}
\hat{p} &= \mathop{\arg\max} \limits_{p} L(p|Y) \
&= \mathop{\arg\max} \limits_{p} C_N^m \left[ m\log p + (N-m)\log (1-p)\right]
\end{aligned}
$$
对参数$p$求导,并求解导数为0时的$p$值:
$$
\begin{aligned}
\frac{\partial \log L(p)}{\partial p} &= C_N^m \left[ \frac{m}{p} - \frac{N-m}{1-p} \right] \
&= C_N^m \left[ \frac{m(1-p) - p(N-m)}{p(1-p)} \right] \
&= C_N^m \frac{m-Np}{p(1-p)} = 0
\end{aligned}
$$
从上式可得,$m-Np=0$,即$\displaystyle P(Y=c_k)=p=\frac{m}{N}$
综上所述,$\displaystyle P(Y=c_k)=p=\frac{m}{N}=\frac{\displaystyle \sum_{i=1}^N I(y_i=c_k)}{N}$,公式(4.8)得证。

第3步:证明公式(4.9)
$$
\displaystyle P(X^{(j)}=a_{jl}|Y=c_k) = \frac{\displaystyle \sum_{i=1}^N I(x_i^{(j)}=a_{jl},y_i=c_k)}{\displaystyle \sum_{i=1}^N I(y_i=c_k)} \
j=1,2,\ldots,n; \quad l = 1,2,\ldots,S_j; \quad k = 1,2,\dots,K
$$
  根据书中第60页,朴素贝叶斯法的条件独立性假设:

  朴素贝叶斯法对条件概率分布作了条件独立性的假设。由于这是一个较强的假设,朴素贝叶斯法也由此得名。具体地,条件独立性假设是:

$$
\begin{aligned}
P(X=x|Y=c_k) &= P(X^{(1)}=x^{(1)},\ldots,X^{(n)}=x^{(n)}|Y=c_k) \
&= \prod_{j=1}^n P(X^{(j)}=x^{(j)}|Y=c_k)
\end{aligned}
$$

  根据上述定义,在条件$Y=c_k$下,随机变量$X$满足条件独立性,假设$P(X^{(j)}=a_{jl}|Y=c_k)$概率为$p$,其中$c_k$在随机变量$Y$中出现的次数$\displaystyle m=\sum_{i=1}^NI(y_i=c_k)$,$y=c_k$和$x^{(j)}=a_{jl}$同时出现的次数$\displaystyle q=\sum_{i=1}^N I(x_i^{(j)}=a_{jl},y_i=c_k)$,可得似然函数为:
$$
\begin{aligned} L(p|X,Y) &= f(X,Y|p) \
&= C_m^q p^q (1-p)^{m-q}
\end{aligned}
$$
与第2步推导过程类似,可求解得到$\displaystyle p=\frac{q}{m}$
综上所述,$\displaystyle P(X^{(j)}=a_{jl}|Y=c_k)=p=\frac{q}{m}=\frac{\displaystyle \sum_{i=1}^N I(x_i^{(j)}=a_{jl},y_i=c_k)}{\displaystyle \sum_{i=1}^N I(y_i=c_k)}$,公式(4.9)得证。

习题4.2

  用贝叶斯估计法推出朴素贝叶斯法中的慨率估计公式(4.10)及公式(4.11)

解答:

解答思路:

  1. 贝叶斯估计的一般步骤(详见习题1.1第4步);
  2. 证明公式4.11:假设概率$P_{\lambda}(Y=c_i)$服从狄利克雷(Dirichlet)分布,根据贝叶斯公式,推导后验概率也服从Dirichlet分布,求参数期望;
  3. 证明公式4.10:证明同公式4.11。

解答步骤:

第1步:贝叶斯估计的一般步骤
参考Wiki:https://en.wikipedia.org/wiki/Bayes_estimator

  1. 确定参数$\theta$的先验概率$p(\theta)$
  2. 根据样本集$D={x_1,x_2,\ldots,x_n}$,计算似然函数$P(D|\theta)$:$\displaystyle P(D|\theta)=\prod_{i=1}^n P(x_n|D)$
  3. 利用贝叶斯公式,求$\theta$的后验概率:$\displaystyle P(\theta|D)=\frac{P(D|\theta)P(\theta)}{\displaystyle \int \limits_\Theta P(D|\theta) P(\theta) d \theta}$
  4. 计算后验概率分布参数$\theta$的期望,并求出贝叶斯估计值:$\displaystyle \hat{\theta}=\int \limits_{\Theta} \theta \cdot P(\theta|D) d \theta$

第2步:证明公式(4.11)
$$
\displaystyle P_\lambda(Y=c_k) = \frac{\displaystyle \sum_{i=1}^N I(y_i=c_k) + \lambda}{N+K \lambda}, \quad k= 1,2,\ldots,K
$$
证明思路:

  1. 条件假设:$P_\lambda(Y=c_k)=u_k$,且服从参数为$\lambda$的Dirichlet分布;随机变量$Y$出现$y=c_k$的次数为$m_k$;
  2. 得到$u$的先验概率$P(u)$;
  3. 得到似然函数$P(m|u)$;
  4. 根据贝叶斯公式,计算后验概率$P(u|m)$
  5. 计算$u$的期望$E(u)$

证明步骤:

  1. 条件假设

  根据朴素贝叶斯法的基本方法,训练数据集$T={(x_1,y_1),(x_2,y_2),\ldots,(x_N,y_N)}$,假设:
(1)随机变量$Y$出现$y=c_k$的次数为$m_k$,即$\displaystyle m_k=\sum_{i=1}^N I(y_i=c_k)$,可知$\displaystyle \sum_{k=1}^K m_k = N$($y$总共有$N$个);
(2)$P_\lambda(Y=c_k)=u_k$,随机变量$u_k$服从参数为$\lambda$的Dirichlet分布。

补充说明:

  1. 狄利克雷(Dirichlet)分布
      参考PRML(Pattern Recognition and Machine Learning)一书的第2.2.1章节:⽤似然函数(2.34)乘以先验(2.38),我们得到了参数${u_k}$的后验分布,形式为

$$
p(u|D,\alpha) \propto p(D|u)p(u|\alpha) \propto \prod_{k=1}^K u_k^{\alpha_k+m_k-1}
$$

  该书中第B.4章节:
狄利克雷分布是$K$个随机变量$0 \leqslant u_k \leqslant 1$的多变量分布,其中$k=1,2,\ldots,K$,并满足以下约束

$$
0 \leqslant u_k \leqslant 1, \quad \sum_{k=1}^K u_k = 1
$$

记$u=(u_1,\ldots,u_K)^T, \alpha=(\alpha_1,\ldots,\alpha_K)^T$,有
$$
Dir(u|\alpha) = C(\alpha) \prod_{k-1}^K u_k^{\alpha_k - 1} \
E(u_k) = \frac{\alpha_k}{\displaystyle \sum_{k=1}^K \alpha_k}
$$

  1. 为什么假设$Y=c_k$的概率服从Dirichlet分布?
    答:原因如下:
    (1)首先,根据PRML第B.4章节,Dirichlet分布是Beta分布的推广。
    (2)由于,Beta分布是二项式分布的共轭分布,Dirichlet分布是多项式分布的共轭分布。Dirichlet分布可以看作是“分布的分布”;
    (3)又因为,Beta分布与Dirichlet分布都是先验共轭的,意味着先验概率和后验概率属于同一个分布。当假设为Beta分布或者Dirichlet分布时,通过获得大量的观测数据,进行数据分布的调整,使得计算出来的概率越来越接近真实值。
    (4)因此,对于一个概率未知的事件,Beta分布或Dirichlet分布能作为表示该事件发生的概率的概率分布。
  1. 得到先验概率
      根据假设(2)和Dirichlet分布的定义,可得先验概率为

$$
\displaystyle P(u)=P(u_1,u_2,\ldots,u_K) = C(\lambda) \prod_{k=1}^K u_k^{\lambda - 1}
$$

  1. 得到似然函数
      记$m=(m_1, m_2, \ldots, m_K)^T$,可得似然函数为

$$
P(m|u) = u_1^{m_1} \cdot u_2^{m_2} \cdots u_K^{m_K} = \prod_{k=1}^K u_k^{m_k}
$$

  1. 得到后验概率分布
      结合贝叶斯公式,求$u$的后验概率分布,可得

$$
P(u|m) = \frac{P(m|u)P(u)}{P(m)}
$$

  根据假设(1),可得
$$
P(u|m,\lambda) \propto P(m|u)P(u|\lambda) \propto \prod_{k=1}^K u_k^{\lambda+m_k-1}
$$
  上式表明,后验概率分布$P(u|m,\lambda)$也服从Dirichlet分布

  1. 得到随机变量$u$的期望
      根据后验概率分布$P(u|m,\lambda)$和假设(1),求随机变量$u$的期望,可得

$$
E(u_k) = \frac{\alpha_k}{\displaystyle \sum_{k=1}^K \alpha_k}
$$

其中$\alpha_k = \lambda+m_k$,则
$$
\begin{aligned}
E(u_k) &= \frac{\alpha_k}{\displaystyle \sum_{k=1}^K \alpha_k} \
&= \frac{\lambda+m_k}{\displaystyle \sum_{k=1}^K (\lambda + m_k)} \
&= \frac{\lambda+m_k}{\displaystyle \sum_{k=1}^K \lambda +\sum_{k=1}^K m_k} \quad(\because \sum_{k=1}^K m_k = N) \
&= \frac{\lambda+m_k}{\displaystyle K \lambda + N } \quad (\because m_k=\sum_{i=1}^N I(y_i=c_k)) \
&= \frac{\displaystyle \sum_{i=1}^N I(y_i=c_k) + \lambda}{N+K \lambda}
\end{aligned}
$$
  随机变量$u_k$取$u_k$的期望,可得
$\displaystyle P_\lambda(Y=c_k) = \frac{\displaystyle \sum_{i=1}^N I(y_i=c_k) + \lambda}{N+K \lambda}$,公式(4.11)得证

第3步:证明公式(4.10)
$$
\displaystyle P_{\lambda}(X^{(j)}=a_{jl} | Y = c_k) = \frac{\displaystyle \sum_{i=1}^N I(x_i^{(j)}=a_{jl},y_i=c_k) + \lambda}{\displaystyle \sum_{i=1}^N I(y_i=c_k) + S_j \lambda}
$$
证明思路:

  1. 条件假设:$P_{\lambda}(X^{(j)}=a_{jl} | Y = c_k)=u_l$,其中$l=1,2,\ldots,S_j$,且服从参数为$\lambda$的Dirichlet分布;出现$x^{(j)}=a_{jl}, y=c_k$的次数为$m_l$;
  2. 得到$u$的先验概率$P(u)$;
  3. 得到似然函数$P(m|u)$;
  4. 根据贝叶斯公式,计算后验概率$P(u|m)$
  5. 计算$u$的期望$E(u)$

证明步骤:

  1. 条件假设

  根据朴素贝叶斯法的基本方法,训练数据集$T={(x_1,y_1),(x_2,y_2),\ldots,(x_N,y_N)}$,假设:
(1)出现$x^{(j)}=a_{jl}, y=c_k$的次数为$m_l$,即$\displaystyle m_l=\sum_{i=1}^N I(x_i^{(j)}=a_{jl},y_i=c_k)$,可知$\displaystyle \sum_{l=1}^{S_j} m_l = \sum_{i=1}^N I(y_i=c_k)$(总共有$\displaystyle \sum_{i=1}^N I(y_i=c_k)$个);
(2)$P_{\lambda}(X^{(j)}=a_{jl} | Y = c_k)=u_l$,随机变量$u_l$服从参数为$\lambda$的Dirichlet分布。

  1. 得到先验概率
      根据假设(2)和Dirichlet分布的定义,可得先验概率为

$$
\displaystyle P(u)=P(u_1,u_2,\ldots,u_{S_j}) = C(\lambda) \prod_{l=1}^{S_j} u_l^{\lambda - 1}
$$

  1. 得到似然函数
      记$m=(m_1, m_2, \ldots, m_{S_j})^T$,可得似然函数为

$$
P(m|u) = u_1^{m_1} \cdot u_2^{m_2} \cdots u_{S_j}^{m_{S_j} } = \prod_{l=1}^{S_j} u_l^{m_l}
$$

  1. 得到后验概率分布
      结合贝叶斯公式,求$u$的后验概率分布,可得

$$
P(u|m) = \frac{P(m|u)P(u)}{P(m)}
$$

  根据假设(1),可得
$$
P(u|m,\lambda) \propto P(m|u)P(u|\lambda) \propto \prod_{l=1}^{S_j} u_l^{\lambda+m_l-1}
$$
  上式表明,后验概率分布$P(u|m,\lambda)$也服从Dirichlet分布

  1. 得到随机变量$u$的期望
      根据后验概率分布$P(u|m,\lambda)$和假设(1),求随机变量$u$的期望,可得

$$
E(u_k) = \frac{\alpha_l}{\displaystyle \sum_{l=1}^{S_j} \alpha_l}
$$

其中$\alpha_l = \lambda+m_l$,则
$$
\begin{aligned}
E(u_l) &= \frac{\alpha_l}{\displaystyle \sum_{l=1}^{S_j} \alpha_l} \
&= \frac{\lambda+m_l}{\displaystyle \sum_{l=1}^{S_j} (\lambda + m_l)} \
&= \frac{\lambda+m_l}{\displaystyle \sum_{l=1}^{S_j} \lambda +\sum_{l=1}^{S_j} m_l} \quad(\because \sum_{l=1}^{S_j} m_l = \sum_{i=1}^N I(y_i=c_k)) \
&= \frac{\lambda+m_l}{\displaystyle S_j \lambda + \sum_{i=1}^N I(y_i=c_k) } \quad (\because m_l=\sum_{i=1}^N I(x_i^{(j)}=a_{jl},y_i=c_k)) \
&= \frac{\displaystyle \sum_{i=1}^N I(x_i^{(j)}=a_{jl},y_i=c_k) + \lambda}{\displaystyle \sum_{i=1}^N I(y_i=c_k) + S_j \lambda}
\end{aligned}
$$

  随机变量$u_k$取$u_k$的期望,可得
$\displaystyle P_{\lambda}(X^{(j)}=a_{jl} | Y = c_k) = \frac{\displaystyle \sum_{i=1}^N I(x_i^{(j)}=a_{jl},y_i=c_k) + \lambda}{\displaystyle \sum_{i=1}^N I(y_i=c_k) + S_j \lambda}$,公式(4.10)得证。

五、决策树

决策树(decision tree)是一种基本的分类与回归方法。决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布,其主要优点是模型具有可读性,分类速度快。学习时,利用训练数据,根据损失函数最小化的原则建立决策树模型。预测时,对新的数据,利用决策树模型进行分类,决策树学习通常包括3个步骤:特征选择、决策树的生成和决策树的修剪。这些决策树学习的思想主要来源于由 Quinlan在1986年提出的ID3算法和1993年提出的C4.5算法,以及由 Breiman 等人在1984年提出的CART算法。

5.1 决策树模型与学习

5.1.1 决策树模型

定义5.1(决策树)分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点(node)和有向边(directed edge)组成,结点有两种类型:内部结点(internal node)和叶结点(leaf node),内部结点表示一个特征或属性,叶结点表示一个类

用决策树分类,从根结点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其子结点:这时,每一个子结点对应着该特征的一个取值。如此递归地对实例进行测试并分配,直至达到叶结点。最后将实例分到叶结点的类中。

决策树模型

5.1.2 决策树与if-then规则

可以将决策树看成一个 if-then规则的集合。将决策树转换成 if-then规则的过程是这样的:由决策树的根结点到叶结点的每一条路径构建一条规则;路径上内部结点的特征对应着规则的条件,而叶结点的类对应着规则的结论。决策树的路径或其对应的if-then规则集合具有一个重要的性质:互斥并且完备。这就是说,每一个实例都被一条路径或一条规则所覆盖,而且只被一条路径或一条规则所覆盖。这里所谓覆盖是指实例的特征与路径上的特征一致或实例满足规则的条件。

5.1.3 决策树与条件概率分布

决策树还表示给定特征条件下类的条件概率分布,这一条件概率分布定义在特征空间的一个划分(partition)上。将特征空间划分为互不相交的单元(cell)或区域(region),并在每个单元定义一个类的概率分布就构成了一个条件概率分布,决策树的一条路径对应于划分中的一个单元。决策树所表示的条件概率分布由各个单元给定条件下类的条件概率分布组成,假设X为表示特征的随机变量,Y为表示类的随机变量,那么这个条件概率分布可以表示为$P(Y|X)$.X取值于给定划分下单元的集合,Y取值于类的集合。各叶结点(单元)上的条件概率往往偏向某一个类,即属于某一类的概率较大,决策树分类时将该结点的实例强行分到条件概率大的那一类去。

决策树对应于条件分布概率

5.1.4 决策树学习

决策树学习本质上是从训练数据集中归纳出一组分类规则。与训练数据集不相矛盾的决策树(即能对训练数据进行正确分类的决策树)可能有多个,也可能一个也没有。我们需要的是一个与训练数据矛盾较小的决策树,同时具有很好的泛化能力。从另一个角度看,决策树学习是由训练数据集估计条件概率模型。基于特征空间划分的类的条件概率模型有无穷多个。我们选择的条件概率模型应该不仅对训练数据有很好的拟合,而且对未知数据有很好的预测。

决策树学习用损失函数表示这一目标,如下所述,决策树学习的损失函数通常是正则化的极大似然函数。决策树学习的策略是以损失函数为目标函数的最小化。

当损失函数确定以后,学习问题就变为在损失函数意义下选择最优决策树的问题,因为从所有可能的决策树中选取最优决策树是NP完全问题,所以现实中决策树学习算法通常采用启发式方法,近似求解这一最优化问题,这样得到的决策树是次最优(sub- optimal)的。

决策树学习的算法通常是一个递归地选择最优特征,并根据该特征对训练数据进行分割,使得对各个子数据集有一个最好的分类的过程。这一过程对应着对特征空间的划分,也对应着决策树的构建。开始,构建根结点,将所有训练数据都放在根结点。选择一个最优特征,按照这一特征将训练数据集分割成子集,使得各个子集有一个在当前条件下最好的分类。如果这些子集已经能够被基本正确分类,那么构建叶结点,并将这些子集分到所对应的叶结点中去;如果还有子集不能被基本正确分类,那么就对这些子集选择新的最优特征,继续对其进行分割,构建相应的结点。如此递归地进行下去,直至所有训练数据子集被基本正确分类,或者没有合适的特征为止。最后每个子集都被分到叶结点上,即都有了明确的类。这就生成了一棵决策树。

以上方法生成的决策树可能对训练数据有很好的分类能力,但对未知的测试数据却未必有很好的分类能力,即可能发生过拟合现象。我们需要对已生成的树自下而上进行剪枝,将树变得更简单,从而使它具有更好的泛化能力。具体地,就是去掉过于细分的叶结点,使其回退到父结点,甚至更高的结点,然后将父结点或更高的结点改为新的叶结点。

如果特征数量很多,也可以在决策树学习开始的时候,对特征进行选择,只留下对训练数据有足够分类能力的特征。

5.2 特征选择

5.2.1 特征选择问题

特征选择在于选取对训练数据具有分类能力的特征。这样可以提高决策树学习的效率。如果利用一个特征进行分类的结果与随机分类的结果没有很大差别,则称这个特征是没有分类能力的。经验上扔掉这样的特征对决策树学习的精度影响不大。通常特征选择的准则是信息增益或信息增益比。

特征选择是决定用哪个特征来划分特征空间。

5.2.2 信息增益

在信息论与概率统计中,熵( entropy)是表示随机变量不确定性的度量。

随机变量X的熵定义为:
$$
H(X)=-\sum_{i=1}^{n} p_{i} \log p_{i}
$$
分布为贝努利分布熵与概率的关系

信息增益(information gain)表示得知特征X的信息而使得类Y的信息的不确定性减少的程度。

定义5.2(信息增益)特征A对训练数据集D的信息增益$g(D,A)$,定义为集合D的经验熵$H(D)$与特征A给定条件下D的经验条件熵$H(D|A)$之差,即
$$
g(D, A)=H(D)-H(D | A)
$$
一般地,熵$H(Y)$与条件熵$H(Y|X)$之差称为互信息(mutual information).决策树学习中的信息增益等价于训练数据集中类与特征的互信息。

根据信息增益准则的特征选择方法是:对训练数据集(或子集)D,计算其每个特征的信息增益,并比较它们的大小,选择信息增益最大的特征。

算法5.1(信息增益的算法)

输入:训练数据集D和特征A

输出:特征A对训练数据集D的信息增益$g(D,A)$

5.2.3 信息增益比

信息增益值的大小是相对于训练数据集而言的,并没有绝对意义。在分类问题困难时,也就是说在训练数据集的经验熵大的时候,信息增益值会偏大。反之,信息增益值会偏小。使用信息增益比(information gain ratio)可以对这一问题进行校正。这是特征选择的另一准则。

定义5.3(信息增益比)特征A对训练数据集D的信息增益比$g_R(D,A)$定义为其信息增益$g(D,A)$与训练数据集D的经验熵$H(D)$之比:
$$
g_{R}(D, A)=\frac{g(D, A)}{H(D)}
$$

5.3 决策树的生成

5.3.1 ID3算法

ID3算法的核心是在决策树各个结点上应用信息增益准则选择特征,递归地构建决策树。具体方法是:从根结点( root node)开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子结点;再对子结点递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止。最后得到一个决策树,ID3相当于用极大似然法进行概率模型的选择。

算法5.2(ID3算法)

ID3算法只有树的生成,所以该算法生成的树容易产生过拟合。

决策树生成

特征进行决策。

5.3.2 C4.5的生成算法

C4.5算法与ID3算法相似,C4.5算法对ID3算法进行了改进。C4.5在生成的过程中,用信息增益比来选择特征.

算法5.3(C4.5的生成算法)

5.4决策树的剪枝

决策树生成算法递归地产生决策树,直到不能继续下去为止、这样产生的树往往对训练数据的分类很准确,但对未知的测试数据的分类却没有那么准确,即出现过拟合现象。过拟合的原因在于学习时过多地考虑如何提高对训练数据的正确分类,从而构建出过于复杂的决策树。解决这个问题的办法是考虑决策树的复杂度,对已生成的决策树进行简化。

在决策树学习中将已生成的树进行简化的过程称为剪枝( pruning),具体地,剪枝从已生成的树上裁掉一些子树或叶结点,并将其根结点或父结点作为新的叶结点,从而简化分类树模型。

决策树的剪枝往往通过极小化决策树整体的损失函数(loss function)或代价函数(cost function)来实现。

决策树生成只考虑了通过提高信息增益(或信息增益比)对训练数据进行更好的拟合。而决策树剪枝通过优化损失函数还考虑了减小模型复杂度。决策树生成学习局部的模型,而决策树剪枝学习整体的模型。

算法5.4(树的剪枝算法)

5.5 CART算法

分类与回归树(classification and regression tree,CART)模型由 Breiman等人在1984年提出,是应用广泛的决策树学习方法。CART同样由特征选择、树的生成及剪枝组成,既可以用于分类也可以用于回归。以下将用于分类与回归的树统称为决策树。

CART是在给定输入随机变量X条件下输出随机变量Y的条件概率分布的学习方法,CART假设决策树是二叉树,内部结点特征的取值为“是”和“否”,左分支是取值为“是”的分支,右分支是取值为“否”的分支。这样的决策树等价于递归地二分每个特征,将输入空间即特征空间划分为有限个单元,并在这些单元上确定预测的概率分布,也就是在输入给定的条件下输出的条件概率分布。

CART算法由以下两步组成:决策树生成,决策树剪枝。

5.5.1 CART生成

决策树的生成就是递归地构建二叉决策树的过程。对回归树用平方误差最小化准则,对分类树用基尼指数(Gini index)最小化准则,进行特征选择,生成二叉树。

  • 回归树生成

算法5.5(最小二乘回归树生成算法)

  • 分类树的生成

分类树用基尼指数选择最优特征,同时决定该特征的最优二值切分点

定义5.4(基尼指数)分类问题中,假设有K个类,样本点属于第k类的概率为$P_k$,则概率分布的基尼指数定义为:
$$
\operatorname{Gini}(p)=\sum_{k=1}^{K} p_{k}\left(1-p_{k}\right)=1-\sum_{k=1}^{K} p_{k}^{2}
$$
对于给定的样本集合D,其基尼指数为
$$
\operatorname{Gini}(D)=1-\sum_{k=1}^{K}\left(\frac{\left|C_{k}\right|}{|D|}\right)^{2}
$$
样本集合D若被分为两部分,则在特征A的条件下,集合D的基尼指数定义为:
$$
\operatorname{Gini}(D, A)=\frac{\left|D_{1}\right|}{|D|} \operatorname{Gini}\left(D_{1}\right)+\frac{\left|D_{2}\right|}{|D|} \operatorname{Gini}\left(D_{2}\right)
$$
基尼指数$Gini(D)$表示集合D的不确定性,基尼指数$Gini(D,A)$表示经A=a分割后集合D的不确定性。基尼指数值越大,样本集合的不确定性也就越大,这一点与熵相似.

二类分类中基尼指数、熵之半和分类误差率的关系

算法5.6(CART生成算法)

5.5.2 CART剪枝

CART剪枝算法从“完全生长”的决策树的底端剪去一些子树,使决策树变小(模型变简单),从而能够对未知数据有更准确的预测。CART剪枝算法由两步组成:首先从生成算法产生的决策树$T_0$底端开始不断剪枝,直到$T_0$的根结点,形成个子树序列${T_0,T_1,…,T_n}$;然后通过交又验证法在独立的验证数据集上对子树序列进行测试,从中选择最优子树.

  • 剪枝,形成一个子树序列

在剪枝过程中,计算子树的损失函数:
$$
C_{\alpha}(T)=C(T)+\alpha|T|
$$

  • 在剪枝得到的子树序列${T_0,T_1,…,T_n}$中交叉验证选取最优子树

算法5.7(CART剪枝算法)

一些解释:

习题

习题5.1 ⭐⭐⭐

  根据表5.1所给的训练数据集,利用信息增益比(C4.5算法)生成决策树。

解答:

表5.1 贷款申请样本数据表

ID 年龄 有工作 有自己的房子 信贷情况 类别
1 青年 一般
2 青年
3 青年
4 青年 一般
5 青年 一般
6 中年 一般
7 中年
8 中年
9 中年 非常好
10 中年 非常好
11 老年 非常好
12 老年
13 老年
14 老年 非常好
15 老年 一般

解答思路:

  1. 列出C4.5的生成算法;
  2. 使用sklearn的DecisionTreeClassifier类构建决策树,并使用graphviz包展示,默认是Gini,这里可以作为自编程的验证
  3. 通过自编程实现C4.5算法生成决策树,并进行特征选择

解题步骤:

第1步:C4.5的生成算法(书中第78页)

输入:训练数据集$D$,特征集$A$阈值$\epsilon$;
输出:决策树$T$。
(1)如果$D$中所有实例属于同一类$C_k$,则置$T$为单结点树,并将$C_k$作为该结点的类,返回$T$;
(2)如果$A = \emptyset$,则置$T$为单结点树,并将$D$中实例数最大的类$C_k$作为该结点的类,返回$T$;
(3)否则,按式$\displaystyle g_R(D,A)=\frac{g(D,A)}{H_A(D)}$计算$A$中各特征对$D$的信息增益比,选择信息增益比最大的特征$A_g$;
(4)如果$A_g$的信息增益比小于阈值$\epsilon$,则置$T$为单结点树,并将$D$中实例数最大的类$C_k$作为该结点的类,返回$T$;
(5)否则,对$A_g$的每一可能值$a_i$,依$A_g=a_i$将$D$分割为子集若干非空$D_i$,将$D_i$中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树$T$,返回$T$;
(6)对结点$i$,以$D_i$为训练集,以$A-{A_g}$为特征集,递归地调用步(1)~步(5),得到子树$T_i$,返回$T_i$

第2步:调用sklearn的DecisionTreeClassifier类构建决策树

from sklearn.tree import DecisionTreeClassifier
from sklearn import preprocessing
import numpy as np
import pandas as pd

from sklearn import tree
import graphviz

features = ["年龄", "有工作", "有自己的房子", "信贷情况"]
X_train = pd.DataFrame([
    ["青年", "否", "否", "一般"],
    ["青年", "否", "否", "好"],
    ["青年", "是", "否", "好"],
    ["青年", "是", "是", "一般"],
    ["青年", "否", "否", "一般"],
    ["中年", "否", "否", "一般"],
    ["中年", "否", "否", "好"],
    ["中年", "是", "是", "好"],
    ["中年", "否", "是", "非常好"],
    ["中年", "否", "是", "非常好"],
    ["老年", "否", "是", "非常好"],
    ["老年", "否", "是", "好"],
    ["老年", "是", "否", "好"],
    ["老年", "是", "否", "非常好"],
    ["老年", "否", "否", "一般"]
])
y_train = pd.DataFrame(["否", "否", "是", "是", "否",
                        "否", "否", "是", "是", "是",
                        "是", "是", "是", "是", "否"])
class_names = [str(k) for k in np.unique(y_train)]
# 数据预处理
le_x = preprocessing.LabelEncoder()
le_x.fit(np.unique(X_train))
X_train = X_train.apply(le_x.transform)
# 调用sklearn的DecisionTreeClassifier建立决策树模型
model_tree = DecisionTreeClassifier()
# 训练模型
model_tree.fit(X_train, y_train)

# 导出决策树的可视化文件,文件格式是dot
dot_data = tree.export_graphviz(model_tree, out_file=None,
                                feature_names=features,
                                class_names=class_names,
                                filled=True, rounded=True,
                                special_characters=True)
# 使用graphviz包,对决策树进行展示
graph = graphviz.Source(dot_data)
# 可使用view方法展示决策树
# 中文乱码:需要对源码_export.py文件(文件路径:sklearn/tree/_export.py)修改,
# 在文件第451行中将helvetica改成SimSun
graph

# 打印决策树
tree_text = tree.export_text(model_tree, feature_names=features)
print(tree_text)
|--- 有自己的房子 <= 3.00
|   |--- 有工作 <= 3.00
|   |   |--- class: 否
|   |--- 有工作 >  3.00
|   |   |--- class: 是
|--- 有自己的房子 >  3.00
|   |--- class: 是

第3步:自编程实现C4.5算法生成决策树

import json
from collections import Counter

import numpy as np


# 节点类
class Node:
    def __init__(self, node_type, class_name, feature_name=None,
                 info_gain_ratio_value=0.0):
        # 结点类型(internal或leaf)
        self.node_type = node_type
        # 特征名
        self.feature_name = feature_name
        # 类别名
        self.class_name = class_name
        # 子结点树
        self.child_nodes = []
        # Gini指数值
        self.info_gain_ratio_value = info_gain_ratio_value

    def __repr__(self):
        return json.dumps(self, indent=3, default=lambda obj: obj.__dict__, ensure_ascii=False)

    def add_sub_tree(self, key, sub_tree):
        self.child_nodes.append({"condition": key, "sub_tree": sub_tree})
class MyDecisionTree:
    def __init__(self, epsilon):
        self.epsilon = epsilon
        self.tree = None

    def fit(self, train_set, y, feature_names):
        features_indices = list(range(len(feature_names)))
        self.tree = self._fit(train_set, y, features_indices, feature_names)
        return self

    # C4.5算法
    def _fit(self, train_data, y, features_indices, feature_labels):
        LEAF = 'leaf'
        INTERNAL = 'internal'
        class_num = len(np.unique(y))

        # (1)如果训练数据集所有实例都属于同一类Ck
        label_set = set(y)
        if len(label_set) == 1:
            # 将Ck作为该结点的类
            return Node(node_type=LEAF, class_name=label_set.pop())

        # (2)如果特征集为空
        # 计算每一个类出现的个数
        class_len = Counter(y).most_common()
        (max_class, max_len) = class_len[0]

        if len(features_indices) == 0:
            # 将实例数最大的类Ck作为该结点的类
            return Node(LEAF, class_name=max_class)

        # (3)按式(5.10)计算信息增益,并选择信息增益最大的特征
        max_feature = 0
        max_gda = 0
        D = y.copy()
        # 计算特征集A中各特征
        for feature in features_indices:
            # 选择训练集中的第feature列(即第feature个特征)
            A = np.array(train_data[:, feature].flat)
            # 计算信息增益
            gda = self._calc_ent_grap(A, D)
            if self._calc_ent(D) != 0:
                # 计算信息增益比
                gda /= self._calc_ent(D)
            # 选择信息增益最大的特征Ag
            if gda > max_gda:
                max_gda, max_feature = gda, feature

        # (4)如果Ag信息增益小于阈值
        if max_gda < self.epsilon:
            # 将训练集中实例数最大的类Ck作为该结点的类
            return Node(LEAF, class_name=max_class)

        max_feature_label = feature_labels[max_feature]

        # (6)移除已选特征Ag
        sub_feature_indecs = np.setdiff1d(features_indices, max_feature)
        sub_feature_labels = np.setdiff1d(feature_labels, max_feature_label)

        # (5)构建非空子集
        # 构建结点
        feature_name = feature_labels[max_feature]
        tree = Node(INTERNAL, class_name=None, feature_name=feature_name,
                    info_gain_ratio_value=max_gda)

        max_feature_col = np.array(train_data[:, max_feature].flat)
        # 将类按照对应的实例数递减顺序排列
        feature_value_list = [x[0] for x in Counter(max_feature_col).most_common()]
        # 遍历Ag的每一个可能值ai
        for feature_value in feature_value_list:
            index = []
            for i in range(len(y)):
                if train_data[i][max_feature] == feature_value:
                    index.append(i)

            # 递归调用步(1)~步(5),得到子树
            sub_train_set = train_data[index]
            sub_train_label = y[index]
            sub_tree = self._fit(sub_train_set, sub_train_label, sub_feature_indecs, sub_feature_labels)
            # 在结点中,添加其子结点构成的树
            tree.add_sub_tree(feature_value, sub_tree)

        return tree

    # 计算数据集x的经验熵H(x)
    def _calc_ent(self, x):
        x_value_list = set([x[i] for i in range(x.shape[0])])
        ent = 0.0
        for x_value in x_value_list:
            p = float(x[x == x_value].shape[0]) / x.shape[0]
            logp = np.log2(p)
            ent -= p * logp

        return ent

    # 计算条件熵H(y/x)
    def _calc_condition_ent(self, x, y):
        x_value_list = set([x[i] for i in range(x.shape[0])])
        ent = 0.0
        for x_value in x_value_list:
            sub_y = y[x == x_value]
            temp_ent = self._calc_ent(sub_y)
            ent += (float(sub_y.shape[0]) / y.shape[0]) * temp_ent

        return ent

    # 计算信息增益
    def _calc_ent_grap(self, x, y):
        base_ent = self._calc_ent(y)
        condition_ent = self._calc_condition_ent(x, y)
        ent_grap = base_ent - condition_ent

        return ent_grap

    def __repr__(self):
        return str(self.tree)
# 表5.1的训练数据集
feature_names = np.array(["年龄", "有工作", "有自己的房子", "信贷情况"])
X_train = np.array([
    ["青年", "否", "否", "一般"],
    ["青年", "否", "否", "好"],
    ["青年", "是", "否", "好"],
    ["青年", "是", "是", "一般"],
    ["青年", "否", "否", "一般"],
    ["中年", "否", "否", "一般"],
    ["中年", "否", "否", "好"],
    ["中年", "是", "是", "好"],
    ["中年", "否", "是", "非常好"],
    ["中年", "否", "是", "非常好"],
    ["老年", "否", "是", "非常好"],
    ["老年", "否", "是", "好"],
    ["老年", "是", "否", "好"],
    ["老年", "是", "否", "非常好"],
    ["老年", "否", "否", "一般"]
])
y = np.array(["否", "否", "是", "是", "否",
              "否", "否", "是", "是", "是",
              "是", "是", "是", "是", "否"])

dt_tree = MyDecisionTree(epsilon=0.1)
dt_tree.fit(X_train, y, feature_names)
dt_tree
{
   "node_type": "internal",
   "feature_name": "有自己的房子",
   "class_name": null,
   "child_nodes": [
      {
         "condition": "否",
         "sub_tree": {
            "node_type": "internal",
            "feature_name": "年龄",
            "class_name": null,
            "child_nodes": [
               {
                  "condition": "否",
                  "sub_tree": {
                     "node_type": "leaf",
                     "feature_name": null,
                     "class_name": "否",
                     "child_nodes": [],
                     "info_gain_ratio_value": 0.0
                  }
               },
               {
                  "condition": "是",
                  "sub_tree": {
                     "node_type": "leaf",
                     "feature_name": null,
                     "class_name": "是",
                     "child_nodes": [],
                     "info_gain_ratio_value": 0.0
                  }
               }
            ],
            "info_gain_ratio_value": 1.0
         }
      },
      {
         "condition": "是",
         "sub_tree": {
            "node_type": "leaf",
            "feature_name": null,
            "class_name": "是",
            "child_nodes": [],
            "info_gain_ratio_value": 0.0
         }
      }
   ],
   "info_gain_ratio_value": 0.4325380677663126
}

习题5.2 ⭐⭐⭐

  已知如表5.2所示的训练数据,试用平方误差损失准则生成一个二叉回归树。
表5.2 训练数据表

$x_i$ 1 2 3 4 5 6 7 8 9 10
$y_i$ 4.50 4.75 4.91 5.34 5.80 7.05 7.90 8.23 8.70 9.00

解答:

解答思路:

  1. 根据书中第81页平方误差损失准则,列出最小二乘回归树生成算法(算法5.5);
  2. 编写代码,实现算法,并用表5.2训练数据进行验证。

解题步骤:

第1步:算法5.5的最小二乘回归树生成算法(书中第82页)

  决策树的生成就是递归地构建二叉决策树的过程,对回归树用平方误差最小化准则,对分类树用基尼指数(Gini index)最小化准则,进行特征选择,生成二叉树。

算法5.5(最小二乘回归树生成算法)
输入:训练数据集$D$
输出:回归树$f(x)$
在训练数据集所在的输入空间中,递归地将每个区域划分为两个子区域并决定每个子区域上的输出值,构建二叉决策树;
(1)选择最优切分变量$j$与切分点$s$,求解

$$
\min_{j,s} \left[ \min_{c_1} \sum_{x_i \in R_1(j,s)} (y_i - c_1)^2 + \min_{c_2} \sum_{x_i \in R_2(j,s)} (y_i - c_2)^2\right]
$$

遍历变量$j$,对固定的切分变量$j$扫描切分点$s$,选择使得上式达到最小值的对$(j,s)$
(2)用选定的对$(j,s)$划分区域并决定相应的输出值:

$$
R_1(j,s)={x|x^{(j)}\leqslant s}, R_2(j,s)={x|x^{(j)} > s} \
\hat{c}m = \frac{1}{N_m} \sum{x_i \in R_m(j,s)} y_i, x \in R_m, m=1,2
$$

(3)继续对两个子区域调用步骤(1),(2),直至满足停止条件
(4)将输入空间划分为$M$个区域$R_1,R_2,\cdots,R_M$,生成决策树:

$$
f(x)=\sum_{m=1}^M \hat{c}_m I(x \in R_m)
$$

第2步:编写代码,实现算法,并用表5.2训练数据进行验证

import json

import numpy as np


# 节点类
class Node:
    def __init__(self, value, feature, left=None, right=None):
        self.value = value.tolist()
        self.feature = feature.tolist()
        self.left = left
        self.right = right

    def __repr__(self):
        return json.dumps(self, indent=3, default=lambda obj: obj.__dict__, ensure_ascii=False)
class MyLeastSquareRegTree:
    def __init__(self, train_X, y, epsilon):
        # 训练集特征值
        self.x = train_X
        # 类别
        self.y = y
        # 特征总数
        self.feature_count = train_X.shape[1]
        # 损失阈值
        self.epsilon = epsilon
        # 回归树
        self.tree = None

    def _fit(self, x, y, feature_count):
        # (1)选择最优切分点变量j与切分点s,得到选定的对(j,s),并解得c1,c2
        (j, s, minval, c1, c2) = self._divide(x, y, feature_count)
        # 初始化树
        tree = Node(feature=j, value=x[s, j], left=None, right=None)
        # 用选定的对(j,s)划分区域,并确定响应的输出值
        if minval < self.epsilon or len(y[np.where(x[:, j] <= x[s, j])]) <= 1:
            tree.left = c1
        else:
            # 对左子区域调用步骤(1)、(2)
            tree.left = self._fit(x[np.where(x[:, j] <= x[s, j])],
                                  y[np.where(x[:, j] <= x[s, j])],
                                  self.feature_count)
        if minval < self.epsilon or len(y[np.where(x[:, j] > s)]) <= 1:
            tree.right = c2
        else:
            # 对右子区域调用步骤(1)、(2)
            tree.right = self._fit(x[np.where(x[:, j] > x[s, j])],
                                   y[np.where(x[:, j] > x[s, j])],
                                   self.feature_count)
        return tree

    def fit(self):
        self.tree = self._fit(self.x, self.y, self.feature_count)
        return self

    @staticmethod
    def _divide(x, y, feature_count):
        # 初始化损失误差
        cost = np.zeros((feature_count, len(x)))
        # 公式5.21
        for i in range(feature_count):
            for k in range(len(x)):
                # k行i列的特征值
                value = x[k, i]
                y1 = y[np.where(x[:, i] <= value)]
                c1 = np.mean(y1)
                y2 = y[np.where(x[:, i] > value)]
                if len(y2) == 0:
                    c2 = 0
                else:
                    c2 = np.mean(y2)
                y1[:] = y1[:] - c1
                y2[:] = y2[:] - c2
                cost[i, k] = np.sum(y1 * y1) + np.sum(y2 * y2)
        # 选取最优损失误差点
        cost_index = np.where(cost == np.min(cost))
        # 所选取的特征
        j = cost_index[0][0]
        # 选取特征的切分点
        s = cost_index[1][0]
        # 求两个区域的均值c1,c2
        c1 = np.mean(y[np.where(x[:, j] <= x[s, j])])
        c2 = np.mean(y[np.where(x[:, j] > x[s, j])])
        return j, s, cost[cost_index], c1, c2

    def __repr__(self):
        return str(self.tree)
train_X = np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]).T
y = np.array([4.50, 4.75, 4.91, 5.34, 5.80, 7.05, 7.90, 8.23, 8.70, 9.00])

model_tree = MyLeastSquareRegTree(train_X, y, epsilon=0.2)
model_tree.fit()
model_tree
{
   "value": 5,
   "feature": 0,
   "left": {
      "value": 3,
      "feature": 0,
      "left": 4.72,
      "right": 5.57
   },
   "right": {
      "value": 7,
      "feature": 0,
      "left": {
         "value": 6,
         "feature": 0,
         "left": 7.05,
         "right": 7.9
      },
      "right": {
         "value": 8,
         "feature": 0,
         "left": 8.23,
         "right": 8.85
      }
   }
}

根据上面程序的输出,可得到用平方误差损失准则生成一个二叉回归树:$$f(x)=\begin{cases}
4.72 & x \le 3\
5.57 & 3 < x \le 5\
7.05 & 5 < x \le 6\
7.9 & 6 < x \le 7 \
8.23 & 7 < x \le 8\
8.85 & x > 8\
\end{cases}$$

习题5.3

  证明 CART 剪枝算法中,当$\alpha$确定时,存在唯一的最小子树$T_{\alpha}$使损失函数$C_{\alpha}(T)$最小。

解答:

解答思路:

  1. 列出CART剪枝算法;
  2. 列出树的剪枝算法;
  3. 采用反证法,假设存在两棵子树使得损失函数最小。

解答步骤:

第1步:CART剪枝算法(书中第87页)

输入:CART算法生成的决策树$T_0$;
输出:最优决策树$T_\alpha$。
(1)设$k=0,T=T_0$。
(2)设$\alpha=+\infty$。
(3)自下而上地对各内部结点$t$计算$C(T_t)$,$|T_t|$,以及

$$
g(t)=\cfrac{C(t)-C(T_t)}{|T_t|-1} \
\alpha = \min(\alpha,g(t))
$$

这里,$T_t$表示以$t$为根结点的子树,$C(T_t)$是对训练数据的预测误差,$|T_t|$是$T_t$的叶结点个数。
(4)对$g(t)=\alpha$的内部结点$t$进行剪枝,并对叶结点$t$以多数表决法决定其类,得到树$T$。
(5)设$k=k+1,\alpha_k=\alpha,T_k=T$。
(6)如果$T_k$不是由根节点和两个叶结点组成的树,则回到步骤(2),否则$T_k=T_n$。
(7)采用交叉验证法在子树序列从$T_0,T_1,\cdots,T_n$中选取最优子树$T_{\alpha}$。

第2步:树的剪枝算法(书中第79页)

输入:生成算法产生的整个树$T$,参数$\alpha$;
输出:修剪后的子树$T_\alpha$。
(1)计算每个结点的经验熵。
(2)递归地从树的叶结点向上回缩。
  设一组叶结点回缩到其父结点之前与之后的整体树分别为$T_B$与$T_A$,其对应的损失函数分别是$C_\alpha(T_B)$与$C_\alpha(T_A)$,如果

$$
C_\alpha(T_A) \leqslant C_\alpha(T_B)
$$

则进行剪枝,即将父结点变为新的叶结点。
(3)返回(2),直至不能继续为止,得到损失函数最小的子树$T_\alpha$。

第3步:采用反证法,假设存在两棵子树使得损失函数最小

  从第1、2步可得到以下结论:

  1. 内部节点是否剪枝只与以该节点为根节点的子树有关;
  2. 当$C_\alpha(t) < C_\alpha(T_t)$时,对以结点$t$为根节点的子树进行剪枝。

  如上图所示,整个树$T_1$有两个子树$T_2$和$T_3$,其剪枝位置分别为$t_2$和$t_3$,假设两棵子树都是$T_1$的最优子树,使得损失函数最小。则满足以下等式:
$$
C_{\alpha}(T_2)=C_{\alpha}(T_3)
$$
  根据子树$T_2$的剪枝位置是$t_2$,可得
$$
C_{\alpha}(t_2)<C_{\alpha}(T_2)
$$
  根据子树$T_3$的剪枝位置是$t_3$,可得
$$
C_{\alpha}(t_3)<C_{\alpha}(T_3)
$$
  由上面公式可得:
$$
C_{\alpha}(t_2)<C_{\alpha}(T_3) \
C_{\alpha}(t_3)<C_{\alpha}(T_2)
$$
  根据上述公式,可知$T_2$和$T_3$都可以进一步剪枝,剪枝之后的子树记作$T_4$:

  因此,如果存在两棵以上的最优子树,其中一棵树总能找到一个来自另一棵子树的剪枝点,使得整体损失进一步下降,所以只能存在唯一的最小子树使得损失函数最小,得证。

习题5.4

  证明 CART 剪枝算法中求出的子树序列${T_0,T_1,\cdots,T_n}$分别是区间$\alpha \in [\alpha_i,\alpha_{i+1})$的最优子树$T_{\alpha}$,这里$i=0,1,\cdots,n,0=\alpha_0 < \alpha_1 < \cdots, \alpha_n < +\infty$。

解答:

解答思路:

  1. 对题目重新进行表述;
  2. 证明当$\alpha=0$和$\alpha= + \infty$时的最优子树;
  3. 证明$T_1$为区间$[\alpha_1,\alpha_2)$的最优子树。

解答步骤:

第1步:对题目重新表述
  原结论可以表述为:将$\alpha$从$0$增大到$+\infty$(即$0=\alpha_0<\alpha_1<\cdots<\alpha_n < +\infty$),在每个区间$[\alpha_i,\alpha_{i+1})$中,其中$i=0,1,\ldots,n$,子树$T_i$是该区间里最优的。

第2步:证明当$\alpha=0$和$\alpha= + \infty$时的最优子树

根据书中第85页:

  当$\alpha$大的时候,最优子树$T_\alpha$偏小;当$\alpha$小的时候,最优子树$T_\alpha$偏大。极端情况,当$\alpha=0$时,整体树是最优的。当$\alpha \rightarrow +\infty$时,根结点组成的单结点树是最优的。

第3步:证明$T_1$是区间$[\alpha_1, \alpha_2)$的最优子树

  根据《Classification and Regression Trees》书中第10.2节的定理10.7:

THEOREM 10.7. Every tree T has a unique smallest optimally pruned subtree $T(\alpha)$. Let T be a nontrivial tree having root $t_1$ and primary branches $T_L$ and $T_R$. Then $$R_{\alpha}(T(\alpha)) = \min[R_{\alpha}(t_1), R_{\alpha}(T_L(\alpha))+R_{\alpha}(T_R(\alpha))]$$ If $R_{\alpha}(t_1) \leqslant R_{\alpha}(T_L(\alpha))+R_{\alpha}(T_R(\alpha))$, then $T(\alpha)={t_1}$; otherwise, $T(\alpha) = {t_1} \cup T_L(\alpha) \cup T_R(\alpha)$.

  根据该书的符号描述,其中:

  1. $T(\alpha)$表示,在$\alpha$条件时,树$T$的最小最优剪枝子树
  2. $R_{\alpha}(T(\alpha))$表示,在$\alpha$条件时,$T(\alpha)$的损失值,即$C_{\alpha}(T_{\alpha})$

  定理10.7表示,树$T$分为根结点$t_1$、左子树$T_L$和右子树$T_R$,计算在$\alpha$条件时的最优子树的损失,可取$R_{\alpha}(t_1)$和$R_{\alpha}(T_L(\alpha))+R_{\alpha}(T_R(\alpha))$中的最小值,并且$T(\alpha)$满足以下公式:
$$
T(\alpha) = \left{ \begin{array}{l}
{t_1 }, \quad R_{\alpha}(t_1) \leqslant R_{\alpha}(T_L(\alpha))+R_{\alpha}(T_R(\alpha)) \
{t_1} \cup T_L(\alpha) \cup T_R(\alpha), \quad \text{otherwise}
\end{array}\right.
$$

  根据定理10.7,可以得到该书定理10.9的第一个推论:

THEOREM 10.9.
(i) If $\alpha_2 \geqslant \alpha_1$, then $T(α2) \preceq T(α1)$.

  定理10.9(i)表示,当$\alpha_2 \geqslant \alpha_1$时,树$T$在$\alpha_2$条件下的最优子树一定是树$T$在$\alpha_1$条件下的最优子树的子树。

  根据CART剪枝算法和定理10.9,可知(见该书的第368页):

  1. 当$\alpha \geqslant \alpha_1$时,$T_0(\alpha)=T_1$
  2. 当$\alpha_1 \leqslant \alpha < \alpha_2$时,$T_0(\alpha) \preceq T_0(\alpha_1) = T_1 \preceq T_0 $

  所以,$T_0(\alpha)=T_1(\alpha)=T_1$,该式说明$T_1$是区间$[\alpha_1, \alpha_2)$的最优子树。

  依次类推,子树$T_i$是区间$[\alpha_i,\alpha_{i+1})$里最优的,原结论得证。

六、逻辑斯蒂回归与最大熵模型

逻辑斯谛回归(logistic regression)是统计学习中的经典分类方法。最大熵是概率模型学习的一个准则,将其推广到分类问题得到最大熵模型(maximum entropy model).逻辑斯谛回归模型与最大熵模型都属于对数线性模型。

6.1 逻辑斯谛回归模型

6.1.1 逻辑斯谛分布

定义6.1(逻辑斯谛分布)设X是连续随机变量,X服从逻辑斯谛分布是指X具有下列分布函数和密度函数:
$$
\begin{aligned}
&F(x)=P(X \leqslant x)=\frac{1}{1+\mathrm{e}^{-(x-\mu) / \gamma}} \
&f(x)=F^{\prime}(x)=\frac{\mathrm{e}^{-(x-\mu) / \gamma}}{\gamma\left(1+\mathrm{e}^{-(x-\mu) / \gamma}\right)^{2}}
\end{aligned}
$$
逻辑斯谛分布的密度函数与分布函数

6.1.2 二项逻辑斯谛回归模型

二项逻辑斯谛回归模型(binomial logistic regression model)是一种分类模型,由条件概率分布$P(Y|X)$表示,形式为参数化的逻辑斯谛分布。这里,随机变量X取值为实数,随机变量Y取值为1或0.我们通过监督学习的方法来估计模型参数.

定义6.2(逻辑斯谛回归模型)二项逻辑斯谛回归模型是如下的条件概率分布
$$
\begin{aligned}
&P(Y=1 \mid x)=\frac{\exp (w \cdot x+b)}{1+\exp (w \cdot x+b)} \
&P(Y=0 \mid x)=\frac{1}{1+\exp (w \cdot x+b)}
\end{aligned}
$$
现在考査逻辑斯谛回归模型的特点,一个事件的几率(ods)是指该事件发生的概率与该事件不发生的概率的比值。如果事件发生的概率是p,那么该事件的几率是$\frac{p}{1-p}$,该事件的对数几率(log odds)或logit函数是:
$$
\operatorname{logit}(p)=\log \frac{p}{1-p}
$$
采用简化的逻辑斯蒂回归:
$$
\log \frac{P(Y=1 \mid x)}{1-P(Y=1 \mid x)}=w \cdot x
$$
这就是说,在逻辑斯谛回归模型中,输出Y=1的对数几率是输入x的线性函数或者说,输出Y=1的对数几率是由输入x的线性函数表示的模型,即逻辑斯谛回归模型。

6.1.3 模型参数估计

6.1.4 多项逻辑斯谛回归

上面介绍的逻辑斯谛回归模型是二项分类模型,用于二类分类,可以将其推广为多项逻辑斯谛回归模型(muli- nominal logistic regression model),用于多类分类。假设离散型随机变量Y的取值集合是$ {1,2,…,K} $,那么多项逻辑斯谛回归模型是

6.2最大熵模型

最大熵模型(maximum entropy model)由最大熵原理推导实现,这里首先叙述一般的最大熵原理,然后讲解最大熵模型的推导,最后给出最大熵模型学习的形式。

6.2.1最大熵原理

最大熵原理是概率模型学习的一个准则。最大熵原理认为,学习概率模型时,在所有可能的概率模型(分布)中,熵最大的模型是最好的模型。通常用约束条件来确定概率模型的集合,所以,最大熵原理也可以表述为在满足约束条件的模型集合中选取熵最大的模型。
$$
H(P)=-\sum_{i=1}^{n} P(X) \log P(X)
$$
熵满足下列不等式:
$$
0 \leqslant H(P) \leqslant \log |X|
$$
式中,X是x的取值个数,当且仅当X的分布是均匀分布时右边的等号成立。这就是说,当X服从均匀分布时,熵最大.

直观地,最大熵原理认为要选择的概率模型首先必须满足已有的事实,即约束条件。在没有更多信息的情况下,那些不确定的部分都是“等可能的”。最大熵原理通过熵的最大化来表示等可能性。“等可能”不容易操作,而熵则是一个可优化的数值指标.

概率模型集合

6.2.2 最大熵模型的定义

最大熵原理是统计学习的一般原理,将它应用到分类得到最大熵模型。

定义6.3(最大熵模型)假设满足所有约東条件的模型集合为
$$
\mathcal{C} \equiv\left{P \in \mathcal{P} \mid E_{P}\left(f_{i}\right)=E_{\tilde{p}}\left(f_{i}\right), \quad i=1,2, \cdots, n\right}
$$
定义在条件概率分布$P(Y|X)$上的条件熵为:
$$
H(P)=-\sum_{x, y} \tilde{P}(x) P(y \mid x) \log P(y \mid x)
$$
则模型集合C中条件熵$H(P)$最大的模型称为最大熵模型。式中的对数为自然对数

6.2.3 最大熵模型的学习

最大熵模型的学习过程就是求解最大熵模型的过程。最大熵模型的学习可以形式化为约束最优化问题.

求解:

6.2.4 极大似然估计

可以将最大熵模型写成更一般的形式

最大模型与逻辑斯谛回归模型有类似的形式,它们又称为对数线性模型(log linear model).模型学习就是在给定的训练数据条件下对模型进行极大似然估计或正则化的极大似然估计。

6.3 模型学习的最优化算法

逻辑斯谛回归模型、最大熵模型学习归结为以似然函数为目标函数的最优化问题,通常通过迭代算法求解。从最优化的观点看,这时的目标函数具有很好的性质,它是光滑的凸函数,因此多种最优化的方法都适用,保证能找到全局最优解,常用的方法有改进的迭代尺度法、梯度下降法、牛顿法或拟牛顿法,牛顿法或拟牛顿法一般收敛速度更快。

6.3.1 改进的选代尺度法

改进的迭代尺度法(improved iterative scaling,IIS)是一种最大熵模型学习的最优化算法。

算法6.1(改进的迭代尺度算法IIS)

6.3.2 拟牛顿法

最大熵模型学习还可以应用牛顿法或拟牛顿法。

算法6.2 (最大熵模型学习的BFGS算法)

一些解释:

习题

习题6.1

  确认Logistic分布属于指数分布族。

解答:

解答思路:

  1. 列出Logistic分布的定义
  2. 列出指数分布族的定义
  3. 通过指数倾斜,证明Logistic分布的分布函数无法表示成指数分布族的分布函数形式

解答步骤:

第1步:Logistic分布的定义

  根据书中第91页的Logistic分布:

设$X$是连续随机变量,$X$服从Logistic分布是指$X$具有下列分布函数和密度函数:

$$
F(x) = P(X \leqslant x) = \frac{1}{1 + \text{e}^{-(x-\mu)/\gamma} } \
f(x) = F’(x) = \frac{\text{e}^{-(x-\mu)/\gamma} }{\gamma(1+ \text{e}^{-(x-\mu)/\gamma})^2}
$$

式中,$\mu$为位置参数,$\gamma > 0$为形状参数。

第2步:指数分布族的定义

参考指数分布族的Wikipedia:https://en.wikipedia.org/wiki/Exponential_family
对于随机变量$x$,在给定参数$\theta$下,其概率分别满足如下形式:

$$
f(x|\theta)=h(x)g(\theta)\exp(\eta(\theta)\cdot T(x))
$$

称之为指数分布族。
其中:$g(\theta)$表示归一化系数,$h(x)>0$

第3步:证明Logistic分布的分布函数无法表示成指数分布族的分布函数形式

  根据指数分布族的Wikipedia:https://en.wikipedia.org/wiki/Exponential_family

Other examples of distributions that are not exponential families are the F-distribution, Cauchy distribution, hypergeometric distribution and logistic distribution

  可知,Logistic分布不属于指数分布族

证明思路:
参考:https://stats.stackexchange.com/questions/275773/does-logistic-distribution-belongs-to-exponential-family

  1. 设$\gamma=1$,可得单参数的Logistic分布;
  2. 计算当$\mu=0$时,函数$f(x|\mu=0)$;
  3. 根据Logistic分布的MGF(矩生成函数),可得$E(\text{e}^{\theta x})$;
  4. 根据指数倾斜的定义,证明单参数$\theta$的指数倾斜密度函数无法表示成Logistic分布的密度函数形式;可证得,Logistic分布不属于指数分布族;

证明步骤:

  1. 单参数Logistic分布:
    设$\gamma=1$,则单参数$\mu$的Logistic分布:

$$
f(x|\mu) = \frac{\text{e}^{-(x-\mu)} }{(1+ \text{e}^{-(x-\mu)})^2}
$$

  1. 计算$f(x|\mu=0)$
    $$f(x|\mu=0) = \frac{\text{e}^{-x} }{(1+ \text{e}^{-x})^2}$$

  2. Logistic分布的MGF矩生成函数

根据Logistic分布的Wikipedia:https://en.wikipedia.org/wiki/Logistic_distribution

Logistic的MGF矩生成函数$M_X(\theta)$:
$$M_X(\theta) = \text{e}^{\mu t}B(1-st, 1+st)$$

其中$t \in (-1/s, 1/s)$,$B$表示Beta函数。

可知,当$\mu=0, s=1$时,
$$
M_X(\theta) = E(\text{e}^{\theta x}) = B(1-\theta, 1+\theta), \quad \theta \in (-1, 1)
$$

  1. 证明单参数$\theta$的指数倾斜密度函数无法表示成Logistic分布的形式

根据指数倾斜的Wikipedia:https://en.wikipedia.org/wiki/Exponential_tilting

  给定一个随机变量$X$,其概率分布为$P$,概率密度为$f$和矩生成函数(MGF)为$M_X(\theta) = E(e^{\theta x})$,指数倾斜$P_{\theta}$定义如下:

$$
P_{\theta}(X \in dx) = \frac{E[\text{e}^{\theta X} I(X \in dx)]}{ M_X(\theta)} = \text{e}^{\theta x - k(\theta)}P(X \in dx)
$$

  其中,$k(\theta)$表示为累积生成函数(CGF),即$\log E(\text{e}^{\theta X})$,称$P_{\theta}(X \in dx)=f_{\theta}(x)$为随机变量$X$的$\theta$-tilted密度分布。

  综上可知
$$
f_{\theta}(x)=\text{e}^{\theta x - k(\theta)} f_0(x)
$$
  其中,$k(\theta)= \log M_X(\theta) = B(1-\theta, 1+\theta), \quad \theta \in (-1, 1)$,根据指数倾斜性质,$f_{\theta}(x)$无法表示Logistic分布的密度函数形式。
  所以,Logistic分布不属于指数分布族。

习题6.2 ⭐⭐⭐

  写出Logistic回归模型学习的梯度下降算法。

解答:

解答思路:

  1. 写出Logistic回归模型
  2. 根据书中附录A梯度下降法,写出Logistic回归模型学习的梯度下降法
  3. 自编程实现Logistic回归模型学习的梯度下降法

解答步骤:

第1步:Logistic回归模型

  根据书中第92页Logistic回归模型的定义:

二项Logistic回归模型是如下的条件概率分别:

$$
P(Y=1 | x)=\frac{\exp (w \cdot x+b)}{1+\exp (w \cdot x+b)} \
P(Y=0 | x)=\frac{1}{1+\exp (w \cdot x+b)}
$$

这里,$x \in R^n$是输入,$Y \in {0, 1 }$是输出,$w \in R^n$和$b \in R^n$是参数,$w$称为权值向量,$b$称为偏置,$w \cdot x$为$w$和$x$的内积。

第2步:Logistic回归模型学习的梯度下降法

  根据书中第93页模型参数估计:

Logistic回归模型的对数似然函数为

$$
L(w)=\sum_{i=1}^N \left[y_i (w \cdot x_i)-\log (1+\exp (w \cdot x_i))\right]
$$

  将对数似然函数求偏导,可得
$$
\frac{\partial L(w)}{\partial w^{(k)} }=\sum_{i=1}^N\left[x_i \cdot y_i-\frac{\exp (w^{(k)} \cdot x_i) \cdot x_i}{1+\exp (w^{(k)} \cdot x_i)}\right]
$$
  梯度函数为
$$
\nabla L(w)=\left[\frac{\partial L(w)}{\partial w^{(0)} }, \cdots, \frac{\partial L(w)}{\partial w^{(n)} }\right]
$$
  根据书中第439页附录A 梯度下降法的算法:

输入:目标函数$f(x)$,梯度函数$g(x)= \nabla f(x)$,计算精度$\varepsilon$;
输出:$f(x)$的极小值$x^$。
(1) 取初始值$x^{(0)} \in R^n$,置$k=0$
(2) 计算$f(x^{(k)})$
(3) 计算梯度$g_k=g(x^{(k)})$,当$|g_k| < \varepsilon$时,停止迭代,令$x^
= x^{(k)}$;否则,令$p_k=-g(x^{(k)})$,求$\lambda_k$,使

$$
\displaystyle f(x^{(k)}+\lambda_k p_k) = \min \limits_{\lambda \geqslant 0}f(x^{(k)}+\lambda p_k)
$$

(4) 置$x^{(k+1)}=x^{(k)}+\lambda_k p_k$,计算$f(x^{(k+1)})$
当$|f(x^{(k+1)}) - f(x^{(k)})| < \varepsilon$或 $|x^{(k+1)} - x^{(k)}| < \varepsilon$时,停止迭代,令$x^* = x^{(k+1)}$
(5) 否则,置$k=k+1$,转(3)

Logistic回归模型学习的梯度下降算法:
输入:目标函数$f(w)$,梯度函数$g(w) = \nabla f(w)$,计算精度$\varepsilon$
输出:$f(w)$的极大值$w^*$

(1) 取初始值$ w^{(0)} \in R^n$,置$k=0$

(2) 计算$ \displaystyle f(w^{(k)})=\sum_{i=1}^N \left[y_i (w^{(k)} \cdot x_i)-\log (1+\exp (w^{(k)} \cdot x_i))\right]$

(3) 计算梯度$ \displaystyle g_k=g(w^{(k)})=\sum_{i=1}^N\left[x_i \cdot y_i-\frac{\exp (w^{(k)} \cdot x_i) \cdot x_i}{1+\exp (w^{(k)} \cdot x_i)}\right]$,当$|g_k| < \varepsilon$时,停止迭代,令$w^* = w^{(k)}$;否则,令$p_k=-g(w^{(k)})$,求$\lambda_k$,使
$$
\displaystyle f(w^{(k)}+\lambda_k p_k) = \max_{\lambda \geqslant 0}f(w^{(k)}+\lambda p_k)
$$
(4) 置$w^{(k+1)}=w^{(k)}+\lambda_k p_k$,计算$f(w^{(k+1)})$
当$ |f(w^{(k+1)}) - f(w^{(k)})| < \varepsilon$或 $ |w^{(k+1)} - w^{(k)}| < \varepsilon$时,停止迭代,令$w^* = w^{(k+1)}$

(5) 否则,置$k=k+1$,转(3)

第3步:自编程实现Logistic回归模型学习的梯度下降法

from scipy.optimize import fminbound
from pylab import mpl
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# 图像显示中文
mpl.rcParams['font.sans-serif'] = ['Microsoft YaHei']


class MyLogisticRegression:
    def __init__(self, max_iter=10000, distance=3, epsilon=1e-6):
        """
        Logistic回归
        :param max_iter: 最大迭代次数 
        :param distance: 一维搜索的长度范围
        :param epsilon: 迭代停止阈值
        """
        self.max_iter = max_iter
        self.epsilon = epsilon
        # 权重
        self.w = None
        self.distance = distance
        self._X = None
        self._y = None

    @staticmethod
    def preprocessing(X):
        """将原始X末尾加上一列,该列数值全部为1"""
        row = X.shape[0]
        y = np.ones(row).reshape(row, 1)
        return np.hstack((X, y))

    @staticmethod
    def sigmod(x):
        return 1 / (1 + np.exp(-x))

    def grad(self, w):
        z = np.dot(self._X, w.T)
        grad = self._X * (self._y - self.sigmod(z))
        grad = grad.sum(axis=0)
        return grad

    def like_func(self, w):
        z = np.dot(self._X, w.T)
        f = self._y * z - np.log(1 + np.exp(z))
        return np.sum(f)

    def fit(self, data_x, data_y):
        self._X = self.preprocessing(data_x)
        self._y = data_y.T
        # (1)取初始化w
        w = np.array([[0] * self._X.shape[1]], dtype=np.float)
        k = 0
        # (2)计算f(w)
        fw = self.like_func(w)
        for _ in range(self.max_iter):
            # 计算梯度g(w)
            grad = self.grad(w)
            # (3)当梯度g(w)的模长小于精度时,停止迭代
            if (np.linalg.norm(grad, axis=0, keepdims=True) < self.epsilon).all():
                self.w = w
                break

            # 梯度方向的一维函数
            def f(x):
                z = w - np.dot(x, grad)
                return -self.like_func(z)

            # (3)进行一维搜索,找到使得函数最大的lambda
            _lambda = fminbound(f, -self.distance, self.distance)

            # (4)设置w(k+1)
            w1 = w - np.dot(_lambda, grad)
            fw1 = self.like_func(w1)

            # (4)当f(w(k+1))-f(w(k))的二范数小于精度,或w(k+1)-w(k)的二范数小于精度
            if np.linalg.norm(fw1 - fw) < self.epsilon or \
                    (np.linalg.norm((w1 - w), axis=0, keepdims=True) < self.epsilon).all():
                self.w = w1
                break

            # (5) 设置k=k+1
            k += 1
            w, fw = w1, fw1

        self.grad_ = grad
        self.n_iter_ = k
        self.coef_ = self.w[0][:-1]
        self.intercept_ = self.w[0][-1]

    def predict(self, x):
        p = self.sigmod(np.dot(self.preprocessing(x), self.w.T))
        p[np.where(p > 0.5)] = 1
        p[np.where(p < 0.5)] = 0
        return p

    def score(self, X, y):
        y_c = self.predict(X)
        # 计算准确率
        error_rate = np.sum(np.abs(y_c - y.T)) / y_c.shape[0]
        return 1 - error_rate

    def draw(self, X, y):
        # 分隔正负实例点
        y = y[0]
        X_po = X[np.where(y == 1)]
        X_ne = X[np.where(y == 0)]
        # 绘制数据集散点图
        ax = plt.axes(projection='3d')
        x_1 = X_po[0, :]
        y_1 = X_po[1, :]
        z_1 = X_po[2, :]
        x_2 = X_ne[0, :]
        y_2 = X_ne[1, :]
        z_2 = X_ne[2, :]
        ax.scatter(x_1, y_1, z_1, c="r", label="正实例")
        ax.scatter(x_2, y_2, z_2, c="b", label="负实例")
        ax.legend(loc='best')
        # 绘制透明度为0.5的分隔超平面
        x = np.linspace(-3, 3, 3)
        y = np.linspace(-3, 3, 3)
        x_3, y_3 = np.meshgrid(x, y)
        a, b, c, d = self.w[0]
        z_3 = -(a * x_3 + b * y_3 + d) / c
        # 调节透明度
        ax.plot_surface(x_3, y_3, z_3, alpha=0.5)
        plt.show()
# 训练数据集
X_train = np.array([[3, 3, 3], [4, 3, 2], [2, 1, 2], [1, 1, 1], [-1, 0, 1], [2, -2, 1]])
y_train = np.array([[1, 1, 1, 0, 0, 0]])
# 构建实例,进行训练
clf = MyLogisticRegression(epsilon=1e-6)
clf.fit(X_train, y_train)
clf.draw(X_train, y_train)
print("迭代次数:{}次".format(clf.n_iter_))
print("梯度:{}".format(clf.grad_))
print("权重:{}".format(clf.w[0]))
print("模型准确率:%0.2f%%" % (clf.score(X_train, y_train) * 100))

迭代次数:2095次
梯度:[ 7.33881397e-05  2.44073067e-05  2.52604176e-04 -5.13424350e-04]
权重:[  4.34496173   2.05340452   9.64074166 -22.85079478]
模型准确率:100.00%

习题6.3 ⭐⭐⭐

  写出最大熵模型学习的DFP算法。(关于一般的DFP算法参见附录B)

解答:

解答思路:

  1. 写出最大熵模型
  2. 根据附录B的DFP算法,写出最大熵模型学习的DFP算法
  3. 自编程实现最大熵模型学习的DFP算法

解答步骤:

第1步:最大熵模型

  根据书中第98页的最大熵模型的定义:

假设满足所有约束条件的模型集合为

$$
\mathcal{C} = {P \in \mathcal{P} | E_P(f_i) = E_{\tilde{P} }(f_i), \quad i=1,2,\cdots,n }
$$

定义在条件概率分布$P(Y|X)$上的条件熵为

$$
H(P) = -\sum \limits_{x,y} \tilde{P}(x) P(y|x) \log P(y|x)
$$

则模型集合$\mathcal{C}$中条件熵$H(P)$最大的模型称为最大熵模型。式中的对数为自然对数。

  根据书中第97页最大熵模型的特征函数:

用特征函数$f(x,y)$描述输入$x$与输出$y$之间的某一个事实。其定义是

$$
f(x, y) = \left { \begin{array}{ll}
1, \quad x与y满足某一事实 \
0, \quad 否则
\end{array} \right.
$$

它是一个二值函数,当$x$和$y$满足这个事实时取值为1,否则取值为0。

第2步:最大熵模型学习的DFP算法

  根据书中第107页的最大熵模型学习:

对于最大熵模型而言,

$$
P_w(y | x)=\frac{\displaystyle \exp \left(\sum_{i=1}^n w_i f_i (x, y)\right)}{\displaystyle \sum_y \exp \left(\sum_{i=1}^n w_i f_i(x, y)\right)}
$$

目标函数:

$$
\min \limits_{w \in R^n} \quad f(w) = \sum_{x} \tilde{P}(x) \log \sum_{y} \exp \left(\sum_{i=1}^{n} w_{i} f_{i}(x, y)\right)-\sum_{x, y} \tilde{P}(x, y) \sum_{i=1}^{n} w_{i} f_{i}(x, y)
$$

梯度:

$$
g(w) = \left( \frac{\partial f(w)}{\partial w_1}, \frac{\partial f(w)}{\partial w_2}, \cdots, \frac{\partial f(w)}{\partial w_n}\right)^T
$$

其中

$$
\frac{\partial f(w)}{\partial w_1} = \sum \limits_{x,y} \tilde{P}(x) P_w(y|x) f_i(x,y) - E_{\tilde{P} }(f_i), \quad i=1,2,\cdots,n
$$

  根据书中第444页附录B的DFP算法:

输入:目标函数$f(x)$,梯度$g(x) = \nabla f(x)$,精度要求$\varepsilon$;
输出:$f(x)$的极小值点$x^$
(1)选定初始点$x^{(0)}$,取$G_0$为正定对称矩阵,置$k=0$
(2)计算$g_k=g(x^{(k)})$,若$|g_k| < \varepsilon$,则停止计算,得近似解$x^
=x^{(k)}$,否则转(3)
(3)置$p_k=-G_k g_k$
(4)一维搜索:求$\lambda_k$使得

$$
f (x^{(k)}+\lambda_k p_k )=\min \limits_{\lambda \geqslant 0} f(x^{(k)}+\lambda p_{k})
$$

(5)置$x^{(k+1)}=x^{(k)}+\lambda_k p_k$
(6)计算$g_{k+1}=g(x^{(k+1)})$,若$|g_{k+1}| < \varepsilon$,则停止计算,得近似解$x^*=x^{(k+1)}$;否则,按照式(B.24)算出$G_{k+1}$
(7)置$k=k+1$,转(3)

  根据公式(B.24),DFP算法的$G_{k+1}$的迭代公式为:
$$
G_{k+1}=G_k+\frac{\delta_k \delta_k^T}{\delta_k^T y_k}-\frac{G_k y_k y_k^T G_k}{y_k^T G_k y_k}
$$
  其中$y_k= g_{k+1} - g_k, \delta_k = w^{(k+1)} - w^{(k)}$。

最大熵模型的DFP算法:
输入:目标函数$f(w)$,梯度$g(w) = \nabla f(w)$,精度要求$\varepsilon$;
输出:$f(w)$的极小值点$w^$
(1)选定初始点$w^{(0)}$,取$G_0$为正定对称矩阵,置$k=0$
(2)计算$g_k=g(w^{(k)})$,若$|g_k| < \varepsilon$,则停止计算,得近似解$w^
=w^{(k)}$,否则转(3)
(3)置$p_k=-G_k g_k$
(4)一维搜索:求$\lambda_k$使得
$$
f(w^{(k)}+\lambda_k p_k) = \min \limits_{\lambda \geqslant 0} f(w^{(k)}+\lambda p_k)
$$
(5)置$w^{(k+1)}=w^{(k)}+\lambda_k p_k$
(6)计算$g_{k+1}=g(w^{(k+1)})$,若$|g_{k+1}| < \varepsilon$,则停止计算,得近似解$w^*=w^{(k+1)}$;否则,按照DFP算法的迭代公式算出$G_{k+1}$
(7)置$k=k+1$,转(3)

第3步:自编程实现最大熵模型学习的DFP算法

import copy
from collections import defaultdict

import numpy as np
from scipy.optimize import fminbound


class MaxEntDFP:
    def __init__(self, epsilon, max_iter=1000, distance=0.01):
        """
        最大熵的DFP算法
        :param epsilon: 迭代停止阈值
        :param max_iter: 最大迭代次数
        :param distance: 一维搜索的长度范围
        """
        self.distance = distance
        self.epsilon = epsilon
        self.max_iter = max_iter
        self.w = None
        self._dataset_X = None
        self._dataset_y = None
        # 标签集合,相当去去重后的y
        self._y = set()
        # key为(x,y), value为对应的索引号ID
        self._xyID = {}
        # key为对应的索引号ID, value为(x,y)
        self._IDxy = {}
        # 经验分布p(x,y)
        self._pxy_dic = defaultdict(int)
        # 样本数
        self._N = 0
        # 特征键值(x,y)的个数
        self._n = 0
        # 实际迭代次数
        self.n_iter_ = 0

    # 初始化参数
    def init_params(self, X, y):
        self._dataset_X = copy.deepcopy(X)
        self._dataset_y = copy.deepcopy(y)
        self._N = X.shape[0]

        for i in range(self._N):
            xi, yi = X[i], y[i]
            self._y.add(yi)
            for _x in xi:
                self._pxy_dic[(_x, yi)] += 1

        self._n = len(self._pxy_dic)
        # 初始化权重w
        self.w = np.zeros(self._n)

        for i, xy in enumerate(self._pxy_dic):
            self._pxy_dic[xy] /= self._N
            self._xyID[xy] = i
            self._IDxy[i] = xy

    def calc_zw(self, X, w):
        """书中第100页公式6.23,计算Zw(x)"""
        zw = 0.0
        for y in self._y:
            zw += self.calc_ewf(X, y, w)
        return zw

    def calc_ewf(self, X, y, w):
        """书中第100页公式6.22,计算分子"""
        sum_wf = self.calc_wf(X, y, w)
        return np.exp(sum_wf)

    def calc_wf(self, X, y, w):
        sum_wf = 0.0
        for x in X:
            if (x, y) in self._pxy_dic:
                sum_wf += w[self._xyID[(x, y)]]
        return sum_wf

    def calc_pw_yx(self, X, y, w):
        """计算Pw(y|x)"""
        return self.calc_ewf(X, y, w) / self.calc_zw(X, w)

    def calc_f(self, w):
        """计算f(w)"""
        fw = 0.0
        for i in range(self._n):
            x, y = self._IDxy[i]
            for dataset_X in self._dataset_X:
                if x not in dataset_X:
                    continue
                fw += np.log(self.calc_zw(x, w)) - \
                    self._pxy_dic[(x, y)] * self.calc_wf(dataset_X, y, w)

        return fw

    # DFP算法
    def fit(self, X, y):
        self.init_params(X, y)

        def calc_dfw(i, w):
            """计算书中第107页的拟牛顿法f(w)的偏导"""

            def calc_ewp(i, w):
                """计算偏导左边的公式"""
                ep = 0.0
                x, y = self._IDxy[i]
                for dataset_X in self._dataset_X:
                    if x not in dataset_X:
                        continue
                    ep += self.calc_pw_yx(dataset_X, y, w) / self._N
                return ep

            def calc_ep(i):
                """计算关于经验分布P(x,y)的期望值"""
                (x, y) = self._IDxy[i]
                return self._pxy_dic[(x, y)]

            return calc_ewp(i, w) - calc_ep(i)

        # 算出g(w),是n*1维矩阵
        def calc_gw(w):
            return np.array([[calc_dfw(i, w) for i in range(self._n)]]).T

        # (1)初始正定对称矩阵,单位矩阵
        Gk = np.array(np.eye(len(self.w), dtype=float))

        # (2)计算g(w0)
        w = self.w
        gk = calc_gw(w)
        # 判断gk的范数是否小于阈值
        if np.linalg.norm(gk, ord=2) < self.epsilon:
            self.w = w
            return

        k = 0
        for _ in range(self.max_iter):
            # (3)计算pk
            pk = -Gk.dot(gk)

            # 梯度方向的一维函数
            def _f(x):
                z = w + np.dot(x, pk).T[0]
                return self.calc_f(z)

            # (4)进行一维搜索,找到使得函数最小的lambda
            _lambda = fminbound(_f, -self.distance, self.distance)

            delta_k = _lambda * pk
            # (5)更新权重
            w += delta_k.T[0]

            # (6)计算gk+1
            gk1 = calc_gw(w)
            # 判断gk1的范数是否小于阈值
            if np.linalg.norm(gk1, ord=2) < self.epsilon:
                self.w = w
                break
            # 根据DFP算法的迭代公式(附录B.24公式)计算Gk
            yk = gk1 - gk
            Pk = delta_k.dot(delta_k.T) / (delta_k.T.dot(yk))
            Qk = Gk.dot(yk).dot(yk.T).dot(Gk) / (yk.T.dot(Gk).dot(yk)) * (-1)
            Gk = Gk + Pk + Qk
            gk = gk1

            # (7)置k=k+1
            k += 1

        self.w = w
        self.n_iter_ = k

    def predict(self, x):
        result = {}
        for y in self._y:
            prob = self.calc_pw_yx(x, y, self.w)
            result[y] = prob

        return result
# 训练数据集
dataset = np.array([['no', 'sunny', 'hot', 'high', 'FALSE'],
                    ['no', 'sunny', 'hot', 'high', 'TRUE'],
                    ['yes', 'overcast', 'hot', 'high', 'FALSE'],
                    ['yes', 'rainy', 'mild', 'high', 'FALSE'],
                    ['yes', 'rainy', 'cool', 'normal', 'FALSE'],
                    ['no', 'rainy', 'cool', 'normal', 'TRUE'],
                    ['yes', 'overcast', 'cool', 'normal', 'TRUE'],
                    ['no', 'sunny', 'mild', 'high', 'FALSE'],
                    ['yes', 'sunny', 'cool', 'normal', 'FALSE'],
                    ['yes', 'rainy', 'mild', 'normal', 'FALSE'],
                    ['yes', 'sunny', 'mild', 'normal', 'TRUE'],
                    ['yes', 'overcast', 'mild', 'high', 'TRUE'],
                    ['yes', 'overcast', 'hot', 'normal', 'FALSE'],
                    ['no', 'rainy', 'mild', 'high', 'TRUE']])

X_train = dataset[:, 1:]
y_train = dataset[:, 0]

mae = MaxEntDFP(epsilon=1e-4, max_iter=1000, distance=0.01)
# 训练模型
mae.fit(X_train, y_train)
print("模型训练迭代次数:{}次".format(mae.n_iter_))
print("模型权重:{}".format(mae.w))

result = mae.predict(['overcast', 'mild', 'high', 'FALSE'])
print("预测结果:", result)
模型训练迭代次数:878次
模型权重:[ 11.72083082  -0.7189626    9.69699232  -8.83693899   5.57382082
  19.50768089   0.7189626   -9.69699232   8.83693899  -4.5237319
   9.15646808  -6.6123125   12.96011049   4.5237319    6.6123125
 -12.96011049  -5.57382082  -9.15646808 -11.72083082]
预测结果: {'no': 2.097720173362797e-16, 'yes': 0.9999999999999998}

七、支持向量机

支持向量机(support vector machines,SVM)是一种二类分类模型。它的基本模型是定义在特征空间上的间隔最大的线性分类器,间隔最大使它有别于感知机;支持向量机还包括核技巧,这使它成为实质上的非线性分类器。支持向量机的学习策略就是间隔最大化,可形式化为一个求解凸二次规划( convex quadratic programming)的问题,也等价于正则化的合页损失函数的最小化问题,支持向量机的学习算法是求解凸二次规划的最优化算法。

7.1 线性可分支持向量机与硬间隔最大化

7.1.1 线性可分支持向量机

考虑一个二类分类问题。假设输入空间与特征空间为两个不同的空间。输入空间为欧氏空间或离散集合,特征空间为欧氏空间或希尔伯特空间。线性可分支持向量机、线性支持向量机假设这两个空间的元素一一对应,并将输入空间中的输入映射为特征空间中的特征向量。非线性支持向量机利用一个从输入空间到特征空间的非线性映射将输入映射为特征向量。所以,输入都由输入空间转换到特征空间,支持向量机的学习是在特征空间进行的.

定义7.1(线性可分支持向量机)给定线性可分训练数据集,通过间隔最大化或等价地求解相应的凸二次规划问题学习得到的分离超平面为:
$$
\begin{gathered}
w^{} \cdot x+b^{}=0
\end{gathered}
$$
以及相应的分类决策函数:
$$
f(x)=\operatorname{sign}\left(w^{} \cdot x+b^{}\right)
$$
称为线性可分支持向量机

二类分类问题

7.1.2 函数间隔和几何间隔

定义7.2(函数间隔)

几何间隔

定义7.3(几何间隔)

7.1.3 间隔最大化

支持向量机学习的基本想法是求解能够正确划分训练数据集并且几何间隔最大的分离超平面,对线性可分的训练数据集而言,线性可分分离超平面有无穷多个(等价于感知机),但是几何间隔最大的分离超平面是唯一的,这里的间隔最大化又称为硬间隔最大化(与将要讨论的训练数据集近似线性可分时的软间隔最大化相对应)

间隔最大化的直观解释是:对训练数据集找到几何间隔最大的超平面意味着以充分大的确信度对训练数据进行分类。也就是说,不仅将正负实例点分开,而且对最难分的实例点(离超平面最近的点)也有足够大的确信度将它们分开。这样的超平面应该对未知的新实例有很好的分类预测能力

  • 最大间隔分离超平面

算法7.1(线性可分支持向量机学习算法最大间隔法)

  • 最大间隔分离超平面的存在唯一性

线性可分训练数据集的最大间隔分离超平面是存在且唯一的.

定理7.1(最大间隔分离超平面的存在唯一性)若训练数据集T线性可分,则可将训练数据集中的样本点完全正确分开的最大间隔分离超平面存在且唯一

  • 支持向量和间隔边界

在线性可分情况下,训练数据集的样本点中与分离超平面距离最近的样本点
的实例称为支持向量(support vector)

支持向量

7.1.4 学习的对偶算法

定理7.2

算法7.2(线性可分支持向量机学习算法)

定义7.4(支持向量)

7.2 线性支持向量机与软间隔最大化

7.2.1 线性支持向量机

线性可分问题的支持向量机学习方法,对线性不可分训练数据是不适用的,因为这时上述方法中的不等式约束并不能都成立。怎么オ能将它扩展到线性不可分问题呢?这就需要修改硬间隔最大化,使其成为软间隔最大化。

线性不可分的线性支持向量机的学习问题变成如下凸二次规划(convex quadratic programming)问题(原始问题)

定义7.5(线性支持向量机)

7.2.2 学习的对偶算法

定理7.3

算法7.3(线性支持向量机学习算法)

7.2.3 支持向量

7.2.4 合页损失函数

定理7.4 线性支持向量机原始最优化问题

合页损失函数

7.3 非线性支持向量机与核函数

对解线性分类问题,线性分类支持向量机是一种非常有效的方法,但是,有时分类问题是非线性的,这时可以使用非线性支持向量机。

7.3.1 核技巧

  • 非线性分类问题

非线性分类问题与核技巧示例

非线性问题往往不好求解,所以希望能用解线性分类问题的方法解决这个问题。所采取的方法是进行一个非线性变换,将非线性问题变换为线性问题,通过解变换后的线性问题的方法求解原来的非线性问题。

  • 核函数的定义

定义7.6(核函数)

  • 核技巧在支持向量机中的应用

在对偶问题的目标函数中的内积可以用核函数来代替。此时对偶问题的目标函数成为:
$$
W(\alpha)=\frac{1}{2} \sum_{i=1}^{N} \sum_{j=1}^{N} \alpha_{i} \alpha_{j} y_{i} y_{j} K\left(x_{i}, x_{j}\right)-\sum_{i=1}^{N} \alpha_{i}
$$
分类决策函数中的内积也可以用核函数代替,而分类决策函数式成为:
$$
f(x)=\operatorname{sign}\left(\sum_{i=1}^{N_{s}} a_{i}^{} y_{i} \phi\left(x_{i}\right) \cdot \phi(x)+b^{}\right)=\operatorname{sign}\left(\sum_{i=1}^{N_{s}} a_{i}^{} y_{i} K\left(x_{i}, x\right)+b^{}\right)
$$

7.3.2 正定核

定理7.5(正定核的充要条件)

定义7.7(正定核的等价定义)

7.3.3 常用核函数

  • 多项式核函数(polynomial kernel function)

$$
K(x, z)=(x \cdot z+1)^{p}
$$
对应的支持向量机是一个 $p$ 次多项式分类器. 分类决策函数成为:
$$
f(x)=\operatorname{sign}\left(\sum_{i=1}^{N_{i}} a_{i}^{} y_{i}\left(x_{i} \cdot x+1\right)^{p}+b^{}\right)
$$

  • 高斯核函数(Gaussian kernel function)

$$
K(x, z)=\exp \left(-\frac{|x-z|^{2}}{2 \sigma^{2}}\right)
$$
对应的支持向量机是高斯径向基函数 (radial basis function) 分类器. 分类决策函数成为
$$
f(x)=\operatorname{sign}\left(\sum_{i=1}^{N_{t}} a_{i}^{} y_{i} \exp \left(-\frac{|x-z|^{2}}{2 \sigma^{2}}\right)+b^{}\right)
$$

  • 字符串核函数( string kernel function)

核函数不仅可以定义在欧氏空间上,还可以定义在离散数据的集合上。比如,字符串核是定义在字符串集合上的核函数。字符串核函数在文本分类、信息检索、生物信息学等方面都有应用。

两个字符串 $s$ 和 $t$ 上的字符串核函数是基于映射 $\phi_{n}$ 的特征空间中的内积:
$$
k_{n}(s, t)=\sum_{u \in \Sigma^{n}}\left[\phi_{n}(s)\right]{u}\left[\phi{n}(t)\right]{u}=\sum{u \in \sum^{n}(i, j)} \sum_{(i)=t(j)=u} \lambda^{(i)} \lambda^{l(j)}
$$
字符串核函数 $k_{n}(s, t)$ 给出了字符串 $s$ 和 $t$ 中长度等于 $n$ 的所有子串组成的特征向量的余弦相似度 (cosine similarity). 直观上, 两个字符串相同的子串越多, 它们就越相似, 字符串核函数的值就越大. 字符串核函数可以由动态规划快速地计算.

7.3.4 非线性支持向量分类机

定义7.8(非线性支持向量机)

算法7.4(非线性支持向量机学习算法)

7.4 序列最小最优化算法

SMO算法是一种启发式算法,其基本思路是:如果所有变量的解都满足此最优化问题的KKT条件(Karush-Kuhn- Tucker conditions),那么这个最优化问题的解就得到了。因为KKT条件是该最优化问题的充分必要条件。否则,选择两个变量,固定其他变量,针对这两个变量构建一个二次规划问题,这个二次规划问题关于这两个变量的解应该更接近原始二次规划问题的解,因为这会使得原始二次规划问题的目标函数值变得更小,重要的是,这时子问题可以通过解析方法求解,这样就可以大大提高整个算法的计算速度子问题有两个变量,一个是违反KKT条件最严重的那一个,另一个由约束条件自动确定。如此,SMO算法将原问题不断分解为子问题并对子问题求解,进而达到求解原问题的目的。

7.4.1 两个变量二次规划的求解方法

定理7.5

7.4.2 变量的选择方法

7.4.3 SMO算法

算法7.5 (SMO算法)

SMO算法是支持向量机学习的一种快速算法,其特点是不断地将原二次规划问题分解为只有两个变量的二次规划子问题,并对子问题进行解析求解,直到所有变量满足KKT条件为止。这样通过启发式的方法得到原二次规划问题的最优解。因为子间题有解析解,所以每次计算子问题都很快,虽然计算子问题次数很多,但在总体上还是高效的。

一些解释:

习题

习题7.1

  比较感知机的对偶形式与线性可分支持向量机的对偶形式。

解答:

解答思路:

  1. 列出感知机的原始形式;
  2. 写出感知机的对偶形式;
  3. 列出线性可分支持向量机的原始形式;
  4. 写出线性可分支持向量机的对偶形式;
  5. 比较感知机和线性可分支持向量机的对偶形式。

解答步骤:

第1步:感知机的原始形式

  根据书中第38页的感知机学习算法的原始形式:

给定一个训练数据集

$$
T={(x_1,y_1),(x_2,y_2),\cdots,(x_N,y_N)}
$$

其中,$x_i \in \mathcal{X} = R^n, y_i \in \mathcal{Y}={-1,1}, i=1,2,\cdots,N$,求参数$w,b$,使其为以下损失函数极小化问题的解

$$
\min \limits_{w,b} L(w,b)=-\sum_{x_i \in M} y_i(w \cdot x_i + b)
$$

其中$M$为误分类点的集合。

  根据书中第39页的算法2.1

输入:训练数据集$T={(x_1,y_1),(x_2,y_2),\cdots,(x_N,y_N)}$,其中$x_i \in \mathcal{X} = R^n, y_i \in \mathcal{Y}={-1,+1}, i=1,2,\cdots,N$;学习率$\eta$($0 < \eta \leqslant 1$);
输出:$w,b$;感知机模型$f(x)=\text{sign}(w \cdot x + b)$
(1)选取初值$w_0,b_0$;
(2)在训练集中选取数据$(x_i, y_i)$;
(3)如果$y_i(w \cdot x_i +b) \leqslant 0$,

$$
w \leftarrow w + \eta y_i x_i \
b \leftarrow b + \eta y_i
$$

(4)转至(2),直至训练集中没有误分类点。

第2步:感知机的对偶形式

  根据书中第44页的算法2.2

输入:线性可分的数据集$T={(x_1,y_1),(x_2,y_2),\cdots,(x_N,y_N)}$,其中$x_i \in R^n, y_i \in {-1, +1}, i=1,2,\cdots,N$;学习率$\eta$($0 < \eta \leqslant 1$);
输出:$a,b$;感知机模型$\displaystyle f(x)=\text{sign} \left( \sum_{j=1}^N \alpha_j y_j x_j \cdot x + b \right)$,其中$\alpha = (\alpha_1, \alpha_2,\cdots, \alpha_N)^T$
(1)$\alpha \leftarrow 0,b \leftarrow 0$;
(2)在训练集中选取数据$(x_i, y_i)$;
(3)如果$\displaystyle y_i\left( \sum_{j=1}^N \alpha_j y_j x_j \cdot x + b \right) \leqslant 0$,

$$
\alpha_i \leftarrow \alpha_i + \eta \
b \leftarrow b + \eta y_i
$$

(4)转至(2),直至训练集中没有误分类数据。

  根据书中第44页对偶形式的基本思想

从学习过程不难看出,最后学习到的$w,b$可以分别表示为

$$
w=\sum_{i=1}^N \alpha_i y_i x_i\
b=\sum_{i=1}^N \alpha_i y_i
$$

这里,$\alpha_i \geqslant 0, i=1,2,\cdots,N$

  综上所述:

  1. 感知机的原始形式中的损失函数:

$$
\min_{w,b} L(w,b)=-\sum_{x_i \in M} y_i(w \cdot x_i + b)
$$

  1. 感知机的对偶形式中的损失函数:可知$w,b$表示为$\langle x_i,y_i \rangle$的线性组合形式,则

$$
\min_{w,b} L(w,b) = \min_{\alpha} L(\alpha) = - \sum \limits_{x_i \in M} ( y_i ( \sum_{j=1}^N \alpha_j y_j x_j \cdot x_i + \sum_{j=1}^N \alpha_j y_j ) )
$$

其中,$\alpha = (\alpha_1, \alpha_2,\cdots, \alpha_N)^T$

第3步:线性可分支持向量机的原始形式

  根据书中第116页的线性可分支持向量机学习的最优化问题,可作为原始最优化问题(7.13)~(7.14):
$$
\begin{array}{cl}
\displaystyle \min_{w,b} & \displaystyle \frac{1}{2} |w|^2 \
\text{s.t.} & y_i(w \cdot x_i + b) -1 \geqslant 0, \quad i=1, 2,\cdots, N
\end{array}
$$

第4步:线性可分支持向量机的对偶形式

  根据书中第123页算法7.2:

输入:线性可分训练集$T={(x_1,y_1),(x_2,y_2), \cdots, (x_N,y_N)}$,其中$x_i \in \mathcal{X} = R^n$,$y_i \in \mathcal{Y} = {-1, +1}$,$i=1,2,\cdots, N$;
输出:分离超平面和分类决策函数。
(1)构造并求解约束最优化问题

$$
\begin{array}{cl}
\displaystyle \min_{\alpha} & \displaystyle \frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (x_i \cdot x_j) - \sum_{i=1}^N
\alpha_i \
\text{s.t.} & \displaystyle \sum_{i=1}^N \alpha_i y_i = 0 \
& \alpha_i \geqslant 0, \quad i=1,2,\cdots,N
\end{array}
$$

求得最优解$ \alpha^=(\alpha_1^, \alpha_2^, \cdots, \alpha_N^)^T$。
(2)计算

$$
w^* = \sum_{i=1}^N \alpha_i^* y_j x_i
$$

并选择$ \alpha^$的一个正分量$ \alpha_j^ > 0$,计算

$$
b^=y_i-\sum_{i=1}^N \alpha_i^ y_i (x_i \cdot x_j)
$$

(3)求得分离超平面

$$
w^* \cdot x + b^* = 0
$$

分类决策函数:
$$
f(x) = \text{sign}(w^* \cdot x + b^*)
$$
综上所述:

  1. 线性可分支持向量机的原始形式中的损失函数:

$$
\min_{w,b} L(w,b) = \frac{1}{2} |w|^2
$$

  1. 线性可分支持向量机的对偶形式中的损失函数:根据定理7.2,可知$w,b$表示为$\langle x_i,y_i \rangle$的线性组合形式,则

$$
\min_{w,b} L(w,b) = \min_{\alpha} L(\alpha) = \frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (x_i \cdot x_j) - \sum_{i=1}^N
\alpha_i
$$

其中,$\alpha = (\alpha_1, \alpha_2,\cdots, \alpha_N)^T$

第5步:感知机和线性可分支持向量机对偶形式的比较

  1. 在两者的对偶形式中,$w,b$都可以表示为$\langle x_i,y_i \rangle$的线性组合形式;
  2. 在两者的对偶形式中,都可以通过求解$\alpha=(\alpha_1, \alpha_2, \cdots, \alpha_N)^T$,最后代入由$x_i,y_i,\alpha_i$表示的$w$和$b$公式中,从而求解最优化问题的解$w^$和$b^$;
  3. 感知机学习得到一个分隔超平面,而线性可分支持向量机学习得到所有分隔超平面中的间隔最大分隔超平面。

习题7.2

  已知正例点$x_1=(1,2)^T,x_2=(2,3)^T,x_3=(3,3)^T$,负例点$x_4=(2,1)^T,x_5=(3,2)^T$,试求最大间隔分离平面和分类决策函数,并在图中画出分离超平面、间隔边界及支持向量。

解答:

解答思路:

  1. 通过调用sklearn.svm的SVC类构建模型,根据题目中的数据训练模型,得到$w$、$b$和支持向量;
  2. 调用matplotlib库,画出分离超平面、间隔边界和支持向量。

解答步骤:

第1步:训练模型,得到$w$、$b$和支持向量

%matplotlib inline
from sklearn.svm import SVC

# 加载数据
X = [[1, 2], [2, 3], [3, 3], [2, 1], [3, 2]]
y = [1, 1, 1, -1, -1]

# 训练SVM模型
clf = SVC(kernel='linear', C=10000)
clf.fit(X, y)

# 得到w、b和支持向量
print("w =", clf.coef_)
print("b =", clf.intercept_)
print("support vectors =", clf.support_vectors_)
w = [[-1.  2.]]
b = [-2.]
support vectors = [[3. 2.]
 [1. 2.]
 [3. 3.]]

可得:

  1. 最大间隔分离超平面:$-x^{(1)}+2x^{(2)}-2=0$
  2. 分类决策函数:$f(x)=\text{sign}(-x^{(1)}+2x^{(2)}-2)$
  3. 支持向量:$x_1=(3,2)^T,x_2=(1,2)^T, x_3=(3,3)^T$

第2步:在图中画出分离超平面、间隔边界和支持向量

import matplotlib.pyplot as plt
import numpy as np

# 绘制数据点
color_seq = ['red' if v==1 else 'blue' for v in y]
plt.scatter([i[0] for i in X], [i[1] for i in X], c=color_seq)

# 得到x轴的所有点
xaxis = np.linspace(0, 3.5)
w = clf.coef_[0]

# 计算斜率
a = -w[0] / w[1]

# 得到分离超平面
y_sep = a * xaxis - (clf.intercept_[0]) / w[1]

# 下边界超平面
b = clf.support_vectors_[0]
yy_down = a * xaxis + (b[1] - a * b[0])

# 上边界超平面
b = clf.support_vectors_[-1]
yy_up = a * xaxis + (b[1] - a * b[0])

# 绘制超平面
plt.plot(xaxis, y_sep, 'k-')
plt.plot(xaxis, yy_down, 'k--')
plt.plot(xaxis, yy_up, 'k--')

# 绘制支持向量
plt.xlabel('$x^{(1)}$')
plt.ylabel('$x^{(2)}$')
plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], 
            s=150, facecolors='none', edgecolors='k')

plt.show()

习题7.3

  线性支持向量机还可以定义为以下形式:
$$
\begin{array}{cl}
\displaystyle \min \limits_{w,b,\xi} & \displaystyle \frac{1}{2} |w|^2 + C \sum_{i=1}^N \xi_i^2 \
\text{s.t.} & y_i(w \cdot x_i + b) \geqslant 1 - \xi_i, \quad i=1,2,\cdots, N \
& \xi_i \geqslant 0, \quad i=1,2,\cdots, N
\end{array}
$$
试求其对偶形式。

解答:

解答思路:

参考书中第127页7.2.2节“学习的对偶算法”内容`

  1. 根据附录C 拉格朗日对偶性,写出拉格朗日函数;
  2. 对 $L(w,b,\xi,\alpha,\mu)$ 求 $w,b,\xi$ 的极小;
  3. 对 $\displaystyle \min \limits_{w,b,\xi} L(w,b,\xi,\alpha,\mu)$ 求 $\alpha$ 的极大;
  4. 整理得到对偶形式。

解答步骤:

第1步:原始问题的拉格朗日函数

  根据书中第447页附录C 拉格朗日对偶性:

假设$f(x),c_i(x),h_j(x)$是定义在$R^n$上的连续可微函数。考虑约束最优化问题

$$
\begin{array}{cl}
\displaystyle \min \limits_{x \in R^n} & f(x) \
\text{s.t.} & c_i(x) \leqslant 0, \quad i=1,2,\cdots, k \
& h_j(x) = 0, \quad j=1,2,\cdots, l
\end{array}
$$

称此约束最优化问题为原始最优化问题,或原始问题。

引入广义拉格朗日函数

$$
L(x,\alpha, \beta) = f(x) + \sum_{i=1}^k \alpha_i c_i(x) + \sum_{j=1}^l \beta_j h_j(x)
$$

这里,$x=(x^{(1)}, x^{(2)}, \cdots, x^{(n)})^T \in R^n$,$\alpha_i,\beta_j$是拉格朗日乘子,$\alpha_i \geqslant 0$。考虑$x$的函数:

$$
\theta_P(x) = \max \limits_{\alpha,\beta:\alpha_i \geqslant 0} L(x, \alpha, \beta)
$$

这里,下标$P$表示原始问题。

  根据书中第448页原始问题极小化:

如果考虑极小化问题

$$
\min \limits_{x} \theta_P(x) = \min \limits_{x} \max \limits_{\alpha,\beta:\alpha_i \geqslant 0} L(x, \alpha, \beta)
$$

上述问题称为广义拉格朗日函数的极小极大问题。

  根据题意,原始问题为
$$
\begin{array}{cl}
\displaystyle \min \limits_{w,b,\xi} & \displaystyle \frac{1}{2} |w|^2 + C \sum_{i=1}^N \xi_i^2 \
\text{s.t.} & y_i(w \cdot x_i + b) \geqslant 1 - \xi_i, \quad i=1,2,\cdots, N \
& \xi_i \geqslant 0, \quad i=1,2,\cdots, N
\end{array}
$$
  根据最优化函数的对应关系,可得
$$
\left { \begin{array}{ll}
\displaystyle f(x) = \frac{1}{2} |w|^2 + C \sum_{i=1}^N \xi_i^2 \
c_i^{(1)}(x) = 1 - \xi_i - y_i(w \cdot x_i + b), \quad i = 1,2,\cdots, N \
c_i^{(2)}(x) = - \xi_i, \quad i = 1,2,\cdots, N
\end{array} \right.
$$
  根据拉格朗日函数的定义,可得原始问题的拉格朗日函数为
$$
L(w,b,\xi, \alpha, \mu) = \frac{1}{2} |w|^2 + C \sum_{i=1}^N \xi_i^2 - \sum_{i=1}^N \alpha_i(y_i (w \cdot x_i + b)-1 + \xi_i) - \sum_{i=1}^N \mu_i \xi_i
$$
其中,$\alpha_i \geqslant 0, \mu_i \geqslant 0$

第2步:对$L(w,b,\xi,\alpha,\mu)$求$w,b,\xi$的极小

  根据拉格朗日对偶性,对偶问题是拉格朗日函数的极大极小问题,先对$L(w,b,\xi,\alpha,\mu)$求$w,b,\xi$的极小,分别对$w,b,\xi$求偏导,并令导数等于0,由
$$
\begin{array}{l}
\displaystyle \nabla_w L(w,b,\xi,\alpha,\mu) = w - \sum_{i=1}^N \alpha_i y_i x_i = 0 \
\displaystyle \nabla_b L(w,b,\xi,\alpha,\mu) = -\sum_{i=1}^N \alpha_i y_i = 0 \
\displaystyle \nabla_{\xi_i} L(w,b,\xi,\alpha,\mu) = 2C \xi_i - \alpha_i - \mu_i = 0
\end{array}
$$
  可得:
$$
\begin{array}{l}
\displaystyle w = \sum_{i=1}^N \alpha_i y_i x_i \
\displaystyle \sum_{i=1}^N \alpha_i y_i = 0 \
\displaystyle 2C \xi_i - \alpha_i - \mu_i = 0
\end{array}
$$
  将上式代入到原始问题的拉格朗日函数中,可得
$$
\begin{aligned}
L(w, b, \xi, \alpha, \mu)
&= \frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (x_i \cdot x_j) + C \sum_{i=1}^N \xi_i^2 - \sum_{i=1}^N \alpha_i y_i \left( \left( \sum_{j=1}^N \alpha_j y_j x_j \right) \cdot x_i + b \right) \
& + \sum_{i=1}^N \alpha_i - \sum_{i=1}^N \alpha_i \xi_i - \sum_{i=1}^N \mu_i \xi_i \
&= -\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (x_i \cdot x_j) + \sum_{i=1}^N \alpha_i + C \sum_{i=1}^N \xi_i^2 - \sum_{i=1}^N \alpha_i \xi_i - \sum_{i=1}^N \mu_i \xi_i \
&= -\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (x_i \cdot x_j) + \sum_{i=1}^N \alpha_i + C \sum_{i=1}^N \xi_i^2 - \sum_{i=1}^N (\alpha_i + \mu_i) \xi_i \
&= -\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (x_i \cdot x_j) + \sum_{i=1}^N \alpha_i + C \sum_{i=1}^N \xi_i^2 - \sum_{i=1}^N \left( 2C \xi_i \right) \xi_i \
&= -\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (x_i \cdot x_j) + \sum_{i=1}^N \alpha_i + C \sum_{i=1}^N \xi_i^2 - 2C \sum_{i=1}^N \xi_i^2 \
&= -\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (x_i \cdot x_j) + \sum_{i=1}^N \alpha_i - C \sum_{i=1}^N \xi_i^2 \
&= -\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (x_i \cdot x_j) + \sum_{i=1}^N \alpha_i - C \sum_{i=1}^N \left(\frac{1}{4 C^2}(\alpha_i + \mu_i)^2 \right) \
&= -\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (x_i \cdot x_j) + \sum_{i=1}^N \alpha_i - \frac{1}{4 C}\sum_{i=1}^N (\alpha_i + \mu_i)^2
\end{aligned}
$$
第3步:对$\displaystyle \min \limits_{w,b,\xi} L(w,b,\xi,\alpha,\mu)$求$\alpha$的极大

  根据第2步,对$\displaystyle \min \limits_{w,b,\xi} L(w,b,\xi,\alpha,\mu)$求$\alpha$的极大,可得到对偶问题:
$$
\begin{array}{cl}
\displaystyle \max \limits_{\alpha} & \displaystyle -\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (x_i \cdot x_j) + \sum_{i=1}^N \alpha_i - \frac{1}{4 C}\sum_{i=1}^N (\alpha_i + \mu_i)^2 \
\text{s.t.} & \displaystyle \sum_{i=1}^N \alpha_i y_i = 0 \
& \displaystyle 2C \xi_i - \alpha_i - \mu_i = 0 \
& \alpha_i \geqslant 0 ,\mu_i \geqslant 0, \xi_i \geqslant 0, \quad i=1,2,\cdots, N
\end{array}
$$
第4步:进行公式变换,得到对偶形式

  再将对目标函数求极大转换为求极小,于是得到原始问题的对偶形式
$$
\begin{array}{cl}
\displaystyle \min \limits_{\alpha} & \displaystyle \frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (x_i \cdot x_j) - \sum_{i=1}^N \alpha_i + \frac{1}{4 C}\sum_{i=1}^N (\alpha_i + \mu_i)^2 \
\text{s.t.} & \displaystyle \sum_{i=1}^N \alpha_i y_i = 0 \
& \displaystyle 2C \xi_i - \alpha_i - \mu_i = 0 \
& \alpha_i \geqslant 0 ,\mu_i \geqslant 0, \xi_i \geqslant 0, \quad i=1,2,\cdots, N
\end{array}
$$

习题7.4

  证明内积的正整数幂函数:$$K(x,z)=(x \bullet z)^p$$是正定核函数,这里$p$是正整数,$x,z\in R^n$。

解答:

解答思路:

  1. 写出正定核函数的判定依据
  2. 使用数学归纳法,证明
    1. 当$p=1$时,根据定理7.5,证明$K(x, z)=x \bullet z$是正定核函数
    2. 假设当$p=k$且$k>1$时,$K(x, z)=(x \bullet z)^k$是正定核函数
    3. 证明当$p=k+1$时,$K(x, z)=(x \bullet z)^{k+1}$是正定核函数

解答步骤:

第1步:列出正定核函数的判定依据

根据书中第139页定理7.5(正定核的充要条件):

设$K: \mathcal{X} \times \mathcal{X} \rightarrow R$是对称函数,则$K(x,z)$为正定核函数的充要条件是对任意$x_i \in \mathcal{X}, i=1,2,\cdots, m$,$K(x, z)$对应的Gram矩阵:

$$
K = [K(x_i, x_j)]_{m \times m}
$$

是半正定矩阵。

第2步:使用数学归纳法,证明$K(x, z)=(x \bullet z)^p$是正定核函数

  1. 当$p=1$时,$K(x, z)=x \bullet z$,对任意$c_1,c_2,\cdots,c_n \in \mathbf{R}$,有

$$
\begin{aligned}
\sum_{i,j=1}^n c_i c_j K(x_i,x_j)
&= \sum_{i,j=1}^n c_i c_j (x_i \bullet x_j) \
&= \left(\sum_{i=1}^m c_i x_i \right) \bullet \left(\sum_{j=1}^m c_j x_j \right) \
&= \Bigg|\left( \sum_{i=1}^m c_i x_i \right)\Bigg|^2 \geqslant 0
\end{aligned}
$$

可得,当$p=1$时,$K(x, z)=x \bullet z$对应的Gram矩阵是半正定的,根据定理7.5,可知$K(x,z)=x \bullet z$是正定核函数。

  1. 假设$p=k$且$k$是大于1的正整数时,$K(x, z)=(x \bullet z)^k$是正定核函数

根据书中第134页定义7.6的(核函数):

设$\mathcal{X}$是输入空间(欧式空间$R^n$的子集或离散集合),又设$\mathcal{H}$为特征空间(希尔伯特空间),如果存在一个从$\mathcal{X}$到$\mathcal{H}$的映射

$$
\phi(x):\mathcal{X} \rightarrow \mathcal{H}
$$

使得对所有$x,z \in \mathcal{X}$,函数$K(x,z)$满足条件

$$
K(x,z) = \phi(x) \bullet \phi(z)
$$

则称$K(x,z)$为核函数,$\phi(x)$为映射函数,式中$\phi(x) \bullet \phi(z)$为$\phi(x)$和$\phi(z)$的内积。

故存在一个输入空间为$R^n$,$R^n$到$\mathcal{H}$的映射
$$
\phi(x):R^n \rightarrow \mathcal{H}
$$
使得对所有$x,z \in R^n$,函数$K(x,z)=(x \bullet z)^k$满足条件
$$
K(x,z) = \phi(x) \bullet \phi(z)
$$
可假设$\phi(x)=(f_1(x), f_2(x), \cdots, f_m(x))^T$,其中$x=(x^{(1)}, x^{(2)}, \cdots, x^{(n)})^T$

  1. 当$p=k+1$时

$$
\begin{aligned}
K(x,z)
&= (x \bullet z)^{k+1} \
&= (x \bullet z)^k (x \bullet z) \
&= (\phi(x) \bullet \phi(z))(x \bullet z) \
&= (f_1(x)f_1(z) + f_2(x)f_2(z) + \cdots + f_m(x)f_m(z))(x^{(1)}z^{(1)} + x^{(2)}z^{(2)} + \cdots + x^{(n)}z^{(n)}) \
&= f_1(x)f_1(z)(x^{(1)}z^{(1)} + x^{(2)}z^{(2)} + \cdots + x^{(n)}z^{(n)}) \
& \quad + f_2(x)f_2(z)(x^{(1)}z^{(1)} + x^{(2)}z^{(2)} + \cdots + x^{(n)}z^{(n)}) + \cdots \
& \quad + f_m(x)f_m(z)(x^{(1)}z^{(1)} + x^{(2)}z^{(2)} + \cdots + x^{(n)}z^{(n)}) \
&= (f_1(x)x^{(1)})(f_1(z)z^{(1)}) + (f_1(x)x^{(2)})(f_1(z)z^{(2)}) + \cdots \
& \quad + (f_1(x)x^{(n)})(f_1(z)z^{(n)}) \
& \quad + (f_2(x)x^{(1)})(f_2(z)z^{(1)}) + (f_2(x)x^{(2)})(f_2(z)z^{(2)}) + \cdots \
& \quad + (f_2(x)x^{(n)})(f_2(z)z^{(n)}) + \cdots \
& \quad + (f_m(x)x^{(1)})(f_m(z)z^{(1)}) + (f_m(x)x^{(2)})(f_m(z)z^{(2)}) + \cdots \
& \quad + (f_m(x)x^{(n)})(f_m(z)z^{(n)})
\end{aligned}
$$

  可得
$$
\begin{aligned}
\phi’(x) &= (f_1(x)x^{(1)}, f_1(x)x^{(2)}, \cdots, f_1(x)x^{(n)}, \
& \quad f_2(x)x^{(1)}, f_2(x)x^{(2)}, \cdots, f_2(x)x^{(n)}, \
& \quad f_m(x)x^{(1)}, \cdots, f_m(x)x^{(n)})^T
\end{aligned}
$$
  故存在从$R^n$到希尔伯特空间$\mathcal{H}$的映射$\phi’(x)$,使得
$$
K(x,z) = (x \bullet z)^{k+1} = \phi’(x) \bullet \phi’(z)
$$

  根据《矩阵分析》书中定理7.5.3:

如果$A,B \in M_n$是半正定矩阵,则$A \bullet B$也是半正定矩阵,此外如果$A$和$B$都是正定矩阵,则$A \bullet B$也是正定矩阵。
其中,$A \bullet B$称为$A$和$B$的Hadamard乘积。

  由根据书中定理7.5,可得$K(x,z)=(x \bullet z)^{k+1}$是正定核函数。

  根据数学归纳法可得:
  当$p$是正整数,$x,z\in R^n$时,内积的正整数幂函数:$$K(x,z)=(x \bullet z)^p$$是正定核函数。

八、提升方法

提升( boosting)方法是一种常用的统计学习方法,应用广泛且有效。在分类问题中,它通过改变训练样本的权重,学习多个分类器,并将这些分类器进行线性组合,提高分类的性能。

8.1 提升方法 Adaboost算法

8.1.1 提升方法的基本思路

提升方法基于这样一种思想:对于一个复杂任务来说,将多个专家的判断进行适当的综合所得出的判断,要比其中任何一个专家单独的判断好。

8.1.2 Adaboost算法

算法8.1 (Adaboost)

8.2 Adaboost算法的训练误差分析

Adaboost最基本的性质是它能在学习过程中不断减少训练误差,即在训练数据集上的分类误差率。

定理8.1( Adaboost的训练误差界)

定理8.2 (二类分类问题 Adaboost的训练误差界)

推论8.1

8.3 Adaboost算法的解释

8.3.1 前向分步算法

算法8.2(前向分步算法)

8.3.2 前向分步算法与 Adaboost

定理8.3 Adaboost算法是前向分步加法算法的特例。这时,模型是由基本分类器组成的加法模型,损失函数是指数函数.

8.4 提升树

提升树是以分类树或回归树为基本分类器的提升方法。提升树被认为是统计学习中性能最好的方法之一。

8.4.1 提升树模型

提升方法实际采用加法模型(即基函数的线性组合)与前向分步算法。以决策树为基函数的提升方法称为提升树(boosting tree).对分类问题决策树是二叉分类树,对回归问题决策树是二叉回归树。提升树模型可以表示为决策树的加法模型:
$$
f_{M}(x)=\sum_{m=1}^{M} T\left(x ; \Theta_{m}\right)
$$

8.4.2 提升树算法

算法8.3(回归问题的提升树算法)

8.4.3 梯度提升

算法8.4(梯度提升算法)

一些解释

习题

习题8.1 ⭐⭐⭐

  某公司招聘职员考查身体、业务能力、发展潜力这3项。身体分为合格1、不合格0两级,业务能力和发展潜力分为上1、中2、下3三级。分类为合格1 、不合格-1两类。已知10个人的数据,如下表所示。假设弱分类器为决策树桩。试用AdaBoost算法学习一个强分类器。

应聘人员情况数据表

   1 2 3 4 5 6 7 8 9 10
身体 0 0 1 1 1 0 1 1 1 0
业务 1 3 2 1 2 1 1 1 3 2
潜力 3 1 2 3 3 2 2 1 1 1
分类 -1 -1 -1 -1 -1 -1 1 1 -1 -1

解答:

解答思路:

  1. 列出AdaBoost算法;
  2. 采用sklearn的AdaBoostClassifier分类器,构建并训练得到强分类器;
  3. 自编程实现AdaBoost算法,并训练得到强分类器。

解答步骤:

第1步:提升方法AdaBoost算法

  根据书中第156页算法8.1:

输入:训练数据集$T={(x_1,y_1), (x_2,y_2), \cdots, (x_N,y_N)}$,其中$x_i \in \mathcal{X} \subseteq R^n$,$y_i \in \mathcal{Y} = {-1, +1}$;弱学习算法;
输出:最终分类器$G(x)$。
(1)初始化训练数据的权值分布

$$
D_1 = (w_{11}, \cdots, w_{1i}, \cdots, w_{1N}), \quad w_{1i} = \frac{1}{N}, \quad i = 1, 2, \cdots, N
$$

(2)对$m=1, 2, \cdots, M$
  (a)使用具有权值分布$D_m$的训练数据集学习,得到基本分类器

$$
G_m(x):\mathcal{X} \rightarrow {-1, +1}
$$

  (b)计算$G_m(x)$在训练数据集上的分类误差率

$$
e_m = \sum_{i=1}^N P(G_m(x_i) \neq y_i) = \sum_{i=1}^N w_{mi}I(G_m(x_i) \neq y_i)
$$

  (c)计算$G_m(x)$的系数

$$
\alpha_m = \frac{1}{2} \log \frac{1 - e_m}{e_m}
$$

  这里的对数是自然对数。
  (d)更新训练数据集的权值分布

$$
D_{m+1} = (w_{m+1,1}, \cdots, w_{m+1, i}, \cdots, w_{m+1,N}) \
w_{m+1,i} = \frac{w_{mi} }{Z_m} \exp(-\alpha_m y_i G_m(x_i)), \quad i = 1, 2, \cdots, N
$$

  这里,$Z_m$是规范化因子

$$
Z_m = \sum_{i=1}^N w_{mi} \exp(-\alpha_m y_i G_m(x_i))
$$

  它使$D_{m+1}$成为一个概率分布。
(3)构建基本分类器的线性组合

$$
f(x) = \sum_{m=1}^M \alpha_m G_m(x)
$$

得到最终分类器

$$
\begin{aligned}
G(x) &= \text{sign}(f(x)) \
&= \text{sign}\left(\sum_{m=1}^M \alpha_m G_m(x) \right)
\end{aligned}
$$

第2步:采用AdaBoostClassifier分类器实现

  根据题目要求弱分类器采用决策树,通过sklearn的AdaBoostClassifier类,构建分类器,由于AdaBoostClassifier分类器默认采用CART决策树弱分类器,故不需要设置base_estimator参数。

from sklearn.ensemble import AdaBoostClassifier
import numpy as np

# 加载训练数据
X = np.array([[0, 1, 3],
              [0, 3, 1],
              [1, 2, 2],
              [1, 1, 3],
              [1, 2, 3],
              [0, 1, 2],
              [1, 1, 2],
              [1, 1, 1],
              [1, 3, 1],
              [0, 2, 1]
              ])
y = np.array([-1, -1, -1, -1, -1, -1, 1, 1, -1, -1])

# 使用sklearn的AdaBoostClassifier分类器
clf = AdaBoostClassifier()
# 进行分类器训练
clf.fit(X, y)
# 对数据进行预测
y_predict = clf.predict(X)
# 得到分类器的预测准确率
score = clf.score(X, y)
print("原始输出:", y)
print("预测输出:", y_predict)
print("预测准确率:{:.2% }".format(score))
原始输出: [-1 -1 -1 -1 -1 -1  1  1 -1 -1]
预测输出: [-1 -1 -1 -1 -1 -1  1  1 -1 -1]
预测准确率:100.00%

第3步:自编程实现AdaBoost算法

代码思路:

  1. 写出fit函数,即分类器训练函数;
  2. 根据书中第158页例8.1,编写build_stump函数,用于得到分类误差最低的基本分类器;
  3. 根据算法第2步(a)~(c),编写代码;
  4. 根据算法第2步(d),编写updata_w函数,用于更新训练数据集的权值分布;
  5. 编写predict函数,用于预测数据;
  6. 【附加】编写score函数,用于计算分类器的预测准确率;
  7. 【附加】编写print_G函数,用于打印最终分类器。
import numpy as np


class MyAdaBoost:
    def __init__(self, tol=0.05, max_iter=10):
        # 特征
        self.X = None
        # 标签
        self.y = None
        # 分类误差小于精度时,分类器训练中止
        self.tol = tol
        # 最大迭代次数
        self.max_iter = max_iter
        # 权值分布
        self.w = None
        # 弱分类器集合
        self.G = []

    def build_stump(self):
        """
        以带权重的分类误差最小为目标,选择最佳分类阈值,得到最佳的决策树桩
        best_stump['dim'] 合适特征的所在维度
        best_stump['thresh']  合适特征的阈值
        best_stump['ineq']  树桩分类的标识lt,rt
        """
        m, n = np.shape(self.X)
        # 分类误差
        min_error = np.inf
        # 小于分类阈值的样本所属的标签类别
        sign = None
        # 最优决策树桩
        best_stump = {}
        for i in range(n):
            # 求每一种特征的最小值和最大值
            range_min = self.X[:, i].min()
            range_max = self.X[:, i].max()
            step_size = (range_max - range_min) / n
            for j in range(-1, int(n) + 1):
                # 根据n的值,构造切分点
                thresh_val = range_min + j * step_size
                # 计算左子树和右子树的误差
                for inequal in ['lt', 'rt']:
                    # (a)得到基本分类器
                    predict_values = self.base_estimator(self.X, i, thresh_val, inequal)
                    # (b)计算在训练集上的分类误差率
                    err_arr = np.array(np.ones(m))
                    err_arr[predict_values.T == self.y.T] = 0
                    weighted_error = np.dot(self.w, err_arr)
                    if weighted_error < min_error:
                        min_error = weighted_error
                        sign = predict_values
                        best_stump['dim'] = i
                        best_stump['thresh'] = thresh_val
                        best_stump['ineq'] = inequal
        return best_stump, sign, min_error

    def updata_w(self, alpha, predict):
        """
        更新样本权重w
        :param alpha: alpha
        :param predict: yi
        :return:
        """
        # (d)根据迭代公式,更新权值分布
        P = self.w * np.exp(-alpha * self.y * predict)
        self.w = P / P.sum()

    @staticmethod
    def base_estimator(X, dimen, thresh_val, thresh_ineq):
        """
        计算单个弱分类器(决策树桩)预测输出
        :param X: 特征
        :param dimen: 特征的位置(即第几个特征)
        :param thresh_val: 切分点
        :param thresh_ineq: 标记结点的位置,可取左子树(lt),右子树(rt)
        :return: 返回预测结果矩阵
        """
        # 预测结果矩阵
        ret_array = np.ones(np.shape(X)[0])
        # 左叶子 ,整个矩阵的样本进行比较赋值
        if thresh_ineq == 'lt':
            ret_array[X[:, dimen] >= thresh_val] = -1.0
        else:
            ret_array[X[:, dimen] < thresh_val] = -1.0
        return ret_array

    def fit(self, X, y):
        """
        对分类器进行训练
        """
        self.X = X
        self.y = y
        # (1)初始化训练数据的权值分布
        self.w = np.full((X.shape[0]), 1 / X.shape[0])
        G = 0
        # (2)对m=1,2,...,M进行遍历
        for i in range(self.max_iter):
            # (b)得到Gm(x)的分类误差error,获取当前迭代最佳分类阈值sign
            best_stump, sign, error = self.build_stump()
            # (c)计算弱分类器Gm(x)的系数
            alpha = 1 / 2 * np.log((1 - error) / error)
            # 弱分类器Gm(x)权重
            best_stump['alpha'] = alpha
            # 保存弱分类器Gm(x),得到分类器集合G
            self.G.append(best_stump)
            # 计算当前总分类器(之前所有弱分类器加权和)误差率
            G += alpha * sign
            y_predict = np.sign(G)
            error_rate = np.sum(np.abs(y_predict - self.y)) / 2 / self.y.shape[0]
            if error_rate < self.tol:
                # 满足中止条件,则跳出循环
                print("迭代次数:{}次".format(i + 1))
                break
            else:
                # (d)更新训练数据集的权值分布
                self.updata_w(alpha, y_predict)

    def predict(self, X):
        """对新数据进行预测"""
        m = np.shape(X)[0]
        G = np.zeros(m)
        for i in range(len(self.G)):
            stump = self.G[i]
            # 遍历每一个弱分类器,进行加权
            _G = self.base_estimator(X, stump['dim'], stump['thresh'], stump['ineq'])
            alpha = stump['alpha']
            # (3)构建基本分类器的线性组合
            G += alpha * _G
        # 计算最终分类器的预测结果
        y_predict = np.sign(G)
        return y_predict.astype(int)

    def score(self, X, y):
        """计算分类器的预测准确率"""
        y_predict = self.predict(X)
        error_rate = np.sum(np.abs(y_predict - y)) / 2 / y.shape[0]
        return 1 - error_rate

    def print_G(self):
        i = 1
        s = "G(x) = sign[f(x)] = sign["
        for stump in self.G:
            if i != 1:
                s += " + "
            s += "{}·G{}(x)".format(round(stump['alpha'], 4), i)
            i += 1
        s += "]"
        return s
# 加载训练数据
X = np.array([[0, 1, 3],
              [0, 3, 1],
              [1, 2, 2],
              [1, 1, 3],
              [1, 2, 3],
              [0, 1, 2],
              [1, 1, 2],
              [1, 1, 1],
              [1, 3, 1],
              [0, 2, 1]
              ])
y = np.array([-1, -1, -1, -1, -1, -1, 1, 1, -1, -1])

clf = MyAdaBoost()
clf.fit(X, y)
y_predict = clf.predict(X)
score = clf.score(X, y)
print("原始输出:", y)
print("预测输出:", y_predict)
print("预测正确率:{:.2%}".format(score))
print("最终分类器G(x)为:", clf.print_G())
迭代次数:8次
原始输出: [-1 -1 -1 -1 -1 -1  1  1 -1 -1]
预测输出: [-1 -1 -1 -1 -1 -1  1  1 -1 -1]
预测正确率:100.00%
最终分类器G(x)为: G(x) = sign[f(x)] = sign[0.6931·G1(x) + 0.7332·G2(x) + 0.4993·G3(x) + 0.6236·G4(x) + 0.7214·G5(x) + 0.5575·G6(x) + 0.6021·G7(x) + 0.8397·G8(x)]

习题8.2

  比较支持向量机、 AdaBoost 、Logistic回归模型的学习策略与算法

解答:

解答思路:

  1. 列出支持向量机的学习策略与学习算法
  2. 列出AdaBoost的学习策略与学习算法
  3. 列出Logistic回归模型的学习策略与学习算法
  4. 比较三者的学习策略与算法

解答步骤:

第1步:支持向量机的学习策略与算法

  根据书中第131页7.2.4节(合页损失函数)

  对于线性支持向量机学习来说,其模型为分离超平面$w^* \cdot x + b^* = 0$及决策函数$f(x)=\text{sign}(w^* \cdot x + b^*)$,其学习策略为软间隔最大化,学习算法为凸二次规划。
  线性支持向量机学习还有另外一种解释,就是最小化一下目标函数:

$$
\sum_{i=1}^N [1 - y_i(w \cdot x_i + b)]_+ + \lambda |w|^2
$$

目标函数的第1项是经验损失或经验风险,函数

$$
L(y(w \cdot b + x)) = [1 - y_i(w \cdot x_i + b)]_+
$$

被称为合页损失函数,第2项是系数为$\lambda$的$w$的$L_2$范数,是正则化项。

  根据书中第142~143页7.4节(序列最小最优化算法)

  SMO算法是一种启发式算法,其基本思路是:如果所有变量的解都满足此最优化问题的KKT条件,那么这个最优化问题的解就得到了。因为KKT条件是该最优化问题的充分必要条件。
  整个SMO算法包括两个部分:求解两个变量二次规划的解析方法和选择变量的启发式方法。

综上所述:

  1. 支持向量机的学习策略:软间隔最大化、最小化由合页损失函数和正则化项组成的目标函数
  2. 支持向量机的学习算法:凸二次规划、SMO算法(序列最小最优化算法)

第2步:AdaBoost的学习策略与算法

  根据书中第162页8.3节(AdbBoost算法的解释)

  AdaBoost算法还有另一个解释,即可认为AdaBoost算法是模型为加法模型、损失函数为指数函数、学习算法为前向分步算法时的二类分类学习方法。
  给定训练数据及损失函数$L(y,f(x))$的条件下,学习加法模型$f(x)$成为经验风险极小化即损失函数极小化问题:

$$
\min \limits_{\beta_m ,\gamma_m} \sum_{i=1}^N L \left(y_i, \sum_{m=1}^M \beta_m b(x_i;\gamma_m) \right)
$$

  定理8.3 AdaBoost算法是前向分步加法算法的特例。这时,模型是由基本分类器组成的加法模型,损失函数是指数函数。

综上所述:

  1. AdaBoost的学习策略:极小化通过加法模型组成的指数损失函数
  2. AdaBoost的学习算法:学习加法模型的前向分步算法

第3步:Logistic回归模型的学习策略与算法

  根据书中第93页6.1.3节(模型参数估计)

  Logistic回归模型学习时,对于给定的训练数据集$T={(x_1,y_1), (x_2,y_2), \cdots, (x_N,y_N)}$,其中$x_i \in R^n$,$y_i \in {0, 1}$,可以应用极大似然估计法估计模型参数,从而得到Logistic回归模型。

  根据书中第103页6.3节(模型学习的最优化算法)

  Logistic回归模型、最大熵模型学习归结为以似然函数为目标函数的最优化问题,通常通过迭代算法求解。常用的方法有改进的迭代尺度法、梯度下降法、牛顿法或拟牛顿法。

综上所述:

  1. Logistic回归模型的学习策略:极大似然估计法
  2. Logistic回归模型的学习算法:改进的迭代尺度法、梯度下降法、牛顿法或拟牛顿法

第4步:比较支持向量机、 AdaBoost 、Logistic回归模型的学习策略与算法

   学习策略 算法
支持向量机 软间隔最大化、最小化由合页损失函数和正则化项组成的目标函数 凸二次规划、SMO算法(序列最小最优化算法)
AdaBoost 极小化通过加法模型组成的指数损失函数 学习加法模型的前向分步算法
Logistic回归 极大似然估计法 改进的迭代尺度法、梯度下降法、牛顿法或拟牛顿法

九、EM算法及其推广

EM算法是一种迭代算法,1977年由 Dempster等人总结提出,用于含有隐变量(hidden variable)的概率模型参数的极大似然估计,或极大后验概率估计EM算法的每次迭代由两步组成:E步,求期望(expectation);M步,求极大(maximization).所以这一算法称为期望极大算法(expectation maximization algorithm),简称EM算法。

9.1 EM算法的引入

概率模型有时既含有观测变量( observable variable),又含有隐变量或潜在变量(latent variable)如果概率模型的变量都是观测变量,那么给定数据,可以直接用极大似然估计法,或贝叶斯估计法估计模型参数。但是,当模型含有隐变量时,就不能简单地使用这些估计方法。EM算法就是含有隐变量的概率模型参数的极大似然估计法,或极大后验概率估计法。

9.1.1 EM算法

算法9.1(EM算法)

定义9.1(Q函数)

9.1.2 EM算法的导出

EM算法是通过不断求解下界的极大化逼近求解对数似然函数极大化的算法。

EM算法不能保证找到全局最优值。

EM算法解释

9.1.3 EM算法在非监督学习中的应用

有时训练数据只有输入没有对应的输出${(x_1,·),(x_2,·)…,(x_N,·)}$,从这样的数据学习模型称为非监督学习问题。EM算法可以用于生成模型的非监督学习,生成模型由联合概率分布$P(X,Y)$表示,可以认为非监督学习训练数据是联合概率分布产生的数据。X为观测数据,Y为未观测数据。

9.2 EM算法的收敛性

EM算法提供一种近似计算含有隐变量概率模型的极大似然估计的方法。EM算法的最大优点是简单性和普适性。我们很自然地要问:EM算法得到的估计序列是否收敛?如果收敛,是否收敛到全局最大值或局部极大值?

定理 9,1

定理 9.2

9.3 EM算法在高斯混合模型学习中的应用

EM算法的一个重要应用是高斯混合模型的参数估计。高斯混合模型应用广泛,在许多情况下,EM算法是学习高斯混合模型(Gaussian misture model)的有
效方法。

9.3.1 高斯混合模型

定义9.2(高斯混合模型)

9.3.2 高斯混合模型参数估计的EM算法

算法9.2 (高斯混合模型参数估计的EM算法)

9.4 EM算法的推广

EM算法还可以解释为F函数(Function)的极大-极大算法(maximization maximization algorithm),基于这个解释有若干变形与推广,如广义期望极大(generalized expectation maximization,GEM)算法。

9.4.1 F函数的极大ー极大算法

定义9.3 (F函数)

引理 9.1

引理 9.2

定理 9.3

定理9.4 EM算法的一次迭代可由F函数的极大-极大算法实现

9.4.2 GEM算法

算法9.3 (GEM算法1)

算法9.4 (GEM算法2)

算法9.5 (GEM算法3)

输入:观测数据,Q函数;

GEM算法的特点是每次迭代増加F函数值(并不一定是极大化F函数),从而增加似然函数值.

一些解释:

习题

习题9.1

  如例9.1的三硬币模型,假设观测数据不变,试选择不同的初值,例如,$\pi^{(0)}=0.46,p^{(0)}=0.55,q^{(0)}=0.67$,求模型参数为$\theta=(\pi,p,q)$的极大似然估计。

解答:

解答思路:

  1. 列出例9.1的三硬币模型;
  2. 写出三硬币模型的EM算法;
  3. 根据上述EM算法,编写代码,并求出模型参数的极大似然估计。

解答步骤:

第1步:例9.1的三硬币模型

  根据书中第175页例9.1(三硬币模型):

  假设有3枚硬币,分别记作A,B,C。这些硬币正面出现的概率分别是$\pi$,$p$和$q$。进行如下掷硬币试验:先掷硬币A,根据其结果选出硬币B或硬币C,正面选硬币B,反面选硬币C;然后掷选出的硬币,掷硬币的结果,出现正面记作1,出现方面记作0;独立地重复$n$次试验(这里,$n=10$),观测结果如下:

$$
1,1,0,1,0,0,1,0,1,1
$$

假设只能观测到掷硬币的结果,不能观测掷硬币的过程。

  三硬币模型可以写作

$$
\begin{aligned}
P(y|\theta) &= \sum_z P(y, z | \theta) = \sum_z P(z|\theta) P(y | z, \theta) \
&= \pi p^y (1-p)^{1-y} + (1 - \pi) q^y (1- q)^{1-y}
\end{aligned}
$$

这里:

  1. 随机变量$y$是观测变量,表示一次试验观测的结果是1或0;
  2. 随机变量$z$是隐变量,表示未观测到的掷硬币A的结果;
  3. $\theta=(\pi, p, q)$是模型参数。

第2步:三硬币模型的EM算法

  根据书中第176页三硬币模型的EM算法:

  EM算法首先选取参数的初值,记作$\theta^{(0)}=(\pi^{(0)}, p^{(0)}, q^{(0)})$,然后通过下面的步骤迭代计算参数的估计值,直至收敛为止。第$i$次迭代参数的估计值为$\theta^{(i)}=(\pi^{(i)}, p^{(i)}, q^{(i)})$。EM算法的第$i+1$次迭代如下:

  E步:计算在模型参数$\pi^{(i)}, p^{(i)}, q^{(i)}$下观测数据$y_j$来自掷硬币B的概率

$$
\mu_j^{(i+1)} = \frac{\pi^{(i)} (p^{(i)})^{y_j} (1-p^{(i)})^{1-y_j} }{\pi^{(i)} (p^{(i)})^{y_j} (1-p^{(i)})^{1-y_j} + (1-\pi^{(i)}) (q^{(i)})^{y_j} (1-q^{(i)})^{1-y_j} }
$$

  M步:计算模型参数的新估计值

$$
\pi^{(i+1)} = \frac{1}{n} \sum_{j=1}^N \mu_j^{(i+1)} \
p^{(i+1)} = \frac{ \displaystyle \sum_{j=1}^n \mu_j^{(i+1)} y_j }{ \displaystyle \sum_{j=1}^n \mu_j^{(i+1)} } \
q^{(i+1)} = \frac{ \displaystyle \sum_{j=1}^n ( 1 - \mu_j^{(i+1)} ) y_j }{ \displaystyle \sum_{j=1}^n ( 1 - \mu_j^{(i+1)} ) }
$$

第3步:编写代码并求出模型参数的极大似然估计

import math


class ThreeCoinEM:
    def __init__(self, prob, tol=1e-6, max_iter=1000):
        """
        初始化模型参数
        :param prob: 模型参数的初值
        :param tol: 收敛阈值
        :param max_iter: 最大迭代次数
        """
        self.prob_A, self.prob_B, self.prob_C = prob
        self.tol = tol
        self.max_iter = max_iter

    def calc_mu(self, j):
        """
        (E步)计算mu
        :param j: 观测数据y的第j个
        :return: 在模型参数下观测数据yj来自掷硬币B的概率
        """
        # 掷硬币A观测结果为正面
        pro_1 = self.prob_A * \
            math.pow(self.prob_B, data[j]) * \
            math.pow((1 - self.prob_B), 1 - data[j])
        # 掷硬币A观测结果为反面
        pro_2 = (1 - self.prob_A) * math.pow(self.prob_C,
                                             data[j]) * math.pow((1 - self.prob_C), 1 - data[j])
        return pro_1 / (pro_1 + pro_2)

    def fit(self, data):
        count = len(data)
        print("模型参数的初值:")
        print("prob_A={}, prob_B={}, prob_C={}".format(
            self.prob_A, self.prob_B, self.prob_C))
        print("EM算法训练过程:")
        for i in range(self.max_iter):
            # (E步)得到在模型参数下观测数据yj来自掷硬币B的概率
            _mu = [self.calc_mu(j) for j in range(count)]
            # (M步)计算模型参数的新估计值
            prob_A = 1 / count * sum(_mu)
            prob_B = sum([_mu[k] * data[k] for k in range(count)]) \
                / sum([_mu[k] for k in range(count)])
            prob_C = sum([(1 - _mu[k]) * data[k] for k in range(count)]) \
                / sum([(1 - _mu[k]) for k in range(count)])
            print('第{}次:prob_A={:.4f}, prob_B={:.4f}, prob_C={:.4f}'.format(
                i + 1, prob_A, prob_B, prob_C))
            # 计算误差值
            error = abs(self.prob_A - prob_A) + \
                abs(self.prob_B - prob_B) + abs(self.prob_C - prob_C)
            self.prob_A = prob_A
            self.prob_B = prob_B
            self.prob_C = prob_C
            # 判断是否收敛
            if error < self.tol:
                print("模型参数的极大似然估计:")
                print("prob_A={:.4f}, prob_B={:.4f}, prob_C={:.4f}".format(self.prob_A, self.prob_B,
                                                                           self.prob_C))
                break
# 加载数据
data = [1, 1, 0, 1, 0, 0, 1, 0, 1, 1]
# 模型参数的初值
init_prob = [0.46, 0.55, 0.67]

# 三硬币模型的EM模型
em = ThreeCoinEM(prob=init_prob, tol=1e-5, max_iter=100)
# 模型训练
em.fit(data)
模型参数的初值:
prob_A=0.46, prob_B=0.55, prob_C=0.67
EM算法训练过程:
第1次:prob_A=0.4619, prob_B=0.5346, prob_C=0.6561
第2次:prob_A=0.4619, prob_B=0.5346, prob_C=0.6561
模型参数的极大似然估计:
prob_A=0.4619, prob_B=0.5346, prob_C=0.6561

  可见通过两次迭代,模型参数已经收敛,三硬币正面出现的概率分别为0.4619,0.5346,0.6561

习题9.2

证明引理9.2。

解答:

解答思路:

  1. 写出需要证明的引理9.2;
  2. 列出$F$函数定义;
  3. 根据引理9.1,进行公式推导;
  4. 根据约束条件$\displaystyle \sum_z \tilde{P}_{\theta}(Z) = 1$,可证明引理9.2。

解答步骤:

第1步:需要证明的引理9.2

  根据书中第188页引理9.2:

若$\tilde{P}_{\theta}(Z)=P(Z | Y, \theta)$,则

$$
F(\tilde{P}, \theta)=\log P(Y|\theta)
$$

第2步:$F$函数定义

  根据书中第187页$F$函数定义:

  假设隐变量数据$Z$的概率分布为$\tilde{P}(Z)$,定义分布$\tilde{P}$与参数$\theta$的函数$F(\tilde{P}, \theta)$如下:

$$
F(\tilde{P}, \theta) = E_{\tilde{P} }[\log P(Y, Z|\theta)] + H(\tilde{P})
$$

称为$F$函数。式中$H(\tilde{P}) = - E_{\tilde{P} } \log \tilde{P}(Z)$是分布$\tilde{P}(Z)$的熵。

第3步:引理9.1

  根据书中第187页引理9.1:

对于固定的$\theta$,存在唯一的分布$\tilde{P}{\theta}$极大化$F(\tilde{P}, \theta)$,这时$\tilde{P}{\theta}$由下式给出:

$$
\tilde{P}_{\theta}(Z) = P(Z | Y, \theta)
$$

并且$\tilde{P}_{\theta}$随$\theta$连续变化。

$\begin{aligned}
\therefore F(\tilde{P}, \theta)
&= E_{\tilde{P} }[\log P(Y, Z|\theta)] + H(\tilde{P}) \
&= E_{\tilde{P} }[\log P(Y,Z|\theta)] -E_{\tilde{P} } \log \tilde{P}(Z) \quad (F函数定义:H(\tilde{P}) = - E_{\tilde{P} } \log \tilde{P}(Z))\
&= \sum_Z \log P(Y,Z|\theta) \tilde{P}_{\theta}(Z) - \sum_Z \log \tilde{P}(Z) \cdot \tilde{P}(Z)
\end{aligned}$

根据引理9.1:$\tilde{P}_{\theta}(Z) = P(Z | Y, \theta)$

$\begin{aligned}
F(\tilde{P}, \theta)
&= \sum_Z \log P(Y,Z|\theta) \tilde{P}_{\theta}(Z) - \sum_Z \log \tilde{P}(Z) \cdot \tilde{P}(Z) \
&= \sum_Z \log P(Y,Z|\theta) P(Z|Y,\theta) - \sum_Z \log P(Z|Y,\theta) \cdot P(Z|Y,\theta) \
&= \sum_Z P(Z|Y,\theta) \left[ \log P(Y,Z|\theta) - \log P(Z|Y,\theta) \right] \
&= \sum_Z P(Z|Y,\theta) \log \frac{P(Y,Z|\theta)}{P(Z|Y,\theta)} \
&= \sum_Z P(Z|Y,\theta) \log P(Y|\theta) \
&= \log P(Y|\theta) \sum_Z P(Z|Y,\theta)
\end{aligned}$

第4步:根据引理9.1,得证

根据引理9.1,可知:$\displaystyle \sum_Z P(Z|Y, \theta) = \sum_Z \tilde{P}_{\theta}(Z) = 1$

$\therefore F(\tilde{P}, \theta) = \log P(Y|\theta)$,引理9.2得证。

习题9.3 ⭐⭐⭐

已知观测数据
-67,-48,6,8,14,16,23,24,28,29,41,49,56,60,75
试估计两个分量的高斯混合模型的5个参数。

解答:

解答思路:

  两个分量的高斯混合模型一共有6个参数$\mu_1, \mu_2, \sigma_1, \sigma_2, \alpha_1, \alpha_2$,其中$\alpha_2$可由$\alpha_2 = 1- \alpha_1$得到,故仅估计5个参数即可。

  1. 写出高斯混合模型;
  2. 写出高斯混合模型参数估计的EM算法;
  3. 采用sklearn的GaussianMixture计算6个参数;
  4. 采用自编程实现高斯混合模型的EM算法。

解答步骤:

第1步:高斯混合模型

  根据书中第183页高斯混合模型:

高斯混合模型是指具有如下形式的概率分布模型:

$$
P(y | \theta) = \sum_{k=1}^K \alpha_k \phi(y|\theta_k)
$$

其中,$\alpha_k$是系数,$\alpha_k \geqslant 0$,$\displaystyle \sum_{k=1}^K \alpha_k = 1$;$\phi(y|\theta)$是高斯分布密度,$\theta_k=(u_k, \sigma_k^2)$,

$$
\phi(y|\theta_k) = \frac{1}{\sqrt{2 \pi} \sigma_k} \exp \left( -\frac{(y - \mu_k)^2}{ 2 \sigma_k^2} \right)
$$

称为第$k$个分模型。

从上述描述中可知,如果是2个高斯混合分模型,一共需要估计的参数有6个$\mu_1, \mu_2, \sigma_1, \sigma_2, \alpha_1, \alpha_2$,其中$\alpha_1 + \alpha_2 = 1$

第2步:高斯混合模型参数估计的EM算法

  根据书中第186页算法9.2:

输入:观测数据$y_1, y_2, \cdots, y_N$,高斯混合模型;
输出:高斯混合模型参数。
(1)取参数的初始值开始迭代;
(2)E步:依据当前模型参数,计算分模型$k$对观测数据$y_i$的响应度

$$
\hat{\gamma}{jk} = \frac{\alpha_k \phi(y_j | \theta_k)}{\displaystyle \sum{k=1}^K \alpha_k \phi(y_j | \theta_k)}, \quad j=1,2,\cdots,N; \quad k=1,2,\cdots,K
$$

(3)M步:计算新一轮迭代的模型参数

$$
\hat{u}k = \frac{\displaystyle \sum{j=1}^N \hat{\gamma}{jk} y_j }{\displaystyle \sum{j=1}^N \hat{\gamma}{jk} }, \quad k=1,2,\cdots,K \
\hat{\sigma}_k^2 = \frac{\displaystyle \sum
{j=1}^N \hat{\gamma}{jk} (y_j - u_k)^2 }{\displaystyle \sum{j=1}^N \hat{\gamma}{jk} }, \quad k=1,2,\cdots,K \
\hat{\alpha}_k = \frac{\displaystyle \sum
{j=1}^N \hat{\gamma}_{jk} }{N}, \quad k=1,2,\cdots,K
$$

(4)重复第(2)步和第(3)步,直到收敛。

第3步:采用sklearn的GaussianMixture计算6个参数

from sklearn.mixture import GaussianMixture
import numpy as np
import matplotlib.pyplot as plt

# 初始化观测数据
data = np.array([-67, -48, 6, 8, 14, 16, 23, 24, 28,
                29, 41, 49, 56, 60, 75]).reshape(-1, 1)

# 设置n_components=2,表示两个分量高斯混合模型
gmm_model = GaussianMixture(n_components=2)
# 对模型进行参数估计
gmm_model.fit(data)
# 对数据进行聚类
labels = gmm_model.predict(data)

# 得到分类结果
print("分类结果:labels = {}\n".format(labels))
print("两个分量高斯混合模型的6个参数如下:")
# 得到参数u1,u2
print("means =", gmm_model.means_.reshape(1, -1))
# 得到参数sigma1, sigma1
print("covariances =", gmm_model.covariances_.reshape(1, -1))
# 得到参数a1, a2
print("weights = ", gmm_model.weights_.reshape(1, -1))
分类结果:labels = [0 0 1 1 1 1 1 1 1 1 1 1 1 1 1]

两个分量高斯混合模型的6个参数如下:
means = [[-57.51107027  32.98489643]]
covariances = [[ 90.24987882 429.45764867]]
weights =  [[0.13317238 0.86682762]]
# 绘制观测数据的聚类情况
for i in range(0, len(labels)):
    if labels[i] == 0:
        plt.scatter(i, data.take(i), s=15, c='red')
    elif labels[i] == 1:
        plt.scatter(i, data.take(i), s=15, c='blue')
plt.title('Gaussian Mixture Model')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

第4步:自编程实现高斯混合模型的EM算法

import numpy as np
import itertools


class MyGMM:
    def __init__(self, alphas_init, means_init, covariances_init, tol=1e-6, n_components=2, max_iter=50):
        # (1)设置参数的初始值
        # 分模型权重
        self.alpha_ = np.array(
            alphas_init, dtype="float16").reshape(n_components, 1)
        # 分模型均值
        self.mean_ = np.array(
            means_init, dtype="float16").reshape(n_components, 1)
        # 分模型标准差(方差的平方)
        self.covariances_ = np.array(
            covariances_init, dtype="float16").reshape(n_components, 1)
        # 迭代停止的阈值
        self.tol = tol
        # 高斯混合模型分量个数
        self.K = n_components
        # 最大迭代次数
        self.max_iter = max_iter
        # 观测数据
        self._y = None
        # 实际迭代次数
        self.n_iter_ = 0

    def gaussian(self, mean, convariances):
        """计算高斯分布概率密度"""
        return 1 / np.sqrt(2 * np.pi * convariances) * np.exp(
            -(self._y - mean) ** 2 / (2 * convariances))

    def update_r(self, mean, convariances, alpha):
        """更新r_jk 分模型k对观测数据yi的响应度"""
        r_jk = alpha * self.gaussian(mean, convariances)
        return r_jk / r_jk.sum(axis=0)

    def update_params(self, r):
        """更新u al si 每个分模型k的均值、权重、方差的平方"""
        u = self.mean_[-1]
        _mean = ((r * self._y).sum(axis=1) / r.sum(axis=1)).reshape(self.K, 1)
        _covariances = ((r * (self._y - u) ** 2).sum(axis=1) /
                        r.sum(axis=1)).reshape(self.K, 1)
        _alpha = (r.sum(axis=1) / self._y.size).reshape(self.K, 1)
        return _mean, _covariances, _alpha

    def judge_stop(self, mean, covariances, alpha):
        """中止条件判断"""
        a = np.linalg.norm(self.mean_ - mean)
        b = np.linalg.norm(self.covariances_ - covariances)
        c = np.linalg.norm(self.alpha_ - alpha)
        return True if np.sqrt(a ** 2 + b ** 2 + c ** 2) < self.tol else False

    def fit(self, y):
        self._y = np.copy(np.array(y))
        """迭代训练获得预估参数"""
        # (2)E步:计算分模型k对观测数据yi的响应度
        r = self.update_r(self.mean_, self.covariances_, self.alpha_)
        # 更新r_jk 分模型k对观测数据yi的响应度
        _mean, _covariances, _alpha = self.update_params(r)
        # 更新u al si 每个分模型k的均值、权重、方差的平方
        for i in range(self.max_iter):
            if not self.judge_stop(_mean, _covariances, _alpha):
                # (4)未达到阈值条件,重复迭代
                r = self.update_r(_mean, _covariances, _alpha)
                # (3)M步:计算新一轮迭代的模型参数
                _mean, _covariances, _alpha = self.update_params(r)
            else:
                # 达到阈值条件,停止迭代
                self.n_iter_ = i
                break

            self.mean_ = _mean
            self.covariances_ = _covariances
            self.alpha_ = _alpha

    def score(self):
        """计算该局部最优解的score,即似然函数值"""
        return (self.alpha_ * self.gaussian(self.mean_, self.covariances_)).sum()
# 观测数据
y = np.array([-67, -48, 6, 8, 14, 16, 23, 24, 28,
             29, 41, 49, 56, 60, 75]).reshape(1, 15)
# 预估均值和方差,以其邻域划分寻优范围
y_mean = y.mean() // 1
y_std = (y.std() ** 2) // 1

# 网格搜索,对不同的初值进行参数估计
alpha = [[i, 1 - i] for i in np.linspace(0.1, 0.9, 9)]
mean = [[y_mean + i, y_mean + j]
        for i in range(-10, 10, 5) for j in range(-10, 10, 5)]
covariances = [[y_std + i, y_std + j]
               for i in range(-1000, 1000, 500) for j in range(-1000, 1000, 500)]
results = []
for i in itertools.product(alpha, mean, covariances):
    init_alpha = i[0]
    init_mean = i[1]
    init_covariances = i[2]
    clf = MyGMM(alphas_init=init_alpha, means_init=init_mean, covariances_init=init_covariances,
                n_components=2, tol=1e-6)
    clf.fit(y)
    # 得到不同初值收敛的局部最优解
    results.append([clf.alpha_, clf.mean_, clf.covariances_, clf.score()])
# 根据score,从所有局部最优解找到相对最优解
best_value = max(results, key=lambda x: x[3])

print("alpha : {}".format(best_value[0].T))
print("mean : {}".format(best_value[1].T))
print("std : {}".format(best_value[2].T))
alpha : [[0.56950675 0.43049325]]
mean : [[27.41762854 12.35515017]]
std : [[ 268.17311145 2772.33989897]]

习题9.4

  EM算法可以用到朴素贝叶斯法的非监督学习,试写出其算法。

解答:

解答思路:
参考: http://www.cs.columbia.edu/~mcollins/em.pdf

  1. 列出EM算法;
  2. 列出朴素贝叶斯算法;
  3. 推导朴素贝叶斯的EM算法。

解答步骤:

第1步:EM算法

  根据书中第178页EM算法:

输入:观测变量数据$Y$,隐变量数据$Z$,联合分布$P(Y,Z|\theta)$,条件分布$P(Z|Y,\theta)$;
输出:模型参数$\theta$。
(1)选择参数的初值$\theta^{(0)}$,开始迭代;
(2)E步:记$\theta^{(i)}$为第$i$次迭代参数$\theta$的估计值,在第$i+1$次迭代的E步,计算

$$
\begin{aligned}
Q(\theta,\theta^{(i)}) &= E_Z[\log P(Y,Z | \theta)| Y,\theta^{(i)}] \
&= \sum_z \log P(Y,Z | \theta) P(Z|Y,\theta^{(i)})
\end{aligned}
$$

这里,$P(Z|Y, \theta)$是在给定观测数据$Y$和当前的参数估计$\theta^{(i)}$下隐变量数据$Z$的条件概率分布;
(3)M步:求使$Q(\theta, \theta^{(i)})$极大化的$\theta$,确定第$i+1$次迭代的参数的估计值$\theta^{(i+1)}$

$$
\theta^{(i+1)} = \arg \max \limits_{\theta} Q(\theta, \theta^{(i)})
$$

(4)重复第(2)步和第(3)步,直至收敛。

第2步:朴素贝叶斯算法

  根据书中第62页朴素贝叶斯算法:

输入:训练数据$T={(x_1, y_1), (x_2, y_2), \cdots, (x_N, y_N)}$,其中$x_i=(x_i^{(1)}, x_i^{(2)}, \cdots, x_i^{(n)})^T$,$x_i^{(j)}$是第$i$个样本的第$j$个特征,$x_i^{(j)} \in {a_{j1}, a_{j2},\cdots, a_{j S_j}}$,$a_{jl}$是第$j$个特征可能取的第$l$个值,$j=1,2,\cdots, n$,$l=1,2,\cdots, S_j$,$y_i \in { c_1, c_2, \cdots, c_K}$;实例$x$;
输出:实例$x$的分类。
(1)计算先验概率及条件概率

$$
P(Y=c_k) = \frac{\displaystyle \sum_{i=1}^N I(y_i=c_k)}{N}, \quad k=1,2,\cdots, K \
P(X^{(j)}=a_{jl}|Y=c_k)= \frac{\displaystyle \sum_{i=1}^N I(x_i^{(j)} = a_{jl}, y_i=c_k) }{\displaystyle \sum_{i=1}^N I(y_i=c_k)} \
j=1,2,\cdots,n; \quad l=1,2,\cdots, S_j; \quad k=1,2,\cdots, K
$$

(2)对于给定的实例$x=(x^{(1)}, x^{(2)}, \cdots, x^{(n)})^T$,计算

$$
P(Y=c_k) \prod_{j=1}^n P(X^{(j)}=x^{(j)} | Y=c_k), \quad k=1,2,\cdots,K
$$

(3)确定实例$x$的类

$$
y = \arg \max \limits_{c_k} P(Y=c_k) \prod_{j=1}^n P(X^{(j)}=x^{(j)} | Y=c_k)
$$

第3步:推导朴素贝叶斯的EM算法

推导思路:

  1. 假设隐变量数据是$y \in \mathcal{Y} = {c_1, c_2, \cdots, c_K}$
  2. 设置初值,$P^{(0)}(Y=y) \geqslant 0$和$P_j^{(0)}(X=x|Y=y) \geqslant 0$,其中$j = 1,2,\cdots, n$,满足

$$
\sum_{y \in \mathcal{Y} } P^{(0)}(Y=y) = 1 \
\sum_{x \in {-1, +1} } P_j^{(0)}(X=x|Y=y)=1
$$

  1. 根据概率公式,可知概率

$$
\delta(y|i) = P(Y=y | X=x_i, \theta^{(t)}) = \frac
{\displaystyle P^{(t)}(Y=y) \prod_{j=1}^n P_j^{(t)}(X=x_i^{(j)} | Y=y) }
{\displaystyle \sum_{y \in \mathcal{Y} } P^{(t)}(Y=y) \prod_{j=1}^n P_j^{(t)}(X=x_i^{(j)} | Y=y)}
$$

其中$\theta$表示朴素贝叶斯模型中所有的参数向量

  1. 迭代更新参数

$$
P^{(t+1)}(Y=y) = \frac{1}{N} \sum_{i=1}^N \delta(y | i) \
P_j^{(t+1)}(X=x_i^{(j)} | y) = \frac
{\displaystyle \sum_{i=1}^N P(X=x_i^{(j)})\delta(y|i) }
{\displaystyle \sum_{i=1}^N \delta(y|i)}
$$

  1. 计算似然函数,得到使得似然函数最大的$\theta$,重复第3步和第4步,直至收敛

$$
\begin{aligned}
\theta^* &= \arg \max \limits_{\theta \in \Omega} L(\theta) \
&= \arg \max \limits_{\theta \in \Omega} \sum_{i=1}^N \sum_{y \in \mathcal{Y} } \delta(y|i) \log \left(P(Y=y) \prod_{j=1}^n P_j (X=x_i^{(j)} | Y=y)\right)
\end{aligned}
$$

所以,朴素贝叶斯的EM算法如下:

输入:隐变量数据是$y \in \mathcal{Y} = {c_1, c_2, \cdots, c_K}$,$x \in \mathcal{X} = (x_1, x_2, \cdots, x_N)$,输入空间$\mathcal{X} \subset R^n$为$n$维向量的集合,$x=(x^{(1)}, x^{(2)}, \cdots, x^{(n)})^T$,$x^{(i)}$取值范围是${-1, +1}$;
输出:参数$P^{(t+1)}(Y=y)$,$P_j^{(t+1)}(X=x_i^{(j)} | y)$;
(1)选择参数的初值$P^{(0)}(Y=y) \geqslant 0$和$P_j^{(0)}(X=x|Y=y) \geqslant 0$,开始迭代;
(2)E步:记$\theta^{(t)}$为第$t$次迭代参数$\theta$的估计值,在第$t+1$次迭代的E步,计算
$$
\delta(y|i) = P(Y=y | X=x_i, \theta^{(t)}) = \frac
{\displaystyle P^{(t)}(Y=y) \prod_{j=1}^n P_j^{(t)}(X=x_i^{(j)} | Y=y) }
{\displaystyle \sum_{y \in \mathcal{Y} } P^{(t)}(Y=y) \prod_{j=1}^n P_j^{(t)}(X=x_i^{(j)} | Y=y)}
$$
(3)M步:求使$Q(\theta, \theta^{(t)})$极大化的$\theta$,确定第$t+1$次迭代的参数的估计值
$$
P^{(t+1)}(Y=y) = \frac{1}{N} \sum_{i=1}^N \delta(y | i) \
P_j^{(t+1)}(X=x_i^{(j)} | y) = \frac
{\displaystyle \sum_{i=1}^N P(X=x_i^{(j)})\delta(y|i) }
{\displaystyle \sum_{i=1}^N \delta(y|i)}
$$
(4)重复第(2)步和第(3)步,直至收敛。

十、隐马尔可夫模型

隐马尔可夫模型( hidden Markov model,HMM)是可用于标注问题的统计学习模型,描述由隐藏的马尔可夫链随机生成观测序列的过程,属于生成模型。

10.1 隐马尔可夫模型的基本概念

10.1.1 隐马尔可夫模型的定义

定义10.1(隐马尔可夫模型)隐马尔可夫模型是关于时序的概率模型,描述由一个隐藏的马尔可夫链随机生成不可观测的状态随机序列,再由各个状态生成一个观测而产生观测随机序列的过程。隐藏的马尔可夫链随机生成的状态的序列,称为状态序列(state sequence);每个状态生成一个观测,而由此产生的观测的随机序列,称为观测序列(observation sequence).序列的每一个位置又可以看作是一个时刻。

10.1.2 观测序列的生成过程

算法10.1(观测序列的生成)

10.1.3 隐马尔可夫模型的3个基本问题

10.2 概率计算算法

10.2.1 直接计算法

10.2.2 前向算法

定义10.2(前向概率)

算法10.2(观测序列概率的前向算法)

10.2.3 后向算法

定义10.3(后向概率)

算法10.3(观測序列概率的后向算法)

10.2.4 一些概率与期望值的计算

=

10.3 学习算法

10.3.1 监督学习方法

10.3.2 Baum- Welch算法

10.3.3 Baum- Welch模型参数估计公式

算法10.4 (Baum- Welch算法)

10.4 预测算法

10.4.1 近似算法

10.4.2 维特比算法

维特比算法实际是用动态规划解隐马尔可夫模型预测问题,即用动态规划(dynamic programming)求概率最大路径(最优路径)。这时一条路径对应着一个状态序列

算法10.5(维特比算法)

一些解释

习题

习题10.1 ⭐⭐⭐

  给定盒子和球组成的隐马尔可夫模型$\lambda=(A,B,\pi)$,其中,

$$
A=\left[\begin{array}{ccc}
0.5&0.2&0.3 \
0.3&0.5&0.2 \
0.2&0.3&0.5
\end{array}\right],
\quad B=\left[\begin{array}{cc}
0.5&0.5 \
0.4&0.6 \
0.7&0.3
\end{array}\right],
\quad \pi=(0.2,0.4,0.4)^T
$$

设$T=4$,$O=(红,白,红,白)$,试用后向算法计算$P(O|\lambda)$。

解答:

解答思路:

  1. 列出隐马尔可夫模型的定义
  2. 列出后向算法
  3. 自编码实现隐马尔可夫的后向算法

解答步骤:

第1步:隐马尔可夫模型

  根据书中第193页隐马尔可夫模型的定义:

  1. 条件假设:
      设$Q$是所有可能的状态的集合,$V$是所有可能的观测的集合:

$$
Q={q_1, q_2, \cdots, q_N}, \quad V={v_1, v_2, \cdots, v_M}
$$

其中,$N$是可能的状态数,$M$是可能的观测数。
  $I$是长度为$T$的状态序列,$O$是对应的观测序列:

$$
I = (i_1,i_2, \cdots, i_T), \quad O=(o_1, o_2, \cdots, o_T)
$$

  1. 状态转移概率矩阵$A$:
      $A$是状态转移概率矩阵:

$$
A = [a_{ij}]_{N \times N}
$$

其中,

$$
a_{ij} = P(i_{t+1}=q_j | i_t = q_i), \quad i=1,2,\cdots, N; \quad j=1,2,\cdots N
$$

是在时刻$t$处于状态$q_i$的条件下,在时刻$t+1$转移到状态$q_j$的概率。

  1. 观测概率矩阵$B$:
      $B$是观测概率矩阵:

$$
B = [b_j(k)]_{N \times M}
$$

其中,

$$
b_j(k) = P(o_t=v_k | i_t = q_j), \quad k=1,2,\cdots, M; \quad j=1,2,\cdots N
$$

是在时刻$t$处于状态$q_j$的条件下,生成观测$v_k$的概率。

  1. 初始状态概率向量$\pi$:
      $\pi$是初始状态概率向量:

$$
\pi = (\pi_i)
$$

其中,

$$
\pi = P(i_1 = q_i), \quad i=1,2,\cdots N
$$

是时刻$t=1$处于状态$q_i$的概率。

  1. 隐马尔可夫模型的表示:
      隐马尔可夫模型由初始状态概率向量$\pi$、状态转移概率矩阵$A$和观测概率矩阵$B$决定。$\pi$和$A$决定状态序列,$B$决定观测序列。因此隐马尔可夫模型$\lambda$可以用三元符号表示,即

$$
\lambda = (A, B, \pi)
$$

$A,B,\pi$称为隐马尔可夫模型的三要素。

第2步:后向算法

  根据书中第201页的观测序列概率的后向算法:

输入:隐马尔可夫模型$\lambda$,观测序列$O$
输出:观测序列概率$P(O|\lambda)$
(1)

$$
\beta_T(i) = 1, \quad i = 1, 2, \cdots, N
$$

(2)对$t= T-1, T-2, \cdots, 1$

$$
\beta_t(i) = \sum_{j=1}^N a_{ij} b_j(o_{t+1}) \beta_{t+1}(j), \quad i=1,2,\cdots, N
$$

(3)

$$
P(O|\lambda) = \sum_{i=1}^N \pi_i b_i(o_1) \beta_1(i)
$$

第3步:自编码实现隐马尔可夫的后向算法

import numpy as np


class HiddenMarkovBackward:
    def __init__(self):
        self.betas = None
        self.backward_P = None

    def backward(self, Q, V, A, B, O, PI):
        """
        后向算法
        :param Q: 所有可能的状态集合
        :param V: 所有可能的观测集合
        :param A: 状态转移概率矩阵
        :param B: 观测概率矩阵
        :param O: 观测序列
        :param PI: 初始状态概率向量
        """
        # 状态序列的大小
        N = len(Q)
        # 观测序列的大小
        M = len(O)
        # (1)初始化后向概率beta值,书中第201页公式(10.19)
        betas = np.ones((N, M))
        self.print_betas_T(N, M)

        # (2)对观测序列逆向遍历,M-2即为T-1
        print("\n从时刻T-1到1观测序列的后向概率:")
        for t in range(M - 2, -1, -1):
            # 得到序列对应的索引
            index_of_o = V.index(O[t + 1])
            # 遍历状态序列
            for i in range(N):
                # 书中第201页公式(10.20)
                betas[i][t] = np.dot(np.multiply(A[i], [b[index_of_o] for b in B]),
                                     [beta[t + 1] for beta in betas])
                real_t = t + 1
                real_i = i + 1
                self.print_betas_t(A, B, N, betas, i, index_of_o, real_i, real_t, t)

        # 取出第一个值索引,用于得到o1
        index_of_o = V.index(O[0])
        self.betas = betas
        # 书中第201页公式(10.21)
        P = np.dot(np.multiply(PI, [b[index_of_o] for b in B]),
                   [beta[0] for beta in betas])
        self.backward_P = P
        self.print_P(B, N, P, PI, betas, index_of_o)

    @staticmethod
    def print_P(B, N, P, PI, betas, index_of_o):
        print("\n观测序列概率:")
        print("P(O|lambda) = ", end="")
        for i in range(N):
            print("%.1f * %.1f * %.5f + "
                  % (PI[0][i], B[i][index_of_o], betas[i][0]), end="")
        print("0 = %f" % P)

    @staticmethod
    def print_betas_t(A, B, N, betas, i, index_of_o, real_i, real_t, t):
        print("beta%d(%d) = sum[a%dj * bj(o%d) * beta%d(j)] = ("
              % (real_t, real_i, real_i, real_t + 1, real_t + 1), end='')
        for j in range(N):
            print("%.2f * %.2f * %.2f + "
                  % (A[i][j], B[j][index_of_o], betas[j][t + 1]), end='')
        print("0) = %.3f" % betas[i][t])

    @staticmethod
    def print_betas_T(N, M):
        print("初始化后向概率:")
        for i in range(N):
            print('beta%d(%d) = 1' % (M, i + 1))
Q = [1, 2, 3]
V = ['红', '白']
A = [[0.5, 0.2, 0.3], [0.3, 0.5, 0.2], [0.2, 0.3, 0.5]]
B = [[0.5, 0.5], [0.4, 0.6], [0.7, 0.3]]
O = ['红', '白', '红', '白']
PI = [[0.2, 0.4, 0.4]]

hmm_backward = HiddenMarkovBackward()
hmm_backward.backward(Q, V, A, B, O, PI)
初始化后向概率:
beta4(1) = 1
beta4(2) = 1
beta4(3) = 1

从时刻T-1到1观测序列的后向概率:
beta3(1) = sum[a1j * bj(o4) * beta4(j)] = (0.50 * 0.50 * 1.00 + 0.20 * 0.60 * 1.00 + 0.30 * 0.30 * 1.00 + 0) = 0.460
beta3(2) = sum[a2j * bj(o4) * beta4(j)] = (0.30 * 0.50 * 1.00 + 0.50 * 0.60 * 1.00 + 0.20 * 0.30 * 1.00 + 0) = 0.510
beta3(3) = sum[a3j * bj(o4) * beta4(j)] = (0.20 * 0.50 * 1.00 + 0.30 * 0.60 * 1.00 + 0.50 * 0.30 * 1.00 + 0) = 0.430
beta2(1) = sum[a1j * bj(o3) * beta3(j)] = (0.50 * 0.50 * 0.46 + 0.20 * 0.40 * 0.51 + 0.30 * 0.70 * 0.43 + 0) = 0.246
beta2(2) = sum[a2j * bj(o3) * beta3(j)] = (0.30 * 0.50 * 0.46 + 0.50 * 0.40 * 0.51 + 0.20 * 0.70 * 0.43 + 0) = 0.231
beta2(3) = sum[a3j * bj(o3) * beta3(j)] = (0.20 * 0.50 * 0.46 + 0.30 * 0.40 * 0.51 + 0.50 * 0.70 * 0.43 + 0) = 0.258
beta1(1) = sum[a1j * bj(o2) * beta2(j)] = (0.50 * 0.50 * 0.25 + 0.20 * 0.60 * 0.23 + 0.30 * 0.30 * 0.26 + 0) = 0.112
beta1(2) = sum[a2j * bj(o2) * beta2(j)] = (0.30 * 0.50 * 0.25 + 0.50 * 0.60 * 0.23 + 0.20 * 0.30 * 0.26 + 0) = 0.122
beta1(3) = sum[a3j * bj(o2) * beta2(j)] = (0.20 * 0.50 * 0.25 + 0.30 * 0.60 * 0.23 + 0.50 * 0.30 * 0.26 + 0) = 0.105

观测序列概率:
P(O|lambda) = 0.2 * 0.5 * 0.11246 + 0.4 * 0.4 * 0.12174 + 0.4 * 0.7 * 0.10488 + 0 = 0.060091

可得$P(O|\lambda) = 0.060091$

习题10.2 ⭐⭐⭐

  给定盒子和球组成的隐马尔可夫模型$\lambda=(A,B,\pi)$,其中,

$$
A=\left[
\begin{array}{ccc}
0.5&0.1&0.4 \
0.3&0.5&0.2 \
0.2&0.2&0.6
\end{array}\right],
\quad B=\left[
\begin{array}{cc}
0.5&0.5 \
0.4&0.6 \
0.7&0.3
\end{array}\right],
\quad \pi=(0.2,0.3,0.5)^T
$$

设$T=8$,$O=(红,白,红,红,白,红,白,白)$,试用前向后向概率计算$P(i_4=q_3|O,\lambda)$

解答:

解答思路:

  1. 列出前向算法
  2. 根据前向概率和后向概率,列出单个状态概率的计算公式
  3. 自编程实现用前向后向概率计算$P(i_4=q_3|O,\lambda)$

解答步骤:

第1步:前向算法

  根据书中第198页观测序列概率的前向算法:

输入:隐马尔可夫模型$\lambda$,观测序列$O$;
输出:观测序列概率$P(O|\lambda)$。
(1)初值

$$
\alpha_1(i) = \pi_i b_i(o_1), \quad i=1,2,\cdots, N
$$

(2)递推,对$t=1,2,\cdots,T-1,$

$$
\alpha_{t+1}(i) = \left[ \sum_{j=1}^N \alpha_t(j) a_{ji} \right] b_i(o_{t+1}), \quad i=1,2,\cdots, N
$$

(3)终止

$$
P(O|\lambda) = \sum_{i=1}^N \alpha_T(i)
$$

第2步:单个状态概率的计算公式

  根据书中第202页单个状态概率的计算公式:

  利用前向概率和后向概率,给定模型$\lambda$和观测$O$,在时刻$t$处于状态$q_i$的概率

$$
\begin{aligned}
\gamma_t(i) &= P(i_t = q_i |O, \lambda) \
&= \frac{P(i_t=q_i,O|\lambda)}{P(O|\lambda)} \
&= \frac{\alpha_t(i) \beta_t(i)}{P(O|\lambda)}
\end{aligned}
$$

第3步:自编程实现前向后向算法

import numpy as np


class HiddenMarkovForwardBackward(HiddenMarkovBackward):
    def __init__(self, verbose=False):
        super(HiddenMarkovBackward, self).__init__()
        self.alphas = None
        self.forward_P = None
        self.verbose = verbose

    def forward(self, Q, V, A, B, O, PI):
        """
        前向算法
        :param Q: 所有可能的状态集合
        :param V: 所有可能的观测集合
        :param A: 状态转移概率矩阵
        :param B: 观测概率矩阵
        :param O: 观测序列
        :param PI: 初始状态概率向量
        """
        # 状态序列的大小
        N = len(Q)
        # 观测序列的大小
        M = len(O)
        # 初始化前向概率alpha值
        alphas = np.zeros((N, M))
        # 时刻数=观测序列数
        T = M
        # (2)对观测序列遍历,遍历每一个时刻,计算前向概率alpha值

        for t in range(T):
            if self.verbose:
                if t == 0:
                    print("前向概率初值:")
                elif t == 1:
                    print("\n从时刻1到T-1观测序列的前向概率:")
            # 得到序列对应的索引
            index_of_o = V.index(O[t])
            # 遍历状态序列
            for i in range(N):
                if t == 0:
                    # (1)初始化alpha初值,书中第198页公式(10.15)
                    alphas[i][t] = PI[t][i] * B[i][index_of_o]
                    if self.verbose:
                        self.print_alpha_t1(alphas, i, t)
                else:
                    # (2)递推,书中第198页公式(10.16)
                    alphas[i][t] = np.dot([alpha[t - 1] for alpha in alphas],
                                          [a[i] for a in A]) * B[i][index_of_o]
                    if self.verbose:
                        self.print_alpha_t(alphas, i, t)
        # (3)终止,书中第198页公式(10.17)
        self.forward_P = np.sum([alpha[M - 1] for alpha in alphas])
        self.alphas = alphas

    @staticmethod
    def print_alpha_t(alphas, i, t):
        print("alpha%d(%d) = [sum alpha%d(i) * ai%d] * b%d(o%d) = %f"
              % (t + 1, i + 1, t - 1, i, i, t, alphas[i][t]))

    @staticmethod
    def print_alpha_t1(alphas, i, t):
        print('alpha1(%d) = pi%d * b%d * b(o1) = %f'
              % (i + 1, i, i, alphas[i][t]))

    def calc_t_qi_prob(self, t, qi):
        result = (self.alphas[qi - 1][t - 1] * self.betas[qi - 1][t - 1]) / self.backward_P[0]
        if self.verbose:
            print("计算P(i%d=q%d|O,lambda):" % (t, qi))
            print("P(i%d=q%d|O,lambda) = alpha%d(%d) * beta%d(%d) / P(O|lambda) = %f"
                  % (t, qi, t, qi, t, qi, result))

        return result
Q = [1, 2, 3]
V = ['红', '白']
A = [[0.5, 0.1, 0.4], [0.3, 0.5, 0.2], [0.2, 0.2, 0.6]]
B = [[0.5, 0.5], [0.4, 0.6], [0.7, 0.3]]
O = ['红', '白', '红', '红', '白', '红', '白', '白']
PI = [[0.2, 0.3, 0.5]]

hmm_forward_backward = HiddenMarkovForwardBackward(verbose=True)
hmm_forward_backward.forward(Q, V, A, B, O, PI)
print()
hmm_forward_backward.backward(Q, V, A, B, O, PI)
print()
hmm_forward_backward.calc_t_qi_prob(t=4, qi=3)
print()
前向概率初值:
alpha1(1) = pi0 * b0 * b(o1) = 0.100000
alpha1(2) = pi1 * b1 * b(o1) = 0.120000
alpha1(3) = pi2 * b2 * b(o1) = 0.350000

从时刻1到T-1观测序列的前向概率:
alpha2(1) = [sum alpha0(i) * ai0] * b0(o1) = 0.078000
alpha2(2) = [sum alpha0(i) * ai1] * b1(o1) = 0.084000
alpha2(3) = [sum alpha0(i) * ai2] * b2(o1) = 0.082200
alpha3(1) = [sum alpha1(i) * ai0] * b0(o2) = 0.040320
alpha3(2) = [sum alpha1(i) * ai1] * b1(o2) = 0.026496
alpha3(3) = [sum alpha1(i) * ai2] * b2(o2) = 0.068124
alpha4(1) = [sum alpha2(i) * ai0] * b0(o3) = 0.020867
alpha4(2) = [sum alpha2(i) * ai1] * b1(o3) = 0.012362
alpha4(3) = [sum alpha2(i) * ai2] * b2(o3) = 0.043611
alpha5(1) = [sum alpha3(i) * ai0] * b0(o4) = 0.011432
alpha5(2) = [sum alpha3(i) * ai1] * b1(o4) = 0.010194
alpha5(3) = [sum alpha3(i) * ai2] * b2(o4) = 0.011096
alpha6(1) = [sum alpha4(i) * ai0] * b0(o5) = 0.005497
alpha6(2) = [sum alpha4(i) * ai1] * b1(o5) = 0.003384
alpha6(3) = [sum alpha4(i) * ai2] * b2(o5) = 0.009288
alpha7(1) = [sum alpha5(i) * ai0] * b0(o6) = 0.002811
alpha7(2) = [sum alpha5(i) * ai1] * b1(o6) = 0.002460
alpha7(3) = [sum alpha5(i) * ai2] * b2(o6) = 0.002535
alpha8(1) = [sum alpha6(i) * ai0] * b0(o7) = 0.001325
alpha8(2) = [sum alpha6(i) * ai1] * b1(o7) = 0.001211
alpha8(3) = [sum alpha6(i) * ai2] * b2(o7) = 0.000941

初始化后向概率:
beta8(1) = 1
beta8(2) = 1
beta8(3) = 1

从时刻T-1到1观测序列的后向概率:
beta7(1) = sum[a1j * bj(o8) * beta8(j)] = (0.50 * 0.50 * 1.00 + 0.10 * 0.60 * 1.00 + 0.40 * 0.30 * 1.00 + 0) = 0.430
beta7(2) = sum[a2j * bj(o8) * beta8(j)] = (0.30 * 0.50 * 1.00 + 0.50 * 0.60 * 1.00 + 0.20 * 0.30 * 1.00 + 0) = 0.510
beta7(3) = sum[a3j * bj(o8) * beta8(j)] = (0.20 * 0.50 * 1.00 + 0.20 * 0.60 * 1.00 + 0.60 * 0.30 * 1.00 + 0) = 0.400
beta6(1) = sum[a1j * bj(o7) * beta7(j)] = (0.50 * 0.50 * 0.43 + 0.10 * 0.60 * 0.51 + 0.40 * 0.30 * 0.40 + 0) = 0.186
beta6(2) = sum[a2j * bj(o7) * beta7(j)] = (0.30 * 0.50 * 0.43 + 0.50 * 0.60 * 0.51 + 0.20 * 0.30 * 0.40 + 0) = 0.241
beta6(3) = sum[a3j * bj(o7) * beta7(j)] = (0.20 * 0.50 * 0.43 + 0.20 * 0.60 * 0.51 + 0.60 * 0.30 * 0.40 + 0) = 0.176
beta5(1) = sum[a1j * bj(o6) * beta6(j)] = (0.50 * 0.50 * 0.19 + 0.10 * 0.40 * 0.24 + 0.40 * 0.70 * 0.18 + 0) = 0.106
beta5(2) = sum[a2j * bj(o6) * beta6(j)] = (0.30 * 0.50 * 0.19 + 0.50 * 0.40 * 0.24 + 0.20 * 0.70 * 0.18 + 0) = 0.101
beta5(3) = sum[a3j * bj(o6) * beta6(j)] = (0.20 * 0.50 * 0.19 + 0.20 * 0.40 * 0.24 + 0.60 * 0.70 * 0.18 + 0) = 0.112
beta4(1) = sum[a1j * bj(o5) * beta5(j)] = (0.50 * 0.50 * 0.11 + 0.10 * 0.60 * 0.10 + 0.40 * 0.30 * 0.11 + 0) = 0.046
beta4(2) = sum[a2j * bj(o5) * beta5(j)] = (0.30 * 0.50 * 0.11 + 0.50 * 0.60 * 0.10 + 0.20 * 0.30 * 0.11 + 0) = 0.053
beta4(3) = sum[a3j * bj(o5) * beta5(j)] = (0.20 * 0.50 * 0.11 + 0.20 * 0.60 * 0.10 + 0.60 * 0.30 * 0.11 + 0) = 0.043
beta3(1) = sum[a1j * bj(o4) * beta4(j)] = (0.50 * 0.50 * 0.05 + 0.10 * 0.40 * 0.05 + 0.40 * 0.70 * 0.04 + 0) = 0.026
beta3(2) = sum[a2j * bj(o4) * beta4(j)] = (0.30 * 0.50 * 0.05 + 0.50 * 0.40 * 0.05 + 0.20 * 0.70 * 0.04 + 0) = 0.023
beta3(3) = sum[a3j * bj(o4) * beta4(j)] = (0.20 * 0.50 * 0.05 + 0.20 * 0.40 * 0.05 + 0.60 * 0.70 * 0.04 + 0) = 0.027
beta2(1) = sum[a1j * bj(o3) * beta3(j)] = (0.50 * 0.50 * 0.03 + 0.10 * 0.40 * 0.02 + 0.40 * 0.70 * 0.03 + 0) = 0.015
beta2(2) = sum[a2j * bj(o3) * beta3(j)] = (0.30 * 0.50 * 0.03 + 0.50 * 0.40 * 0.02 + 0.20 * 0.70 * 0.03 + 0) = 0.012
beta2(3) = sum[a3j * bj(o3) * beta3(j)] = (0.20 * 0.50 * 0.03 + 0.20 * 0.40 * 0.02 + 0.60 * 0.70 * 0.03 + 0) = 0.016
beta1(1) = sum[a1j * bj(o2) * beta2(j)] = (0.50 * 0.50 * 0.01 + 0.10 * 0.60 * 0.01 + 0.40 * 0.30 * 0.02 + 0) = 0.006
beta1(2) = sum[a2j * bj(o2) * beta2(j)] = (0.30 * 0.50 * 0.01 + 0.50 * 0.60 * 0.01 + 0.20 * 0.30 * 0.02 + 0) = 0.007
beta1(3) = sum[a3j * bj(o2) * beta2(j)] = (0.20 * 0.50 * 0.01 + 0.20 * 0.60 * 0.01 + 0.60 * 0.30 * 0.02 + 0) = 0.006

观测序列概率:
P(O|lambda) = 0.2 * 0.5 * 0.00633 + 0.3 * 0.4 * 0.00685 + 0.5 * 0.7 * 0.00578 + 0 = 0.003477

计算P(i4=q3|O,lambda):
P(i4=q3|O,lambda) = alpha4(3) * beta4(3) / P(O|lambda) = 0.536952

可知,$\displaystyle P(i_4=q_3|O,\lambda)=\frac{P(i_4=q_3,O|\lambda)}{P(O|\lambda)}=\frac{\alpha_4(3)\beta_4(3)}{P(O|\lambda)} = 0.536952$

习题10.3 ⭐⭐⭐

  在习题10.1中,试用维特比算法求最优路径$I^=(i_1^,i_2^,i_3^,i_4^*)$。

解答:

解答思路:

  1. 列出维特比算法
  2. 自编程实现维特比算法,并求最优路径

解答步骤:

第1步:维特比算法

  根据书中第209页维特比算法:

输入:模型$\lambda=(A,B, \pi)$和观测$O=(o_1, o_2, \cdots, o_T)$;
输出:最优路径$I^* = (i_1^, i_2^, \cdots, i_T^*)$。
(1)初始化

$$
\delta_1(i) = \pi_i b_i(o_1), \quad i=1,2, \cdots, N \
\Psi_1(i) = 0, \quad i=1,2, \cdots, N
$$

(2)递推。对$t=2,3, \cdots, T$

$$
\delta_t(i) = \max \limits_{1 \leqslant j \leqslant N} [\delta_{t-1}(j) a_{ji}] b_i(o_t), \quad i=1,2, \cdots, N \
\Psi_t(i) = \arg \max \limits_{1 \leqslant j \leqslant N} [\delta_{t-1}(j) a_{ji}], \quad i=1,2, \cdots, N
$$

(3)终止

$$
P^* = \max \limits_{1 \leqslant i \leqslant N} \delta_T(i) \
i_T^* = \arg \max \limits_{1 \leqslant i \leqslant N} [\delta_T(i)]
$$

(4)最优路径回溯。对$t=T-1, T-2, \cdots , 1$

$$
i_t^* = \Psi_{t+1}(i_{t+1}^*)
$$

求得最优路径$I^* = (i_1^, i_2^, \cdots, i_T^*)$。

第2步:自编程实现维特比算法

import numpy as np


class HiddenMarkovViterbi:
    def __init__(self, verbose=False):
        self.verbose = verbose

    def viterbi(self, Q, V, A, B, O, PI):
        """
        维特比算法
        :param Q: 所有可能的状态集合
        :param V: 所有可能的观测集合
        :param A: 状态转移概率矩阵
        :param B: 观测概率矩阵
        :param O: 观测序列
        :param PI: 初始状态概率向量
        """
        # 状态序列的大小
        N = len(Q)
        # 观测序列的大小
        M = len(O)
        # 初始化deltas
        deltas = np.zeros((N, M))
        # 初始化psis
        psis = np.zeros((N, M))

        # 初始化最优路径矩阵,该矩阵维度与观测序列维度相同
        I = np.zeros((1, M))
        # (2)递推,遍历观测序列
        for t in range(M):
            if self.verbose:
                if t == 0:
                    print("初始化Psi1和delta1:")
                elif t == 1:
                    print("\n从时刻2到T的所有单个路径中概率"
                          "最大值delta和概率最大的路径的第t-1个结点Psi:")

            # (2)递推从t=2开始
            real_t = t + 1
            # 得到序列对应的索引
            index_of_o = V.index(O[t])
            for i in range(N):
                real_i = i + 1
                if t == 0:
                    # (1)初始化
                    deltas[i][t] = PI[0][i] * B[i][index_of_o]
                    psis[i][t] = 0

                    self.print_delta_t1(
                        B, PI, deltas, i, index_of_o, real_i, t)
                    self.print_psi_t1(real_i)
                else:
                    # (2)递推,对t=2,3,...,T
                    deltas[i][t] = np.max(np.multiply([delta[t - 1] for delta in deltas],
                                                      [a[i] for a in A])) * B[i][index_of_o]
                    self.print_delta_t(
                        A, B, deltas, i, index_of_o, real_i, real_t, t)

                    psis[i][t] = np.argmax(np.multiply([delta[t - 1] for delta in deltas],
                                                       [a[i] for a in A]))
                    self.print_psi_t(i, psis, real_i, real_t, t)

        last_deltas = [delta[M - 1] for delta in deltas]
        # (3)终止,得到所有路径的终结点最大的概率值
        P = np.max(last_deltas)
        # (3)得到最优路径的终结点
        I[0][M - 1] = np.argmax(last_deltas)
        if self.verbose:
            print("\n所有路径的终结点最大的概率值:")
            print("P = %f" % P)
        if self.verbose:
            print("\n最优路径的终结点:")
            print("i%d = argmax[deltaT(i)] = %d" % (M, I[0][M - 1] + 1))
            print("\n最优路径的其他结点:")

        # (4)递归由后向前得到其他结点
        for t in range(M - 2, -1, -1):
            I[0][t] = psis[int(I[0][t + 1])][t + 1]
            if self.verbose:
                print("i%d = Psi%d(i%d) = %d" %
                      (t + 1, t + 2, t + 2, I[0][t] + 1))

        # 输出最优路径
        print("\n最优路径是:", "->".join([str(int(i + 1)) for i in I[0]]))

    def print_psi_t(self, i, psis, real_i, real_t, t):
        if self.verbose:
            print("Psi%d(%d) = argmax[delta%d(j) * aj%d] = %d"
                  % (real_t, real_i, real_t - 1, real_i, psis[i][t]))

    def print_delta_t(self, A, B, deltas, i, index_of_o, real_i, real_t, t):
        if self.verbose:
            print("delta%d(%d) = max[delta%d(j) * aj%d] * b%d(o%d) = %.2f * %.2f = %.5f"
                  % (real_t, real_i, real_t - 1, real_i, real_i, real_t,
                     np.max(np.multiply([delta[t - 1] for delta in deltas],
                                        [a[i] for a in A])),
                     B[i][index_of_o], deltas[i][t]))

    def print_psi_t1(self, real_i):
        if self.verbose:
            print("Psi1(%d) = 0" % real_i)

    def print_delta_t1(self, B, PI, deltas, i, index_of_o, real_i, t):
        if self.verbose:
            print("delta1(%d) = pi%d * b%d(o1) = %.2f * %.2f = %.2f"
                  % (real_i, real_i, real_i, PI[0][i], B[i][index_of_o], deltas[i][t]))
Q = [1, 2, 3]
V = ['红', '白']
A = [[0.5, 0.2, 0.3], [0.3, 0.5, 0.2], [0.2, 0.3, 0.5]]
B = [[0.5, 0.5], [0.4, 0.6], [0.7, 0.3]]
O = ['红', '白', '红', '白']
PI = [[0.2, 0.4, 0.4]]

HMM = HiddenMarkovViterbi(verbose=True)
HMM.viterbi(Q, V, A, B, O, PI)
初始化Psi1和delta1:
delta1(1) = pi1 * b1(o1) = 0.20 * 0.50 = 0.10
Psi1(1) = 0
delta1(2) = pi2 * b2(o1) = 0.40 * 0.40 = 0.16
Psi1(2) = 0
delta1(3) = pi3 * b3(o1) = 0.40 * 0.70 = 0.28
Psi1(3) = 0

从时刻2到T的所有单个路径中概率最大值delta和概率最大的路径的第t-1个结点Psi:
delta2(1) = max[delta1(j) * aj1] * b1(o2) = 0.06 * 0.50 = 0.02800
Psi2(1) = argmax[delta1(j) * aj1] = 2
delta2(2) = max[delta1(j) * aj2] * b2(o2) = 0.08 * 0.60 = 0.05040
Psi2(2) = argmax[delta1(j) * aj2] = 2
delta2(3) = max[delta1(j) * aj3] * b3(o2) = 0.14 * 0.30 = 0.04200
Psi2(3) = argmax[delta1(j) * aj3] = 2
delta3(1) = max[delta2(j) * aj1] * b1(o3) = 0.02 * 0.50 = 0.00756
Psi3(1) = argmax[delta2(j) * aj1] = 1
delta3(2) = max[delta2(j) * aj2] * b2(o3) = 0.03 * 0.40 = 0.01008
Psi3(2) = argmax[delta2(j) * aj2] = 1
delta3(3) = max[delta2(j) * aj3] * b3(o3) = 0.02 * 0.70 = 0.01470
Psi3(3) = argmax[delta2(j) * aj3] = 2
delta4(1) = max[delta3(j) * aj1] * b1(o4) = 0.00 * 0.50 = 0.00189
Psi4(1) = argmax[delta3(j) * aj1] = 0
delta4(2) = max[delta3(j) * aj2] * b2(o4) = 0.01 * 0.60 = 0.00302
Psi4(2) = argmax[delta3(j) * aj2] = 1
delta4(3) = max[delta3(j) * aj3] * b3(o4) = 0.01 * 0.30 = 0.00220
Psi4(3) = argmax[delta3(j) * aj3] = 2

所有路径的终结点最大的概率值:
P = 0.003024

最优路径的终结点:
i4 = argmax[deltaT(i)] = 2

最优路径的其他结点:
i3 = Psi4(i4) = 2
i2 = Psi3(i3) = 2
i1 = Psi2(i2) = 3

最优路径是: 3->2->2->2

所以,最优路径$I^=(i_1^,i_2^,i_3^,i_4^*)=(3,2,2,2)$。

习题10.4

  试用前向概率和后向概率推导$$P(O|\lambda)=\sum_{i=1}^N\sum_{j=1}^N\alpha_t(i)a_{ij}b_j(o_{t+1})\beta_{t+1}(j),\quad t=1,2,\cdots,T-1$$

解答:

解答思路:

  1. 将$P(O|\lambda)$按照定义展开,即$P(O|\lambda) = P(o_1,o_2,…,o_T|\lambda)$
  2. 假设在时刻$t$状态为$q_i$的条件下,将概率拆分为两个条件概率(前向概率、后向概率)
  3. 在后向概率中,假设在时刻$t+1$状态为$q_j$,继续拆分为两个条件概率($t+1$时刻的概率和$t+2$至$T$的概率)
  4. 将$t+1$时刻的概率拆分为$t+1$时刻的观测概率和状态转移概率
  5. 按照前向概率和后向概率定义,使用$\alpha,\beta,a,b$来表示

解答步骤:

第1步:$P(O|\lambda)$展开推导

$$\begin{aligned}
P(O|\lambda)
&= P(o_1,o_2,…,o_T|\lambda) \
&= \sum_{i=1}^N P(o_1,..,o_t,i_t=q_i|\lambda) P(o_{t+1},..,o_T|i_t=q_i,\lambda) \
&= \sum_{i=1}^N \sum_{j=1}^N P(o_1,..,o_t,i_t=q_i|\lambda) P(o_{t+1},i_{t+1}=q_j|i_t=q_i,\lambda)P(o_{t+2},..,o_T|i_{t+1}=q_j,\lambda) \
&= \sum_{i=1}^N \sum_{j=1}^N [P(o_1,..,o_t,i_t=q_i|\lambda) P(o_{t+1}|i_{t+1}=q_j,\lambda) P(i_{t+1}=q_j|i_t=q_i,\lambda) \
& \quad \quad \quad \quad P(o_{t+2},..,o_T|i_{t+1}=q_j,\lambda)] \
\end{aligned}$$

第2步:前向概率和后向概率

根据书中第198页前向概率:

  给定隐马尔可夫模型$\lambda$,定义到时刻$t$部分观测序列为$o_1, o_2, \cdots, o_t$且状态为$q_i$的概率为前向概率,记作

$$
\alpha_t(i) = P(o_1, o_2, \cdots, o_t, i_t=q_i | \lambda)
$$

可以递推地求得前向概率$\alpha_t(i)$及观测序列概率$P(O|\lambda)$

根据书中第201页后向概率:

  给定隐马尔可夫模型$\lambda$,定义在时刻$t$状态为$q_i$的条件下,从$t+1$到$T$的部分观测序列为$o_{t+1}, o_{t+2}, \cdots, o_T$的后向概率,记作

$$
\beta_t(i) = P(o_{t+1}, o_{t+2}, \cdots, o_T| | i_t=q_i , \lambda)
$$

可以用递推的方法求得后向概率$\beta_t(i)$及观测序列概率$P(O|\lambda)$

第3步:用$\alpha,\beta,a,b$表示

根据书中第193~194页状态转移概率矩阵公式(10.2)和观测概率矩阵公式(10.4)的定义:
$$
a_{ij} = P(i_{t+1}=q_j | i_t = q_i), \quad i=1,2,\cdots,N; \quad j = 1,2,\cdots, N \
b_j(k) = P(o_t = v_k | i_t = q_j), \quad k=1,2, \cdots, M; \quad j=1,2,\cdots,N
$$


$$
\begin{aligned}
P(O|\lambda)
&= \sum_{i=1}^N \sum_{j=1}^N [P(o_1,..,o_t,i_t=q_i|\lambda) P(o_{t+1}|i_{t+1}=q_j,\lambda) P(i_{t+1}=q_j|i_t=q_i,\lambda) \
& \quad \quad \quad \quad P(o_{t+2},..,o_T|i_{t+1}=q_j,\lambda)] \
&= \sum_{i=1}^N \sum_{j=1}^N \alpha_t(i) a_{ij} b_j(o_{t+1}) \beta_{t+1}(j), \quad t=1,2,…,T-1 \
\end{aligned}
$$
命题得证。

习题10.5

  比较维特比算法中变量$\delta$的计算和前向算法中变量$\alpha$的计算的主要区别。

解答:

解答思路:

  1. 列出维特比算法中变量$\delta$的计算;
  2. 列出前向算法中变量$\alpha$的计算;
  3. 比较两个变量计算的主要区别。

解答步骤:

第1步:维特比算法中变量$\delta$的计算

  根据书中第209页维特比算法:

(1)初始化

$$
\delta_1(i)=\pi_ib_i(o_1),i=1,2,\cdots,N
$$

(2)递推,对$t=2,3,\cdots,T$

$$
\delta_t(i)=\max_{1 \leqslant j \leqslant N} [\delta_{t-1}(j)a_{ji}]b_i(o_t), i=1,2,\cdots,N
$$

第2步:前向算法中变量$\alpha$的计算

  根据书中第198页观测序列概率的前向算法:

(1)初值

$$
\alpha_1(i)=\pi_ib_i(o_i),i=1,2,\cdots,N
$$

(2)递推,对$t=1,2,\cdots,T-1$:

$$
\alpha_{t+1}(i)=\left[\sum_{j=1}^N \alpha_t(j) a_{ji} \right]b_i(o_{t+1}),i=1,2,\cdots,N
$$

第3步:比较两个变量计算的主要区别

通过比较两个变量的计算,主要区别包括计算目标不同和关注对象不同两个方面:

  1. 计算目标不同

    • 前向算法:计算长度为$t$的观测序列概率,以某一时刻某一状态为最终状态的概率;
    • 维特比算法:计算长度为$t$的观测序列概率,选择当前观测序列的最大可能概率;
  2. 关注对象不同

    • 前向算法:包含到$t-1$时刻所有可能的观测序列概率和,其中不变的是观测序列上的最终状态;
    • 维特比算法:包含出现观测序列中所有$t$个时刻相应观测的最大概率,其中不变的是整条观测序列。

十一、条件随机场

条件随机场(conditional random field,CRF)是给定一组输入随机变量条件下另一组输出随机变量的条件概率分布模型,其特点是假设输出随机变量构成马尔可夫随机场,条件随机场可以用于不同的预测问题。因此主要讲述线性链( linear chain)条件随机场,这时,问题变成了由输入序列对输出序列预测的判别模型,形式为对数线性模型,其学习方法通常是极大似然估计或正则化的极大似然估计,线性链条件随机场应用于标注问题是由Lafferty等人于2001年提出的。

11.1 概率无向图模型

11.1.1 模型定义

局部马尔可夫性

全局马尔可夫性

定义11.1(概率无向图模型)设有联合概率分布$P(Y)$,由无向图$G=(V,E)$表示,在图G中,结点表示随机变量,边表示随机变量之间的依赖关系。如果联合概率分布$P(Y)$满足成对、局部或全局马尔可夫性,就称此联合概率分布为概率无向图模型(probability undirected graphical model),或马尔可夫随机场(Markovr random field)

11.1.2 概率无向图模型的因子分解

定义11.2(团与最大团)无向图G中任何两个结点均有边连接的结点子集称为团(clique).若C是无向图G的一个团,并且不能再加进任何一个G的结点使其成为一个更大的团,则称此C为最大团(maximal clique).

定理11.1(Hammersley- Clifford定理)

11.2 条件随机场的定义与形式

11.2.1 条件随机场的定义

定义11.3(条件随机场)

定义11.4(线性链条件随机场)

11.2.2 条件随机场的参数化形式

定理11.2(线性链条件随机场的参数化形式)

11.2.3 条件随机场的简化形式

条件随机场可以写成向量w与$F(y,x)$的内积的形式:
$$
\begin{aligned}
P_{w}(y \mid x)=\frac{\exp (w \cdot F(y, x))}{Z_{w}(x)}
\end{aligned}
$$
其中,
$$
Z_{w}(x)=\sum_{y} \exp (w \cdot F(y, x))
$$

11.2.4 条件随机场的矩阵形式

11.3 条件随机场的概率计算问题

11.3.1 前向-一后向算法

11.3.2 概率计算

11.3.3 期望值的计算

11.4 条件随机场的学习算法

11.4.1 改进的迭代尺度法

算法11.1(条件随机场模型学习的改进的迭代尺度法)

11.4.2 拟牛顿法

算法11.2 (条件随机场模型学习的BFGS算法)

11.5 条件随机场的预测算法

算法11.3(条件随机场预测的维特比算法)

一些解释:

习题

习题11.1

  写出图11.3中无向图描述的概率图模型的因子分解式。

解答:

解答思路:

  1. 给出无向图中团与最大团的定义;
  2. 给出概率无向图模型的因子分解的定义;
  3. 计算概率无向图模型的因子分解式。

解答步骤:

第1步:无向图中团与最大团的定义

  根据书中第217页团与最大团的定义:

  无向图$G$中任意两个结点均有边连接的结点子集称为团(clique),若$C$是无向图$G$的一个团,并且不能再加进任何一个$G$的结点使其成为一个更大的团,则称此$C$为最大团(maximal clique)。

第2步:概率无向图模型的因子分解的定义

  根据书中第218页概率无向图模型的因子分解定义:

  将概率无向图模型的联合概率分布表示为其最大团上的随机变量的函数的乘积形式的操作,称为概率无向图模型的因子分解(factorization)。

  根据书中第218页概率无向图模型的联合概率分布:

  给定概率无向图模型,设其无向图为$G$,$C$为$G$上的最大团,$Y_C$表示$C$对应的随机变量。那么概率无向图模型的联合概率分布$P(Y)$可写作图中所有最大团$C$上的函数$\Psi_C(Y_C)$的乘积形式,即

$$
P(Y) = \frac{1}{Z} \prod_C \Psi_C(Y_C)
$$

其中,$Z$是规范化因子(normaliztion factor),由式

$$
Z = \sum_Y \prod_C \Psi_C(Y_C)
$$

给出。

第3步:计算概率无向图模型的因子分解式

由图11.3可知,该图是由4个结点组成的无向图,结点分别为$Y_1, Y_2, Y_3, Y_4$,根据第1步的团和最大团定义,可得:

  1. 图中由2个结点组成的团有5个:${Y_1,Y_2},{Y_2,Y_3},{Y_3,Y_4},{Y_4,Y_2}$和${Y_1,Y_3}$
  2. 图中包括2个最大团:${Y_1,Y_2,Y_3}$和${Y_2,Y_3,Y_4}$
  3. 由于$Y_1$和$Y_4$没有边连接,所以${Y_1,Y_2,Y_3,Y_4}$不是一个团。

根据第2步中概率图模型的因子分解定义和联合概率分布的计算公式,可得因子分解:
$$
P(Y)=\frac{\Psi_{(1,2,3)}(Y_{(1,2,3)})\cdot\Psi_{(2,3,4)}(Y_{(2,3,4)})}
{\displaystyle \sum_Y \left[ \Psi_{(1,2,3)}(Y_{(1,2,3)})\cdot\Psi_{(2,3,4)}(Y_{(2,3,4)})\right]}
$$

习题11.2

  证明$Z(x)=a_n^T(x) \cdot \boldsymbol{1} = \boldsymbol{1}^T \cdot \beta_0(x)$,其中$\boldsymbol{1}$是元素均为1的$m$维列向量。

解答:

解答思路:

  1. 给出$Z(x)$的定义公式;
  2. 根据书中第225页前向-后向算法,推导$\alpha_n^T(x)$和$\beta_0(x)$;
  3. 证明$Z(x) = \alpha_n^T(x) \cdot \boldsymbol{1}$
  4. 证明$Z(x) = \boldsymbol{1}^T\cdot\beta_0(x)$

解答步骤:

第1步:给出$Z(x)$的定义式

  根据书中第223页条件随机场的矩阵形式:

  假设$P_w(y|x)$是由式(11.15)~式(11.16)给出的线性链条件随机场,表示对给定观测序列$x$,相应的标记序列$y$的条件概率。对每个标记序列引进特殊的起点和终点状态标记$y_0 = \text{start}$和$y_{n+1} = \text{stop}$,这时标注序列的概率$P_w(y|x)$可以通过矩阵形式表示并有效计算。
  对观测序列$x$的每一个位置$i=1,2,\cdots, n+1$,由于$y_{i-1}$和$y_i$在$m$个标记中取值,可以定义一个$m$阶矩阵随机变量

$$
M_i(x) = [M_i(y_{i-1}, y_i | x)]
$$

条件概率$P_w(y|x)$是

$$
P_w(y|x) = \frac{1}{Z_w(x)} \prod_{i=1}^{n+1} M_i(y_{i-1}, y_i | x)
$$

其中,$Z_w(x)$为规范化因子,是$n+1$个矩阵的乘积的(start, stop)元素,即

$$
Z_w(x) = \left[M_1(x)M_2(x)\cdots M_{n+1}(x)\right]_{\text{start}, \text{stop} }
$$

注意,$y_0 = \text{start}$与$y_{n+1} = \text{stop}$表示开始状态与终止状态,规范化因子$Z_w(x)$是以start为起点stop为终点,通过状态的所有路径$y_1 y_2 \cdots y_n$的非规范化概率$\displaystyle \prod_{i=1}^{n+1} M_i(y_{i-1}, y_i | x)$之和。

第2步:给出$\alpha_n^T(x)$和$\beta_1(x)$的定义式

  根据书中第225页前向-后向算法:

  对每个指标$i=0,1,\cdots, n+1$,定义前向向量$\alpha_i(x)$:

$$
\alpha_0(y|x)=\left { \begin{array}{ll}
1, & y=\text{start} \
0, & 否则
\end{array} \right.
$$

递推公式为:

$$
\alpha_i^T (y_i|x) = \alpha_{i-1}^T (y_{i-1}|x) M_i(y_{i-1},y_i|x), \quad i=1, 2, \cdots, n+1
$$

又可表示为

$$
\alpha_i^T (x) = \alpha_{i-1}^T(x) M_i(x)
$$

$\alpha_i(y_i|x)$表示在位置$i$的标记是$y_i$,并且从1到$i$的前部分标记序列的非规范化概率,$y_i$可取的值有$m$个,所以$\alpha_i(x)$是$m$维列向量。

  根据书中第225页前向-后向算法:

  对每个指标$i=0,1,\cdots, n+1$,定义后向向量$\beta_i(x)$:

$$
\beta_{n+1}(y_{n+1}|x) = \left {
\begin{array}{ll}
1, & y_{n+1}=\text{stop} \
0, & 否则
\end{array} \right.
$$

$$
\beta_i(y_i|x) = [M_{i+1}(y_i, y_{i+1} | x)] \beta_{i+1}(y_{i+1} | x)
$$

又可表示为

$$
\beta_i(x) = M_{i+1}(x) \beta_{i+1}(x)
$$

$\beta_i(y_i | x)$表示在位置$i$的标记为$y_i$,并且从$i+1$到$n$的后部分标记序列的非规范化概率。

  根据参考文献 Shallow Parsing with Conditional Random Fields 中的第2章Conditional Random Fields:
$$
\alpha_i = \left {
\begin{array}{ll}
\alpha_{i-1} M_i, & 0 < i \leqslant n \
\boldsymbol{1}, & i = 0
\end{array}
\right.
$$

$$
\beta_i^T = \left {
\begin{array}{ll}
M_{i+1} \beta_{i+1}^T, & 1 \leqslant i < n \
\boldsymbol{1}, & i = n
\end{array}
\right.
$$

  由上述可得:

  1. 当观测序列$x$有n个结点时,有

$$
\alpha_{n}^T(x) = \alpha_0^T(x) M_1(x)\cdots M_{n}(x)
$$

其中$y_0=\text{start}$和$y_{n}=\text{stop}$分别表示开始状态和终止状态,且$Z(x)=[M_1(x) M_2(x) \cdots M_n(x)]_{\text{start, stop} }$

  1. 当观测序列$x$有n-1个结点时,有

$$
\beta_1(x) = M_2(x)\cdots M_{n}(x)\beta_{n}(x)
$$

其中$y_1=\text{start}$和$y_{n}=\text{stop}$分别表示开始状态和终止状态,且$Z(x)=[M_2(x) \cdots M_n(x)]_{\text{start, stop} }$

第3步:证明$Z(x) = \alpha_{n}^T(x) \cdot \boldsymbol{1}$

$ \because \alpha_0(y|x)=\left { \begin{array}{ll}
1, & y_0 = \text{start} \
0, & 否则
\end{array} \right.$

$ \begin{aligned}
\therefore \alpha_n^T(x) \cdot \boldsymbol{1}
&= \alpha_0^T(x) M_1(x)\cdots M_{n}(x) \cdot \boldsymbol{1} \
&= \boldsymbol{1}^T \cdot M_1(x)\cdots M_{n}(x) \cdot \boldsymbol{1} \
&= Z(x)
\end{aligned}$

第4步:证明$Z(x)=\boldsymbol{1}^T \cdot \beta_1(x)$

$\because \beta_{n}(y_{n}|x) = \left {
\begin{array}{ll}
1, & y_{n}=\text{stop} \
0, & 否则
\end{array} \right.$

$\begin{aligned}
\therefore \boldsymbol{1}^T \cdot \beta_1(x)
&= \boldsymbol{1}^T \cdot M_2(x) \cdots M_{n}(x) \beta_{n}(x) \
&= \boldsymbol{1}^T \cdot M_2(x) \cdots M_{n}(x) \cdot \boldsymbol{1} \
&= Z(x)
\end{aligned}$

综上所述:$Z(x)=a_{n}^T(x) \cdot \boldsymbol{1} = \boldsymbol{1}^T \cdot \beta_1(x)$,命题得证。

习题11.3

  写出条件随机场模型学习的梯度下降法。

解答:

解答思路:

  1. 给出条件随机场模型的对数似然函数;
  2. 写出条件随机场模型学习的梯度下降法。

解答步骤:

第1步:条件随机场模型的对数似然函数

  根据书中第220页线性链条件随机场的参数化形式:

设$P(Y|X)$为线性链条件随机场,则在随机变量$X$取值为$x$的条件下,随机变量$Y$取值为$y$的条件概率具有如下形式:

$$
P(y|x) = \frac{1}{Z(x)} \exp \left( \sum_{i,k} \lambda_k t_k (y_{i-1}, y_i, x, i) + \sum_{i,l} \mu_l s_l (y_i, x, i) \right)
$$

其中,

$$
Z(x) = \sum_y \exp \left( \sum_{i,k} \lambda_k t_k (y_{i-1}, y_i, x, i) + \sum_{i,l} \mu_l s_l (y_i, x, i) \right)
$$

式中,$t_k$和$s_l$是特征函数,$\lambda_k$和$\mu_l$是对应的权值。$Z(x)$是规范化因子,求和是在所有可能的输出序列上进行的。

  根据书中第221页~第222页条件随机场的简化形式:

设有$K_1$个转移特征,$K_2$个状态特征,$K=K_1 + K_2$,记

$$
f_k(y_{i-1}, y_i, x, i) = \left { \begin{array}{ll}
t_k(y_{i-1}, y_i, x, i), & k=1,2,\cdots,K_1 \
s_l(y_i, x, i), & k=K_1 + l; \quad l = 1,2,\cdots,K_2
\end{array} \right.
$$

然后,对转移特征与状态特征在各个位置$i$求和,记作

$$
f_k(y,x) = \sum_{i=1}^n f_k(y_{i-1}, y_i, x, i), \quad k=1,2, \cdots, K
$$

用$w_k$表示特征$f_k(y,x)$的权值,即

$$
w_k = \left { \begin{array}{ll}
\lambda_k, & k=1,2,\cdots, K_1 \
\mu_l, & k=K_1 + l; \quad l = 1,2,\cdots,K_2
\end{array}\right.
$$

于是,条件随机场可表示为

$$
P(y|x) = \frac{1}{Z(x)} \exp \sum_{k=1}^K w_k f_k(y,x) \
Z(x) = \sum_y \exp \sum_{k=1}^K w_k f_k(y,x)
$$

  根据书中第227页条件随机场模型的对数似然函数:

当$P_w$是一个由式(11.15)和(11.16)给出的条件随机场模型时,对数似然函数为

$$
L(w)=\sum^N_{j=1} \sum^K_{k=1} w_k f_k(y_j,x_j)-\sum^N_{j=1} \log{Z_w(x_j)}
$$

  将对数似然函数求偏导,可得
$$
\begin{aligned}
g(w^{(n)}) = \frac{\partial L(w)}{\partial w^{(n)} }
&= \sum^N_{j=1} f_n(y_j,x_j) - \sum^N_{j=1} \frac{1}{Z_w(x_j)} \cdot \frac{\partial{Z_w(x_j)} }{\partial{w_n} }\
&= \sum^N_{j=1} f_n(y_j,x_j) - \sum^N_{i=1} \frac{1}{Z_w(x_i)} \cdot \sum^N_{j=1} \left[ \left( \exp{\sum^K_{k=1} w_k f_k (y_j,x_i)} \right) \cdot f_n(y_j, x_i) \right]
\end{aligned}
$$
  梯度函数为
$$
\nabla L(w)=\left[\frac{\partial L(w)}{\partial w^{(0)} }, \cdots, \frac{\partial L(w)}{\partial w^{(N)} }\right]
$$
第2步:写出条件随机场模型学习的梯度下降法

  根据书中第439页附录A 梯度下降法的算法:

输入:目标函数$f(x)$,梯度函数$g(x)= \nabla f(x)$,计算精度$\varepsilon$;
输出:$f(x)$的极小值$x^$。
(1) 取初始值$x^{(0)} \in R^n$,置$k=0$
(2) 计算$f(x^{(k)})$
(3) 计算梯度$g_k=g(x^{(k)})$,当$|g_k| < \varepsilon$时,停止迭代,令$x^
= x^{(k)}$;否则,令$p_k=-g(x^{(k)})$,求$\lambda_k$,使

$$
\displaystyle f(x^{(k)}+\lambda_k p_k) = \min \limits_{\lambda \geqslant 0}f(x^{(k)}+\lambda p_k)
$$

(4) 置$x^{(k+1)}=x^{(k)}+\lambda_k p_k$,计算$f(x^{(k+1)})$
当$|f(x^{(k+1)}) - f(x^{(k)})| < \varepsilon$或 $|x^{(k+1)} - x^{(k)}| < \varepsilon$时,停止迭代,令$x^* = x^{(k+1)}$
(5) 否则,置$k=k+1$,转(3)

条件随机场模型学习的梯度下降法:

输入:目标函数$f(w)$,梯度函数$g(w) = \nabla f(w)$,计算精度$\varepsilon$
输出:$f(w)$的极大值$w^$
(1) 取初始值$w^{(0)} \in R^n$,置$k=0$
(2) 计算$f(w^{(n)})$
(3) 计算梯度$g_n=g(w^{(n)})$,当$|g_n| < \varepsilon$时,停止迭代,令$w^
= w^{(n)}$;否则,令$p_n=-g(w^{(n)})$,求$\lambda_n$,使
$$
\displaystyle f(w^{(n)}+\lambda_n p_n) = \max_{\lambda \geqslant 0}f(w^{(n)}+\lambda p_n)
$$
(4) 置$w^{(n+1)}=w^{(n)}+\lambda_n p_n$,计算$f(w^{(n+1)})$
当$|f(w^{(n+1)}) - f(w^{(n)})| < \varepsilon$或 $|w^{(n+1)} - w^{(n)}| < \varepsilon$时,停止迭代,令$w^* = w^{(n+1)}$
(5) 否则,置$n=n+1$,转(3)

习题11.4

参考图11.6的状态路径图,假设随机矩阵$M_1(x),M_2(x),M_3(x),M_4(x)$分别是
$$
M_1(x)=\begin{bmatrix}0&0 \ 0.5&0.5 \end{bmatrix} ,
M_2(x)=\begin{bmatrix}0.3&0.7 \ 0.7&0.3\end{bmatrix} \
M_3(x)=\begin{bmatrix}0.5&0.5 \ 0.6&0.4\end{bmatrix},
M_4(x)=\begin{bmatrix}0&1 \ 0&1\end{bmatrix}
$$
求以$\text{start}=2$为起点$\text{stop}=2$为终点的所有路径的状态序列$y$的概率及概率最大的状态序列。

解答:

解答思路:

  根据书中第223页条件随机场的矩阵形式,以及例题11.2,通过自编程实现计算所有路径状态序列的概率及概率最大的状态序列。

解答步骤:

import numpy as np


class CRFMatrix:
    def __init__(self, M, start, stop):
        # 随机矩阵
        self.M = M
        #
        self.start = start
        self.stop = stop
        self.path_prob = None

    def _create_path(self):
        """按照图11.6的状态路径图,生成路径"""
        # 初始化start结点
        path = [self.start]
        for i in range(1, len(self.M)):
            paths = []
            for _, r in enumerate(path):
                temp = np.transpose(r)
                # 添加状态结点1
                paths.append(np.append(temp, 1))
                # 添加状态结点2
                paths.append(np.append(temp, 2))
            path = paths.copy()

        # 添加stop结点
        path = [np.append(r, self.stop) for _, r in enumerate(path)]
        return path

    def fit(self):
        path = self._create_path()
        pr = []
        for _, row in enumerate(path):
            p = 1
            for i in range(len(row) - 1):
                a = row[i]
                b = row[i + 1]
                # 根据公式11.24,计算条件概率
                p *= M[i][a - 1][b - 1]
            pr.append((row.tolist(), p))
        # 按照概率从大到小排列
        pr = sorted(pr, key=lambda x: x[1], reverse=True)
        self.path_prob = pr

    def print(self):
        # 打印结果
        print("以start=%s为起点stop=%s为终点的所有路径的状态序列y的概率为:" % (self.start, self.stop))
        for path, p in self.path_prob:
            print("    路径为:" + "->".join([str(x) for x in path]), end=" ")
            print("概率为:" + str(p))
        print("概率最大[" + str(self.path_prob[0][1]) + "]的状态序列为:",
              "->".join([str(x) for x in self.path_prob[0][0]]))
# 创建随机矩阵
M1 = [[0, 0], [0.5, 0.5]]
M2 = [[0.3, 0.7], [0.7, 0.3]]
M3 = [[0.5, 0.5], [0.6, 0.4]]
M4 = [[0, 1], [0, 1]]
M = [M1, M2, M3, M4]
# 构建条件随机场的矩阵模型
crf = CRFMatrix(M=M, start=2, stop=2)
# 得到所有路径的状态序列的概率
crf.fit()
# 打印结果
crf.print()
以start=2为起点stop=2为终点的所有路径的状态序列y的概率为:
    路径为:2->1->2->1->2 概率为:0.21
    路径为:2->2->1->1->2 概率为:0.175
    路径为:2->2->1->2->2 概率为:0.175
    路径为:2->1->2->2->2 概率为:0.13999999999999999
    路径为:2->2->2->1->2 概率为:0.09
    路径为:2->1->1->1->2 概率为:0.075
    路径为:2->1->1->2->2 概率为:0.075
    路径为:2->2->2->2->2 概率为:0.06
概率最大[0.21]的状态序列为: 2->1->2->1->2

十二、统计学习方法总结

本书共介绍了10种主要的统计学习方法:感知机、k近邻法、朴素贝叶斯法、决策树、逻辑斯谛回归与最大熵模型、支持向量机、提升方法、EM算法、隐马尔可夫模型和条件随机场。

附录

A 梯度下降法

B 牛顿法与拟牛顿法

C 拉格朗日对偶性

参考


文章作者: 杰克成
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 杰克成 !
评论
  目录