第17章 多种降维方法

17 降维简介

当特征选择完成后,可以直接训练模型了,但是可能由于特征矩阵过大,导致计算量大,训练时间长的问题,因此降低特征矩阵维度也是必不可少的。常见的降维方法除了以上提到的基于L1惩罚项的模型以外,另外还有主成分分析法(PCA)和线性判别分析(LDA),线性判别分析本身也是一个分类模型。PCA和LDA有很多的相似点,其本质是要将原始的样本映射到维度更低的样本空间中,但是PCA和LDA的映射目标不一样:PCA是为了让映射后的样本具有最大的发散性;而LDA是为了让映射后的样本有最好的分类性能。所以说PCA是一种无监督的降维方法,而LDA是一种有监督的降维方法。
PCA、LDA降维一般假设数据集为线性可分,如果用这两种方法,对线性不可分的数据集进行降维,效果往往不理想。本质上PCA和LDA还是一种线性变换。而线性不可分数据应该是很普遍的,对线性不可分数据集该如何进行降维呢?这里我们介绍一种核PCA方法,这样降维方法综合了核技巧及PCA思想,对非线性数据集降维有非常好的效果。
此外,这里我们还介绍SVD方法,这也是一种非常有效的降维方法。

17.1 PCA简介

主成分分析(Principal Components Analysis),简称PCA,是一种数据降维技术,用于数据预处理。一般我们获取的原始数据维度都很高,比如1000个特征,在这1000个特征中可能包含了很多无用的信息或者噪声,真正有用的特征才50个或更少,那么我们可以运用PCA算法将1000个特征降到50个特征。这样不仅可以去除无用的噪声,还能减少很大的计算量。
PCA算法是如何实现的?
简单来说,就是将数据从原特征空间转换到新的特征空间中,例如原始的空间是三维的(x,y,z),x、y、z分别是原始空间的三个基,我们可以通过某种方法,用新的坐标系(a,b,c)来表示原始的数据,那么a、b、c就是新的基,它们组成新的特征空间。在新的特征空间中,可能所有的数据在c上的投影都接近于0,即可以忽略,那么我们就可以直接用(a,b)来表示数据,这样数据就从三维的(x,y,z)降到了二维的(a,b)。
问题是如何求新的基(a,b,c)?
一般步骤是这样的:
1)对原始数据集做标准化处理。
2)求协方差矩阵。
3)计算协方差矩阵的特征值和特征向量。
4)选择前k个最大的特征向量,k小于原数据集维度。
5)通过前k个特征向量组成了新的特征空间,设为W。
6)通过矩阵W,把原数据转换到新的k维特征子空间。

17.2 PCA算法实现

这里以葡萄酒数据为例,数据集特征如下:

数据来源于:https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data
1)对原数据集做标准化处理
导入需要的库及数据

部分内容:

为便于后续处理,把数据集分为训练集和测试集,划分比例为7:3

对原数据进行标准化处理

2) 求协方差矩阵
这里使用numpy.cov函数,求标准化后数据的协方差矩阵

3)计算协方差矩阵的特征值和特征向量
使用np.linalg.eig函数,求协方差的特征值和特征向量

得到13个特征向量:
Eigenvalues
[ 4.8923083 2.46635032 1.42809973 1.01233462 0.84906459 0.60181514 0.52251546 0.08414846 0.33051429 0.29595018 0.16831254 0.21432212 0.2399553 ]
要实现降维,我们可以选择前k个最多信息(或方差最大)特征向量组成新的子集,由于特征值的大小决定了特征向量的重要性,因此,可以通过对特征值的排序,获取前k个特征值。特征值λ_i的方差贡献率是指特征值λ_i与所有特征值和的比例:

我们可以通过numpy.cumsum函数计算累计方差。

从这个图可以看出第一个主成分占了方差总和的40%左右,前两个主成分占了近60%。
4)选择前k个最大的特征向量,k小于原数据集维度
首先,按特征值按降序排序

5)通过前k个特征向量组成了新的特征空间,设为W。
为便于数据可视化,这里我们取k=2,实际上前2个特征值已占了总方差的近60%。

这样我们就可得到一个由这两个特征向量构成的13*2矩阵W:
Matrix W:
[[ 0.14669811 0.50417079]
[-0.24224554 0.24216889]
[-0.02993442 0.28698484]
[-0.25519002 -0.06468718]
[ 0.12079772 0.22995385]
[ 0.38934455 0.09363991]
[ 0.42326486 0.01088622]
[-0.30634956 0.01870216]
[ 0.30572219 0.03040352]
[-0.09869191 0.54527081]
[ 0.30032535 -0.27924322]
[ 0.36821154 -0.174365 ]
[ 0.29259713 0.36315461]]

6)通过矩阵W,把原数据转换到新的k维特征子空间
通过这个特征矩阵W,把原样本x转换到PCA的子空间上,得到一个新样本x^,。
x^,=xW
训练集与W点积后,把这个训练集转换到包括两个主成分的子空间上。然后,把子空间的数据可视化。

从以上图形可以看出,大部分数据沿PC1方向分布,而且可以线性划分,在可视化图形时,为便于标识点,这里采用了y_train标签信息。

我们用来6步来实现PCA,这个过程还是比较麻烦的,是否有更简单的方法呢?
有的,接下来我们介绍利用Scikit-learn中PCA类进行降维。

17.3 利用Scikit-learn进行主成分分析

我们将使用Scikit-learn中PCA对数据集进行预测处理,然后使用逻辑斯谛回归对转换后的数据进行分类,最后对数据进行可视化。
1)数据预处理

得到主成分数据:
array([ 0.37329648, 0.18818926, 0.10896791, 0.07724389, 0.06478595, 0.04592014, 0.03986936, 0.02521914, 0.02258181, 0.01830924, 0.01635336, 0.01284271, 0.00642076])

2)可视化主成分方差贡献率图

3)获取前2个主成分

5)利用回归模型对数据进行分类。

6)为了更好看到分类后情况,这里我们定义一个函数plot_decision_regions,通过这个函数对决策区域数据可视化。

7)把训练数据转换到前两个主成分轴后生成决策区域图形

对高维数据集进行降维除了PCA方法,还有线性判别分析(Linear Discriminant Analysis, LDA)、决策树、核主成分分析、SVD等等。

17.4 LDA 降维

LDA的基本概念与PCA类似,PCA是在数据集中找到方差最大的正交的主成分分量的轴。而LDA的目标是发现可以最优化分类的特征子空间。两者都是可以用于降维的线性转换方法,其中,PCA是无监督算法,LDA是监督算法。与PCA相比,LDA是一种更优越的用于分类的特征提取技术。
LDA的主要步骤:
(1)对d维数据集进行标准化处理(d为特征数量)
(2)对每一类别,计算d维的均值向量
(3)构造类间的散布矩阵S_B以及类内的散布矩阵S_W
(4)计算矩阵〖S_W〗^(-1) S_B的特征值所对应的特征向量,
(5)选取前k个特征值对应的特征向量,构造一个d x k维的转换矩阵W,其中特征向量以列的形式排列
(6)使用转换矩阵W将样本映射到新的特征子空间上.
以下还是以下葡萄酒数据为例,用代码实现以上各步:
(1)对d维数据集进行标准化处理

(2)对每一类别,计算d维的均值向量

运行结果
MV 1: [ 0.9259 -0.3091 0.2592 -0.7989 0.3039 0.9608 1.0515 -0.6306 0.5354 0.2209 0.4855 0.798 1.2017]

MV 2: [-0.8727 -0.3854 -0.4437 0.2481 -0.2409 -0.1059 0.0187 -0.0164 0.1095 -0.8796 0.4392 0.2776 -0.7016]

MV 3: [ 0.1637 0.8929 0.3249 0.5658 -0.01 -0.9499 -1.228 0.7436 -0.7652 0.979 -1.1698 -1.3007 -0.3912]
(3)构造类间的散布矩阵S_B以及类内的散布矩阵S_W
通过均值向量计算类内散布矩阵Sw:

通过累加各类别i的散布矩阵Si来计算:

运行结果
Within-class scatter matrix: 13x13

计算各类标样本数

运行结果为:
Class label distribution: [40 49 35]
由此看出,各类记录数不很均匀,为此,需要对SB进行归一化处理:

运行结果
Scaled within-class scatter matrix: 13x13

计算类间散布矩阵:

运行结果
Between-class scatter matrix: 13x13

(5)选取前k个特征值对应的特征向量,构造一个d x k维的转换矩阵W,其中特征向量以列的形式排列

求得广义特征值之后,按照降序对特征值排序

运行结果
Eigenvalues in decreasing order:

452.721581245
156.43636122
7.05575044266e-14
5.68434188608e-14
3.41129233161e-14
3.40797229523e-14
3.40797229523e-14
1.16775565372e-14
1.16775565372e-14
8.59477909861e-15
8.59477909861e-15
4.24523361436e-15
2.6858909629e-15
d x d维协方差矩阵的秩最大为d-1,得到两个非0的特征值。
与PCA一样,我们可视化各特征贡献率

运行结果

(6)使用转换矩阵W将样本映射到新的特征子空间上.
由上面两个新得到两个特征构成一个新矩阵

d x d维协方差矩阵的秩最大为d-1,得到两个非0的特征值。Matrix W:
[[-0.0662 -0.3797]
[ 0.0386 -0.2206]
[-0.0217 -0.3816]
[ 0.184 0.3018]
[-0.0034 0.0141]
[ 0.2326 0.0234]
[-0.7747 0.1869]
[-0.0811 0.0696]
[ 0.0875 0.1796]
[ 0.185 -0.284 ]
[-0.066 0.2349]
[-0.3805 0.073 ]
[-0.3285 -0.5971]]
将样本映射到新的特征空间

运行结果

17.5 利用Scikit-learn进行LDA分析

下面我们利用scikit-learn中对LDA类的实现
这里先定义一个函数,后面需要用到

对数据先LDA处理,然后用逻辑回归进行分类。

运行结果

还有几个点划分错误,下面通过正则化,效果将更好

运行结果

17.6使用核PCA降维

前面我们介绍了两种降维方法,PCA及LDA.这两种方法,如果用于线性不可分数据集上进行分类,效果往往不很理想,原因是通过他们无法把线性不可分数据集变为线性可分数据集。如果遇到线性不可分数据集(这样的数据集往往比较普遍),有什么好方法,既降维,又可把线性不可分数据集变为线性可分数据集?
在SVM中,我们了解到核函数的神奇,把可以通过把线性不可分的数据集映射到一个高维空间,变得线性可分。基于这点,如果我们在降维时也采用核技术是否也可以呢?可以的,这就是接下来我们要介绍的内容---核PCA.
核PCA=核技术+PCA,具体步骤如下:
(1)计算核矩阵,也就是计算任意两个训练样本。这里以向基核函数(RBF)为例
经向基函数核(又称高斯核)为:

得到以下矩阵:

(2)对核矩阵K进行中心化处理

其中,是n*n的矩阵,n=训练集样本数,中每个元素都等于.l_n中的每个元素都是1/n
(3)求核矩阵的特征向量,并按降序排列,提取前k个特征向量。
不同于标准PCA,这里的特征向量并不是主成分轴。
下面我们根据以上三个步骤,实现一个核PCA。借助SciPy和NumPy,其实实现核PCA很简单。

下面以一分离同心数据集为例,分别用PCA和核PCA对数据集进行处理,然后处理后的结果,具体请看以下代码及生成的图形:

这是典型线性不可数据集,现在我们分别用PCA及核PCA进行处理。
(1)用PCA处理,然后进行分类

(2)用核PCA处理,然后进行分类

(3)使用sklearn实现核PCA
源数据的图形为

这里通过核PCA把该数据变为线性可分数据集,实现代码如下:

17.7 SVD矩阵分解

(1)SVD奇异值分解的定义
假设有一个mxn矩阵,如果存在一个分解

其中U为的mxm酉矩阵,∑为mxn的半正定对角矩阵,除了对角元素不为0,其他元素都为0,并且对角元素是从大到小排列的,前面的元素比较大,后面的很多元素接近0。这些对角元素就是奇异值。V^T为V的共轭转置矩阵,且为nxn的酉矩阵。这样的分解称为的奇异值分解,对角线上的元素称为奇异值,U称为左奇异矩阵,V^T称为右奇异矩阵。
SVD在信息检索(隐性语义索引)、图像压缩、推荐系统、金融等领域都有应用。
(2)SVD奇异值分解与特征值分解的关系
特征值分解与SVD奇异值分解的目的都是提取一个矩阵最重要的特征。然而,特征值分解只适用于方阵,而SVD奇异值分解适用于任意的矩阵,不一定是方阵。

这里M^T M和MM^T都是方阵,UU^T和VV^T都是单位矩阵,V是M^T M的特征向量,U是MM^T的特征向量。
(3)SVD奇异值分解的作用和意义
奇异值分解最大的作用就是数据的降维,当然,还有其他很多的作用,这里主要讨论数据的降维,对于mxn的M矩阵,进行奇异值分解

取其前k个非零奇异值,可以还原原来的矩阵,即前k个非零奇异值对应的奇异向量代表了矩阵的主要特征。可以表示为

17.8 用Python实现SVD,并用于图像压缩
(1)首先读取一张图片(128*128*3):

(2)然后可以利用python的numpy库对彩色图像的3个通道进行SVD分解

(3)然后便可以根据需要压缩图像(丢弃分解出来的三个矩阵中的数据),利用的奇异值个数越少,则压缩的越厉害。下面来看一下不同程度压缩后,重构图像的清晰度:

(4)其中restore函数定义为

第16章 回归模型(分别用解方程、迭代、自动求导等方法求权重参数)

16.1 目的

回归分析(regression analysis)是确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。运用十分广泛,回归分析按照涉及的变量的多少,分为一元回归和多元回归分析;按照因变量的多少,可分为简单回归分析和多重回归分析;按照自变量和因变量之间的关系类型,可分为线性回归分析和非线性回归分析。如果在回归分析中,只包括一个自变量和一个因变量,且二者的关系可用一条直线近似表示,这种回归分析称为一元线性回归分析。如果回归分析中包括两个或两个以上的自变量,且自变量之间存在线性相关,则称为多重线性回归分析.

16.2 模型

16.3 问题

假设我们有一组某企业广告费与销售额之间的对应关系的实际数据:对这些数据可视化结果如下:

可视化代码如下:

如果该企业先增加广告费,如20万,那么它可能带来的销售额大概是多少?
解决这个问题的关键就是如何根据已有数据的总体趋势,拟合出一条直线,如图1.1中虚线所示,那么新给出的广告费就可预测其对应的销售额?如何拟合这条直线呢?

16.4 简单入手

假设我们要拟合的这条直线为一元一次直线,表达式为:
y=ax+b (1.1)
要拟合这条直线或确定这条直线,只要求出a和b即可。那如何得到a和b呢?
这条直线要满足什么条件才是最好的或最能体现这些样本的趋势?
如果能使预测值与实际值的差距最小,应该是一个不错的直线。
由此,想到最小二乘法,利用最小二乘法作为损失函数,然后通过使损失函数最小化来求参数a和b。

最损失函数的最小值,而且损失函数为凸函数,故可利用梯度为0来求出参数a和b
具体过程如下:

如此,a,b确定后,自然直线y=ax+b也就确定了,这样便可以根据新的x值,预测其y值了。

16.5、用Python解方程求出a和b

打印结果为:
所求直线为: y=1.980810*x+2.251599

16.6 用迭代方式求参数

以下利用迭代的方法求出参数a和b
直接通过解方程来求参数a和b,如果参数比较多,样本数也很多的情况下,计算量非常大,而且也不现实。因此,我们需要另辟蹊径!
还是以求函数y=x^2的最小值为例,我们可以通过其导数为0来求来求的是y最小的x值;我们也可以通过迭代的方式来求,先从某点开始,如x_0开始,然后沿梯度的方向,不断靠近最小值点,如下图所示:

为啥每次修改的值,都能往函数最小值那个方向前进呢?这里的奥秘在于,我们每次都是向函数的梯度的相反方向来修改。

什么是梯度呢?翻开大学高数课的课本,我们会发现梯度是一个向量,它指向函数值上升最快的方向。显然,梯度的反方向当然就是函数值下降最快的方向了。我们每次沿着梯度相反方向去修改的值,当然就能走到函数的最小值附近。

之所以是最小值附近而不是最小值那个点,是因为我们每次移动的步长不会那么恰到好处,有可能最后一次迭代走远了越过了最小值那个点。步长的选择是门手艺,如果选择小了,那么就会迭代很多轮才能走到最小值附近;如果选择大了,那可能就会越过最小值很远,收敛不到一个好的点上。

按照上面的讨论,我们就可以写出梯度下降算法的公式

打印结果:
参数b的值:2.44
参数a的值:1.95

通过迭代方法求得的参数a,b与通过梯度求得的参数进行比较,发现他们非常接近,有异曲同工之妙!
这种方法也可看作只有一个神经元的神经网络,并且没有激活函数的这种。

16.7 迭代中用矩阵代替循环

这里利用梯度下降更新参数时采用一个循环,循环一般比较耗费资源,如果有一千万个数据,将需要循环一千万次,这是不可接受的,那么我们是否能不用循环?
当然可以,如果用采用矩阵方式,需要进行如下操作:
 把输入X,Y变为矩阵;
 把模型变为矩阵或向量;
 把式(1.13)变为矩阵与向量的点乘
 把式(1.14)变为矩阵的累加
具体代码实现如下:
(1)定义一个线性回归类,在这个类中初始化两个参数,并定义几个函数。

(2)把输入变为10x1矩阵,如何运行以上函数

打印结果:
参数a的值:1.995
参数b的值:2.130
(3)把迭代过程可视化

16.8 用Tensorflow架构实现自动求导,求参数

运行结果
初始权重值w: [-0.99174666]
0 [ 3.35317731] [ 0.51469594]
100 [ 2.17306757] [ 0.62030703]
200 [ 2.14828968] [ 0.83054471]
300 [ 2.12670541] [ 1.01368761]
400 [ 2.10790277] [ 1.17322707]
500 [ 2.09152317] [ 1.31220567]
600 [ 2.07725477] [ 1.43327284]
700 [ 2.06482506] [ 1.53873765]
800 [ 2.05399752] [ 1.63060951]
900 [ 2.0445652] [ 1.71064162]
6.90384
最后权重值w: [ 2.03642535]
最后偏移量b: [ 1.77970874]

16.9 拓展

第15章 K-means聚类(分别用python、sklearn、Tensorflow实现)

15.1 聚类概述

“物以类聚人以群分",现实生活中很多事物都存在类似现象,当然这些现象反映在数据上,就需要我们通过一定算法,找出这些类或族。聚类算法就是解决类似问题而提出的。
聚类就是按照某个特定标准(如距离准则)把一个数据集分割成不同的类或簇,使得同一个簇内的数据对象的相似性尽可能大,同时不在同一个簇中的数据对象的差异性也尽可能地大。即聚类后同一类的数据尽可能聚集到一起,不同数据尽量分离。
目前,有大量的聚类算法。而对于具体应用,聚类算法的选择取决于数据的类型、聚类的目的。如果聚类分析被用作描述或探查的工具,可以对同样的数据尝试多种算法,以发现数据可能揭示的结果。
主要的聚类算法可以划分为如下几类:划分方法、层次方法、基于密度的方法、基于网格的方法以及基于模型的方法
每一类中都存在着得到广泛应用的算法,例如:划分方法中的k-means聚类算法、层次方法中的层次聚类算法、基于模型方法中的高斯混合聚类算法等。
目前,聚类问题的研究不仅仅局限于上述的硬聚类,即每一个数据只能被归为一类,模糊聚类也是聚类分析中研究较为广泛的一个分支。模糊聚类通过隶 属函数来确定每个数据隶属于各个簇的程度,而不是将一个数据对象硬性地归类到某一簇中。目前已有很多关于模糊聚类的算法被提出,如著名的高斯混合聚类等。
下图演示了K-Means进行聚类的迭代过程:


下图为高斯混合聚类迭代过程:

15.2 k-means模型

算法步骤:
(1)首先我们选择一些类/组,并随机初始化它们各自的中心点。
(2)计算每个数据点到中心点的距离,数据点距离哪个中心点最近就划分到哪一类中。
(3)重新计算每一类中心点作为新的中心点,各中心点求每个类中的平均值。
(4)重复以上步骤,直到每一类中心在每次迭代后变化不大为止。也可以多次随机初始化中心点,然后选择运行结果最好的一个。

优点:
计算简便
缺点:
我们必须提前知道数据有多少类/组。

15.3 简单实例

假定我们有如下8个点:
A1(2, 10) A2(2, 5) A3(8, 4) A4(5, 8) A5(7, 5) A6(6, 4) A7(1, 2) A8(4, 9)
现希望分成3个聚类(即k=3)
初始化选择 A1(2, 10), A4(5, 8) ,A7(1, 2)为聚类中心点,假设两点距离定义为ρ(a, b) = |x2 – x1| + |y2 – y1| . (当然也可以定义为其它格式,如欧氏距离)
第一步:选择3个聚类中,分别为A1,A4,A7

