Linear Discriminant Analysis(LDA)

在颓废了一段时间后,我内心是自责不安~~,决定重新踏上量子机器学习的康庄大道,由于近期学习的一篇论文里面涉及到一些机器学习算法,为了能将论文顺利看懂,“被逼”学习LDA,这也算是比较高阶的机器学习算法了,之前也从未接触过,下面做出详解!

一 . LDA简介

之前的博客学习中,我们接触过PCA主成分分析法,它是一种常见的数据降维算法,而本次我们重点介绍的LDA在某种程度上和PCA有着异曲同工之妙:线性判别分析(LDA)最常在模式分类和机器学习应用的预处理步骤中用作降维技术。目的是将数据集投影到具有良好类可分离性的低维空间上,以避免过度拟合(“维数的诅咒”)并降低计算成本。

因此,简而言之,LDA的目标通常是将特征空间(数据集的n维样本)投影到较小的子空间 k ( k < n − 1 ) k(k<n-1) k(k<n1) 上,同时保留类别歧视信息。

什么是良好的特征子空间?

毫无疑问,我们的目标是缩小 d d d 维数据,将其投影到 k k k 维子空间上,其中 k < d k<d k<d。那么。如何判断这个新映射的子特征空间能够很好的表示原先的数据呢?

与传统的图像数据处理利用特征值和特征向量相似,这次是将原始数据中的特征向量(分量)收集到一个所谓的分散矩阵中,也就是后面将会提到的类间分散矩阵和类内分散矩阵)。

由于每个特征值都对应其特征向量,如果观察到所有的特征值都有相似的大小,那么,这就在间接的告诉我们:此时原始数据已经投射到了一个好的特征空间上

如果映射子空间后得到的特征值大小分布并不均衡,有些特征值大于,甚至是远大于其他的特征值,此时,我们应该果断留下那些对应着特征值较大的特征向量,因为他们包含着更多的关键信息和有用数据,其余的特征向量可以直接去除,这里的思想和奇异值分解处理模糊图像的方法不能说是毫无关系,简直是一模一样!!!

LDA思想
LDA是一种监督学习的降维技术,也就是说它的数据集的每个样本是有类别输出的。它的主要思想可以用一句话概括,就是 “投影后类内方差最小,类间方差最大” ,如下图所示。 我们要将数据在低维度上进行投影,投影后希望每一种类别数据的投影点尽可能的接近,而不同类别的数据的类别中心之间的距离尽可能的大

还说看图说话,变抽象为具象:
在这里插入图片描述
假设有两类数据,分别为红色和蓝色,如上图所示,这些数据特征是二维的,希望将这些数据投影到一维的一条直线,让每一种类别数据的投影点尽可能的接近,而红色和蓝色数据中心之间的距离尽可能的大,显然,上图右边的结果更符合LDA的核心思想!

二 . 算法推导

算法的主要推导对象是数据的映射方向!

由于公式的复杂性,这里采用二维空间中的两类样本为例,也符合上图,根据前面 “投影后类内方差最小,类间方差最大” 的主要思想我们做出如下推导:

假设有一个数据集 D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) ⋯ ( x m , y m ) } D = \left \{ (x_{1},y_{1}),(x_{2},y_{2})\cdots (x_{m},y_{m})\right \} D={(x1,y1),(x2,y2)(xm,ym)},其中,任意样本 x i x_{i} xi n n n 维向量,且定义 N j N_{j} Nj 为 第 j j j 类样本的个数, X j X_{j} Xj 为第 j j j 类 样本的合集,而 μ j \mu_{j} μj 为 第 j j j 类样本的均值向量, Σ j \Sigma_{j} Σj为样本的 方差,且这里的 j j j 1 1 1 2 2 2,也就是数据集中的 x x x 样本和 y y y 样本。

