这一章节,我么分别对NNs和PR进行简要介绍,为下一章节讨论它们两者之间的等价性进行一些铺垫。html
一元线性回归模型git
扩展成一元多项式回归模型(degree = d)就是:github
一元多项式回归模型web
通常地,考虑 n 维特征(x1,x2,…,xn),d次幂的状况(n元d次幂多项式):算法
其中,shell
上式即为n元d次多项式的通用表达式,中间部分是一个排列组合公式的省略写法。api
从特征向量维度的角度来看,PolynomialFeatures(degree=d)将维度为n的原始特征(n元特征)扩展到了一个维度为的新特征空间。能够形象的理解为,将d个相同小球排成一排后,用n个隔板将其进行分割,每一个隔间分配多少小球的问题,排列组合的结果为
种方法。数组
值得注意的一点是,n元d次多项式在特征空间上具备两个主要特色:安全
# -*- coding: utf-8 -*- import matplotlib.pyplot as plt import numpy as np from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression if __name__ == '__main__': # generate a random dataset np.random.seed(42) m = 100 X = 6 * np.random.rand(m, 1) - 3 y = 0.5 * X ** 2 + X + 2 + np.random.randn(m, 1) plt.plot(X, y, "b.") plt.xlabel("$x_1$", fontsize=18) plt.ylabel("$y$", rotation=0, fontsize=18) plt.axis([-3, 3, 0, 10]) plt.show() # use Scikit-Learn PolynomialFeature class to constructing parameter terms. # a,b,degree=2: [a, b, a^2, ab, b^2] # a,b,degree=3: [a, b, a^2, ab, b^2, a^3, a^2b, ab^2, b^3] # a,b,c,degree=3: [a, b, c, a^2, ab, ac, b^2, bc, c^2, a^3, a^2b, a^2c, ab^2, ac^2, abc, b^3, b^2c, bc^2, c^3] poly_features = PolynomialFeatures(degree=2, include_bias=False) # fit the dataset with Polynomial Regression Function, and X_poly is the fitting X result X_poly = poly_features.fit_transform(X) print "X: ", X print "X_poly: ", X_poly lin_reg = LinearRegression() lin_reg.fit(X_poly, y) print(lin_reg.intercept_, lin_reg.coef_) # draw the prediction curve X_new = np.linspace(-3, 3, 100).reshape(100, 1) # fit the X_new dataset with Polynomial Regression Function, and X_new_poly is the fitting X result X_new_poly = poly_features.transform(X_new) y_new = lin_reg.predict(X_new_poly) plt.plot(X, y, "b.") plt.plot(X_new, y_new, "r-", linewidth=2, label="Predictions") plt.xlabel("$x_1$", fontsize=18) plt.ylabel("$y$", rotation=0, fontsize=18) plt.legend(loc="upper left", fontsize=14) plt.axis([-3, 3, 0, 10]) plt.show()
代码中有一个细节须要注意一下,PolynomialFeatures的参数d=2,即2次幂多项式。这里之因此选2次幂是由于咱们事先知道了数据集的几率分布,形状大体为一个二次函数。实际上,读者朋友能够本身修改代码,改成degree=3/4..,多项式依然能够拟合的很好,由于这个数据集中的噪声点不是不少,不容易发生过拟合。网络
可是在实际的工程中,数据集的维度数十万都是很正常的,咱们不可能事先知道最适合的d次幂参数是多少。一个最经常使用的理论和方法就是设置一个相对较大的d次幂,即便用一个相对复杂的多项式函数去拟合数据,固然,d次幂参数也不能设置的过大,由于过于复杂的多项式函数会致使过拟合的发生。
Relevant Link:
https://blog.csdn.net/tsinghuahui/article/details/80229299 https://www.jianshu.com/p/9185bc96bfa9 https://blog.csdn.net/qq_36523839/article/details/82924804
单个神经元是神经网络的最基本组成单元(1层1神经元的神经网络退化为感知机模型),单个的感知机的本质就是一个一元线性分类函数,用它能够划出一条线,把一维平面分割开:
可是,当面对更复杂的问题时,一元线性分类函数(一维平面)就没法解决了。例如”电路模拟中的XOR运算问题“。
在数字逻辑中,异或是对两个运算元的一种逻辑分析类型,符号为XOR或EOR或⊕。与通常的或(OR)不一样,当两两数值相同时为否,而数值不一样时为真。异或的真值表以下:
Input | Output | |
---|---|---|
A | B | |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
单层的神经元模型能够对“与/与非/或”等逻辑进行很好的模拟,可是惟独没法解决异或问题,以下图:
神经元模型对“与”、“与非”、“或”问题均可以找到一条完美的决策面。可是,对于XOR问题中的数据点,不管如何调整,都没法找到一个完美的决策面。
没法找出一条直线做为决策边界,可使(0,0)和(1,1)在一个区域,而(1,0)和(0,1)在另外一个区域。
该隐藏神经元构造的决策边界斜率等于-1,在下图中给出其位置:
隐藏层底部的神经元标注为“Neuron 2”,有:
隐藏元构造的决策边界方向和位置由下图给出:
咱们能够看到,两个隐藏神经元已经各自完成了一半的分类任务,如今须要的是一个“复合决策函数”,将它们分类能力进行一个综合,获得原来两个隐藏神经元造成的决策边界构造线性组合。
输出层的神经元标注为“Neuron 3”,有:
底部隐藏神经元由一个兴奋(正)链接到输出神经元,而顶部隐藏神经元由一个更强的抑制(负)链接到输出神经元。这样,经过构造一个隐层(本质上是一个复合线性函数),咱们成功地解决了XOR问题。
从复合线性函数的角度来分析,上图所示的神经网络等价于:
合并同类项后有:
能够看到,上式本质上是一个多元1次幂线性方程组。
不管单条直线决策面如何调整,都是完成二分类任务
面对这种状况,就须要增长线性元的数量,改用多元复合线性函数来进行多元线性切割,以下图:
从理论上说,若是容许复合线性函数中的单个线性函数彼此平行或相交,则几乎全部数据集均可以经过多元线性复合函数进行线性切割。换句话说,多元线性复合函数能够无限逼近任意几率分布(通用逼近理论)。
决策树的多层树结构,本质就是感知机DNN的多层结构。从这个角度来看,决策树和感知机DNN一样都存在过拟合问题。
延伸思考:
以R2二维空间为例,属于目标函数的点可能分布在空间中的任何位置,模型训练的过程就是须要找到一个超分界面,将全部的点都分类到合适的类别中,这就是所谓的”模型记忆“。须要注意的是,如下两个观点都是错误的:
正确的理解是:最少须要的神经元数量取决于目标函数几率分布的规律性,若是目标函数在特征空间中不一样类别是彼此交错分布的,那么为了正确地”切割“出一个合适的超平面,就须要远大于pattern数的基函数,这样切出来的超平面边界会很是的锯齿状,相应的也能够想象,抗扰动能力也会相应降低。这也是为何说越复杂的模型越容易过拟合的缘由。
上一小节留下的问题是,有没有既能实现完美分类,同时又能有效控制函数元数量的复合函数呢?答案是确定的,这就是咱们接下来要讨论的非线性复合函数(包含非线性激活函数的神经网络)。
咱们知道,使用阶跃激活函数的多元感知机神经网络,本质上是多个线性分界面的组合,以下图:
上图中,若是咱们能构造出一个弯曲的决策超曲面,就可能实现用少许的非线性函数,直接对数据集进行分类。
非线性激活函数有不少,不一样的数学公式形式带来了不一样的数学特性,这里咱们以sigmoid函数为例:
单神经元后增长了一个非线性激活函数的神经网络
Relevant Link:
https://www.cnblogs.com/LittleHann/p/6629069.html - Neural Networks and Deep Learning(神经网络与深度学习) - 学习笔记
这个章节咱们来从神经网络的观点来看多项式拟合函数,并分析其等价性。
对于一元一次幂的逼近函数:
能够当作为以下图的三层神经网络,
左:输入层到隐层的权系数均为常值1
右:输入层到隐层当作为“直接代入”(用虚线表示)
神经网络中只有一个隐层。隐层上有个节点,激活函数分别为基函数
(从这里咱们将基函数称为“激活函数”)。输入层到隐层的权设为常值1(左图),也能够当作为将输入层的值“直接代入”到激活函数(右图)。隐层到输出层的权为基函数的组合系数
。
考虑通常的逼近函数。设
中的一组基函数为
。则函数
可当作为以下图的一个三层的神经网络,
注意这里隐层的激活函数都是维函数,从输入层到隐层也是直接代入。输出层的各个份量
共享隐层的激活函数。
通常地, 一个多元线性回归方程,等价于一个3层人工神经网络。也就是说,只要包含一个隐层的人工神经网络,就能够等价全部多形式回归模型。
更进一步地,若是给人工神经网络加上非线性激活函数、增长网络深度,这只是在增长神经网络的自由度,多项式回归依然可以在一个限定的偏差ε内,近似地等价于该神经网络。
并且在实际工程中,这个近似的程度还得具体目标分布有关(目标问题场景),若是目标分布较简单,则在正则化稀疏学习的做用下,神经网络会退化为一个多项式函数。这就是为什咱们在某些简单的问题上,用随机森林和深度神经网络的效果是差很少的,甚至传统随机森林效果还要更好。
Relevant Link:
http://staff.ustc.edu.cn/~lgliu/Resources/DL/What_is_DeepLearning.html
上一章节咱们讨论了NNs和PR的等价性,基本上来讲,咱们能够将NNs和PR视为同一种函数模型。这个章节咱们就来讨论一个对它们两者都使用的通用逼近理论(universal approximation theorems),通用逼近理论告诉咱们,必定存在一个多层神经网络或者多项式函数,能够在必定的偏差ε内,近似地逼近任意函数分布。
虽然通用逼近定理并无给出如何找到这个NNs或PR,可是它从理论上证实了强存在性,这个存在性定理实在使人振奋,由于这意味着,在具体工程项目中,咱们总能够应用深度神经网络取得一个不错的结果。
在讨论具体的理论以前,咱们首先经过一个简单的案例,对逼近问题创建一个直观的感觉。
咱们先考虑最简单的情形,即实数到实数的一元函数。假设经过实验得到了m个样本点
。咱们但愿求得反映这些样本点规律的一个函数关系
,以下图所示。
若是要求函数严格经过每一个样本点,即:,则求解函数的问题称为插值问题(Interpolation)。插值问题通常须要针对样本数据直接求解线性方程组的解。
插值问题更多仅限于理论分析,在实际的工程中,由于偏差和目标函数未知的缘故,几乎不可能找到一个函数能完美经过全部的样本点。因此,更多时候,咱们须要讨论逼近问题,而插值问题就是逼近问题的一个特例(偏差为0的逼近),相关讨论,能够参阅这篇文章。
通常地,因为实验数据带有观测偏差,所以在大部分状况下,咱们只要求函数反映这些样本点的趋势,即函数靠近样本点且偏差在某种度量意义下最小,称为逼近问题(Approximation)。
若记在某点的偏差为,且记偏差向量为
。逼近问题就是要求向量
的某种范数
最小。通常采用欧氏范数(
范数)做为偏差度量的标准(均方偏差),即求以下极小化问题:
极小化问题,通常可经过极大似然估计或者矩估计的方法实现。
通用逼近理论讨论的就是函数逼近问题,咱们接下来围绕这个主题展开讨论。
在科学技术的各领域中,咱们所研究的事件通常都是有规律(因果关系)的,即自变量集合与应变量集合之间存在的对应关系一般用映射来描述,按照模型(函数)是否具有明确的函数表达式(几率分布函数),能够将模型大体分为两类:
函数的表示是函数逼近论中的基本问题。在数学的理论研究和实际应用中常常遇到下类问题:在选定的一类函数中寻找某个函数,使它与已知函数(或观测数据)在必定意义下为最佳近似表示,并求出用近似表示而产生的偏差。这就是函数逼近问题。
在实际问题中,首先要肯定函数的具体形式。这不单纯是数学问题,还与所研究问题的运动规律及观测数据有关,也与用户的经验有关。通常地,咱们在某个较简单的函数类中去寻找咱们所须要的函数。这种函数类叫作逼近函数类。
逼近函数类能够有多种选择,通常能够在不一样的函数空间(好比由一些基函数经过线性组合所张成的函数空间)中进行选择。以下是一些经常使用的函数类。
n次代数多项式,即由次数不大于n的幂基的线性组合的多项式函数:
其中为实系数。
更经常使用的是由n次Bernstein基函数来表达的多项式形式(称为Bernstein多项式或Bezier多项式):
其中Bernstein基函数
n阶三角多项式,即由阶数不大于n的三角函数基的线性组合的三角函数:
其中为实系数。
这些是经常使用的逼近函数类。在逼近论中,还有许多其余形式的逼近函数类,好比由代数多项式的比构成的有理分式集(有理逼近);按照必定条件定义的样条函数集(样条逼近);径向基函数(RBF逼近);由正交函数系的线性组合构成的(维数固定的)函数集等。
在逼近论中,还有许多其余形式的逼近函数类,好比:
在函数逼近论中,若是一组函数成为一组“基”函数,须要知足一些比较好的性质,好比:
咱们重点来讨论一下完备性,即“万能逼近定理”,
【Weierstrass逼近定理】
对上的任意连续函数g,及任意给定的
,必存在n次代数多项式
,使得:
Weierstrass逼近定理代表,只要次数n足够高,n次多项式就能以任何精度逼近给定的函数。具体的构造方法有Bernstein多项式或Chebyshev多项式等。
相似地,由Fourier分析理论(或Weierstrass第二逼近定理),只要阶数足够高,n阶三角函数就能以任何精度逼近给定的周期函数,n阶高斯函数就能组成GMM分布以逼近任意给定的几率分布函数。
这些理论代表,多项式函数类、三角函数类、高斯函数在函数空间是“稠密”的,这就保障了用这些函数类来做为逼近函数是“合理”的。
在一个逼近问题中选择什么样的函数类做逼近函数类,这要取决于被逼近函数自己的特色,也和逼近问题的条件、要求等因素有关。在实际应用中,存在着两个最大的挑战,
用不一样次数的多项式拟合样本点(蓝色点)。
左:欠拟合;中:合适的拟合;右:过拟合。
这里须要说起的是,一个逼近函数“表达能力”体如今该函数的未知参数(例如多项式中的系数)与样本点个数的差,也称为“自由度”。
若是逼近函数的未知参数越多,则表达能力越强。然而,在实际的拟合问题中,逼近函数的拟合能力并不是越强越好。由于若是只关注样本点处的拟合偏差的话,很是强的表达能力会使得样本点以外的函数值远远偏离指望的目标,反而下降拟合函数的预测性能,产生过拟合,如上图(右)所示。拟合能力和过拟合规避之间的平衡,就是经过对自由度的控制来实现。
这一小节,咱们来讨论一下通用神经网络,主要是探寻NNs是如何同时实现通用逼近和防止过拟合这2个目标的,PR和NNs是等价的,所以本章的讨论对PR也一样成立。
对于通用神经网络来讲,网络的结构设置都存在着以下两个主要挑战:
接下来咱们来讨论通用神经网络是如何解决上述两大挑战的。
如何在没有太多领域先验的状况下,选择合适的“基函数”的另外一个策略是“原子化构建基础,数据驱动结构生成”。
注意到,对于任意一个很是值的一元函数,这里咱们称为元函数,其沿着x方向的平移函数
,以及沿着x方向的伸缩函数
都与原函数线性无关。
也就是说,若是能有足够多的元函数通过平移和伸缩变换,其线性组合所张成的函数空间就能有充分的表达能力(高秩矩阵)。因此接下来的问题就是,如何有效地获得
。
一个天然的想法就是,咱们能够以这个做为激活函数,让网络自动地去学习这些激活函数的变换
,来表达所须要的拟合函数呢?以下图所示,
一元(单变量)函数的神经元结构
对单神经元来讲,变量乘以一个伸缩,加上一个平移(称为偏置“bias”),即变量的仿射变换,成为神经元的输入
,而后经过激活函数
复合后成为该神经元的输出
。
对于多变量的情形(多元函数),神经元的结构以下图所示,
在多神经元网络中,每一层的全部神经元都互相链接,变量的线性组合,加上一个平移(称为偏置“bias”),即变量的仿射变换,成为神经元的输入
;而后经过激活函数
复合后成为该神经元的输出
。
一个多元函数的神经网络的结构以下图所示,有一个输入层,一个隐层及一个输出层,
这个网络的全部权系数(层与层神经元之间的权),
(偏置项)及做为这个神经网络的参数变量,须要经过极小化损失函数来求解的。这个过程称为“训练”或“学习”。
和回归分析相似,神经网络的学习过程本质上就是在学习全部的系数参数。最后获得的拟合函数为一些基函数的线性组合表达。这些组合函数
实质上就是表达函数
的“基函数”。这样就经过数据驱动的方式,获得了一个最优的基函数线性组合。
从这个观点来看,神经网络本质上就是传统的逼近论中的逼近函数的一种推广。它不是经过指定的理论完备的基函数(例如多项式,三角多项式等)来表达函数的,而是经过简单的基元函数(激活函数)的不断变换获得的“基函数”来表达函数的。
解决了基函数选择的问题,咱们还要问个问题:将函数通过充分多的平移和伸缩(包括它们的组合)所线性张成的函数空间,其表达能力足够强吗?这个函数空间是否在全部函数空间是稠密的?
若是结论是确定的,那么就是说,对于任何一个给定的函数,总能找到函数的屡次平移和缩放的函数,其线性组合可以逼近给定的这个函数。也就是说,神经网络只要隐层的节点数足够多,该网络所表达的函数就能逼近任意的函数。
这个结论在大多数状况是成立的,由【万能逼近定理】所保证。
记为空间中的单位立方体,咱们在这个定义域中来描述万能逼近定理。记
为
上的连续函数空间,
为
上的可测函数空间,
为
上相对测度μ的可积函数空间(即
)。
设给定一元激活函数,首先给出以下定义,
【定义1】称函数为压缩函数,若是
单调不减,且知足
【定义2】称函数为可分辨的,若对于有限测度μ,由
可获得
【定义3】记
为全部由激活函数变换及线性累加所构成的m维函数空间(即具备n个节点的单隐层神经网络所表达的m维函数)。
由以上定义,有如下几个定理(涉及实分析和泛函分析),
【定理1】若是压缩函数,则
在
中一致稠密,在
中按以下距离
下稠密:
【定理2】若是可分辨的,则
在
中按连续函数距离下稠密。
【定理3】若是连续有界的很是值函数,则
在
中稠密。
【定理4】若是无界的很是值函数,则
在
中稠密。
通俗地说就是:对任意给定的一个中的函数
,只要项数
足够多,
中就存在一个函数
,使得
在必定精度下逼近
。也就是说,包含m个神经元的单隐层的神经网络所表达的
维函数可以逼近
中的任意一个函数。
基于万能逼近定理,人工神经网络每每会选择一个超完备集神经元,即用大于目标函数维度的神经元数量,来构建一个复杂神经网络,以保证近似逼近能力。
使用超完备集在得到万能逼近能力的同时,会带来过拟合问题。在人工神经网络中加入稀疏学习,能够有效避免该现象。
Relevant Link:
http://staff.ustc.edu.cn/~lgliu/Resources/DL/What_is_DeepLearning.html K. Hornik, et al. Multilayer feedforward networks are universal approximations. Neural Networks, 2: 359-366, 1989. G. Cybenko. Approximation by superpositions of a sigmoidal function. Math. Control Signals System, 2: 303-314, 1989. K. Hornik. Approximation capabilities of multilayer feedforward networks. Neural Networks, 4: 251-257, 1991.
在实际工程项目中,无论是直接应用VGG-xx或者本身设计一种全新的网络结构,网络的参数动辄都上千万,网络愈来愈复杂,参数愈来愈多。
但须要注意的是,拟合函数所带的参数的个数与样本数据的个数之间的差表明着这个拟合函数的“自由度”。网络愈来愈“深”后,拟合模型中的可调整参数的数量就很是大。所以,层数很大的深度网络(模型过于复杂)可以表达一个自由度很是大的函数空间,甚至远高于目标函数空间(过完备空间),即自由度远大于0。这样就很容易致使过拟合(Overfitting),例以下图所示,
过拟合可使得拟合模型可以插值全部样本数据(拟合偏差为0!)。但拟合偏差为0不表明模型就是好的,由于模型只在训练集上表现好;因为模型拟合了训练样本数据中的噪声,使得它在测试集上表现可能很差,泛化性能差等。
为此,人们采起了不一样的方法来缓解过拟合(没法彻底避免),好比正则化、数据增广、Dropout、网络剪枝等。这些方法的底层原理,归结为一句话都是:稀疏表达和稀疏学习。
缓解逼近函数过拟合的有效手段是,在函数公式中对回归变量施加范数的正则项,例如L1/L2正则项,以达到对回归变量进行稀疏化,即大部分回归变量为0(少数回归变量非0)。
这种优化称为稀疏优化。也就是说,对回归变量施加范数可以“自动”对基函数进行选择,值为0的系数所对应的基函数对最后的逼近无贡献。这些非0的基函数反映了的样本点集合的“特征”,所以也称为特征选择。
咱们每每能够多选取一些基函数(甚至能够是线性相关的)及较高的次幂,使得基函数的个数比输入向量的维数还要大,称为“超完备”基(Over-complete basis)或过冗余基,在稀疏学习中亦称为“字典”。而后经过对基函数的系数进行稀疏优化(稀疏学习/字典学习),选择出合适(非0系数)的基函数的组合来表达逼近函数。
稀疏学习与最近十年来流行的压缩感知(Compressive sensing)理论与方法很是相关,也是机器学习领域的一种重要方法。其理论基础由华裔数学家陶哲轩(2006年国际数学家大会菲尔兹奖得主)、斯坦福大学统计学教授David Donoho(2018年国际数学家大会高斯奖得主)等人所创建,已成功用于信号处理、图像与视频处理、语音处理等领域。
Relevant Link:
https://cosx.org/2016/06/discussion-of-sparse-coding-in-deep-learning/
所谓多重共线性,简单来讲,是指回归模型中存在两个或两个以上的自变量彼此相关。其实本质上说,多重共线性和咱们上一章讨论的超完备基本质上是同样的,由于超完备基经常是稀疏的,其内部每每存在较多线性相关的结构。
在NNs和PR中,也一样存在多重共线性问题,因此这章咱们也来讨论这个问题,经过这些讨论,咱们可以更加深入理解NNs和PR的等价性。
# -*- coding: utf-8 -*- import numpy as np import matplotlib.pyplot as plt from sklearn.linear_model import LinearRegression from sklearn import cross_validation if __name__ == '__main__': # 首先捏造一份好的数据,样本量为100,特征数为8,且知足方程: y = 5x_0 + 6x_1 + 7x_2 + 8x_3 + 9x_4 + 10x_5 + 11x_6 + 12x_7 + b coef0 = np.array([5, 6, 7, 8, 9, 10, 11, 12]) X1 = np.random.rand(100, 8) # 偏差项是指望为0,标准差为1.5的正态分布随机变量。 y = np.dot(X1, coef0) + np.random.normal(0, 1.5, size=100) training = np.random.choice([True, False], p=[0.8, 0.2], size=100) lr1 = LinearRegression() lr1.fit(X1[training], y[training]) # 系数的均方偏差MSE print "lr1 MSE: ", (((lr1.coef_ - coef0) ** 2).sum() / 8) # 测试集准确率(R2) print "lr1 R2:", (lr1.score(X1[~training], y[~training])) # 平均测试集准确率 print "lr1 MR2:", (cross_validation.cross_val_score(lr1, X1, y, cv=5).mean()) # 基于上面构造数据,另外构造出两份数据, # 1. X2: 第一份数据则增长两个共线性特征,目的是显著增长其VIF值 # 2. X3: 第二份数据增长两个随机的特征用做对比 X2 = np.column_stack([X1, np.dot(X1[:, [0, 1]], np.array([1, 1])) + np.random.normal(0, 0.05, size=100)]) X2 = np.column_stack([X2, np.dot(X2[:, [1, 2, 3]], np.array([1, 1, 1])) + np.random.normal(0, 0.05, size=100)]) X3 = np.column_stack([X1, np.random.rand(100, 2)]) # 拿这两份数据从新用线性回归拟合模型 lr2 = LinearRegression() lr2.fit(X2[training], y[training]) # 系数的均方偏差MSE # 对于第二份共线性构造数据X2,由于多重共线性,能够看到MSE增长了不少,准确率也降低了0.2%: print "lr2 MSE: ", (((lr2.coef_[:8] - coef0) ** 2).sum() / 8) # 测试集准确率(R2) print "lr2 R2: ", (lr2.score(X2[~training], y[~training])) # 平均测试集准确率 print "lr2 MR2: ", (cross_validation.cross_val_score(lr2, X2, y, cv=5).mean()) lr3 = LinearRegression() lr3.fit(X3[training], y[training]) # 系数的均方偏差MSE # X3没有明显变化 print "lr3 MSE: ", (((lr3.coef_[:8] - coef0) ** 2).sum() / 8) # 测试集准确率(R2) print "lr3 R2: ", (lr3.score(X3[~training], y[~training])) # 平均测试集准确率 print "lr3 MR2: ", (cross_validation.cross_val_score(lr3, X3, y, cv=5).mean()) # show lr2 VIF result vif2 = np.zeros((10, 1)) for i in range(10): tmp = [k for k in range(10) if k != i] lr2.fit(X2[:, tmp], X2[:, i]) vifi = 1 / (1 - lr2.score(X2[:, tmp], X2[:, i])) vif2[i] = vifi vif3 = np.zeros((10, 1)) for i in range(10): tmp = [k for k in range(10) if k != i] lr2.fit(X3[:, tmp], X3[:, i]) vifi = 1 / (1 - lr2.score(X3[:, tmp], X3[:, i])) vif3[i] = vifi plt.figure() ax = plt.gca() ax.plot(vif2) ax.plot(vif3) plt.xlabel('feature') plt.ylabel('VIF') plt.title('VIF coefficients of the features') plt.axis('tight') plt.show()
能够看到,0、一、二、三、八、9个特征的VIF都太高,其中第9个是咱们人工构造出了存在线性相关依赖的新特征变量。
在神经网络中,同层的神经元和层与层之间的神经元之间都有可能存在多重共线性(线性相关),层内的多重共线性能够经过正则化进行缓解,相比之下,层与层神经元之间存在的多重共线性就没法避免了,它是广泛存在的。
计算层与层之间神经元的平均VIF结果以下:
能够看到随着前向传递的进行,后面层的神经元的VIF愈来愈大,以层的视角来看,层与层之间的线性相关性逐渐提升,这个结论对PR也是一样成立的。
这也从另外一个层面看到,对于神经网络来讲,真正起做用的也只有最后一层隐层,虽然训练过程是全网络总体反馈调整的,可是最终输出层的结果大部分由最有一层隐层的基函数决定。
Relevant Link:
https://baike.baidu.com/item/%E6%96%B9%E5%B7%AE%E6%89%A9%E5%A4%A7%E5%9B%A0%E5%AD%90 https://www.jianshu.com/p/0925347c5066 https://www.jianshu.com/p/ef1b27b8aee0 https://arxiv.org/pdf/1806.06850v1.pdf
通常来讲,模型的输入层是可解释性最强的,例如原始专家经验特征、图像像素矩阵、文本原始词序列向量等。
假设输入层维度为d,可解释性模型的维度d’应该小于等于输入层维度d,用”0/1“编码来表征输入层的每个特征是否出现,即,
Local Interpretable Model-agnostic Explanations(局部线性逼近可解释模型)须要同时平衡两个对立的目标:
经过极大似然估计来得到一个最优结果:
LIME捕获局部线性特征的过程以下图所示,
将目标函数f()当作是一个零先验黑盒,经过不断重复f(x)->add random noise to x->f(x),勾勒出目标函数的近似局部边界,并将获取的样本做为打标数据输入LIMIE模型进行负反馈训练,这个作法和蒙特卡洛采样的思想是相似的。
还有一点值得注意,LIMIE同时使用正则化来进行稀疏学习,进一步减小可解释性单元,将可解释单元集中在特定的一些重点特征上,提升人类可读性。
咱们知道,随机森林本质上是一个最优赫夫曼编码函数。在随机森林每棵树中,特征节点的选择和各个特征节点所处的位置,自己就包含了一个复合线性决策函数的能力。可是,咱们最多也只能定性地了解有限特征的相对重要性,对每个特征定量的重要性评估没法得知。
咱们经过LIME对一个随机森林模型进行“local linear approximation(局部线性近似)”,借助线性函数的强可解释性,来定量研究随机森林在预测中,各个特征向量各自起到了多少的贡献(似然几率)。
# -*- coding: utf-8 -*- import lime import sklearn import numpy as np import sklearn import sklearn.ensemble import sklearn.metrics from sklearn.datasets import fetch_20newsgroups from lime import lime_text from sklearn.pipeline import make_pipeline from lime.lime_text import LimeTextExplainer if __name__ == '__main__': # we'll be using the 20 newsgroups dataset. # In particular, for simplicity, we'll use a 2-class subset: atheism and christianity. categories = ['alt.atheism', 'soc.religion.christian'] newsgroups_train = fetch_20newsgroups(subset='train', categories=categories) newsgroups_test = fetch_20newsgroups(subset='test', categories=categories) class_names = ['atheism', 'christian'] # use the tfidf vectorizer, commonly used for text. vectorizer = sklearn.feature_extraction.text.TfidfVectorizer(lowercase=False) train_vectors = vectorizer.fit_transform(newsgroups_train.data) test_vectors = vectorizer.transform(newsgroups_test.data) # use random forests for classification. # It's usually hard to understand what random forests are doing, especially with many trees. rf = sklearn.ensemble.RandomForestClassifier(n_estimators=500) rf.fit(train_vectors, newsgroups_train.target) pred = rf.predict(test_vectors) print sklearn.metrics.f1_score(newsgroups_test.target, pred, average='binary') # Lime explainers assume that classifiers act on raw text, # but sklearn classifiers act on vectorized representation of texts. # For this purpose, we use sklearn's pipeline, and implements predict_proba on raw_text lists. c = make_pipeline(vectorizer, rf) print(c.predict_proba([newsgroups_test.data[0]])) # Now we create an explainer object. We pass the class_names a an argument for prettier display. explainer = LimeTextExplainer(class_names=class_names) # We then generate an explanation with at most 6 features for an arbitrary document in the test set. idx = 83 exp = explainer.explain_instance(newsgroups_test.data[idx], c.predict_proba, num_features=6) print('Document id: %d' % idx) print('Probability(christian) =', c.predict_proba([newsgroups_test.data[idx]])[0, 1]) print('True class: %s' % class_names[newsgroups_test.target[idx]]) # The classifier got this example right (it predicted atheism). # The explanation is presented below as a list of weighted features. print "exp.as_list(): ", exp.as_list() # These weighted features are a linear model, # which approximates the behaviour of the random forest classifier in the vicinity of the test example. # Roughly, if we remove 'Posting' and 'Host' from the document , # the prediction should move towards the opposite class (Christianity) by about 0.27 (the sum of the weights for both features). # Let's see if this is the case. print('Original prediction:', rf.predict_proba(test_vectors[idx])[0, 1]) tmp = test_vectors[idx].copy() tmp[0, vectorizer.vocabulary_['Posting']] = 0 tmp[0, vectorizer.vocabulary_['Host']] = 0 print('Prediction removing some features:', rf.predict_proba(tmp)[0, 1]) print('Difference:', rf.predict_proba(tmp)[0, 1] - rf.predict_proba(test_vectors[idx])[0, 1]) # Visualizing explanations # The explanations can be returned as a matplotlib barplot: fig = exp.as_pyplot_figure() fig.show() exp.save_to_file('./oi.html')
能够看到,在将某一个document预测为“atheism”这一类别的时候,总共有“edu”、“NNTP”、“Posting”、“Host”、“There”、“hava”这些单词起到了似然几率贡献,而且它们各自的贡献比是不一样的。
google的InceptionV3神经网络模型,采用卷积网络对图像进行了预训练。这节,咱们用局部线性逼近方法,来对该网络的局部可解释性进行分析。
# -*- coding: utf-8 -*- import matplotlib.pyplot as plt import numpy as np import os import keras import os, sys try: import lime except: sys.path.append(os.path.join('..', '..')) # add the current directory import lime from lime import lime_image from keras.applications import inception_v3 as inc_net from keras.preprocessing import image from keras.applications.imagenet_utils import decode_predictions from skimage.io import imread from skimage.segmentation import mark_boundaries print('run using keras:', keras.__version__) def transform_img_fn(path_list): out = [] for img_path in path_list: img = image.load_img(img_path, target_size=(299, 299)) x = image.img_to_array(img) x = np.expand_dims(x, axis=0) x = inc_net.preprocess_input(x) out.append(x) return np.vstack(out) if __name__ == '__main__': # Using Inception # Here we create a standard InceptionV3 pretrained model, # and use it on images by first preprocessing them with the preprocessing tools inet_model = inc_net.InceptionV3() # Let's see the top 5 prediction for some image img_path = os.path.join('data', 'cat_and_mouse.jpg') print "img_path: ", img_path images = transform_img_fn([img_path]) # I'm dividing by 2 and adding 0.5 because of how this Inception represents images plt.imshow(images[0] / 2 + 0.5) plt.show() preds = inet_model.predict(images) for x in decode_predictions(preds)[0]: print(x) # Explanation # Now let's get an explanation explainer = lime_image.LimeImageExplainer() # hide_color is the color for a superpixel turned OFF. # Alternatively, if it is NONE, the superpixel will be replaced by the average of its pixels. # Here, we set it to 0 (in the representation used by inception model, 0 means gray) explanation = explainer.explain_instance(images[0], inet_model.predict, top_labels=5, hide_color=0, num_samples=1000) # Now let's see the explanation for the Top class # We can see the top 5 superpixels that are most positive towards the class with the rest of the image hidden temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=True, num_features=5, hide_rest=True) plt.imshow(mark_boundaries(temp / 2 + 0.5, mask)) plt.show() # Or with the rest of the image present: temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=True, num_features=5, hide_rest=False) plt.imshow(mark_boundaries(temp / 2 + 0.5, mask)) plt.show() # We can also see the 'pros and cons' (pros in green, cons in red) temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=False, num_features=10, hide_rest=False) plt.imshow(mark_boundaries(temp / 2 + 0.5, mask)) plt.show() # Or the pros and cons that have weight at least 0.1 temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=False, num_features=1000, hide_rest=False, min_weight=0.1) plt.imshow(mark_boundaries(temp / 2 + 0.5, mask)) plt.show()
原始待预测打标图像
经过局部线性逼近获得的top预测类(tabby)分界面(核心特征区)
with the rest of the image present
pros and cons' (pros in green, cons in red)
经过这个例子,咱们能够更加深入的认识到,卷积神经网络是如何经过选取捕获像素图中特定区域,实现目标检测与目标识别任务的。
# -*- coding: utf-8 -*- import matplotlib.pyplot as plt import numpy as np import pandas as pd from keras.models import Sequential from keras.layers import LSTM, Dropout, Dense from keras.optimizers import Adam from keras.utils import to_categorical from sklearn.preprocessing import MinMaxScaler from sklearn.metrics import classification_report from lime import lime_tabular def reshape_data(seq, n_timesteps): N = len(seq) - n_timesteps - 1 nf = seq.shape[1] if N <= 0: raise ValueError('I need more data!') new_seq = np.zeros((N, n_timesteps, nf)) for i in range(N): new_seq[i, :, :] = seq[i:i+n_timesteps] return new_seq if __name__ == '__main__': # We will use the CO2 dataset, which measures the concentration of CO2 above Mauna Loa every week since about 1960. # The classification task will be deciding if the concentration is rising, # this is a problem that needs recurrency to solve (since the answer comes from the derivative), # and is less trivial than it sounds because there is noise in the data. df = pd.read_csv('data/co2_data.csv', index_col=0, parse_dates=True) fig, (left, right) = plt.subplots(nrows=1, ncols=2, figsize=(13, 5)) df[['co2']].plot(ax=left) df[['co2_detrended']].plot(ax=right) fig.show() # Reshaping the dataset to be appropriate for the model N_TIMESTEPS = 12 # Use 1 year of lookback data_columns = ['co2', 'co2_detrended'] target_columns = ['rising'] scaler = MinMaxScaler(feature_range=(-1, 1)) X_original = scaler.fit_transform(df[data_columns].values) X = reshape_data(X_original, n_timesteps=N_TIMESTEPS) y = to_categorical((df[target_columns].values[N_TIMESTEPS:-1]).astype(int)) # Train on the first 2000, and test on the last 276 samples X_train = X[:2000] y_train = y[:2000] X_test = X[2000:] y_test = y[2000:] print(X.shape, y.shape) # Define the model model = Sequential() model.add(LSTM(32, input_shape=(N_TIMESTEPS, len(data_columns)))) model.add(Dropout(0.2)) model.add(Dense(2, activation='softmax')) optimizer = Adam(lr=1e-4) model.compile(loss='binary_crossentropy', optimizer=optimizer) # train the model model.fit(X_train, y_train, batch_size=100, epochs=100, validation_data=(X_test, y_test), verbose=2) y_pred = np.argmax(model.predict(X_test), axis=1) y_true = np.argmax(y_test, axis=1) print(classification_report(y_true, y_pred)) plt.plot(y_true, lw=3, alpha=0.3, label='Truth') plt.plot(y_pred, '--', label='Predictions') plt.legend(loc='best') plt.show() # Explain the model with LIME explainer = lime_tabular.RecurrentTabularExplainer(X_train, training_labels=y_train, feature_names=data_columns, discretize_continuous=True, class_names=['Falling', 'Rising'], discretizer='decile') exp = explainer.explain_instance(X_test[50], model.predict, num_features=10, labels=(1,)) print exp exp.show_in_notebook()
We can see that the most important features are the de-trended CO2 concentration several timesteps in the past. In particular, we see that if that feature is low in the recent past, then the concentration is now probably rising.
100个黑/300个白
TF_IDF词素似然几率
测试集预测结果
LIME中各个子线性模型的似然几率占比
能够看到,对于这个判黑样原本说,主要是'eval'、'base64_decode'、'gzinflate'这些关键词起到了似然几率贡献做用,这也和咱们的安全领域先验知识是吻合的。
同时,为了进一步理解线性基元对目标函数的局部线性近似逼近,咱们手动disable掉2个top似然几率的基元函数,并观察目标函数的预测结果,
去掉eval和base64_decode以后,目标函数的预测几率值等于这2个函数各自的基元函数的可解释似然几率
这个实验结果,证明了LIME基于随机扰动负反馈的局部线性逼近的有效性,LIME的局部线性基元函数能够较好的表明目标函数的整体特征,局部汇总=整体,即1+1+1=3。
反过来,这种可解释性也为webshell领域里的文本畸形变化对抗提供了理论依据,在文本词维度的扰动能够干扰文本词维度的检测机制。做为防护方,要对抗这种对抗,就须要将模型抽象维度拉升到更高的维度,例如apicall、opcode、汇编代码层等。
LIME基元函数中对判黑和判白的各自似然几率占比
Relevant Link:
https://github.com/marcotcr/lime/blob/master/doc/notebooks/Tutorial%20-%20Image%20Classification%20Keras.ipynb https://arxiv.org/pdf/1602.04938.pdf