这些点的分布图如下:

图1
第二步:计算各点到3个类中心的距离,那个点里类中心最近,就把这个样本点
划归到这个类。选定3个类中心(即:A1,A4,A7),如下图:
图2
对A1点,计算其到每个cluster 的距离
A1->class1 = |2-2|+|10-10}=0
A1->class2 = |2-5|+|10-8|=5
A1->class3 = |2-1|+|10-2|=9
因此A1 属于cluster1,如下表:
(
按照类似方法,算出各点到聚类中心的距离,然后按照最近原则,把样本点放在那个族中。如下表:

根据距离最短原则,样本点的第一次样本划分,如下图:
图3
第三步:求出各类中样本点的均值,并以此为类的中心。
cluster1只有1个点,因此A1为中心点
cluster2的中心点为 ( (8+5+7+6+4)/5,(4+8+5+4+9)/5 )=(6,6)。注意:这个点并非样本点。
cluster3的中心点为( (2+1)/2, (5+2)/2 )= (1.5, 3.5),
新族的具体请看下图中x点:

图4
第四步:计算各样本点到各聚类中心的距离,重复以上第二、第三步,把样本划分到新聚类中,如下图:
图5
持续迭代,直到前后两次迭代不发生变化为止,如下:
图6

15.4 简单实例用Python实现

(1)生成数据

(2)创建距离函数

(3)手工选择3个聚类中心

(4)创聚类函数

(5)运行

运行结果
(array([[3, 9],
[7, 4],
[1, 3]]), matrix([[ 0., 4.],
[ 2., 9.],
[ 1., 1.],
[ 0., 9.],
[ 1., 1.],
[ 1., 1.],
[ 2., 1.],
[ 0., 1.]]))
这个运行结果与图6的结果一致。

15.5 简单实例用sklearn实现

(1)导入需要的库或模块

(2)创建数据

(3)利用kmeans进行聚类,并把结果可视化

15.6 简单实例用Tensorflow实现

这里需要用到很多tensorflow函数,大家可参考:
https://www.cnblogs.com/wuzhitj/p/6648563.html
(1)导入需要的库,初始化参数

(2)创建数据集,并可视化三个族中心

(3)可视化样本

(4)计算各样本的到各族中心距离

(5)定义函数,更新各族中心坐标

(6)可视化迭代过程

迭代1次就到达最佳结果,看来是要tensorflow效果不错!

15.7 改进

由于 K-means 算法的分类结果会受到初始点的选取而有所区别,因此有提出这种算法的改进: K-means++。其实这个算法也只是对初始点的选择有改进而已,其他步骤都一样。初始质心选取的基本思路就是,初始的聚类中心之间的相互距离要尽可能的远。整个算法的过程如下:
下面结合一个简单的例子说明K-means++是如何选取初始聚类中心的。数据集中共有8个样本,分布以及对应序号如下图所示:
图7
假设经过图7的步骤一后6号点被选择为第一个初始聚类中心,那在进行步骤二时每个样本的D(x)和被选择为第二个聚类中心的概率如下表所示:

其中的P(x)就是每个样本被选为下一个聚类中心的概率。最后一行的Sum是概率P(x)的累加和,用于轮盘法选择出第二个聚类中心。方法是随机产生出一个0~1之间的随机数,判断它属于哪个区间,那么该区间对应的序号就是被选择出来的第二个聚类中心了。例如1号点的区间为[0,0.2),2号点的区间为[0.2, 0.525)。
从上表可以直观的看到第二个初始聚类中心是1号,2号,3号,4号中的一个的概率为0.9。而这4个点正好是离第一个初始聚类中心6号点较远的四个点。这也验证了K-means的改进思想:即离当前已有聚类中心较远的点有更大的概率被选为下一个聚类中心。可以看到,该例的K值取2是比较合适的。当K值大于2时,每个样本会有多个距离,需要取最小的那个距离作为D(x)。

第4章 Scrapy爬虫入门实例


本章利用Scrapy架构,实现一个爬虫任务,从一个网站爬取一些教师信息(如教师姓名、职称、简介),然后把爬取的信息分别存放到cvs文件、MySQL。
环境为python3.6

4.1 Scrapy 框架入门简介

4.1.1 Scrapy 框架

Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架,用途非常广泛。框架的力量,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以及各种图片,非常之方便。Scrapy 使用了 Twisted'twɪstɪd异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。

Scrapy架构图(其中绿线是数据流向)

Scrapy Engine(引擎):
负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。
Scheduler(调度器):
它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。

Downloader(下载器):
负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,
Spider(爬虫):
它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),
Item Pipeline(管道):
它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方.
Downloader Middlewares(下载中间件):
你可以当作是一个可以自定义扩展下载功能的组件。
Spider Middlewares(Spider中间件):
你可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)

4.1.2 Scrapy 流程

Scrapy运行流程大概如下:
引擎从调度器中取出一个链接(URL)用于接下来的抓取
引擎把URL封装成一个请求(Request)传给下载器
下载器把资源下载下来,并封装成应答包(Response)
爬虫解析Response
解析出实体(Item),则交给实体管道进行进一步的处理
解析出的是链接(URL),则把URL交给调度器等待抓取
详细流程如下:
1 引擎:Hi!Spider, 你要处理哪一个网站?
2 Spider:老大要我处理xxxx.com。
3 引擎:你把第一个需要处理的URL给我吧。
4 Spider:给你,第一个URL是xxxxxxx.com。
5 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
6 调度器:好的,正在处理你等一下。
7 引擎:Hi!调度器,把你处理好的request请求给我。
8 调度器:给你,这是我处理好的request
9 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求
10 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
11 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
12 Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
13 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
14 管道  调度器:好的,现在就做!

4.1.3 Scrapy 开发一般步骤

1、新建项目 (scrapy startproject xxx):新建一个新的爬虫项目
2、明确目标 (编写items.py):明确你想要抓取的目标
3、制作爬虫 (spiders/xxspider.py):制作爬虫开始爬取网页
4、存储内容 (pipelines.py):设计管道存储爬取内容
本实例的流程如下图

4.1.4.Scrapy安装

验证安装是否成功

4.2入门案例

主要步骤:
1、创建一个Scrapy项目
2、定义提取的结构化数据(Item)
3、编写爬取网站的 Spider 并提取出结构化数据(Item)
4、编写 Item Pipelines 来存储提取到的Item(即结构化数据)

4.2.1 创建项目

新建项目(scrapy my_project)
在开始爬取之前,必须创建一个新的Scrapy项目。进入自定义的项目目录中,运行下列命令:

其中, my_project 为项目名称,可以看到将会创建一个 my_project 文件夹,目录结构大致如下:

下面来简单介绍一下各个主要文件的作用:
scrapy.cfg 是爬虫执行的入口文件。当输入“scrapy crawl”命令让爬虫开始工作时,首先会读取该文件中的配置项内容。
my_project/items.py 文件定义了爬虫抓取下来的数据,是以何种组织方式存储信息的。比如爬虫抓取的结果可以是标题字符串,也可以是结构化的JSON对象,或者是一张图片对应的字节流,items.py就是用来定义结构化对象中的属性名。
my_project/pipelines.py文件定义了信息的保存方式。爬虫抓取的内容,存放在内存对象中,如何保存这些信息,用户可以定义多种方式,比如写入文件、存入DB或者直接在控制台输出。Scrapy会采用管道(pipeline)方式,把内存中的信息依次交给每个管道文件。
my_project/settings.py 文件保存了爬虫运行时所依赖的配置信息。比如用户定义了两个pipelines.py文件,希望把抓取的内容先写入DB,再输出到控制台,那么就可以在setttings.py文件中定义ITEM_PIPELINES属性,其值分别给出了两个管道文件的文件名。比如:

my_project/spiders/ 是用来存放具体爬虫文件的目录。爬虫文件需要用户手动创建,在该目录下可以同时存在多个爬虫文件。

【备注】为保证输出格式为json文件也支持中文,在settings.py添加如下语句:

4.2.2 明确目标

我们打算抓取:http://www.itcast.cn/channel/teacher.shtml 网站里的所有讲师的姓名、职称和个人信息。打开my_project目录下的items.py
Item 定义结构化数据字段,用来保存爬取到的数据,有点像Python中的dict,但是提供了一些额外的保护减少错误。
可以通过创建一个 scrapy.Item 类, 并且定义类型为
scrapy.Field的类属性来定义一个Item。接下来,创建一个ItcastItem 类,和构建item模型(model)。

4.2.3 制作爬虫

主要编辑spiders/itcastSpider.py
爬虫功能要分两步:
1. 爬数据
在当前目录下输入命令,将在my_project /spider目录下创建一个名为itcast的爬虫,并指定爬取域的范围:

打开 my_project /spider目录里的 itcast.py,默认增加了下列代码:

其实也可以由我们自行创建itcast.py并编写上面的代码,只不过使用命令可以免去编写固定代码的麻烦要建立一个Spider, 你必须用scrapy.Spider类创建一个子类,并确定了三个强制的属性 和 一个方法。
name = "" :这个爬虫的识别名称,必须是唯一的,在不同的爬虫必须定义不同的名字。
allow_domains = [] 是搜索的域名范围,也就是爬虫的约束区域,规定爬虫只爬取这个域名下的网页,不存在的URL会被忽略。
start_urls = () :爬取的URL元祖/列表。爬虫从这里开始抓取数据,所以,第一次下载的数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成。
parse(self, response) :解析的方法,每个初始URL完成下载后将被调用,调用的时候传入从每一个URL传回的Response对象来作为唯一参数,主要作用如下:
负责解析返回的网页数据(response.body),提取结构化数据(生成item)
生成需要下一页的URL请求。
将start_urls的值修改为需要爬取的第一个url

修改parse()方法

然后运行一下看看,在my_project目录下(含文件scrapy.cfg所在的目录)执行:

2. 取数据
爬取整个网页完毕,接下来的就是的取过程了,首先观察页面源码:

是不是一目了然?直接上XPath开始提取数据吧。
我们之前在my_project /items.py 里定义了一个ItcastItem类。 这里引入进来

然后将我们得到的数据封装到一个 ItcastItem 对象中,可以保存每个老师的属性,修改itcast.py文件如下。

【备注】这里补充一点xpath有关内容,更多内容大家可以通过google或百度查询
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
下面列出了最有用的路径表达式:

3. 运行
在scrapy.cfg 所在文件的目录下,运行如下命令

运行完成后,在当前目录下,将生成一个文件:teachers.csv

查看该文件:

4、修改settles.py文件
一些变量可存放在settles.py文件中,把数据库的连接信息存放在该文件,便于以后维护管理

5、利用pipeline,把数据导入mysql数据库
pipeline.py文件定义数据存储的方式,此处定义数据存储的逻辑,可以将数据存储加载到MySQL数据库,MongoDB数据库,文件,CSV,Excel等存储介质中,如下以存储载CSV为例。
编辑pipeline.py文件

6、修改settles.py文件
为说明通过pipeline导入数据程序,在文件settings.py后,添加如下一行:

7、运行以下命令

8、查询结果
csv文件内容:

查看数据库对应表部分记录:

第3章 Python 爬虫入门实例


如果你对爬虫感兴趣,但没有相关基础,这个实例就比较适合你。本实例非常简单,特别适合入门。本实例主要步骤如下本实例爬取一个有关教学研究人员网站 WISE(http://www.wise.xmu.edu.cn/people/faculty),提取这个页面上所有老师的姓名和个人主页的链接,然后简单解析相关标签(tag),然后把解析出来的内容存放在文件和数据库中。
讲完这个实例后面还有一个更高级的爬虫实例,使用Scrapy架构爬取网站内容,这个架构分布式、平行处理等我们就无需做过多配置,系统自动为我们配置好。
接下来,我们还是从一个简单实例开始吧。

3.1下载网页

使用requests下载网页,它是第三方库

把需要的网页内容提取出来:

到这一步我们已经获取了网页的源代码。具体源 代码是什么样的呢?右键,点击“查看源文件”或者“查看源”就可以看到。

3.2 解析网页

从源代码里面找。找到的我们需要的信息如下:

解析使用bs4模块,该模块有最常用的几个BeautifulSoup对象的方法(method)。我们使用的这几个方法,主要是通过HTML的标签和标签里面的参数来定位,然后用特定方法(method)提取数据。

使用了BeautifulSoup对象的find方法。这个方法的意思是找到带有‘div’这个标签并且参数包含" class = 'people_list' "的HTML代码。如果有多个的话,find方法就取第一个。那么如果有多个呢?可以使用find_all,现在我们要取出所有的“a”标签里面的内容:

显示结果
'助理教授'

这里我们使用find_all方法取出所有标签为“a”并且参数包含“ target = ‘_blank‘ ”的代码,返回一个列表。“a”标签里面的“href”参数是我们需要的老师个人主页的信息,而标签里面的文字是老师的姓名

运行结果:

3.3 拓展一

通过源码分析,可知教师的姓名,职位,邮箱等信息包含在td标签内,接下来我们解析源码并把解析结果保存到cvs文件中。具体步骤如下:
1、在td标签里,包含教师的姓名,职位,邮箱等信息

打印结果为
327
查看前几行数据

['敖萌幪',
'助理教授',
'mengmengao@xmu.edu.cn',
'Bowers, Roslyn',
'英语教师',
'bowers.roslyn@yahoo.com',
'Brown, Katherine',
'英语教师',
'kbrownanne@yahoo.com']
把数据保存到cvs文件中

3.4 拓展二

取出所以的td的内容,并按姓名,职位,邮箱格式写入数据库,采用mysql数据库,连接用pymysql,具体内容如下:

数据表创建成功
数据导入成功
查看数据库部分记录:

本文参考:https://zhuanlan.zhihu.com/p/21377121

第26 章 自动驾驶实例---交通标志识别

26.1交通标志识别简介

26.1.1 交通标记易识特性

由于交通标志采用特定的文字、图形和符号,在一定范围内具有标准、醒目、信息明确的特性,一直是图像识别技术在交通领域应用的首选。从图像识别技术诞生之日起,交通标志识别的算法和模型就一直受到广泛的关注,也让这一技术发展相对成熟,并应用到自动驾驶领域中来。

图1 交通标志识别技术
我国的交通标志一共有一百余种,按类别可分为黄底黑边的警告标志、白底红圈的禁令标志、蓝底白字的指示标志,形状上以三角形、圆形和矩形为主。明确的形状和颜色区分、有限的标志数量,都为图像识别提供了一个相对稳定的应用环境。

图2 我国的交通标志

26.1.2交通标志识别技术的原理

利用图像识别技术的交通标志识别系统一般分为以下几个工作步骤:

图3 图像识别的步骤
1. 图像预处理:
在实际的交通场景中,由于运动抖动、自然光、天气条件等各种因素的影响,不可避免的会在采集的图像中引入一定程度的干扰和噪声,所以首先需要将这些不利因素消除,对采集到的图像进行一些预处理。通过图像均衡、图像增强和图像去噪等算法,将图像的光线均衡,突出关键信息。这一步基本和美图秀秀中的那些工具类似,都是将图像变得清晰、易辨认。

图4 对图像进行预处理,去除噪声、提高亮度和对比度
2. 交通标志分割:
预处理后的图像仍然包含很多信息,交通标志在其中只有很小的一个区域,为了减小处理的数据量,加快处理速度,一般都会先将交通标志的区域检测出来,再去判断这个区域中的交通标志的具体含义。交通标志在颜色和形状上都有一定的特殊性,并可按照下图进行一定程度的分类,所以一般通过这两个特征去检测交通标志。

图5 交通标志按颜色和形状分类
2.1颜色分割:
颜色按照国际标准可划分为RGB、HSV、HSI等颜色空间,并进行量化分析,以RGB空间为例,将颜色按照红色、绿色、蓝色三种颜色进行分割,通过给定交通标志牌中常用的蓝色、黄色、红色的色度坐标范围,即可过滤掉与之不相关的颜色信息,快速检测到交通标志牌。

图 6 通过RGB色彩空间处理,快速定位红色区域
2.2形状分割:
仅仅检测颜色显然又是不够的,由于光照、背景色的影响和干扰,还需要在颜色检测结果的基础上对相应区域进行形状检测。交通标志具有边缘清晰、形状简单易辨认的特点。这些特征在排除颜色影响后的灰度图像中更加明显,因此通过一定的边缘检测算子去判断图像像素中出现的灰度阶跃变化,一般就能较为准确的检测出交通标志的形状和轮廓特征。

图7 对检测区域进行灰度处理,再通过灰度阶跃检测其形状边界
3. 交通标志特征提取
在图像检测完成以后,图像中基本就只剩下了交通标志的关键信息,这些信息简单直观,但计算机依然不会知道这些信息的具体含义,这时候需要再进一步对这些图像特征进行提取和比对,才能对具体的信息进行识别。
图像的关键特征,是识别具体信息的关键因素,特征的好坏直接决定了识别的准确度。一般说来这些关键特征需要具有可区分性、简约性和抗干扰等几个要素,可区分性即不同标志的特征要具有足够的差异性,简约性是在保证可区分性的前提下用尽量少的数据表示图像特征,这可以保证检测的速度和效率,抗干扰度即图像特征信息要保证尽量少的会被噪声、自然光和图像畸变影响。
在交通标志识别上,一般会提取颜色特征、线条变化特征、矩特征、灰度直方图统计特征等等,并会在事先维护一个足够样本数量的特征库,包含现有交通标志的图像特征信息。在识别的时候将采集到的图像的特征提取出来与数据库中的条件进行比对,即可判断出该交通标志的实际意义。
4. 识别结果匹配
目前有多种方法实现图像特征与特征库数据的比对,最为简单直接的方式是模板匹配:即在特征库中将不同交通标志的特征参数规定为某些特定的参数,当所采集图像的特征参数在某个范围内,就判断是这个交通标志信息。但由于图像在采集的时候难免发生形状畸变、颜色失真等误差,在实际使用场景中用模板匹配来识别的成功率和准确度并不是特别高,即便优化了图像处理算法,也还有很多局限。

图 8 通过匹配特征库信息识别标志
近些年机器学习技术的发展,让图像识别也有了很大的变化,通过设定一些简单的判断条件,并在特征库中加入各种形态和场景下的学习样本,让系统不断加深交通标志识别的认知和识别概率。机器学习让识别不再依靠具体固定的参数,而是通过一系列的条件判断让系统找到概率最大的目标,以此提升识别的准确度和灵活性。这一技术在目前成为研究的热点,并有效提高了图像识别的准确率。

图 9 机器学习在图像识别中的应用

26.1.3 总结

交通标志识别是图像识别技术在自动驾驶领域较为成功的应用,其情景相对简单固定,识别准确度和成功率都让人满意。现在自动驾驶中摄像头识别车辆、行人、自行车、车道线等其他目标的工作原理基本和交通标志识别类似,只是针对不同的对象所用的的算法和模型也会进行一定的调整和优化,并维护一个更为多样的样本学习库。Mobileye在自动驾驶摄像头领域已经耕耘了17年,其算法集成优化、样本库丰富度、识别精确度都处于绝对领先,也为自动驾驶的普及带来了巨大的福音。

26.2 交通标志识别常用模型

1、LeNet(X)
是交通标志识别中非常经典的算法结构,其结构如下:

但是要实现交通标志识别还需要对该算法的初始结构进行调整。比如输出层的分类数调整为43;Subsampling layers 转为max pooling layers;增加Dropout 层,初始设置keep_prob=0.9;激活函数采用RELU。
改进后的架构流程如下表所示:

2、AlexNet
AlexNet是2012年发表的一个经典网络,在当年取得了ImageNet的最好成绩。
这是AlexNet的网络结构图:

网络共有8层,其中前5层是卷积层,后边3层是全连接层,在每一个卷积层中包含了激励函数RELU以及局部响应归一化(LRN)处理,然后再经过池化(max pooling),最后的一个全连接层的输出是具有1000个输出的softmax层,最后的优化目标是最大化平均的多元逻辑回归。
3、其它多层神经网络
36.3 实例1(使用多层神经网络)
这个Project的目的是利用神经卷积网路(CNN)来分类(classify)常见的交通标志。 CNN 在读图领域已经全面超过了传统的机器学习方法(SVC, OpenCV)。大量的资料是深度学习准确性的保证, 在资料不够的情况下也可以人为的对原有资料进行小改动从而来提高识别的准确度。汇入必要的软体包(pickle, numpy, cv2, matplotlib, sklearn, tensorflow, Keras)
这里使用python3.6,TensorFlow1.6 等,使用GPU比使用CPU快约100倍!
1)导入需要的模块或库

【注意】如果环境已安装keras,可以直接使用,导入方式可改为:

资料来源: 和大部分的机器学习的要求一样, CNN需要大量有label的资料,German Traffic Sign Dataset提供了对于这个project的研究所需的数据,本章数据集下载

2)导入数据

打印结果
Number of training examples = 39209
Number of testing examples = 12630
Image data shape = (32, 32, 3)
Number of classes = 43

3)探索数据
从上面我们可以看到有39209个用作训练的影象 和 12630个testing data。 39209张照片对于训练CNN来说是不够的(100000张以上是比较理想的资料量), 所以之后要加入data augment 的模组来人为增加资料。每张影象的大小是是32×32 并且有3个通道。总共有43个不同的label。我们也可以把每个label对应的图片随机选择一张画出来。

4)查看各种标志的分布情况

从上图可以看到这43个类别的资料量的分配是很不均匀的。这个会给CNN带来bias(偏见):CNN会更倾向于预测在training data里出现频率多的那些分类。

5)数据预处理
资料前期处理 根据这篇论文[Sermanet, LeCun], 把RGB的照片转化成YUV(除了RGB模型外,还有一种广泛采用的模型,称为YUV模型,又被称为亮度-色度模型(Luma-ChromaModel)。它是通过数学转换,将RGB三通道转换为一个代表亮度的通道(Y,又称为Luma),和两个代表色度的通道(UV,并称为Chroma)来记录图像的模型) 然后只选择Y通道的图片可以在不影响精度的同时减少资料计算量。然后每张图片都转化成以0为平均值, 以1为标准差的阵列。

人为添加数据(data augment) 对于图片来时, 一定程度的旋转, 上下左右移动, 放大或者缩小都应该不会影响它的标签。 虽然图片数据已经完全不一样了, 我们肉眼还是能够识别的出来, 这能够在增加数据量的同时帮助CNN 总结(generalize). Keras 有个很方便的函数ImageDataGenerator(rotation_range=15., zoom_range=0.2, width_shift_range=0.1, height_shift_range=0.1) 可以实现这个, 这个函数可以设置 旋转的角度rotation_range, 放大或缩小的倍数zoom_range, 左右移动的比例width_shift_range 和上下移动的比例height_shift_range , 随机在区间内改动原来的照片并产生无数新的照片, 下面我选择一张作为示范:

6)搭建神经网络
搭建CNN 每一层的神经网络都加了dropout 来防止overfitting。这个CNN的特点是把两层conv的output做了一个合成:fc0 = tf.concat([flatten(drop1), flatten(drop2)],1 ) 然后再连接到fully_connected layer 和output layer(43 classes)。文献中说这样做的好处是“the classifier is explicitly provided both the local “motifs” (learned by conv1) and the more “global” shapes and structure (learned by conv2) found in the features.” 我的理解是: CNN 能够在图片的局部和整体都能作为判断的依据,从而提高准确率

【备注】如果lr大小,如小于0.0001可能需要花费更多训练时间,如果太大,如0.1可能很难提高精度,所以选择合适lr很重要。
定义超参数

【备注】大家可以根据情况,增加迭代次数,批量大小,批量一般不宜过大,否则将迅速增加训练时间。
7)训练CNN模型

【备注】keep_prob参数控制dropout的比例,这个参数很重要,大家可以修改该参数,看它对精度的影响。
Training...

EPOCH 1 ...
Training Accuracy = 0.224

EPOCH 2 ...
Training Accuracy = 0.339

EPOCH 3 ...
Training Accuracy = 0.400

EPOCH 4 ...
Training Accuracy = 0.452

EPOCH 5 ...
Training Accuracy = 0.508

EPOCH 6 ...
Training Accuracy = 0.548

EPOCH 7 ...
Training Accuracy = 0.587

EPOCH 8 ...
Training Accuracy = 0.609

EPOCH 9 ...
Training Accuracy = 0.658

.......................

EPOCH 90 ...
Training Accuracy = 0.971

EPOCH 91 ...
Training Accuracy = 0.968

EPOCH 92 ...
Training Accuracy = 0.971

EPOCH 93 ...
Training Accuracy = 0.975

EPOCH 94 ...
Training Accuracy = 0.965

EPOCH 95 ...
Training Accuracy = 0.977

EPOCH 96 ...
Training Accuracy = 0.967

EPOCH 97 ...
Training Accuracy = 0.976

EPOCH 98 ...
Training Accuracy = 0.976

EPOCH 99 ...
Training Accuracy = 0.976

EPOCH 100 ...
Training Accuracy = 0.977
【说明】因时间关系,我这里只训练100次,看来精度也还可以,如果增加迭代次数,可以达到99%左右
8)用测试数据验证模型

26.3 实例2(使用LeNet神经网络)

待续

第25章 自动驾驶汽车实例--使汽车在两条白线间行驶

本文将介绍如何使用 Keras + Tensorflow1.6,python为3.6 创建卷积神经网络,并对其进行训练,以使得车辆保持在两条白线之间,如下图:

下载数据

备注:由于下载经常出现超时情况,大家可以先下载该文件,然后放到指定目录。

步骤1:获取驾驶数据

数据集由 ~7900 个图像和手动开车时收集的转向角组成。大约三分之二的图像与线之间的汽车。另外三分之一的车开始偏离航线,并且驶回线路之间。
数据集由 2 个 pickled 数组组成。X 是图像阵列,Y 是相应转向角度的阵列。

打印结果
X.shape: (7892, 120, 160, 3)
Y.shape: (7892,)

步骤2:拆分数据

在这里,对数据进行洗牌(shuffle),并将数据分成三部分。训练数据将用于训练我们的驾驶模型,使用验证数据避免过度拟合模型,测试数据用于测试我们的模型。

打印结果
7892

打印结果
7892

步骤3:增强训练数据

为了加倍我们的训练数据并防止转向偏差,我们翻转每个图像和转向角并将其添加到数据集中。还有其他的方法来增加使用翻译和假阴影驾驶数据,但我没有使用这些自动驾驶仪。

打印结果
12626

步骤4:创建驾驶模式

这种驾驶模式将是一个端到端的神经网络,接受图像阵列作为输入,并输出-90(左)和90(右)之间的转向角。 要做到这一点,我们将使用一个完全连接图层的3层卷积网络。该模型基于 Otavio 的 Carputer,但不产生油门值输出,不使用过去的转向值作为模型的输入,并且使用较少的卷积层。

使用 TensorFlow 后端。
查看目前image数据格式,是tensorflow格式(channels_last) 还是theano格式(channels_first)

'channels_last'

如果不是tensorflow格式,可以通过K.set_image_data_format('channels_last')进行修改,或修改~/.keras/keras.json文件。 前者只对当前会话有效,后者修改,将永久有效。

打印结果
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
img_in (InputLayer) (None, 120, 160, 3) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 118, 158, 8) 224
_________________________________________________________________
activation_1 (Activation) (None, 118, 158, 8) 0
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 59, 79, 8) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 57, 77, 16) 1168
_________________________________________________________________
activation_2 (Activation) (None, 57, 77, 16) 0
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 28, 38, 16) 0
_________________________________________________________________
conv2d_3 (Conv2D) (None, 26, 36, 32) 4640
_________________________________________________________________
activation_3 (Activation) (None, 26, 36, 32) 0
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 13, 18, 32) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 7488) 0
_________________________________________________________________
dense_1 (Dense) (None, 256) 1917184
_________________________________________________________________
activation_4 (Activation) (None, 256) 0
_________________________________________________________________
dropout_1 (Dropout) (None, 256) 0
_________________________________________________________________
angle_out (Dense) (None, 1) 257
=================================================================
Total params: 1,923,473
Trainable params: 1,923,473
Non-trainable params: 0

步骤5:训练模型

我们已经学会了很难的方法,即使这一切都是完美的,如果你没有正确地训练,你的自动驾驶仪将无法工作。我遇到的最大的问题是过度适应模型,以至于在很少的情况下都不能正常工作。 这里是 2 个 Keras回调,将节省您的时间。 警告 - 如果仅使用CPU,则需要较长时间,这里我采用GPU进行训练

因时间关系,这里只进行 10 次迭代(epochs),训练时间2分钟左右,比较快。

运行结果
Train on 12626 samples, validate on 789 samples
Epoch 1/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 41288.4184
Epoch 00001: val_loss improved from inf to 656.89127, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 22s 2ms/step - loss: 41230.2161 - val_loss: 656.8913

Epoch 2/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 550.5565
Epoch 00002: val_loss improved from 656.89127 to 543.14232, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 550.1622 - val_loss: 543.1423

Epoch 3/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 476.8377
Epoch 00003: val_loss improved from 543.14232 to 417.43844, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 476.6054 - val_loss: 417.4384

Epoch 4/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 420.1108
Epoch 00004: val_loss improved from 417.43844 to 416.97928, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 420.0584 - val_loss: 416.9793

Epoch 5/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 399.4089
Epoch 00005: val_loss did not improve
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 399.0109 - val_loss: 420.7120

Epoch 6/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 373.5299
Epoch 00006: val_loss improved from 416.97928 to 411.31800, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 373.7999 - val_loss: 411.3180

Epoch 7/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 356.3707
Epoch 00007: val_loss improved from 411.31800 to 384.65238, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 356.4031 - val_loss: 384.6524

Epoch 8/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 338.6780
Epoch 00008: val_loss did not improve
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 338.4380 - val_loss: 435.1508

Epoch 9/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 325.1715
Epoch 00009: val_loss improved from 384.65238 to 348.49219, saving model to /home/wumg/best_autopilot.hdf5
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 325.0649 - val_loss: 348.4922

Epoch 10/10
12608/12626 [============================>.]12608/12626 [============================>.] - ETA: 0s - loss: 304.1439
Epoch 00010: val_loss did not improve
12626/12626 [==============================]12626/12626 [==============================] - 15s 1ms/step - loss: 304.2825 - val_loss: 349.3760

步骤6:评估性能

我们可以通过绘制预测值和实际值来检查我们的模型预测是否合理。第一个图表显示我们的测试数据中存在一个学习关系(在训练期间,部分测试数据我们的模型没有注意到)。

第二张图,使用包含训练数据的非混洗(unshuffled)数据,来显示预测角度紧跟实际转向角度。

后续可进一步完善

改善模型,这个模型是纯粹(navie)的,因为它不使用过去的值来帮助预测未来。我们可以通过将过去的转向角度作为模型的输入来进行试验,添加一个递归层,或者只是改变卷积层的结构。
添加更多数据,随着我们添加更多驾驶数据,此模型将会得到改进。 预测油门,输出目前自动驾驶仪只能转向并保持恒定的速度。一个更复杂的模型将加速在直路上,并在路缘之前放缓。

第4章 概率与信息论