因此,两类的均值向量为:
μ i = 1 N i ∑ x ∈ ω i x \mu_{i}=\frac{1}{N_{i}} \sum_{x \in \omega_{i}} x μi=Ni1xωix
同时保证让投影之后的中心距离尽可能的大,也就是:
J ( w ) = ∣ μ 1 ~ − μ 2 ~ ∣ = ∣ w T ( μ 1 − μ 2 ) ∣ \mathrm{J}(\mathrm{w})=\left|\widetilde{\mu_{1}}-\widetilde{\mu_{2}}\right|=\left|w^{T}\left(\mu_{1}-\mu_{2}\right)\right| J(w)=μ1 μ2 =wT(μ1μ2)
其中,
μ ~ i = 1 N i ∑ y ∈ ω i y = 1 N i ∑ x ∈ ω i w T x = w T μ i \tilde{\mu}_{i}=\frac{1}{N_{i}} \sum_{y \in \omega_{i}} y=\frac{1}{N_{i}} \sum_{x \in \omega_{i}} w^{T} x=w^{T} \mu_{i} μ~i=Ni1yωiy=Ni1xωiwTx=wTμi是来自类别 i i i 的投影数据的均值 , w T w^{T} wT 是投影向量。但是,通过增大 w w w,这个表达式可以任意增大。为了解决这个问题,我们可以将 w w w限制为单位长度,即 ∑ i w i 2 = 1 \sum_{i} w_{i}^{2}=1 iwi2=1
至此,投影的建立已经算是完成了,接下来需要做的就是最小化类内的方差,接着上面的推导,投影结束之后,假设样本的坐标为 y y y, 投影也就是 y = w T x y=\mathbf{w}^{T} \mathbf{x} y=wTx , 那么投影后的方差就是:
s ~ i 2 = ∑ y ∈ ω i ( y − μ ~ i ) 2 \widetilde{s}_{i}^{2}=\sum_{y \in \omega_{i}}\left(y-\tilde{\mu}_{i}\right)^{2} s i2=yωi(yμ~i)2

所以两个样本的方差相加为 s ~ 2 1 + s ~ 2 2 \tilde{s}_{2}^{1}+\tilde{s}_{2}^{2} s~21+s~22 ,再根据 “投影后类内方差最小,类间方差最大” 的原则,我们需要最大化:
J ( w ) = ∣ μ 1 ~ − μ 2 ~ ∣ 2 s ~ 1 2 + s ~ 2 2 \mathrm{J}(\mathrm{w})=\frac{\left|\widetilde{\mu_{1}}-\widetilde{\mu_{2}}\right|^{2}}{\tilde{\mathrm{s}}_{1}^{2}+\tilde{\mathrm{s}}_{2}^{2}} J(w)=s~12+s~22μ1 μ2 2

现在我们需要改写这个式子,使得能让我们在数学层面上有方法最大化它,因为 μ k ~ = w T μ k \widetilde{\mu_{k}}=\mathbf{w}^{T} \mu_{k} μk =wTμk y = w T x y=\mathbf{w}^{T} \mathbf{x} y=wTx,分子改写为:
( μ ~ 1 − μ ~ 2 ) 2 = ( w ⊤ μ 1 − w ⊤ μ 2 ) 2 = w ⊤ ( μ 1 − μ 2 ) ( μ 1 − μ 2 ) ⊤ w = w ⊤ S B W \left(\tilde{\mu}_{1}-\tilde{\mu}_{2}\right)^{2}=\left(w^{\top} \mu_{1}-w^{\top} \mu_{2}\right)^{2}=w^{\top}\left(\mu_{1}-\mu_{2}\right)\left(\mu_{1}-\mu_{2}\right)^{\top} w=w^{\top} S_{B} W (μ~1μ~2)2=(wμ1wμ2)2=w(μ1μ2)(μ1μ2)w=wSBW

分母中的每一项课改写为:
s ~ i 2 = ∑ y ∈ ω i ( y − μ ~ i ) 2 = ∑ x ∈ ω i ( w T x − w T μ i ) 2 = ∑ x ∈ ω i w T ( x − μ i ) ( x − μ i ) T w \tilde{s}_{i}^{2}=\sum_{y \in \omega_{i}}\left(y-\tilde{\mu}_{i}\right)^{2}=\sum_{x \in \omega_{i}}\left(w^{T} x-w^{T} \mu_{i}\right)^{2}=\sum_{x \in \omega_{i}} w^{T}\left(x-\mu_{i}\right)\left(x-\mu_{i}\right)^{T} w s~i2=yωi(yμ~i)2=xωi(wTxwTμi)2=xωiwT(xμi)(xμi)Tw

