AI学习笔记之特征选择与提取、边缘提取
1、特征选择
(1)特征的概念
在一些实际问题中,我们得到的样本数据都是多维度的,即一个样本使用多个特征来表征的。比如在预测房价的问题中,影响房价y的因素有房子类型x1,卧室数量x2等,我们得到的样本数据及时(x1,x2)这样一些样本点,这里的x1,x2又被称为特征。
(2)为什么要做特征选择
在现实生活中,一个对象往往具有很多属性(以下称为特征),这些特征大致可以分为三种主要类型:
⋅
\cdot
⋅相关特征:对于学习任务(例如分类问题)有帮助,可以提升学习算法的效果;
⋅
\cdot
⋅无关特征:对于我们的算法没有任何帮助,不会给算法的效果带来任何提升;
⋅
\cdot
⋅冗余特征:不会对我们的算法带来新的信息,或者这种特征的信息可以由其他特征推断出。
但是对于一个特定的学习算法来说,哪一个特征是有效是未知的。因此,需要从所有特征中选择出对于学习算法有有一的相关特征。
进行特征选择的主要目的:
⋅
\cdot
⋅降维
⋅
\cdot
⋅降低学习任务的难度
⋅
\cdot
⋅提升模型的效率
(3)什么是特征选择
定义:
从N个特种中选择M(M<N)个子特征,并且在M个子特征中,准则函数可以达到最优解。
特征选择想要做的是:选择可能少的子特征,模型的效果不会线束下降,并且结果的类别分布尽可能接近真实的类别分别。
(4)怎么做特征选择
特征选择主要包括四个过程:
1、生成过程:生成候选的特征子集;
2、评价函数:评价特征子集的好坏;
3、停止条件:决定什么时候该停止;
4、验证过程:特征子集是否有效;
特征选择:生成过程
生成过程是一个搜索过程,这个过程主要有以下三个策略:
1、完全搜索:根据评价函数做完全搜索。完全搜索主要有两种:穷举搜索和非穷举搜索;
2、启发式搜索:根据一些启发式规则在每次迭代时,决定剩下的特征应该是被选择还是被拒绝。这种方法很简单并且速度很快。
3、随机搜索:每次迭代会设置一些参数,参数的选择会影响特征选择的效果。由于设置一些参数(例如最大迭代次数)
特征选择:停止条件
停止条件用来决定迭代过程什么时候停止,生成过程和评价函数可能会对于怎么选择停止条件产生影响。停止条件有以下四种特征:
1、达到预定义的最大迭代次数;
2、达到预定义的最大特征数;
3、增加(删除)任何特征不会产生更好的特征子集;
4、根据评价函数,产生最优特征子集;
特征选择:评价函数
评价函数主要用来评价选出的特征子集的好坏,一个特征子集是最优的往往指相对于特征的评价函数来说的。评价函数主要用来度量一个特征(或者特征子集)可以区分不同类别的能力。根据具体的评价方法要有三类:
⋅
\cdot
⋅过滤式(filter):先进行特征选择,然后去训练学习器,所以特征选择的过程与学习器无关。相当于先对与特征进行过滤操作,然后用特征子集来训练分类器。对每一维的特征“打分”,即给每一维的特征赋予权重,这样的权重就代表着该维特征的重要性,然后依据权重排序。
⋅
\cdot
⋅包裹式(filter):直接把最后要使用的分类器作为特征选择的评价函数,对于特定的分类器选择最优的特征子集。将子集的选择看作是一个搜索寻优问题,生成不同的组合,对组合进行评价,再与其他的组合进行比较。这样就将子集的选择看作是一个优化问题,
⋅
\cdot
⋅Filter和Wrapper组合式算法:先使用Filter进行特征选择,去掉不相关的特征,降低特征维度;然后利用Wrapper进行特征选择。
⋅
\cdot
⋅嵌入式(embedding):把特征选择的过程与分类器学习的过程融合一起,在学习的过程中进行特征选择。其主要思想是:在模型既定的情况下学习出对提高模型准确性最好的属性。这句话并不是很好理解,其实是讲在确定模型的过程中,挑选出那些对模型的训练有重要意义的属性。
一般有5中比较常见的评价函数:
⋅
\cdot
⋅ 距离度量:如果 X 在不同类别中能产生比 Y 大的差异,那么就说明 X 要好于 Y;
⋅
\cdot
⋅信息度量:主要是计算一个特征的信息增益(度量先验不确定性和期望, 后验不确定性之间的差异);
⋅
\cdot
⋅ 依赖度量:主要用来度量从一个变量的值预测另一个变量值的能力。最常见的是相关系数:用来发现一个特征和一个类别的相关性。如果 X 和类别的相关性高于 Y与类别的相关性,那么X优于Y。对相关系数做一点改变,用来计算两个特征之间的依赖性,值代表着两个特征之间的冗余度。
⋅
\cdot
⋅ 一致性度量:对于两个样本,如果它们的类别不同,但是特征值是相同的,那么它们是不一致的;否则是一致的。找到与全集具有同样区分能力的最小子集。严重依赖于特定的训练集和 最小特征偏见(Min-Feature bias)的用法;找到满足可接受的不一致率(用户指定的参数)的最小规模的特征子集。
⋅
\cdot
⋅误分类率度量:主要用于Wrapper式的评价方法中。使用特定的分类器,利用选择的特征子集来预测测试集的类别,用分类器的准确率来作为指标。这种方法准确率很高,但是计算开销较大。
(5)特征选择算法
(6)搜索方法
完全搜索
广度优先搜索(Breadth First Search):主要采用完全搜索策略和距离度量评价函数。使用广度优先算法遍历所有可能的特征子集,选择出最优的特征子集。
主要采用完全搜索和距离度量。B&B从所有的特征上开始搜索,每次迭代从中去掉一个特征,每次给评价函数的值一个限制条件。因为评价函数满足单调性原理(一个特征子集不会好于所有包含这个特征子集的更大的特征子集),所以如果一个特征使得评价函数的值小于这个限制,那么就删除这个特征。类似于在穷举搜索中进行剪枝。
定向搜索(Beam Search):主要采用完全搜索策略和误分类率作为评价函数。选择得分最高的特征作为特征子集,把它加入到一个有长度限制的队列中,从头到尾依次是性能最优到最差的特征子集。每次从队列总取得分最高的子集,然后穷举向该子集中加入一个特征后所有的特征集,按照得分把这些子集加入到队列中。
最优优先搜索(Best First Search):和定位搜索类似,不同点在于不限制队列的长度。
启发式搜索
序列前向选择(SFS , Sequential Forward Selection):使用误分类率作为评价函数。从空集开始搜索,每次把一个特征加入到这个特征子集中,使得评价函数达到最优值。如果候选的特征子集不如上一轮的特征子集,那么停止迭代,并将上一轮的特征子集作为最优的特征选择结果。
广义序列前向选择(GSFS ,Generalized Sequential Forward Selection):该方法是SFS算法的加速算法,它可以一次性向特征集合中加入r个特征。在候选特征中选择一个规模为r的特征子集,使得评价函数取得最优值。
序列后向选择(SBS , Sequential Backward Selection):把误分类率作为评价函数。从特征的全集开始搜索,每次从特征子集中去掉一个特征,使得评价函数达到最优值。
广义序列后向选择(GSBS,Generalized Sequential Backward Selection):该方法是SBS的加速,可以一次性的从特征子集中去除一定数量的特征。是实际应用中的快速特征选择算法,性能相对较好。但是有可能消除操作太快,去除掉重要的信息,导致很难找到最优特征子集。
双向搜索(BDS , Bi-directional Search):分别使用SFS和SBS同时进行搜索,只有当两者达到一个相同的特征子集时才停止搜索。为了保证能够达到一个相同的特征子集,需要满足两个条件:1) 被SFS选中的特征不能被SBS去除;2) 被SBS去除的特征就不能SFS选择;
增L去R选择算法(LRS , Plus L Minus R Selection):采用误分类率作为评价函数。允许特征选择的过程中进行回溯,这种算法主要有两种形式:1)当L>R时,是一种自下而上的方法,从空集开始搜索,每次使用SFS增加L个特征,然后用SBS从中去掉R个特征;2)当L<R时,是一种自上而下的算法,从特征的全集开始搜索,每次使用SBS去除其中的R个特征,使用SFS增加L个特征;
序列浮动选择(Sequential Floating Selection):和增L去R算法类似,只不过序列浮动算法的L和R不是固定的,每次会产生变化,这种算法有两种形式:
序列浮动前向选择(SFFS , Sequential Floating Forward Selection):从空集开始搜索,每次选择一个特征子集,使得评价函数可以达到最优,然后在选择一个特征子集的子集,把它去掉使得评价函数达到最优;
序列浮动后向选择(SFBS , Sequential Floating Backward Selection):从特征全集开始搜索,每次先去除一个子集,然后在加入一个特征子集。
决策树算法(DTM , Decision Tree Method):采用信息增益作为评价函数。在训练集中使用C4.5算法,等到决策树充分增长,利用评价函数对决策树进行剪枝。最后,出现在任意一个叶子节点的路径上的所有特征子集的并集就是特征选择的结果。
随机搜索
LVF(Las Vegas Filter):使用一致性度量作为评价函数。使用拉斯维加斯算法随机搜索子集空间,这样可以很快达到最优解。对于每一个候选子集,计算它的不一致性,如果大于阈值,则去除这个子集。否则,如果这个候选子集中的特征数量小于之前最优子集的数量,则该子集作为最优子集。这个方法在有噪声的数据集达到最优解,它是很简单被实现而且保证产生比较好的特征子集。但是在一些特定问题上,它会花费比启发式搜索更多的时间,因为它没有利用到先验知识。
遗传算法
使用误分类率作为评价函数。随机产生一批特征子集,然后使用评价函数对于子集进行评分,通过选择、交叉、突变操作产生下一代特征子集,并且得分越高的子集被选中产生下一代的几率越高。经过N代迭代之后,种群中就会形成评价函数值最高的特征子集。它比较依赖于随机性,因为选择、交叉、突变都由一定的几率控制,所以很难复现结果。遗传算法的过程如下:
1、随机产生初始种群;
2、在非支配排序后,通过遗传算法的三个算子(选择算子,交叉算子,变异算子)进 行变更操作得到第一代种群;
3、将父代种群与子代种群合并得到大小为N的初始化种群;
4、对包括N个个体的种群进行快速非支配排序;
5、对每个非支配层中的个体进行拥挤度计算;
6、根据非支配关系及个体的拥挤度选取合适的个体组成新的父代种群;
7、通过遗传算法的基本变更操作产生新的子代种群;
8、重复 3 到 7 直到满足程序结束的条件(即遗传进化代数);
2、特征提取
(1)特征提取与特征选择
常见的特征有边缘、角、区域等。特征提取(Feature extraction)是通过属性间的关系,如组合不同的属性得到新的属性,这样就改变了原来的特征空间,而特征选择(feature selection)是从原始特征数据集中选择出子集,是一种包含关系,更改原始的特征空间。两者均属于降维(Dimension reduction)。
(2)特征提取方法
目前图像特征提取主要有两种方法:传统图像特征提取方法和深度学习方法。传统的特征提取方法是指基于图像本身的特征进行提取;而深度学习方法是基于样本自动训练处区分图像的特征分类器。
传统的图像特征提取一般分为三个步骤:预处理、特征提取、特征处理;然后再利用机器学习等方法对特征进行分类等操作。
预处理:预处理的目的主要是排除干扰因素,突出特征信息,主要方法有:图像标准化(调整图片尺寸)和图像归一化(调整图片重心为0);
特征提取:利用特殊的特征提取对图像进行特征提取。
特征处理:主要是为了排除信息量小的特征,减少计算量等;常见的特征处理方法是降维,常见的降维方法有:
1、主成分分析(PCA);
2、奇异值分解(SVD);
3、现行判别分析;
3、PCA
(1)PCA简介
主成分分析(Principal Component Analysis | 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、对协方差矩阵求特征向量和特征值,这些特征向量组成新的特征空间。
中心化即是指变量减去它的均值,使均值为0。就是一个平移的过程,平移后是的数据的中心点是(0,0)
只有中心化数据之后,计算得到的方向才能比较好的“概括”原来的数据。下图表述了中心化的几何意义,就是将样本集的中心评议到坐标系的原点O上。
我们对于一组数据,如果他在某一个坐标轴上的方差越大,说明坐标点越分散,该属性能够比较好的反映源数据。所以在进行降维的时候,主要目的是找到一个超平面,它能是的数据点的分布方差最大,这样数据表现在放心的坐标轴上时候已经足够分散了。
方差:
s
2
=
∑
i
=
1
n
(
X
i
−
X
‾
)
2
n
−
1
s^2=\frac{\sum_{i=1}^n\left(X_i-\overline X\right)^2}{n-1}
s2=n−1∑i=1n(Xi−X)2
PCA算法优化的目标就是:
1、降维后同一维度的方差最大
2、不同维度之间的相关性为0
(2)协方差矩阵
协方差就是一种用来度量两个随机变量关系的统计量。
同一元素的协方差就表示钙元素的方差,不同元素之间的协方差就表示他们的相关性。
c
o
v
(
X
,
Y
)
=
∑
i
=
1
n
(
X
i
−
X
‾
)
(
Y
i
−
Y
‾
)
n
−
1
cov(X,Y)=\frac{\sum_{i=1}^n\left(X_i-\overline X\right)\left(Y_i-\overline Y\right)}{n-1}
cov(X,Y)=n−1∑i=1n(Xi−X)(Yi−Y)
协方差的性质:
1、
C
o
v
(
X
,
Y
)
=
C
o
v
(
Y
,
X
)
Cov(X,Y)=Cov(Y,X)
Cov(X,Y)=Cov(Y,X)
2、
C
o
v
(
a
X
,
b
Y
)
=
a
b
C
o
v
(
Y
,
X
)
Cov(aX,bY)=abCov(Y,X)
Cov(aX,bY)=abCov(Y,X),(a,b是常数)
3、
C
o
v
(
X
1
+
X
2
,
Y
)
=
C
o
v
(
X
1
,
Y
)
+
C
o
v
(
X
2
,
Y
)
Cov(X_1+X_2,Y)=Cov(X_1,Y)+Cov(X_2,Y)
Cov(X1+X2,Y)=Cov(X1,Y)+Cov(X2,Y)
4、
C
o
v
(
X
,
X
)
=
D
(
X
)
,
C
o
v
(
Y
,
Y
)
=
D
(
Y
)
Cov(X,X)=D(X), Cov(Y,Y)=D(Y)
Cov(X,X)=D(X),Cov(Y,Y)=D(Y)
高维协方差矩阵定义为:
C
=
(
c
i
j
)
n
×
n
(
c
11
c
12
…
c
1
n
c
21
c
22
…
c
2
n
⋮
⋮
⋱
⋮
c
n
1
c
n
2
…
c
n
n
)
c
i
j
=
C
o
v
(
X
i
,
X
j
)
,
i
,
j
=
1
,
2
,
…
n
C={(c_{ij})}_{n\times n}\begin{pmatrix}c_{11}&c_{12}&\dots&c_{1n}\\c_{21}&c_{22}&\dots&c_{2n}\\\vdots&\vdots&\ddots&\vdots\\c_{n1}&c_{n2}&\dots&c_{nn}\end{pmatrix}\;\;\;c_{ij}=Cov(X_i,X_j),i,j=1,2,\dots n
C=(cij)n×n⎝⎜⎜⎜⎛c11c21⋮cn1c12c22⋮cn2……⋱…c1nc2n⋮cnn⎠⎟⎟⎟⎞cij=Cov(Xi,Xj),i,j=1,2,…n
比如,三维(x,y,z)的协方差矩阵:
C
=
(
c
o
v
(
x
,
x
)
c
o
v
(
x
,
y
)
c
o
v
(
x
,
z
)
c
o
v
(
y
,
x
)
c
o
v
(
y
,
y
)
c
o
v
(
y
,
z
)
c
o
v
(
z
,
x
)
c
o
v
(
z
,
y
)
c
o
v
(
z
,
z
)
)
C=\begin{pmatrix}\mathrm{co}v(x,x)&\mathrm{co}v(x,y)&\mathrm{co}v(x,z)\\\mathrm{co}v(y,x)&\mathrm{co}v(y,y)&\mathrm{co}v(y,z)\\\mathrm{co}v(z,x)&\mathrm{co}v(z,y)&\mathrm{co}v(z,z)\end{pmatrix}
C=⎝⎛cov(x,x)cov(y,x)cov(z,x)cov(x,y)cov(y,y)cov(z,y)cov(x,z)cov(y,z)cov(z,z)⎠⎞
协方差衡量了两属性之间的关系:
当
c
o
v
(
X
,
Y
)
>
0
cov(X,Y)>0
cov(X,Y)>0时,表示X和Y正相关;
当
c
o
v
(
X
,
Y
)
<
0
cov(X,Y)<0
cov(X,Y)<0时,表示X和Y负相关;
当
c
o
v
(
X
,
Y
)
=
0
cov(X,Y)=0
cov(X,Y)=0时,表示X和Y不相关;
协方差矩阵的特点:
⋅
\cdot
⋅协方差矩阵计算的是不同维度之间的协方差,而不是不同样本之间的。
⋅
\cdot
⋅样本矩阵的每行都是一个样本,每列为一个维度,所以我们要按列计算均值
⋅
\cdot
⋅协方差矩阵的对角线就是各个维度上的方差
特别地,如果做了中心化,协方差矩阵为(中心化矩阵的协方差矩阵公式):
D
=
1
m
Z
T
Z
D=\frac1mZ^TZ
D=m1ZTZ
(3)求特征值、特征矩阵
A为n阶矩阵,若数
λ
\lambda
λ和n维非0列向量满足
A
x
=
λ
x
Ax=\lambda x
Ax=λx,那么数
λ
\lambda
λ称为A的特征值,x称为A的对应特征值
λ
\lambda
λ的特征向量。
式
A
x
=
λ
x
Ax=\lambda x
Ax=λx也可以写成
(
A
−
λ
)
x
=
0
(A-\lambda)x=0
(A−λ)x=0,并且
∣
A
E
−
λ
∣
\left|AE-\lambda\right|
∣AE−λ∣叫做A的特征多项式。当特征多项式等于0的时候,称为A的特征方程,特征方程式一个齐次线性方程组,求解特征值的过程其实就是求解特征方程的解。
对于协方差矩阵A,其特征值
λ
\lambda
λ(可能有多个)计算方法为:
∣
A
E
−
λ
∣
=
0
\left|AE-\lambda\right|=0
∣AE−λ∣=0
举个栗子:
A
=
(
3
2
1
4
)
∣
A
−
λ
E
∣
=
0
d
e
t
(
A
−
a
I
)
=
d
e
t
(
3
−
a
2
1
4
−
a
)
=
a
2
−
7
a
+
10
=
0
A=\begin{pmatrix}3&2\\1&4\end{pmatrix}\;\;\;\;\;\;\;\;\;\left|A-\lambda E\right|=0\\det\left(A-aI\right)=det\begin{pmatrix}3-a&2\\1&4-a\end{pmatrix}\;=a^2-7a+10=0
A=(3124)∣A−λE∣=0det(A−aI)=det(3−a124−a)=a2−7a+10=0
该方程有两个根,他们就是特征值:
a
1
=
2
,
a
2
=
5
a_1=2,a_2=5
a1=2,a2=5
带入求得对应的特征向量:
{
(
A
−
a
1
E
)
h
1
=
(
1
2
1
2
)
h
1
=
0
⇒
h
1
=
(
2
−
1
)
(
A
−
a
2
E
)
h
2
=
(
−
2
2
1
−
1
)
h
2
=
0
⇒
h
2
=
(
1
1
)
\left\{\begin{array}{l}(A-a_1E)h_1=\begin{pmatrix}1&2\\1&2\end{pmatrix}h_1=0\Rightarrow h_1=\begin{pmatrix}2\\-1\end{pmatrix}\\(A-a_2E)h_2=\begin{pmatrix}-2&2\\1&-1\end{pmatrix}h_2=0\Rightarrow h_2=\begin{pmatrix}1\\1\end{pmatrix}\end{array}\right.
⎩⎪⎪⎨⎪⎪⎧(A−a1E)h1=(1122)h1=0⇒h1=(2−1)(A−a2E)h2=(−212−1)h2=0⇒h2=(11)
(3)对特征值进行排序
将特征值按照从大到小排序,选择其中最大的k个,然后将其对应的k个特征向量分别作为列向量组成特征向量矩阵
W
n
x
k
W_{nxk}
Wnxk;
计算
X
n
e
w
W
X_{new}W
XnewW,即将数据集
X
n
e
w
X_{new}
Xnew投影到选取的特征向量上,这样就得到了我们需要的已经降为的数据集
X
n
e
w
W
X_{new}W
XnewW。
(4)评价模型的好坏,k值的确定
通过特征值的计算我们可以得到主成分所占的百分比,用来衡量模型的好坏。
对于前k个特征值所保留下的信息量计算方法如下:
η
k
=
∑
j
=
1
k
λ
j
∑
j
=
1
n
λ
j
×
100
%
\eta_k=\frac{\sum_{j=1}^k\lambda_j}{\sum_{j=1}^n\lambda_j}\times100\%
ηk=∑j=1nλj∑j=1kλj×100%
(5)PCA算法的优缺点
优点:
1、它是无监督学习,完全无参数限制的。在PCA的计算过程中完全不需要人为的设定参数或是根据任何经验模型对计算进行干预,最后的结果只与数据相关,与用户是独立的。
2、用PCA技术可以对数据进行降维,同时对新求出的“主元”向量的重要性进行排序,根据需要取前面最重要的部分,将后面的维数省去,可以达到降维从而简化模型或是对数据进行压缩的效果。同时最大程度的保持了原有数据的信息。
3、各主成分之间正交,可消除原始数据成分间的相互影响。
4、计算方法简单,易于在计算机上实现。
缺点:
1、如果用户对观测对象有一定的先验知识,掌握了数据的一些特征,却无法通过参数化等方法对处理过程进行干预,可能会得不到预期的效果,效率也不高。
2、贡献率小的主成分往往可能含有对样本差异的重要信息。
3、特征值矩阵的正交向量空间是否唯一有待讨论。
4、在非高斯分布的情况下,PCA方法得出的主元可能并不是最优的,此时在寻找主元时不能将方差作为衡量重要性的标准。
下面用numpy实现PCA过程:
import numpy as np
class PCA():
def __init__(self, n_components):
self.n_components = n_components
def fit_transform(self, X):
self.n_features_ = X.shape[1]
# 求协方差矩阵
X = X - X.mean(axis=0)
self.covariance = np.dot(X.T, X) / X.shape[0]
# 求协方差矩阵的特征值和特征向量
eig_vals, eig_vectors = np.linalg.eig(self.covariance)
# 获得降序排列特征值的序号
idx = np.argsort(-eig_vals)
# 降维矩阵
self.components_ = eig_vectors[:, idx[:self.n_components]]
# 对X进行降维
return np.dot(X, self.components_)
# 调用
pca = PCA(n_components=2)
X = np.array([[-1, 2, 66, -1], [-2, 6, 58, -1], [-3, 8, 45, -2],
[1, 9, 36, 1], [2, 10, 62, 1], [3, 5, 83, 2]]) # 导入数据,维度为4
newX = pca.fit_transform(X)
print(newX) # 输出降维后的数据
运行结果如下:
[[ 7.96504337 -4.12166867]
[ -0.43650137 -2.07052079]
[-13.63653266 -1.86686164]
[-22.28361821 2.32219188]
[ 3.47849303 3.95193502]
[ 24.91311585 1.78492421]]
下面用sklearn.decomposition实现PCA过程:
import numpy as np
from sklearn.decomposition import PCA
X = np.array([[-1, 2, 66, -1], [-2, 6, 58, -1], [-3, 8, 45, -2],
[1, 9, 36, 1], [2, 10, 62, 1], [3, 5, 83, 2]]) # 导入数据,维度为4
pca = PCA(n_components=2) # 降到2维
pca.fit(X) # 训练
newX = pca.fit_transform(X) # 降维后的数据
# PCA(copy=True, n_components=2, whiten=False)
print('贡献率:', pca.explained_variance_ratio_) # 输出贡献率
print('降维后的数据:\n', newX) # 输出降维后的数据
运行结果如下:
贡献率: [0.95713353 0.03398198]
降维后的数据:
[[ 7.96504337 4.12166867]
[ -0.43650137 2.07052079]
[-13.63653266 1.86686164]
[-22.28361821 -2.32219188]
[ 3.47849303 -3.95193502]
[ 24.91311585 -1.78492421]]
下面用PCA实现鸢尾花分类:
import matplotlib.pyplot as plt
import sklearn.decomposition as dp
from sklearn.datasets import load_iris
x, y = load_iris(return_X_y=True) # 加载数据,x表示数据集中的属性数据,y表示数据标签
pca = dp.PCA(n_components=2) # 加载pca算法,设置降维后主成分数目为2
reduced_x = pca.fit_transform(x) # 对原始数据进行降维,保存在reduced_x中
red_x, red_y = [], []
blue_x, blue_y = [], []
green_x, green_y = [], []
for i in range(len(reduced_x)): # 按鸢尾花的类别将降维后的数据点保存在不同的表中
if y[i] == 0:
red_x.append(reduced_x[i][0])
red_y.append(reduced_x[i][1])
elif y[i] == 1:
blue_x.append(reduced_x[i][0])
blue_y.append(reduced_x[i][1])
else:
green_x.append(reduced_x[i][0])
green_y.append(reduced_x[i][1])
plt.scatter(red_x, red_y, c='r', marker='x')
plt.scatter(blue_x, blue_y, c='b', marker='D')
plt.scatter(green_x, green_y, c='g', marker='.')
plt.show()
运行结果如下:
4、边缘提取
梯度:梯度的本意是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为梯度的模)
∇
f
(
x
0
,
y
0
)
=
(
∂
f
∂
x
,
∂
f
∂
y
)
x
=
x
0
,
y
=
y
0
\nabla f(x_0,y_0)={\left(\frac{\partial f}{\partial x},\frac{\partial f}{\partial y}\right)}_{x=x_0,y=y_0}
∇f(x0,y0)=(∂x∂f,∂y∂f)x=x0,y=y0
(1)什么是边缘
图像的边缘是指预想局部区域亮度变化显著的部分,该区域的灰度剖面一般可以看做是一个解约,既从一个灰度值在很小的缓冲区域内急剧变化到另一个灰度相差较大的灰度值。
边缘没有正负之分,就像导数有正值也有负值一样;由暗到亮为正,由亮到暗为负。
求边缘幅度的算法:
一阶导数:sobel、Roberts、prewitt等算子
二阶导数:Laplacian、Canny算子(Canny比其他效果好,但实现起来有点麻烦)
边缘检测真不要是图像灰度变化的度量、检测和定位。如下图:
(2)高频信号和低频信号
图像中的低频信号和高频信号也叫做低频分量和高频分量。高频分量即图像强度(亮度/灰度)变化剧烈的地方,也就是边缘(轮廓)。低频分量即图像强度(亮度/灰度)变换平缓的地方,也就是大片色块的地方。人眼对图像中的高频信号更为敏感。
(3)边缘检测的原理和步骤
1)滤波:边缘检测的算法主要是基于图像强度的一阶和二阶导数,但导数通常对噪声很敏感,因此必须采用滤波器来改善与噪声有关的边缘检测器的性能。常见的滤波方法主要有高斯滤波。
2)增强:增强边缘的基础是确定图像个点邻域强度的变化值。增强算法可以将图像灰度点邻域强度值有显著变化的点凸显出来。在具体编程实现时,可通过计算梯度幅值来确定。
3)检测:经过增强的图像,往往邻域中有很多点的梯度值比较大,而在特定的应用中,这些点并不是我们要找的边缘点,所以应该采用某种方法来对这些点进行取舍。实际工程中,常用的方法是通过阈值化方法来检测。
边缘检测的原理
关于边缘检测的基础来自与一个事实,即在边缘部分,像素值出现“跳跃”或者较大的变化,如果在此边缘部分求取一节导数,就会看到极值的出现。
而在一阶导数为极值的地方,二阶导数为0,基于这个原理,就可以进行边缘检测。
在边缘部分求取一阶导数,你会看到极值的出现:
如果在边缘部分求二阶导数会有如下图形:
(4)图像锐化
图像锐化(image sharpening)是补偿图像的轮廓,增强图像的边缘即灰度跳变的部分,时图像变得更清晰,分为空域处理和频域处理两类。
图像锐化是为了突出图像上物体的边缘、轮廓,或某些现行目标要素的特征。这种滤波方法提高了物体的边缘与周围元素之间的反差,因此也被称为边缘增强。其常用拉帕拉斯变换核函数如下图所示:
(5)图像平滑
图像平滑是指用于突出图像的宽大区域、低频成分、猪肝部分或抑制图像噪声和干扰高频成分的图像处理方法,目的是使图像亮度平缓渐变,减小突变梯度,改善图像质量。用Gx来卷积下面这张图的话,就会在中间黑白边界获得比较大的值。
G
x
=
[
1
0
−
1
1
0
−
1
1
0
−
1
]
G
y
=
[
1
1
1
0
0
0
−
1
−
1
−
1
]
G_x=\begin{bmatrix}1&0&-1\\1&0&-1\\1&0&-1\end{bmatrix}\;\;\;\;\;\;G_y=\begin{bmatrix}1&1&1\\0&0&0\\-1&-1&-1\end{bmatrix}\;
Gx=⎣⎡111000−1−1−1⎦⎤Gy=⎣⎡10−110−110−1⎦⎤
(6)Sobel算子
Sobel算子是典型的基于一阶导数的边缘检测算子,由于概算自中引入了类似局部平均的运算,因此对噪声具有平滑作用,能很好的消除噪声的影响。
Sobel算子对于像素的位置的影响做了加权,因此与Prewitt算子、Roberts算子相比效果更好。
Sobel算子好汉两组3x3的矩阵,分别为横向及纵向模板,将之与图像做平面卷积,即可分别得出横向及纵向的亮度差分近似值。实际使用中,常用如下两个模板来检测图像边缘:
S
x
=
[
−
1
0
1
−
2
0
2
−
1
0
1
]
S
=
[
1
2
1
0
0
0
−
1
−
2
−
1
]
K
=
[
a
0
a
1
a
2
a
7
[
i
,
j
]
a
3
a
6
a
5
a
4
]
S_x=\begin{bmatrix}-1&0&1\\-2&0&2\\-1&0&1\end{bmatrix}\;\;\;\;\;\;S=\begin{bmatrix}1&2&1\\0&0&0\\-1&-2&-1\end{bmatrix}\;\;\;\;\;K=\begin{bmatrix}a_0&a_1&a_2\\a7&\left[i,j\right]&a3\\a_6&a_5&a_4\end{bmatrix}
Sx=⎣⎡−1−2−1000121⎦⎤S=⎣⎡10−120−210−1⎦⎤K=⎣⎡a0a7a6a1[i,j]a5a2a3a4⎦⎤
梯度幅值为:
G
[
i
,
j
]
=
s
x
2
+
s
y
2
s
x
=
(
a
2
+
2
a
3
+
a
4
)
−
(
a
0
+
2
a
7
+
a
6
)
s
y
=
(
a
0
+
2
a
1
+
a
2
)
−
(
a
6
+
2
a
5
+
a
4
)
G\left[i,j\right]=\sqrt{s_x^2+s_y^2}\\s_x=\left(a_2+2a_3+a_4\right)-\left(a_0+2a_7+a_6\right)\\s_y=\left(a_0+2a_1+a_2\right)-\left(a_6+2a_5+a_4\right)
G[i,j]=sx2+sy2sx=(a2+2a3+a4)−(a0+2a7+a6)sy=(a0+2a1+a2)−(a6+2a5+a4)
其效果如下图所示:
(7)Prewitt算子
Prewitt算子时一种一阶微分算子的边缘检测,利用像素点上下、左右邻点的灰度差,在边缘处达到极值检测边缘,去掉部分伪边缘,对噪声具有平滑作用。其原理是在图像空间利用两个方向模板和退昂进行邻域卷积来完成的。这两个方向模板一个检测水平边缘,一个检测垂直边缘。
s
x
=
[
−
1
0
1
−
1
0
1
−
1
0
1
]
,
s
y
=
[
1
1
1
0
0
0
−
1
−
1
−
1
]
s_x=\begin{bmatrix}-1&0&1\\-1&0&1\\-1&0&1\end{bmatrix},s_y=\begin{bmatrix}1&1&1\\0&0&0\\-1&-1&-1\end{bmatrix}
sx=⎣⎡−1−1−1000111⎦⎤,sy=⎣⎡10−110−110−1⎦⎤
其原理与Sobel算子一样。
(8)Laplace算子
从上例中我们可以推论检测边缘可以通过定位梯度值大于邻域的像素的方法找到(或者推广到大于一个阈值)。同样,二阶导数也可以用来检测边缘。因为图像是二维,我们需要在两个方向求导。使用Lappacian算子将会使求导过程变得简单。
Laplacian定义:
L
a
p
l
a
c
e
(
f
)
=
∂
2
f
∂
x
2
+
∂
2
f
∂
y
2
Laplace(f)=\frac{\partial{}^2f}{\partial x^2}+\frac{\partial{}^2f}{\partial y^2}
Laplace(f)=∂x2∂2f+∂y2∂2f
其代码实现如下:
"""
Laplacian算子
在OpenCV-Python中,Laplace算子的函数原型如下:
dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
第一个参数是需要处理的图像;
第二个参数是图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;
dst不用解释了;
ksize是算子的大小,必须为1、3、5、7。默认为1。
scale是缩放导数的比例常数,默认情况下没有伸缩系数;
delta是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中;
borderType是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。
"""
import cv2
import numpy as np
img = cv2.imread('lenna.png', 0)
# 为了让结果更清晰,这里的ksize设为3,
gray_lap = cv2.Laplacian(img, cv2.CV_16S, ksize=3) # 拉式算子
dst = cv2.convertScaleAbs(gray_lap)
cv2.imshow('laplacian', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果如下:
(9)Canny边缘检测
Canny是目前最优秀的边缘检测算法,其目标为找到一个最优的边缘,其最优的边缘定义为:
1、好的检测:算法能够尽可能的标出图像中实际边缘
2、好的定位:标识出的边缘要与实际图像中的边缘尽可能接近
3、最小响应:图像中的边缘只能标记一次
Canny边缘检测算法
1、对图像进行灰度化:
方法1:Gray=(R+G+B)/3;
方法2:Gray=0.299R+0.587G+0.114B;(这种参数考虑到了人眼的生理特点)
2、对图像进行高斯滤波
根据待滤波的像素点及其邻域点的灰度值按照一定的参数规则进行加权平均。这样可以有效考虑去理想图像中叠加的高频噪声。
3、检测图像的水平、垂直和对角边缘(如Prewitt、Sobel算子等)。
4、对梯度负值进行非极大值一致
5、用双阈值算法检测和连接边缘
高斯平滑
高斯平滑水平和垂直方向呈现高斯分布,更突出了中心点在像素平滑后的权重,相对于均值滤波而言,有着更好的平滑效果。
H
i
j
=
1
2
π
σ
2
e
x
p
(
−
(
i
−
(
k
+
1
)
2
+
(
j
−
(
k
+
1
)
2
2
σ
2
)
;
1
⩽
i
,
j
⩽
(
2
k
+
1
)
H_{ij}=\frac1{2\pi\sigma^2}exp\left(-\frac{(i-{(k+1)}^2+(j-{(k+1)}^2}{2\sigma^2}\right);1\leqslant i,j\leqslant(2k+1)
Hij=2πσ21exp(−2σ2(i−(k+1)2+(j−(k+1)2);1⩽i,j⩽(2k+1)
高斯卷积核大小的选择将影响Canny检测器的性能;尺寸越大,检测器对噪声的敏感性越低,但是边缘检测的定位误差也将略有增加。一般5x5是一个比较不错的trade off。
下面是一个sigma=1.4,尺寸为3x3的高斯卷积核的例子:
H
=
[
0.0924
0.1192
0.0924
0.1192
0.1583
0.1192
0.0924
0.1192
0.0924
]
H=\begin{bmatrix}0.0924&0.1192&0.0924\\0.1192&0.1583&0.1192\\0.0924&0.1192&0.0924\end{bmatrix}
H=⎣⎡0.09240.11920.09240.11920.15830.11920.09240.11920.0924⎦⎤
非极大值抑制
非极大值抑制,简称为NMS(Non-Maximum Supperssion)算法,其思想是搜索局部最大值,抑制极大值。其在不同的应用中具体实现方式不太一样,但思想都是一样的。
为什么要用非极大值抑制
以目标检测为例:目标检测的过程在同一个目标的位置上会产生大量的候选框,这些候选框相互之间可能会有重叠,此时我们需要利用非极大值抑制找到最佳的目标边界,消除冗余的边界框。
对于重叠的候选框,若大于规定阈值(某一前提设定的置信度),则删除;低于阈值则保留。对于无重叠候选框,都保留。
1)将当前像素的梯度强度与沿负梯度方向上的两个像素进行比较。
2)如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则该像素点则被抑制(灰度值置为0)。
用双阈值算法检测(滞后检测)
完成非极大值抑制后,会得到一个二值不想,非边缘的点的灰度值均为0,可能为边缘局部灰度极大值点可设置其灰度为128。这样一个检测结果还包含了很多噪声及tetanus原因造成的假边缘。因此还需要进一步处理。
双阈值检测:
⋅
\cdot
⋅如果边缘像素的梯度值高于高阈值,则将其标记为强边缘像素;
⋅
\cdot
⋅如果边缘像素的梯度小于高阈值并且大于地狱之,则将其标记为若边缘像素;
⋅
\cdot
⋅如果边缘像素的梯度值小于低阈值,则会被抑制。
大于高阈值直为强边缘,小于低阈值不是边缘。结余中间是弱边缘。阈值的选择取决于给定输入图像的内容。
抑制孤立低阈值点
到目前为止,被划分为强边界的像素点已经被确定为边界,因此他们是从图像中的真实边缘中提取出来的。然而,对于若边缘像素,将会有一些争议,因为这些像素可以从真实边缘提取也可以是因为噪声或颜色变化引起的。
为了获得准确的结果,应该抑制有后者引发的弱边缘:
⋅
\cdot
⋅通常,有真实边缘引起的弱边缘像素将连接到强边缘像素,而噪声相应未连接。
⋅
\cdot
⋅为了跟踪边缘连接,通过查看弱边缘像素及其8个邻域像素,只要其中一个为强边界像素,则该弱边缘点就可以保留为真实的边缘。
i
f
G
p
=
=
L
o
w
T
h
r
e
s
h
o
l
d
a
n
d
G
p
c
o
n
n
e
c
t
e
d
t
o
a
s
t
r
o
n
g
e
d
g
e
p
i
x
e
l
G
p
i
s
a
n
s
t
r
o
n
g
e
d
g
e
e
l
s
e
G
p
s
h
o
u
l
d
b
e
s
u
p
p
r
e
s
s
e
d
if\;G_p==LowThreshold\;and\;G_p\;connected\;to\;a\;strong\;edge\;pixel\\\;\;\;\;G_{p\;}\;is\;an\;strong\;edge\\else\\\;\;\;\;G_p\;should\;be\;suppressed
ifGp==LowThresholdandGpconnectedtoastrongedgepixelGpisanstrongedgeelseGpshouldbesuppressed
下面用opencv、numpy实现canny算法:
''
Canny边缘检测:优化的程序
'''
import cv2
import numpy as np
def CannyThreshold(lowThreshold):
detected_edges = cv2.GaussianBlur(gray, (3, 3), 0) # 高斯滤波
detected_edges = cv2.Canny(detected_edges,
lowThreshold,
lowThreshold * ratio,
apertureSize=kernel_size) # 边缘检测
# just add some colours to edges from original image.
dst = cv2.bitwise_and(img, img, mask=detected_edges) # 用原始颜色添加到检测的边缘上
cv2.imshow('canny demo', dst)
lowThreshold = 0
max_lowThreshold = 100
ratio = 3
kernel_size = 3
img = cv2.imread('lenna.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换彩色图像为灰度图
cv2.namedWindow('canny demo')
# 设置调节杠,
'''
下面是第二个函数,cv2.createTrackbar()
共有5个参数,其实这五个参数看变量名就大概能知道是什么意思了
第一个参数,是这个trackbar对象的名字
第二个参数,是这个trackbar对象所在面板的名字
第三个参数,是这个trackbar的默认值,也是调节的对象
第四个参数,是这个trackbar上调节的范围(0~count)
第五个参数,是调节trackbar时调用的回调函数名
'''
cv2.createTrackbar(
'Min threshold',
'canny demo',
lowThreshold,
max_lowThreshold,
CannyThreshold)
CannyThreshold(0) # initialization
if cv2.waitKey(0) == 27: # wait for ESC key to exit cv2
cv2.destroyAllWindows()
运行结果如下(可通过滑块调节低阈值):
sobel、laplace、Canny算法代码如下:
mport cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread("lenna.png", 1)
img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
'''
Sobel算子
Sobel算子函数原型如下:
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
前四个是必须的参数:
第一个参数是需要处理的图像;
第二个参数是图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;
dx和dy表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。
其后是可选的参数:
dst是目标图像;
ksize是Sobel算子的大小,必须为1、3、5、7。
scale是缩放导数的比例常数,默认情况下没有伸缩系数;
delta是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中;
borderType是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。
'''
img_sobel_x = cv2.Sobel(img_gray, cv2.CV_64F, 1, 0, ksize=3) # 对x求导
img_sobel_y = cv2.Sobel(img_gray, cv2.CV_64F, 0, 1, ksize=3) # 对y求导
# Laplace 算子
img_laplace = cv2.Laplacian(img_gray, cv2.CV_64F, ksize=3)
# Canny 算子
img_canny = cv2.Canny(img_gray, 100, 150)
plt.subplot(231), plt.imshow(img_gray, "gray"), plt.title("Original")
plt.subplot(232), plt.imshow(img_sobel_x, "gray"), plt.title("Sobel_x")
plt.subplot(233), plt.imshow(img_sobel_y, "gray"), plt.title("Sobel_y")
plt.subplot(234), plt.imshow(img_laplace, "gray"), plt.title("Laplace")
plt.subplot(235), plt.imshow(img_canny, "gray"), plt.title("Canny")
plt.show()
运行结果如下: