线性判别分析(LDA)

LDA (Linear Discriminant Analysis)名称为线性判别分析,它也是一种直接、快速的分类模型。它本身也是监督算法,训练时需要提供有标注的数据。我们简要描述下该算法模型的分类过程(以文本分类为例),假设:我们现在有 0、1 两个类别的样本:

  1. 将训练的样本转换为 TF-IDF 向量;
  2. 在 0 类样本中计算出满足某些选择条件的质心;
  3. 在 1 类样本中计算出满足某些选择条件的质心;
  4. 将两个质心连线,得到一个条用于分类的线性模型;

我们接下来,结合下面的图来理解下 LDA 算法的思想:

ttu 图-1

我们有蓝色、红色两个类别的样本,接下来就是大白话了,我们就像找到一条直线能用于这两类样本的分类问题。接下来:

  1. 将蓝色样本投射到一个条直线上,找到投射后的中心点作为质心;
  2. 将蓝色样本投射到一个条直线上,找到投射后的中心点作为质心;
  3. 然后将这两个点之间连线,这个连线就是我们要找的 LDA 线性判别模型;

当将所有训练样本投射到该直线时,投射的含义如下图所示:

图-2

但是,如上图,这样的直线有很多种,那我们究竟要找满足什么条件的两个质心连成的直线呢?

  1. 两个质心的之间距离要尽可能的大;
  2. 质心所属样本集内部的方差要尽可能小;
  3. 要找到上面两个条件最大值时得到的直线;

上面的两个条件是不是很熟悉,在接触到对中心聚类算法的评估方法时,评估一个维度就是簇之间的分离程度,簇内的内聚程度。但是,聚类算法是无监督算法,而 LDA 是监督学习算法。

我们再结合 图-2,会发现右侧的线性模型更加符合我们的条件,此时我们就找到了我们的判别模型。当我们得到这个线性模型,如何得到预测分类结果呢?

  1. 将输入的句子编码成 TD-IDF 向量;
  2. 投射该 TF-IDF 向量到线性模型上,会得到一个 score;
  3. 将 score 归一化到 0-1 之间;
  4. 如果 score > 0.5 则将其归为 1 类别,否则将其归为 0 类别;

接下来,用公式表示 LDA 的优化目标:

μ 表示 0 或者 1 类别所有样本投射到线性模型后中心点位置,由于我们每一个样本形状都是 (1, N) 的向量,投射到线性模型后就是一个常数值,取均值即可找到 0 或者 1 类别的质心;

w 表示我们找到的线性模型,即:线性模型的参数,这也是我们要学习的参数,我们希望这两个点之间的值尽可能的大,所以可以表示为两个质心差的绝对值,我们希望该值能够尽可能大,越大越说明两个类别样本更容易区分:

我们还得表示出每个类内所有样本的方差,如下公式所示:

我们希望 \(s_{1}、s_{2} \) 都尽可能的小,越小说明该类别的样本越相似。所以可以将其加起来:

我们上面的两个条件合并到一起,作为我们的目标函数:

对于上面的函数,我们希望分子越大越好,分母越小越好,则整体就是希望越大越好。我们希望最后得出的结果能够在 0-1 之间,所以需要对分母进行归一化。

其余公式后面再进行推导,接下来使用 scikit-learn 的 LDA 接口来实现对文本进行分类。

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.feature_selection import VarianceThreshold
import jieba


if __name__ == '__main__':

    # 数据加载
    inputs_neg = [' '.join(jieba.lcut(line.strip())) for line in open('data/neg.txt', encoding='gb18030')]
    inputs_pos = [' '.join(jieba.lcut(line.strip())) for line in open('data/pos.txt', encoding='gb18030')]
    labels_neg = [0] * len(inputs_neg)
    labels_pos = [1] * len(inputs_pos)
    inputs, labels = inputs_neg + inputs_pos, labels_neg + labels_pos

    # 数据分割
    x_train, x_test, y_train, y_test = \
        train_test_split(inputs, labels, test_size=0.1, stratify=labels)
    # 停用词
    stopwords = [word.strip() for word in open('data/stopwords.txt')]
    # 转换向量
    tf = TfidfVectorizer(stop_words=stopwords, max_features=5000)
    tf.fit(x_train)
    x_train = tf.transform(x_train).toarray()
    print(x_train.shape)
    vt = VarianceThreshold(threshold=0.001)
    vt.fit(x_train)
    x_train = vt.transform(x_train)
    print(x_train.shape)

    # 模型训练
    model = LinearDiscriminantAnalysis()
    model.fit(x_train, y_train)
    # 模型测试
    x_test = tf.transform(x_test).toarray()
    x_test = vt.transform(x_test)
    print(x_test.shape)
    acc = model.score(x_test, y_test)
    print('测试集 Acc:', acc)

程序输出结果:

(9000, 5000)
(9000, 192)
(1000, 192)
测试集 Acc: 0.847

上面代码中,先将输入向量转换为 TF-IDF 向量,再方差降维,即可以提高训练速度,也可以提高模型的泛化能力。

逻辑回归模型、感知机模型、支持向量机、线性判别分析模型看起来都在寻找一条直线(超平面)用于分类任务,不同的是这些模型找直线的规则不同:

  1. 逻辑回归找对数损失最小的那条直接,也就是让正样本的预测概率尽可能接近于1,而负样本的预测概率接近于 0;
  2. 感知机模型则是尽可能将所有样本分类正确;
  3. 支持向量机则是在感知机基础上,增加一个最大间隔:即:在样本分类正确的情况下,还要距离正负样本的距离最大,感知机模型的结果从理论上讲并不唯一,但是支持向量机间隔最大化就使得结果唯一;
  4. 线性判别分析模型则是找到一条直线,使得投影到该直线上的样本尽可能的内聚,两个类别样本之间的距离尽可能的大。

PCA 和 LDA 的异同点:

  1. PCA 是无监督的学习方法,LDA 则是有监督的学习方法;
  2. PCA 和 LDA 都可以用于对数据进行特征提取,数据降维,但是 LDA 也可以用于分类任务。

PCA 降维时,对样本的协方差矩阵计算特征值分解,即:PCA 考虑的是数据内部特征之间的相关性,然后计算在各个主成分的投影,得到降维后的数据。LDA 则是对考了不同类内离散程度、类间离散程度的矩阵进行特征值分解,即:LDA 更多的是考虑数据的分布,然后计算在各个主成分的投影,得到降维后的数据。

关于 PCA 和 LDA,这篇文章不错:https://www.jianshu.com/p/982c8f6760de

未经允许不得转载:一亩三分地 » 线性判别分析(LDA)