最终化简为:
J ( w ) = w T S B w w T S W w J(\boldsymbol{w})=\frac{\boldsymbol{w}^{T} \boldsymbol{S}_{B} \boldsymbol{w}}{\boldsymbol{w}^{T} \boldsymbol{S}_{W} \boldsymbol{w}} J(w)=wTSWwwTSBw

其中,矩阵 S B \boldsymbol{S}_{B} SB S W \boldsymbol{S}_{W} SW 就是我们开头说的类间散度矩阵和类内散度矩阵 ,求极值的通法都是求导,现直接对 J ( w ) J(\boldsymbol{w}) J(w) 关于 w w w求导,令之为0,得到取到极值的条件为:
( w T S B w ) S W w = ( w T S W w ) S B w \left(\boldsymbol{w}^{T} \boldsymbol{S}_{B} \boldsymbol{w}\right) \boldsymbol{S}_{W} \boldsymbol{w}=\left(\boldsymbol{w}^{T} \boldsymbol{S}_{W} \boldsymbol{w}\right) \boldsymbol{S}_{B} \boldsymbol{w} (wTSBw)SWw=(wTSWw)SBw
由于 ( w T S B w ) \left(w^{T} S_{B} w\right) (wTSBw) ( w T S W w ) \left(w^{T} S_{W} w\right) (wTSWw)是标量,可得:
S B w = λ S W w S_{B} w=\lambda S_{W} w SBw=λSWw
同时左乘 S W − 1 S_{W}^{-1} SW1 得:
S W − 1 S B w = λ w S_{W}^{-1} S_{B} w=\lambda w SW1SBw=λw

显然,这个就是特征方程了,终于到达了前面说的特征值和特征向量!

再回到前面推导时候说过: S B = ( μ 1 − μ 2 ) ( μ 1 − μ 2 ) T S_{B}=\left(\mu_{1}-\mu_{2}\right)\left(\mu_{1}-\mu_{2}\right)^{T} SB=(μ1μ2)(μ1μ2)T,所以 S B w S_{B}w SBw的方向就是 ( μ 2 − μ 1 ) \left(\mu_{2}-\mu_{1}\right) (μ2μ1),可用 λ ( μ 2 − μ 1 ) \lambda\left(\mu_{2}-\mu_{1}\right) λ(μ2μ1) 来代替。再结合前面的额特征方程,我们可以得到:
w ∝ S W − 1 ( μ 2 ~ − μ 1 ~ ) w \propto S_{W}^{-1}\left(\widetilde{\mu_{2}}-\tilde{\mu_{1}}\right) wSW1(μ2 μ1~)

最后,我们恍然大悟,只需要求出原始样本的均值和方差就可以求出最佳的方向 w w w

总的来说,推导过程的理解还是不难的,只需要线性代数的简单基础就行了,没有什么高级的地方。

那么回过头来,上述推导是建立在二维数据降到一维的情况,那真的做项目的时候可能就没那么顺利了,有可能是多维数据,要下降几个维度,但是基本上还是换汤不换药,方法类似,通过下面这个图也能非常清晰的看出高阶数据的LDA方法:
在这里插入图片描述

三 . 小项目练习

从上文中我们可以总结出,LDA降维一般分为5个步骤:

  1. 计算数据集中每个类别样本的均值向量。
  2. 通过均值向量,计算类间散度矩阵 S B S_{B} SB和类内散度矩阵 S w S_{w} Sw
  3. S W − 1 S B W = λ W S_{W}^{-1} S_{B} W=\lambda W SW1SBW=λW,进行特征值求解, 求出 S W − 1 S B S_{W}^{-1} S_{B} SW1SB的特征向量和特征值。
  4. 对特征向量按照特征值的大小降序排列,并选择前K个特征向量组成投影矩阵 W W W
  5. 通过 D × K D\times K D×K维的特征值矩阵将样本点投影到新的子空间中, Y = X ⋅ W Y = X\cdot W Y=XW

本次使用传统的鸢尾花数据集进行试验,因为该数据集有很多的类,方便我们降维,其中包含萼片长度、萼片宽度、花瓣长度、花瓣宽度四个关键类,单位都是cm,除此之外,本数据集中,有三种不同种类的鸢尾花,他们分别是 I r i s − s e t o s a Iris-setosa Irissetosa I r i s − v e r s i c o l o r Iris-versicolor Irisversicolor I r i s − v i r g i n i c a Iris-virginica Irisvirginica

