SVD Python代码注释

原英文链接:Latent Semantic Analysis (LSA) Tutorial - Part 1 - Creating the Count Matrix


LSA的第一步是建立标题(或文档)词矩阵。在该矩阵中,每个索引词是一行,每个标题是一列。矩阵中的每个值表示该词在该标题中的出现次数。比如在下图中,单词“book”在T3和T4中各出现1次,而单词“investing”在每个标题中都出现了1次。一般而言,由LSA产生的矩阵都很大并且稀疏。因为每个标题或文档都只包含几个词。在很复杂的LSA实现中,在内存和时间上这些稀疏性可以被充分利用。在下面的矩阵中,为了减少噪音,我们去掉了矩阵中的0值。

下面开始讲解Python 的实现:

源代码可以在这里下载

在本文中,我们将给出LSA中所需的所有步骤的python代码实现。我们将通过代码来逐步解释一切。如果在你的机器上运行该代码,需要首先安装NumPy库和SciPy库。

Python-Import Functions

首先需要从Python库中导入一些函数来处理我们需要做的数学问题。NumPy是一个Python数值运算库,我们将要导入zeros, 这个函数可以建立一个零矩阵,这个零矩阵将会在建立词-标题矩阵时候使用。从scipy.linalg导入svd函数来执行奇异值分解,这是LSA的核心部分。

from numpy import zeros 
from scipy.linalg import svd

 Python-Define Data

