隐性语义索引(Latent Semantic Idexing, LSI),也叫Latent Semantic Analysis(LSA),是信息检索领域一类非常重要的技术思想。它通过对词项-文档矩阵的奇异值分解,在理论上成功地解决了潜在语义(或者叫隐性语义)的检索问题。本文将介绍关于LSI的原理和实现方法。
隐含语义问题
基于关键词的文档检索是IR中最简单,也是最普遍的技术手段。一般来说,基本做法是:当查询的词(一个或多个)与文档中的词相匹配时,该文档被“命中”,如果是多关键词查询,则遵循匹配的词项越多文档的检索排名越靠前的原则。这种思路非常经典,Salton等人在70年代就据此提出了至今仍非常有效的向量空间模型(VSM),即将文档和查询解析为由词项的存在性构成的二进制的特征向量,计算向量的内积(或者是计算cos相似度等等)即可得到用于检索排序的得分。更进一步地,为了使检索排名更加准确,随后又在二进制向量的基础上构建了词项的权重算法 和其各种变形。
总的来说,依赖关键词匹配与否的做法是有效而实用的。但是随着信息量的爆炸式增长,信息的结构越发复杂,完全靠匹配判断就显得有所不足了。举个例子,我查找一个词“加密”,那你说假如一个文档中存在“密码”,“信息安全”,“隐私保护”,“解密”这些词,那么即便它没有“加密”这个词,是不是也应该被检索出?而且应该排名还不会太差。不难发现,这里面的关键是存在“隐性语义”的问题,具体地:
- 词a和词b在形式上可能完全不同,但在语义上是相近的,甚至是几乎一样的。比如“中国”和“China”,“电脑”和“计算机”,“北邮”和“北京邮电大学”等等);
- 词a和词b在形式上完全相同,但在语义上是几乎无关的。比如“苹果”和”Mac”, “ios”在一起时的含义与“苹果”和“农药”,“梨”, 在一起时的含义几乎是完全无关的。
关于隐含语义,有一个最基本的原则:在大型的文档集中,两个词的语义越相近,它们共现的概率也就越大。这样,我们可以利用对于词项和文档之间关系的分析,得到概念与词项之间的关系。这里的概念其实也叫主题,比如上面说的“中国”和“China”,“电脑”和“计算机”等等虽然形式上不同,却是属于同一主题的。
以上就是LSI的动机了,接下来,我们一步步推导,如何利用词项和文档之间关系的分析,得到概念与词项之间的关系。
奇异值分解的作用
从词项到概念,很容易想到降维的方法,即把表达同一个概念的多个词项通过降维的方式映射到一个维度上。这一点和PCA(主成分分析)的基本思想是一致的:PCA能实现降维是因为不同的数据维度之间存在相关关系,比如 越大, 也越大这种情况,这种相关关系在坐标系上的直观体现就是可以先转换坐标系,再将这些点映射到低维的超平面上;而词项的检索也是一样,不同词项之间也可能存在着相关关系,比如,如果词 出现,则词 以极大的概率出现,相应的,我们自然想到可以用类似于PCA的降维方式对词项-文档矩阵降维,使得每个文档对应的词向量的维度降低。而维度降低之后的元素则不是词项了,我们把它叫做“概念”(或者叫“主题”)。降维是LSI的核心思想,只不过他的出发点不是要加速计算,而是解决如何将一个个的词,映射为统一的语义(概念、主题)。实际上,LSI的处理方法与PCA在最后一步是有所不同的,看完本文你会发现,对于搜索算法的复杂度来说,LSI并未真正降维,而是将每个维度的词变换为一个词义。换句话说,LSI实现的是一种概念匹配,而不是VSM所依据的词项匹配。
注1:关于主成分分析(PCA)的详细介绍可以参照我的博客:主成分分析(PCA)原理与实现
注2:关于奇异值分解(SVD)的详细介绍可以参照我的博客:矩阵的分解:满秩分解和奇异值分解
如果不是非常熟悉,建议可以先理解一下PCA和SVD,再看LSI.
假设现在有词项-文档矩阵(记为 ),它由 个词项, 个文档构成,为形象起见,我画出下表:
cloud | 1 | 0 | 1 | 0 | 1 | 0 |
server | 1 | 0 | 1 | 0 | 0 | 1 |
hadoop | 0 | 0 | 1 | 0 | 1 | 0 |
distribute | 1 | 0 | 1 | 0 | 1 | 0 |
money | 0 | 1 | 0 | 0 | 0 | 1 |
bank | 0 | 1 | 0 | 1 | 0 | 1 |
tax | 0 | 1 | 0 | 1 | 0 | 0 |
表中的数据是我故意这样设置的,大家一眼就能看出来,它分为了2个概念,一个是计算机学科的,一个是经济学的。这样方便后面的计算举例。
和PCA的方法一样,现在对
做奇异值分解。关于奇异值分解的原理和实现,我在之前的博客 中已经给出了详细的阐述,这里略过,直接给出结论:
拿上面表格中的例子来说,奇异值分解为如下的形式:
上式的计算可以通过Python的Numpy库直接计算得到,代码如下:
import numpy as np
A = np.array([[1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 1], [0, 0, 1, 0, 1, 0], [1, 0, 1, 0, 1, 0], [0, 1, 0, 0, 0, 1], [0, 1, 0, 1, 0, 1], [0, 1, 0, 1, 0, 0]])
U, Sigma, VT = np.linalg.svd(A)
print(U)
print(Sigma)
print(VT)
至于分解的意义,放到LSI中,可以这样理解:奇异值构成的对角矩阵 中(假设 秩为 ),那么这 个元素其实代表了 个概念,而相应的值则代表了 个概念在文档集中的用于区分文档的显著程度。根据信息检索中数据的特征提取的原则,当然是区分程度越好的特征越应该被当做索引项。为了起到降维的作用,我们再次选取较大的 个特征值保留,而将剩下的 个特征值变为0。那么现在 的SVD分解可以近似的写成下面的形式:
拿上面表格中的例子来说,我把奇异值3和2.44留下,再的置为0. 然后重新计算 ,为示区别,记为 :
观察一下这个新的 矩阵和 的区别:
- 形状是一样的,可见LSI降维的目的只是整合概念,计算的复杂度并没有降低;
- 文档中不曾出现的词项也是有权重的,只是会小一点,比如词hadoop没有出现在文档1中,但是你看他的权重现在是0.62,说明还是很相关的;
- 新矩阵的每个元素 实际上代表词 在文档 中的词义权重,而并非 中的存在性;
做完以上步骤,LSI的主要工作就完成了,我们得到了很重要的一个东西:词义-文档矩阵,也就是上式中近似分解计算出来的 。其中每个列向量代表了一个文档,这个文档的每个元素由“词义”决定,而并非最初用的存在性(即0或1)。
文档相似度的计算
现在要解决的问题是如何利用 计算查询和文档的相关度。基于关键词的检索,从具体的应用上来说分为两种:一是通过关键词查找相关文档;二是通过文档之间由关键词构成的向量计算文档之间的相似度。无论是哪一种方法,道理是一样的,都是计算两个向量之间的相似度。那就先来看看文档相似度的计算:这个很简单,直接根据新生成的概念-文档矩阵 中的两个行向量,计算其相似度即可(比如计算内积或者余弦相似度)。那么通过关键词构成的查询向量查询文档呢?一样的做法,将查询向量看成一个“伪文档”,和真实的文档集放在一起做SVD,再如同计算文档之间的相似度那样,计算即可。
总结
综上所述,我们可以清晰地总结出LSI的优缺点:
优点:
- 算法原理很简单,一次奇异值分解即可;
- 对于隐私语义的检索,实验发现确实效果不错;
缺点:
- 每一次查询都要重新计算一次SVD,而SVD是十分消耗计算资源的;
- 概念数量 的选择并不容易;
- LSI得到的不是一个概率模型,缺乏统计基础,结果难以直观的解释;