我们首先导入数据,并展示最后五行数据的大致情况:

import pandas as pd
df = pd.read_csv('iris.data', header=None)

feature_dict = {i:label for i,label in zip(
                range(4),
                  ('sepal length',
                  'sepal width',
                  'petal length',
                  'petal width', ))}
df.columns = [l for i,l in sorted(feature_dict.items())] + ['class label']
df.dropna(how="all", inplace=True) 
df.tail()

在这里插入图片描述
为了面处理的方便,我们将其全部转变为数字,不留文本格式。

from sklearn.preprocessing import LabelEncoder
X = df[['sepal length in cm', 'sepal width in cm', 'petal length in cm', 'petal width in cm']].values
y = df['class label'].values
enc = LabelEncoder()
label_encoder = enc.fit(y)
y = label_encoder.transform(y) + 1
label_dict = {1: 'Setosa', 2: 'Versicolor', 3:'Virginica'}

上面代码将前面行列的文本全部标签化了,下面我们画出各个种类的柱状图:

import math
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(12,6))

for ax,cnt in zip(axes.ravel(), range(4)):  

    # set bin sizes
    min_b = math.floor(np.min(X[:,cnt]))
    max_b = math.ceil(np.max(X[:,cnt]))
    bins = np.linspace(min_b, max_b, 25)

    # plottling the histograms
    for lab,col in zip(range(1,4), ('blue', 'red', 'green')):
        ax.hist(X[y==lab, cnt],
                   color=col,
                   label='class %s' %label_dict[lab],
                   bins=bins,
                   alpha=0.5,)
    ylims = ax.get_ylim()

    # plot annotation
    leg = ax.legend(loc='upper right', fancybox=True, fontsize=8)
    leg.get_frame().set_alpha(0.5)
    ax.set_ylim([0, max(ylims)+2])
    ax.set_xlabel(feature_dict[cnt])
    ax.set_title('Iris histogram #%s' %str(cnt+1))

    # hide axis ticks
    ax.tick_params(axis="both", which="both", bottom="off", top="off",  
            labelbottom="on", left="off", right="off", labelleft="on")

    # remove axis spines
    ax.spines["top"].set_visible(False)  
    ax.spines["right"].set_visible(False)
    ax.spines["bottom"].set_visible(False)
    ax.spines["left"].set_visible(False)    

axes[0][0].set_ylabel('count')
axes[1][0].set_ylabel('count')

fig.tight_layout()       

plt.show()

分别得到鸢尾花中四个参数(萼片长度、宽度,花瓣长度、宽度)下,三个不同种类鸢尾花的柱状图。
在这里插入图片描述
显然,四幅图中上方两幅区分性不强,而下面代表花瓣宽度和长度的数据区分度比较大,因为这2个特征的直方图中,不同的类别的样本较为分散!

下一步就需要计算特征样本的均值和方差了,由于纯数学问题,就直接上代码了:

np.set_printoptions(precision=4)

mean_vectors = []
for cl in range(1,4):
    mean_vectors.append(np.mean(X[y==cl], axis=0))
    print('Mean Vector class %s: %s\n' %(cl, mean_vectors[cl-1]))

在这里插入图片描述
接下来,根据前面的介绍,关键的类内散度矩阵和类间散度矩阵就来了:

#类内散度矩阵:Sw
S_W = np.zeros((4,4))
for cl,mv in zip(range(1,4), mean_vectors):
    class_sc_mat = np.zeros((4,4))                  # scatter matrix for every class
    for row in X[y == cl]:
        row, mv = row.reshape(4,1), mv.reshape(4,1) # make column vectors
        class_sc_mat += (row-mv).dot((row-mv).T)
    S_W += class_sc_mat                             # sum class scatter matrices
print('within-class Scatter Matrix:\n', S_W)

# 类间散度矩阵:Sb
overall_mean = np.mean(X, axis=0)
S_B = np.zeros((4,4))
for i,mean_vec in enumerate(mean_vectors):  
    n = X[y==i+1,:].shape[0]
    mean_vec = mean_vec.reshape(4,1) # make column vector
    overall_mean = overall_mean.reshape(4,1) # make column vector
    S_B += n * (mean_vec - overall_mean).dot((mean_vec - overall_mean).T)
print('between-class Scatter Matrix:\n', S_B)

结果分别为:
在这里插入图片描述
在这里插入图片描述

然后,就是 S W − 1 S B S_{W}^{-1} S_{B} SW1SB的特征方程求解了:

eig_vals, eig_vecs = np.linalg.eig(np.linalg.inv(S_W).dot(S_B))

for i in range(len(eig_vals)):
    eigvec_sc = eig_vecs[:,i].reshape(4,1)   
    print('\nEigenvector {}: \n{}'.format(i+1, eigvec_sc.real))
    print('Eigenvalue {:}: {:.2e}'.format(i+1, eig_vals[i].real))

在这里插入图片描述
此时,我们就可以确定投影方向了,也就能选择新的特征空间了:

# Make a list of (eigenvalue, eigenvector) tuples
eig_pairs = [(np.abs(eig_vals[i]), eig_vecs[:,i]) for i in range(len(eig_vals))]
# Sort the (eigenvalue, eigenvector) tuples from high to low
eig_pairs = sorted(eig_pairs, key=lambda k: k[0], reverse=True)
# Visually confirm that the list is correctly sorted by decreasing eigenvalues
print('Eigenvalues in decreasing order:\n')
for i in eig_pairs:
    print (i[0], i[1])

先将特征向量按照特征值的大小降序排列,老板的线代告诉我:特征向量和特征值代表了变换后的方向以及该方向上的缩放比例,因此特征值越大,说明这个方向在变换中越显著,也就是信息量最大,同时抛弃特征值较小的方向,我们只需要选取前k个特征值对应的特征向量,就得到了映射矩阵W:

在这里插入图片描述
显然,上图中,只有第一列是特征值,有两个值以及无限接近0了。我们映射的有用特征值只有2个!

# 我们通过特征值的比例来体现方差的分布:
print('Variance explained:\n')
eigv_sum = sum(eig_vals)
for i,j in enumerate(eig_pairs):
    print('eigenvalue {0:}: {1:.2%}'.format(i+1, (j[0]/eigv_sum).real))

在这里插入图片描述
注意,这里我们只取前两个!

# 选择K个特征向量作为映射矩阵,这里选了前2个有信息的。
W = np.hstack((eig_pairs[0][1].reshape(4,1), eig_pairs[1][1].reshape(4,1)))
print('Matrix W:\n', W.real)

在这里插入图片描述就快结束了,我们现在唯一需要做的收尾工作就是将样本投影到新的空间

X_lda = X.dot(W)
assert X_lda.shape == (150,2), "The matrix is not 150x2 dimensional."

def plot_step_lda():

    ax = plt.subplot(111)
    for label,marker,color in zip(
        range(1,4),('^', 's', 'o'),('blue', 'red', 'green')):

        plt.scatter(x=X_lda[:,0].real[y == label],
                y=X_lda[:,1].real[y == label],
                marker=marker,
                color=color,
                alpha=0.5,
                label=label_dict[label]
                )

    plt.xlabel('LD1')
    plt.ylabel('LD2')

    leg = plt.legend(loc='upper right', fancybox=True)
    leg.get_frame().set_alpha(0.5)
    plt.title('LDA: Iris projection onto the first 2 linear discriminants')

    # hide axis ticks
    plt.tick_params(axis="both", which="both", bottom="off", top="off",  
            labelbottom="on", left="off", right="off", labelleft="on")

    # remove axis spines
    ax.spines["top"].set_visible(False)  
    ax.spines["right"].set_visible(False)
    ax.spines["bottom"].set_visible(False)
    ax.spines["left"].set_visible(False)    

    plt.grid()
    plt.tight_layout
    plt.show()

plot_step_lda()

在这里插入图片描述

从上面这个散点图中,我们可以看出,第一个线性判别LD1可以很好地将类别区分开来,但是第二个线性判别LD2,由于不含有充分的信息(可以从之前的特征向量中看出)。并没有很好的区分性。原先需要四个图表示的多维向量数据,在这里,用一个二维散点图就可以了!

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值