接下来,定义我们将要使用的数据。Titles包含我们收集的9本书的标题,stopwords包含8个我们在计算标题单词个数时将要忽略的停用词,ignorechars包含我们需要中删除的标点符号。我们使用Python的三重引用串("""),因此只有4个标点符号需要删除:(,),(:),('),(!)

titles =
 
 
[  
"The Neatest Little Guide to Stock Market Investing",  
"Investing For Dummies, 4th Edition",  
"The Little Book of Common Sense Investing: The Only Way to Guarantee Your Fair Share of Stock Market Returns",  
"The Little Book of Value Investing",  
"Value Investing: From Graham to Buffett and Beyond",  
"Rich Dad's Guide to Investing: What the Rich Invest in, That the Poor and the Middle Class Do Not!",  
"Investing in Real Estate, 5th Edition",  
"Stock Investing For Dummies",  
"Rich Dad's Advisors: The ABC's of Real Estate Investing: The Secrets of Finding Hidden Profits Most Investors Miss"  
]
stopwords = ['and','edition','for','in','little','of','the','to']  
ignorechars = ''',:'!'''

 Python-Define LSA Class

LSA类包含初始化方法,分析文档方法,建立词计数矩阵方法,计算方法,第一个方法是__init__方法,该方法在初始化LSA对象时被调用。它存储了将在下文使用的stopwords和ignorechars,然后初始化词-词典(wdict)和文档计数变量(dcount)

class LSA(object):
 
 
def __init__(self, stopwords, ignorechars):
self.stopwords = stopwords  
self.ignorechars = ignorechars  
self.wdict = {}  
self.dcount = 0

 Python - Parse Documents

parse方法将一个文档解析成词,并删除需要忽略的字符,将所有单词转换为小写。在执行过程中,如果一个单词是停用词,忽略它并移到下一个单词,否则将该词放入词典中,并对该词增加它所在文档的的标记以跟踪该词出现在哪篇文档中。

单词所在的文档将会保存在以该单词为索引的列表中。比如说,单词"book"出现在T3和T4中,那个当所有的标题被分析后,将会有self.wdict['book'] = [3, 4]

当处理完当前文档的所有词后,文档计数+1以准备分析下一篇文档。

 
 
def parse(self, doc):
words = doc.split();  
for w in words:
w = w.lower().translate(None, self.ignorechars)  
if w in self.stopwords:
continue
elif w in self.wdict:
self.wdict[w].append(self.dcount)
else:
self.wdict[w] = [self.dcount]
self.dcount += 1

Python- Build the Count Matrix

一旦分析了所有的文档,在文档中出现次数大于1的词(词典的键)将会被提取出来并且排序。基于此会建立一个矩阵,这个矩阵的行数等于单词的个数,矩阵的列数等于文档个数。对每一个<词, 文档>对,所对应的矩阵值将会加1.

 
 
def build(self):
self.keys = [k for k in self.wdict.keys() if len(self.wdict[k]) > 1]  
self.keys.sort()  
self.A = zeros([len(self.keys), self.dcount])  
for i, k in enumerate(self.keys):
for d in self.wdict[k]:
self.A[i,d] += 1

 Python - Print the Count Matrix

printA()方法非常简单,只是简单地输出上文建立起来的矩阵以供检查。

 
 
def printA(self):
print self.A

Python - Test the LSA Class

在定义了LSA类后,就可以使用上面定义的Title中的9本书标题测试了。首先创建一个LSA对象,叫做mylsa,将定义的停用词stopwords和忽略字符ignorechars传递给该对象。在创建过程中,存储stopwords和ignorechard的__init__方法会被调用,并会初始化词-词典和文档计数。

接下来,对每一个文档标题调用parse()方法。该方法从每个标题中提取出单词,删除掉标点字符,将每个单词转换为小写,移除停用词,然后把剩下的单词存到一个词典中,同时记录每个单词所在的文档标号。

最后,调用build()方法来建立词-标题矩阵。这会提取出词典中所有的单词,并移除在所有文档中出现次数少于2的单词,然后排序,建立一个对应大小的零矩阵,然后根据<词,词所在文档>对改变矩阵里面对应的元素值。

mylsa = LSA(stopwords, ignorechars)  
for t in titles:
 
 
mylsa.parse(t)
mylsa.build()  
mylsa.printA()

接下来是printA()输出的生成的矩阵。

[[ 0. 0. 1. 1. 0. 0. 0. 0. 0.] 
[ 0. 0. 0. 0. 0. 1. 0. 0. 1.] 
[ 0. 1. 0. 0. 0. 0. 0. 1. 0.] 
[ 0. 0. 0. 0. 0. 0. 1. 0. 1.] 
[ 1. 0. 0. 0. 0. 1. 0. 0. 0.] 
[ 1. 1. 1. 1. 1. 1. 1. 1. 1.] 
[ 1. 0. 1. 0. 0. 0. 0. 0. 0.] 
[ 0. 0. 0. 0. 0. 0. 1. 0. 1.] 
[ 0. 0. 0. 0. 0. 2. 0. 0. 1.] 
[ 1. 0. 1. 0. 0. 0. 0. 1. 0.] 
[ 0. 0. 0. 1. 1. 0. 0. 0. 0.]]

在比较复杂的LSA系统中,矩阵的值通常会让出现较少的单词的权重比共现词的权重大。比如说,一个单词在所有文档中只以5%的概率出现时,它应该比一个在所有文档中以90%的概率出现的单词拥有更高的权重。最常用的权重计算方法是TFIDF。如果使用这种方法,矩阵中每个单元的值将会使用下面的公式来计算:

TFIDFi,j = ( Ni,j / N*,j ) * log( D / Di ) where

  • Ni,j = the number of times word i appears in document j (the original cell count).   //Ni, j表示单词i在文档j中出现的次数。
  • N*,j = the number of total words in document j (just add the counts in column j).    //N*,j表示文档j中的所有单词数。
  • D = the number of documents (the number of columns).                                              //D表示文档数(矩阵的列数)。
  • Di = the number of documents in which word i appears (the number of non-zero columns in row i)       //Di表示单词i在多少篇文档中出现过。
在这个公式中,强调了集中于某一文档中出现多次的单词(根据 N i,j  / N *,j )。也强调了只在跟少的文档中出现的词(根据 log( D / D i  ) 项)。

在我们这个例子中,我们将会跳过这一步(计算TFIDF)来直接计算SVD。但是,如果你确实想要在LSA类中增加TFIDF值,可以在我们Python文件的的头部加入以下行,以导入log, asarray, 和 sum函数。

from math import log 
from numpy import asarray, sum

 接下来在LSA类中增加TFIDF方法。WordsPerDoc(N*, j)存储了矩阵中每一列的和,表示每个文档中所有索引单词的出现个数。DocsPerWord(Di)使用asarray创建一个数组,

根据矩阵中各个单元的值来计算每个单词的在几篇文档中出现过。最后,根据矩阵的每个单元来计算TFIDF。最后需要把列数转换为浮点数,以防止整数之间相除(只会得到整数值)

 
 
def TFIDF(self):
WordsPerDoc = sum(self.A, axis=0)  
DocsPerWord = sum(asarray(self.A > 0, 'i'), axis=1)  
rows, cols = self.A.shape  
for i in range(rows):
for j in range(cols):
self.A[i,j] = (self.A[i,j] / WordsPerDoc[j]) * log(float(cols) / DocsPerWord[i])

一旦创建了词标题矩阵,我们就可以根据SVD来分析该矩阵了。如果想进一步了解SVD,请参看这一篇文章。Singular Value Decomposition(SVD) Tutorial

SVD有用的原因是它能够找到一个初始矩阵的降维表示方法,并强调了其中的联系部分,排除了噪音。换句话说,它使用最少的信息来最好地重构原矩阵。

为了达到这个目的,它删除了没有意义的噪音,并且强调了最我们非常有帮助的模式和趋势。使用SVD的技巧是在近似一个矩阵时,强调了应该使用多少维或者概念(“concepts”)。Too few dimensions and important patterns are left out, too many and noise caused by random word choices will creep back in(不知道该怎么翻译).

SVD算法有一些难度,但幸运的是Python给我们提供了相应的库,这使得我们使用它变得非常简单。通过在LSA类中增加下面所述的方法,我们能够把我们上文生成的矩阵分解成三个矩阵。其中矩阵U告诉了我们在概念(“concept)”空间中每个单词的坐标,矩阵Vt告诉了我们在概念(”concept“)空间中每个文档的坐标,奇异值矩阵S告诉了我们我们需要包含多少个维度或者说概念”concept“。

 
 
def calc(self):
self.U, self.S, self.Vt = svd(self.A)

为了选择正确的维度个数,我们可以建立奇异值平方的柱状图。这个图将会描述每个奇异值在近似我们的矩阵的所占得重要程度。下图是我们例子的柱状图。


在大规模的文档集合中,一般使用的维度范围在100~500之间。在我们这个小例子中,由于我们想用图表描述它,我们选取3个维度,排除了第一个维度,使用第2和第3维。

我们删除第一维的原因很有趣。对文档而言,第一列和文档的长度有关。对词而言,它和单词出现在所有文档中的次数有关。如果我们以矩阵为中心,通过从每一列中减去列值的平均值,那么我们会使用第一维。类似的,考虑高尔夫得分,我们并不想知道实际的分数,我们想知道的是从减去标准杆后的得分。那告诉我们选手是否打了个小鸟球,标准计数还是其他的什么等等。(原文: The reason we throw out the first dimension is interesting. For documents, the first dimension correlates with the length of the document. For words, it correlates with the number of times that word has been used in all documents. If we had centered our matrix, by subtracting the average column value from each column, then we would use the first dimension. As an analogy, consider golf scores. We don’t want to know the actual score, we want to know the score after subtracting it from par. That tells us whether the player made a birdie, bogie, etc.

在使用LSA时我们不关注矩阵的原因是,我们想把一个稀疏矩阵转换成一个密集矩阵,以大幅度地提高内存和计算性能。如果不专注于矩阵并扔掉第一维将会使它更有效。

下图是我们矩阵完整的三维奇异值分解。每个单词都有三个和它相关的值,每个值都对应一维。第一个值和该单词在所有文档中的出现次数相关,并和第二维和第三维表示的信息不一样。同理,每个标题也有三个值和它相关,每个值对应一维。第一维也不太有意思,因为它和每一个标题中的单词数目相关。



我们也可以把数值转换为颜色。比如说,下图是一个颜色展示,它和上面的标题矩阵相关。它和标题矩阵包含相同的信息,其中蓝色表示负值,红色表示正值,值越小,越接近白色。比如说,标题T9的三个维度的值都是正的并且值最大,所以它的颜色最深。


我们可以使用这些颜色来对标题进行聚类。当聚类时我们忽略掉第一维因为在这一维中所有的标题都是红色的。在第二维中,我们有以下的结果:

Dim2Titles
red6-7, 9
blue1-5, 8
使用第3维,我们使用同样的方式能够再切分每一个组。比如,看第三维,标题T6是蓝色的,但是标题7和9是红色的。对这两组都执行该过程,那么就可以分为四组。

Dim2Dim3Titles
redred7, 9
redblue6
bluered2, 4-5, 8
blueblue1, 3

如果将此表和我们下面得到结果相比较,得到的结论将会非常有趣。

正如我们上文所述的,忽略掉第一维后,我们使用第二维和第三维分别对应X,Y轴画图。然后把单词和标题画到图中。将这个坐标图和上面的表进行比较,我们会发现结论非常有趣。

在下图中,单词用红色的方块表示,标题用蓝色的圆圈表示。比如单词”book“的维度值为(0.15, -0.27, 0.04),忽略掉第一维值0.15值后,把”book“放置到坐标为(x=-0.27, y=0.04)的位置上。标题也使用相同的方法进行放置。



该技术的一个优势是可以将单词和标题都放到一个图中。我们不仅能识别标题的聚类结果,也可以通过看单词所在的簇位置来标记这些簇。比如,左下角的簇有标题1和3,这是和股票市场投资有关的。而单词”stock“和”market“也在这个簇中,使得很方便地看到这个簇的类型。另一个例子是图中中间的簇,包含标题2,4,5,和一个扩展后相关的8,。标题2,4,5和单词”value“ ”investing“相近,而这两个单词又能够比较好地表示这些标题。


LSA有很多好的属性,使得它被广泛用于解决各种问题。

1. 首先,文档和单词最终可以映射到统一主题空间上。在这个空间中,我们可以聚类文档,聚类单词,更重要的是,通过观察这些簇的内容(单词和文档),我们可以根据单词来检索文档,反之亦然。

2. 相比于原始矩阵,该主题空间已经大大减小了。不仅如此,这些维度是被特别选取的,因为它们包含最重要的信息和最少的噪音。这使得新的主题空间对于将来的算法很理想,比如说测试聚类算法。

3. 最后,优于LSA本质上是一个全局算法,它能够从所有的文档和单词中寻找趋势和模式,这使得它能够从不太明显的关系中找出有用的东西,而这些不太明显的关系对于基于局部的算法来讲,很难提取其中的有用信息。跟局部算法相比,比如最近邻算法,它也是非常有效的。

但是,在决定使用LSA之前,也需要了解它的一些限制条件。

1. LSA假设了一个高斯分布和Frobenius范数,该假设可能不对所有问题适用。比如说,文档中的单词如果服从泊松分布,则将会不适用。

2. LSA不能够有效地处理一词多义。它假设每个相同的词都有相同的主题,而该假设对于那些一词多义的词带来问题,比如说”bank“,根据上下文,他有多种含义。

3. LSA是非常依赖于SVD,而SVD是计算密集型的,并且当新文档到来时,SVD很难进行更新。但是,最近的工作已经提出了有效的算法来更新SVD。

虽然这些限制,LSA已经被广泛用于发现和组织搜索结果,将文档组织成簇,垃圾邮件过滤,语音识别,专利调查,自动的文章评价等。

举个例子,iMetaSearch 使用LSA来映射搜索结果和单词到一个主题空间。用户可以找到哪个结果和哪一个单词最相近,反之亦然。这个LSA结果也被用于聚类搜索结果,当你寻找相关的结果时,会节省时间。



  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值