机器学习、深度学习有三块基石:线性代数、概率与信息论、数值分析。线性代数上章已介绍,数值分析后续将介绍,三块基石缺一块都会地动山摇。本章讨论概率和信息论,概率是用于表示不确定性陈述的数学框架,即它是对事物不确定性的度量,而信息论主要研究信号或随机变量所包含的信息量。
在人工智能领域,概率法则告诉我们AI系统应该如何推理,概率和统计从理论上分析我们提出的AI系统的行为。
计算机科学的许多分支处理的对象都是完全确定的实体,但机器学习却大量使用概率。如果你了解机器学习的工作原理,或许你有更深的体会。因为机器学习大部分时候处理的都是不确定量或随机量。
概率论和信息论是众多学科的基础,也是机器学习、深度学习的重要基础。
如果你对概率论和信息论很熟悉了,可以跳过这章。如果你觉得本章内容还不够,希望进一步了解更多,可以参考相关专业教材。本章主要内容包括:

  • 为何要学概率与信息论
  • 样本空间与随机变量
  • 概率分布
  • 边缘概率
  • 条件概率
  • 期望、方差及协方差
  • 贝叶斯定理
  • 信息论

4.1为何要学概率、信息论

机器学习、深度学习需要借助概率、信息论?
概率研究对象不是预先知道或确定的事情,而是预先不确定或随机的事件,研究这些不确定或随机事件背后的规律或规则。或许有人会说,这些不确定或随机事件有啥好研究?他们本来就不确定或随机的,飘忽不定、不可捉摸。表面上看似如此,有句话说得好:偶然中有必然,必然中有偶然。就拿我们比较熟悉微积分来说吧,如果单看有限的几步,很多问题都显得杂乱无章,毫无规律可言,而且还很难处理,但是一旦加上一个无穷大(∞)这个“照妖镜”,其背后规律立显,原来难处理的也好处理了。如大数定律、各种分布等带给我们这样的认识。
信息论主要研究对一个信号包含信息的多少进行量化。它的基本思想是一个不太可能的事件居然发生了,其提供的信息量要比一个非常可能发生的事件更多。这中情况也似乎与我们的直觉相矛盾。
机器学习、深度学习与概率、信息论有哪些内在关联呢?
(1)被建模系统内在的随机性。例如一个假想的纸牌游戏,在这个游戏中我们假设纸牌被真正混洗成了随机顺序。
(2不完全观测。即使是确定的系统,当我们不能观测到所有驱动系统行为的所有变量或因素时,该系统也会呈现随机性。
(3)不完全建模。假设我们制作了一个机器人,它可以准确观察周围每一个对象的位置。在对这些对象将来的位置进行预测时,如果机器人采用的是离散化的空间,那么离散化的方法将使得机器人无法确定对象们的精确位置:因为每个对象都可能处于它被观测到的离散单元的任何一个角落。也就是说,当不完全建模时,我们不能明确的确定结果,这个时候的不确定,就需要借助概率来处理。
由此看来,概率、信息论很重要,机器学习、深度学习确实很需要它们。后续我们可以看到很多实例,见证概率、信息论在机器学习、深度学习中是如何发挥它们作用的。

4.2样本空间与随机变量

随机试验中,每一个可能的结果,在试验中发生与否,都带有随机性,所以称为随机事件。而所有可能结果构成的全体,称为样本空间。随机变量、样本空间这两个概念非常重要,以下就这两个概念作进一步说明。
样本空间
样本空间是一个实验或随机试验所有可能结果的集合,随机试验中的每个可能结果称为样本点。例如,如果抛掷一枚硬币,那么样本空间就是集合{正面,反面}。如果投掷一个骰子,那么样本空间就是{1,2,3,4,5,6}。
随机变量
随机变量,顾名思义,就是“其值随机而定”的变量,一个随机试验有许多可能结果,到底出现哪个预先是不知道的,其结果只有等到试验完成后,才能确定。如掷骰子,掷出的点数X是一个随机变量,它可以取1,2,3,4,5,6中的任何一个,到底是哪一个,要等掷了骰子以后才知道。因此,随机变量又是试验结果的函数,它为每一个试验结果分配一个值。比如,在一次扔硬币事件中,如果把获得的背面的次数作为随机变量X,则X可以取两个值,分别是0和1。如果随机变量X的取值是有限的或者是可数无穷尽的值,如:
则称 X为离散随机变量。如果X由全部实数或者由一部分区间组成,如:
X={x| a≤x≤b},其中a<b,它们都为实数。
则称 X为连续随机变量,连续随机变量的取值是不可数及无穷尽的。
有些随机现象需要同时用多个随机变量来描述。例如对地面目标射击,弹着点的位置需要两个坐标(X,Y)才能确定,X,Y都是随机变量,而(X,Y)称为一个二维随机变量或二维随机向量,多维随机向量含义依次类推。

4.3概率分布

概率分布用来描述随机变量(含随机向量)在每一个可能状态的可能性大小。概率分布有不同方式,这取决于随机变量是离散的还是连续的。
对于随机变量X,其概率分布通常记为P(X=x),或X ~P(x),表示X服从概率分布P(x)。
概率分布描述了取单点值的可能性或概率,但在实际应用中,我们并不关心取某一值的概率,特别是对连续型随机变量,它在某点的概率都是0,这个后续章节将介绍。因此,我们通常比较关心随机变量落在某一区间的概率,为此,引入分布函数的概念。

4.3.1 离散型随机变量

设x_1,x_2,…,x_n是随机变量X的所有可能取值,对每个取值x_i,X = x_i是其样本空间S上的一个事件,为描述随机变量X,还需知道这些事件发生的可能性(概率)。
设离散型随机变量X的所有可能取值为x_i (i=1,2,…,n)。
P(X = x_i) = P_i,i= 1,2,...n
称之为X的概率分布或分布律,也称概率函数。
常用表格形式来表示X的概率分布:


例1:某篮球运动员投中篮圈的概率是0.8,求他两次独立投篮投中次数X的概率分布。

且P(X=0)+P(X=1)+P(X=2)=0.04+0.32+0.64=1
于是随机变量X的概率分布可表示为:
X         0      1         2
P_i 0.04    0.32    0.64
若已知一个离散型随机变量X的概率分布:

例如,设X的概率分布由例1给出,则
F(2)=P(X≤2)=P(X=0)+P(X=l)=0.04+0.32=0.36
常见的离散随机变量的分布有:
(1)两点分布
若随机变量X只可能取0和1两个值,且它的分布列为P(X=1)=p,P(X = 0) = l − P其中(0 < P < 1),则称X服从参数为p的两点分布,记作X~B(1, p)。其分布函数为

(2)二项分布
二项分布是重要的离散概率分布之一,由瑞士数学家雅各布•伯努利(Jokab Bernoulli)提出。一般用二项分布来计算概率的前提是,每次抽出样品后再放回去,并且只能有两种试验结果,比如黑球或红球,正品或次品等。二项分布指出,假设某样品在随机一次试验出现的概率为p,那么在n次试验中出现k次的概率为:

假设随机变量X满足二项分布,且知道n,p,k等参数,我们如何求出各种情况的概率值呢?方法比较多,这里介绍一种比较简单的方法,利用scipy库的统计接口stats即可,具体如下:

运行后的二项分布图如图4-1所示。

图4-1 二项分布图
(3)泊松(Poisson)分布
若随机变量X所有可能取值为0,1,2,…,它取各个值的概率为:

这里介绍了离散型随机变量的分布情况,如果X是连续型随机变量,其分布函数通常通过密度函数来描述,具体请看下一节。

4.3.2 连续型随机变量

与离散型随机变量不同,连续型随机变量采用概率密度函数来描述变量的概率分布。如果一个函数f(x)是密度函数,满足以下三个性质,我们就称f(x)为概率密度函数。
(1)f(x)≥0,注意这里不要求f(x)≤1。

图4-2 概率密度函数
对连续型随机变量在任意一点的概率处处为0。

最常见的正态分布的密度函数为:

这个连续分布被称之为正态分布,或者高斯分布。其密度函数的曲线呈对称钟形,因此又被称之为钟形曲线,其中μ是平均值,σ是标准差(何为平均值、标准差后续我们会介绍)。正态分布是一种理想分布。
正态分布如何用Python实现呢?同样,我们可以借助其scipy库中stats来实现,非常方便。

sigmal系统与正态分布如图4-3所示。


图4-3 sigmal系统与正态分布
正态分布的取值可以从负无穷到正无穷。这里我们为便于可视化,只取把X数据定义在[-6,6]之间,用stats.norm.pdf得到正态分布的概率密度函数。另外从图形可以看出,上面两图的均值u都是0,只是标准差(σ)不同,这就导致图像的离散程度不同,标准差大的更分散,个中原因,我们在介绍随机变量的数字特征时将进一步说明。

4.4边缘概率

对于多维随机变量,如二维随机变量(X,Y),假设其联合概率分布为F(x,y),我们经常遇到求其中一个随机变量的概率分布的情况。这种定义在子集上的概率分布称为边缘概率分布。
例如,假设有两个离散的随机变量X,Y,且知道P(X,Y),那么我们可以通过下面求和的方法,得到边缘概率P(X):

边缘概率如何计算呢?我们通过一个实例来说明。假设有两个离散型随机变量X,Y,其联合分布概率如表4-1所示。
表4-1:X与Y的联合分布

如果我们要求P(Y=0)的边缘概率,根据式(4.7)可得:
P(Y=0)=P(X=1,Y=0)+P(X=2,Y=0)=0.05+0.28=0.33

4.5条件概率

上一节我们介绍了边缘概率,它是多维随机变量一个子集(或分量)上的概率分布。对于含多个随机变量的事件中,经常遇到求某个事件在其他事件发生的概率,例如,在表4-1的分布中,假设我们要求当Y=0的条件下,求X=1的概率?这种概率叫作条件概率。条件概率如何求?我们先看一般情况。
设有两个随机变量X,Y,我们将把X=x,Y=y发生的条件概率记为P(Y=y|X=x),那么这个条件概率可以通过以下公式计算:

其中P(Y=0)是一个边缘概率,其值为:P(X=1,Y=0)+P(X=2,Y=0)=0.05+0.28=0.33
而P(X=1,Y=0)=0.05.故P(X=1|Y=0)=0.05/0.33=5/33
式(4.10)为离散型随机变量的条件概率,对连续型随机变量也有类似公式。假设(X,Y)为二维连续型随机变量,它们的密度函数为f(x,y),关于Y的边缘概率密度函数为f_Y (y),且满足f_Y (y)>0,假设

在X=x的条件下,关于Y的条件分布函数为:

4.6条件概率的链式法则

条件概率的链式法则,又称为乘法法则,把式(4.10)变形,可得到条件概率的乘法法则:
P(X,Y)=P(X)×P(Y|X)                                                                   (4.16)
根据式(4.16)可以推广到多维随机变量,如:
P(X,Y,Z)=P(Y,Z) ×P(X|Y,Z)
而P(Y,Z)=P(Z) ×P(Y|Z)
由此可得:P(X,Y,Z)=P(X|Y,Z) ×P(Y|Z) ×P(Z)                          (4.17)
推广到n维随机变量的情况,可得:

4.7独立性及条件独立性

两个随机变量X,Y,如果它们的概率分布可以表示为两个因子的乘积,且一个因子只含x,另一个因子只含y,那么我们就称这两个随机变量互相独立。这句话可能不好理解,我们换一种方式的来表达。或许更好理解。
如果对∀x∈X,y∈Y,P(X=x,Y=y)=P(X=x)P(Y=y) 成立,那么随机变量X,Y互相独立。
在机器学习中,随机变量为互相独立的情况非常普遍,一旦互相独立,联合分布的计算就变得非常简单。
这是不带条件的随机变量的独立性定义,如果两个随机变量带有条件,如P(X,Y|Z),它的独立性如何定义呢?这个与上面的定义类似。具体定义如下:
如果对∀x∈X,y∈Y,z∈Z,P(X=x,Y=y|Z=z)=P(X=x|Z=z)P(Y=y|Z=z) 成立
那么随机变量X,Y在给定随机变量Z时是条件独立的。
为便于表达,如果随机变量X,Y互相独立,又可记为X⊥Y,如果随机变量X,Y在给定时互相独立,则可记为X⊥Y|Z。
以上主要介绍离散型随机变量的独立性和条件独立性,如果是连续型随机变量,我们只要把概率换成随机变量的密度函数即可。

4.8期望、方差及协方差

在机器学习、深度学习中经常需要分析随机变量的数据特征及随机变量间的关系等,对于这些指标的衡量在概率统计中有相关的内容,如用来衡量随机变量的取值大小的期望(Expectation)值或平均值、衡量随机变量数据离散程度的方差(Variance)、揭示随机向量间关系的协调方差(Convariance)等。这些衡量指标的定义及公式就是本节主要内容。
首先我们看随机变量的数学期望的定义:
对离散型随机变量X,设其分布律为:

如果是随机变量函数,如随机变量X的g(x)的期望,公式与式(4.21)或式(4.22)类似,只要把x换成g(x)即可,即随机变量函数g(x)的期望为:

期望有一些重要性质,具体如下:
设a,b为一个常数,X和Y是两个随机变量。则有:
(1)E(a)=a
(2)E(aX)=aE(X)
(3)E(aX+bY)=aE(X)+bE(Y) (4.25)
(4)当X和Y相互独立时,则有:
E(XY)=E(X)E(Y) (4.26)
数学期望也常称为均值,即随机变量取值的平均值之意,当然这个平均,是指以概率为权的加权平均。期望值可大致描述数据的大小,但无法描述数据的离散程度,这里我们介绍一种刻画随机变量在其中心位置附近离散程度的数字特征,即方差。如何定义方差?
假设随机向量X有均值E(X)=a。试验中,X取的值当然不一定恰好是a,可能会有所偏离。偏离的量X-a本身也是一个随机变量。如果我们用X-a来刻画随机变量X的离散程度,当然不能取X-a的均值,因E(X-a)=0 ,说明正负偏离抵消了,当然我们可以取|X-a|这样可以防止正负抵消的情况,但绝对值在实际运算时很不方便。人们就考虑另一种方法,先对
方差的平方根被称为标准差。
对于多维随机向量,如二维随机向量(X,Y)如何刻画这些分量间的关系?显然均值、方差都无能为力。这里我们引入协方差的定义,我们知道方差是X-EX乘以X-EX的均值,如果我们把其中一个换成Y-EY,就得到E(X-EX)(Y-EY),其形式接近方差,又有X,Y两者的参与,由此得出协方差的定义,随机变量X,Y的协方差,记为:Cov(X,Y)
Cov(X,Y) =E(X-EX)(Y-EY)                                    (4.28)
协方差的另一种表达方式:
Cov(X,Y) =E(XY)-EX×EY                                       (4.29)
方差可以用来衡量随机变量与均值的偏离程度或随机变量取值的离散度,而协方差则可衡量随机变量间的相关性强度,如果X与Y独立,那么它们的协方差为0。反之,并不一定成立,独立性比协方差为0的条件更强。不过如果随机变量X、Y都是正态分布,此时独立和协方差为0是一个概念。
当协方差为正时,表示随机变量X、Y为正相关;如果协方差为负,表示随机变量X、Y为负相关。
为了更好的衡量随机变量间的相关性,我们一般使用相关系数来衡量,相关系数将每个变量的贡献进行归一化,使其只衡量变量的相关性而不受各变量尺寸大小的影响,相关系统的计算公式如下:
ρ_xy=(Cov(X,Y))/(√(Var(X)) √(Var(Y)))                                             (4.30)
由式(4.30)可知,相关系统是在协方差的基础上进行了正则化,从而把相关系数的值限制在[-1,1]之间。如果ρ_xy=1,说明随机变量X、Y是线性相关的,即可表示为Y=kX+b,其中k,b为任意实数,且k>0;如果ρ_xy=-1,说明随机变量X、Y是负线性相关的,即可表示为Y=-kX+b,其中k>0。
上面我们主要以两个随机变量为例,实际上协方差可以推广到n个随机变量的情况或n维的随机向量。对n维的随机向量,我们可以的第一个nxn的协方差矩阵,而且满足:

求随机变量的方差、协方差、相关系统等,使用Python的numpy相关的函数,如用numpy.var求方差,numpy.cov求协方差,使用numpy.corrcoef求相关系数,比较简单,这里就不展开来说。
在机器学习中多维随机向量,通常以矩阵的方式出现,所以求随机变量间的线性相关性,就转换为求矩阵中列或行的线性相关性。这里我们举一个简单实例,来说明如果分析向量间的线性相关性并可视化结果。这个例子中使用的随机向量(或特征值)共有三个,一个是气温(temp),一个体感温度(atemp),一个是标签(label)说明共享单车每日出租量,以下是这三个特征的部分数据:
表4-2 共享单车示例数据

这里使用Python中数据分析库pandas及画图库matplotlib、sns等。

从图4-4可以看出,特征temp与atemp是线性相关的,其分布接近正态分布。

图4-4 特征分布及相关性

4.9贝叶斯定理

贝叶斯定理是概率论中的一个定理,它跟随机变量的条件概率以及边缘概率分布有关。在有些关于概率的解释中,贝叶斯定理(贝叶斯公式)能够告知我们如何利用新证据修改已有的看法。这个名称来自于托马斯•贝叶斯。
通常,事件A在事件B(发生)的条件下的概率,与事件B在事件A(发生)的条件下的概率是不一样的;然而,这两者是有确定的关系的,贝叶斯定理就是这种关系的陈述。贝叶斯公式的一个用途在于通过已知的三个概率函数推出第四个。
贝叶斯公式为:
P(B|A)=(P(B)P(A|B))/(P(A))                                                               (4.31)
在贝叶斯定理中,每项都有约定俗成的名称:

  • P(B|A)是已知A发生后B的条件概率,也由于得自A的取值而被称作B的后验概率。
  • P(B)是B的先验概率(或边缘概率)。之所以称为"先验"是因为它不考虑任何A方面的因素。
  • P(A|B)是已知B发生后A的条件概率,也称为似然(likelihood),也由于得自B的取值而被称作A的后验概率。
  • P(A)是A的先验概率或边缘概率。

4.10信息论

信息论是应用数学的一个分支,主要研究的是对信号所含信息的多少进行量化。它的基本想法是一个不太可能的事件居然发生了,要比一个非常可能的事件发生能提供更多的信息。本节主要介绍度量信息的几种常用指标,如信息量、信息熵、条件熵、互信息、交叉熵等。
1.信息量
1948年克劳德•香农(Claude Shannon)发表的论文“通信的数学理论”是世界上首次将通讯过程建立了数学模型的论文,这篇论文和1949年发表的另一篇论文一起奠定了现代信息论的基础。信息量是信息论中度量信息多少的一个物理量,它从量上反应具有确定概率的事件发生时所传递的信息。香农把信息看作是“一种消除不确定性”的量,而概率正好是表示随机事件发生的可能性大小的一个量,因此,可以用概率来定量地描述信息。
在实际运用中,信息量常用概率的负对数来表示,即,。为此,可能有人会问,为何要用对数,前面还要带上负号?
用对数表示是为了计算方便。因为直接用概率表示,在求多条信息总共包含的信息量时,要用乘法,而对数可以变求积为求和。另外,随机事件的概率总是小于1,而真实小于1的对数为负的,概率的对数之前冠以负号,其值便成为正数。所以通过消去不确定性,获取的信息量总是正的。
2.信息熵
信息熵(entropy)又简称为熵,是对随机变量不确定性的度量。熵的概念由鲁道夫•克劳修斯(Rudolf Clausius)于1850年提出,并应用在热力学中。1948年,克劳德•艾尔伍德•香农(Claude Elwood Shannon)第一次将熵的概念引入信息论中,因此它又称为香农熵。
用熵来评价整个随机变量X平均的信息量,而平均最好的量度就是随机变量的期望,即熵的定义如下:

信息熵越大,包含的信息就越多,那么随机变量的不确定性就越大。
以下我们通过一个实例进一步说明这个关系。
假设随机变量X服从0-1分布,其概率分布为:
P(X=1)=p,P(X=0)=1-p
这时,X的熵为:
H(X)=-p log_2(p)-(1-p) log_2(1-p)
我们利用Python具体实现以下概率p与H(X)的关系:

图4-5 概率与信息熵
从这个图形可以看出,当概率为0或1时,H(X)为0,说明此时随机变量没有不确定性,当p=0.5时,随机变量的不确定性最大,即信息量最大。H(X)此时取最大值。
3.条件熵
设二维随机变量(X,Y),其联合概率分布为:

注意,这个条件熵,不是指随机变量X在给定某个数的情况下,另一个变量的熵是多少,变量的不确定性是多少?而是期望!因为条件熵中X也是一个变量,意思是在一个变量X的条件下(变量X的每个值都会取),另一个变量Y熵对X的期望。
条件熵比熵多了一些背景知识,按理说条件熵的不确定性小于熵的不确定,即H(Y|X)≤H(Y),事实也是如此,下面这个定理有力地说明了这一点。
定理:对二维随机变量(X,Y),条件熵H(Y|X)和信息熵H(Y)满足如下关系:
H(Y|X)≤H(Y)                                                                                      (4.36)
4.互信息
互信息(mutual information)又称为信息增益,用来评价一个事件的出现对于另一个事件的出现所贡献的信息量。记为:
I(X,Y)=H(Y)-H(Y|X)                                                                               (2.37)
在决策树的特征选择中,信息增益为主要依据。在给定训练数据集D,假设数据集由n维特征构成,构建决策树时,一个核心问题就是选择哪个特征来划分数据集,使得划分后的纯度最大,一般而言,信息增益越大,意味着使用某属性a来划分所得“纯度提升”越大。因此,我们常用信息增益来构建决策树划分属性。
5.相对熵
相对熵(relative entropy),所谓相对,一般是在两个随机变量之间来说,又被称为KL散度(Kullback–Leibler divergence,KLD),这里我们假设 p(x) 和 q(x) 是 X 取值的两个概率分布,如p(x)表示X的真实分布,q(x)表示X的训练分布或预测分布,则 p 对 q 的相对熵为:

相对熵有些重要性质:
(1)相对熵不是传统意义上的距离,它没有对称性,即
KL(p(x)||q(x))≠KL(q(x)||p(x))
(2)当预测分布q(x)与真实分布p(x)完全相等时,相对熵为0;
(3)如果两个分别差异越大,那么相对熵也越大;反之,如果两个分布差异越小,相对熵也越小。
(4)相对熵满足非负性,即 KL(p(x)||q(x))≥0
6.交叉熵
交叉熵可在神经网络(机器学习)中作为代价函数,p表示真实标记的分布,q则为训练后的模型的预测标记分布,交叉熵代价函数可以衡量p与q的相似性。交叉熵作为代价函数还有一个好处是使用sigmoid函数在梯度下降时能避免均方误差代价函数学习速率降低的问题,因为学习速率可以被输出的误差所控制。

4.11 小结

概率与信息论是机器学习的重要基础及重要理论依据。本章介绍了概率论、信息论的一些基本概念,如样本空间、随机变量等。根据随机变量取值不同,又可分为有离型和连续型随机变量;根据随机变量的维度又可分为一维或多维随机变量。概率分布、边缘分布是刻画随机变量的重要特征,而期望、方差及协方差是随机变量三个常用统计量。信息论是刻画随机变量的另一种方式,信息论在深度学习、人工智能中应用非常广泛,后续章节也经常会出现。

下载本章代码及数据

第3章 线性代数


机器学习、深度学习的基础除了编程语言外,还有一个就是应用数学。它一般包括线性代数、概率与信息论、概率图、数值计算与最优化等。其中线性代数又是基础的基础。线性代数是数学的一个重要分支,广泛应用于科学和工程领域。大数据、人工智能的源数据在模型训练前,都需要转换为向量或矩阵,而这些运算正是线性代数的主要内容。
如在深度学习的图像处理中,如果1张图由28*28像素点构成,那这28*28就是一个矩阵。在深度学习的神经网络中,权重一般都是矩阵,我们经常把权重矩阵W与输入X相乘,输入X一般是向量,这就涉及矩阵与向量相乘的问题。诸如此类,向量或矩阵之间的运算在深度学习中非常普遍,也非常重要。
本章主要介绍如下内容:
标量、向量、矩阵和张量
矩阵和向量运算
特殊矩阵与向量
线性相关性及向量空间

3.1标量、向量、矩阵和张量

在机器学习、深度学习中,首先遇到的就是数据,如果按类别来划分,我们通常会遇到以下4种类型的数据。
1.标量(scalar)
一个标量就是一个单独的数,一般用小写的变量名称表示,如a,x等。
2.向量(vector)
向量就是一列数或一个一维数组,这些数是有序排列的。通过次序中的索引,我们可以确定向量中每个单独的数。通常我们赋予向量粗体的小写变量名称,如x、y等。一个向量一般有很多元素,这些元素如何表示?我们一般通过带脚标的斜体表示,如x_1 表示向量x中的第一个元素,x_2表示第二元素,依次类推。
当需要明确表示向量中的元素时,我们一般将元素排列成一个方括号包围的纵列:

我们可以把向量看作空间中的点,每个元素是不同的坐标轴上的坐标。
向量可以这样表示,那我们如何用编程语言如python来实现呢?如何表示一个向量?如何获取向量中每个元素呢?请看如下实例:

打印结果如下:
5
1 2 4 8

这说明向量元素个数为5,向量中索引一般从0开始,如a[0]表示第一个元素1,a[1]
表示第二个元素2,a[2]表示第三个元素4,依次类推。这是从左到右的排列顺序,如果从右到左,我们可用负数来表示,如a[-1]表示第1个元素(注:从右到左),a[-2]表示第2个元素,依次类推。
3.矩阵(matrix)
矩阵是二维数组,其中的每一个元素被两个索引而非一个所确定。我们通常会赋予矩阵粗体的大写变量名称,比如A。如果一个实数矩阵高度为m,宽度为n,那么我们说A∈R^mxn。
与向量类似,可以通过给定行和列的下标表示矩阵中元素,下标用逗号分隔,如A_1,1表示A左上的元素,A_1,2表示第一行第二列对应的元素,依次类推;这是表示单个元素,如果我们想表示1列或1行,该如何表示呢?我们可以引入冒号":"来表示,如第1行,可用A1,:表示,第2行,用A2,:表示,第1列用A:,1表示,第n列用A:,n表示。
如何用Python来表示或创建矩阵呢?如果希望获取其中某个元素,该如何实现呢?请看如下实例:

打印结果:
[[1 2 3]
[4 5 6]]
6
(2, 3)
1 2 5
[4 5 6]

矩阵可以用嵌套向量生成,和向量一样,在Numpy中,矩阵元素的下标索引也是从0开始的。
4.张量(tensor)
几何代数中定义的张量是向量和矩阵的推广,通俗一点理解的话,我们可以将标量视为零阶张量,向量视为一阶张量,那么矩阵就是二阶张量,三阶的就称为三阶张量,以此类推。在机器学习、深度学习中经常遇到多维矩阵,如一张彩色图片就是一个三阶张量,三个维度分别是图片的高度、宽度和色彩数据。
张量(tensor)也是深度学习框架TensorFlow的重要概念。TensorFlow由tensor(张量)+flow(流)构成。
同样我们可以用Python来生成张量及获取其中某个元素或部分元素,请看实例:

打印结果如下:
[[[ 0 1 2 3]
[ 4 5 6 7]]

[[ 8 9 10 11]
[12 13 14 15]]]
16
(2, 2, 4)
0 1 5
[4 5 6 7]
5.转置(transpose)
转置以主对角线(左上到右下)为轴进行镜像操作,通俗一点来说就是行列互换。将矩

向量可以看作只有一列的矩阵,把(3.1)式中向量x进行转置,得到下式。

用Numpy如何实现张量的转置?很简单,利用张量的T属性即可,示例如下:

打印结果如下:
[[1 2 3]
[4 5 6]]
[[1 4]
[2 5]
[3 6]]

3.2矩阵和向量运算

矩阵加法和乘法是矩阵运算中最常用的操作之一,两个矩阵相加,需要它们的形状相同,进行对应元素的相加,如:C=A+B,其中C_(i,j)=A_(i,j)+B_(i,j)。矩阵也可以和向量相加,只要它们的列数相同,相加的结果是矩阵每行与向量相加,这种隐式地复制向量b到很多位置的方式称为广播(broadcasting),以下我们通过一个代码实例来说明。

打印结果为:
[[11 22 33]
[14 25 36]]

两个矩阵相加,要求它们的形状相同,如果两个矩阵相乘,如A和B相乘,结果为矩阵C,矩阵A和B需要什么条件呢?条件比较简单,只要矩阵A的列数和矩阵B的行数相同即可。如果矩阵A的形状为m×n,矩阵B的形状为n×p,那么矩阵C的形状就是m×p,例如:
C=AB,则它们的具体乘法操作定义为:

矩阵乘积有很多重要性质,如满足分配律A(B+C)=AB+AC 和结合律,A(BC)=(AB)C。大家思考一下是否满足交换律?

3.3特殊矩阵与向量

上一节我们介绍了一般矩阵的运算,实际上在机器学习或深度学习中,我们还经常遇到一些特殊类型的矩阵,如可逆矩阵、对称矩阵、对角矩阵、单位矩阵、正交矩阵等等。这些特殊矩阵有特殊属性,下面我们逐一进行说明。
1.可逆矩阵
先简单介绍一下可逆矩阵,因后续需要用到。在(3.3)式中,假设矩阵W已知,向量b已知,如何求向量x?为求解向量x,我们需要引入一个称为逆矩阵的概念。而为了求逆矩阵,又牵涉到单位矩阵,何为单位矩阵?单位矩阵的结构很简单,就是所有沿主对角线上的元素都是1,而其他位置的元素都是0的方阵(行数等于列数的矩阵),一般记为I_n,如:


对此后续我们有更详细的讨论及代码实现。
2.对角矩阵
对角矩阵只有在主对角线上才有非零元素,其余都是0。从形式上来看,如果A为对角矩阵,当且仅当对所有i≠j,A_(i,j)=0。对角矩阵可以是方阵(行数等于列数)也可以不是方阵,如下矩阵,就是一个对角矩阵。

对角矩阵有非常好的性质,这些性质使很多计算非常高效,在机器学习、深度学习中经常会遇到对角矩阵。
对于对角矩阵为方阵的情况,我们可以把对角矩阵简单记为:

从上面两个式子可以看到对角矩阵的简洁高效。
(3)对称矩阵
对称矩阵,对于任意一个n阶方阵A,若A满足:A=A^T成立,则称方阵A为对称矩阵。
(4)单位向量

3.4线性相关性及向量空间

前面我们介绍了向量、矩阵等概念,接下来我们将介绍向量组、线性组合、线性相关性、秩等重要概念。
由多个同维度的列向量构成的集合称为向量组,矩阵可以看成是由行向量或列向量构成的向量组。
1.线性组合

2.线性相关

秩是一个重要概念,运用非常广泛,实际上矩阵我们可以看成是一个向量组。如果把矩阵看成是由所有行向量构成的向量组,这样矩阵的行秩就等于行向量组的秩;如果把矩阵看成是由所有列向量构成的向量组,这样矩阵的列秩就等于列向量组的秩。矩阵的行秩与列秩相等,因此,把矩阵的行秩和列秩统称为矩阵的秩。

3.5范数

数有大小,向量也有大小,向量的大小我们通过范数(Norm)来衡量。范数在机器学习、深度学习中运用非常广泛,特别在限制模型复杂度、提升模型的泛化能力方面效果不错。p范数的定义如下: