LSA 背景介绍
文本挖掘中,主题模型。聚类算法关注于从样本特征的相似度方面将数据聚类。比如通过数据样本之间的欧式距离,曼哈顿距离的大小聚类等。而主题模型,顾名思义,就是对文字中隐含主题的一种建模方法。
比如从“人民的名义”和“达康书记”这两个词我们很容易发现对应的文本有很大的主题相关度,但是如果通过词特征来聚类的话则很难找出,因为聚类方法不能考虑到到隐含的主题这一块。
那么如何找到隐含的主题呢?这个一个大问题。常用的方法一般都是基于统计学的生成方法。即假设以一定的概率选择了一个主题,然后以一定的概率选择当前主题的词。最后这些词组成了我们当前的文本。所有词的统计概率分布可以从语料库获得,具体如何以“一定的概率选择”,这就是各种具体的主题模型算法的任务了。当然还有一些不是基于统计的方法,比如我们下面讲到的LSI。
潜在语义索引(LSI),又称为潜在语义分析(LSA),主要是在解决两类问题,一类是一词多义,如“bank”一词,可以指银行,也可以指河岸;另一类是一义多词,即同义词问题,如“car”和“automobile”具有相同的含义,如果在检索的过程中,在计算这两类问题的相似性时,依靠余弦相似性的方法将不能很好的处理这样的问题。所以提出了潜在语义索引的方法,利用SVD降维的方法将词项和文本映射到一个新的空间。
LSI 优缺点
LSI是最早出现的主题模型,算法也比较简单,通过一次奇异值分解就可以得到主题模型,同时解决词义问题,但是由于存在的问题现在已不再使用:
- SVD分解耗时,尤其是对于文本,词和文本都是非常大的,面对高维的奇异值分解是很困难的
- 主题值的选区对结果的影响很大,很难选择一个合适的K值
- LSI得到的不是一个概率模型,缺乏统计基础,结果很难直观的解释
对于问题1,可以使用非负矩阵分解来解决分解速度慢的问题;对于问题2,大多数都是凭借经验,比较新的层次狄利克雷过程(HDP)可以自动选择K值;对于问题3,使用隐狄利克雷(LDA)或pLSA这类基于概率分布的主题模型代替基于矩阵分解的主题模型**(推荐使用HDP和LDA,除非数据规模较小,像快速粗粒度的找出一些主题分布的关系)**
LSA
LSA使用SVD进行单词-文档分解
这就是一个矩阵,不一样的是,这里的一行表示一个词在哪些title(文档)中出现了(一行就是一维feature),一列表示一个title中有哪些词,(这个矩阵是那种一行是一个sample的形式的一种转置,这个会使得我们的左右奇异向量的意义产生变化,但是不会影响我们计算的过程)。比如说T1这个title中就有guide、investing、market、stock四个词,各出现了一次,我们将这个矩阵进行SVD,得到下面的矩阵:
左 奇 异 矩 阵 , 中 间 奇 异 矩 阵 , 右 奇 异 矩 阵 左奇异矩阵,中间奇异矩阵,右奇异矩阵 左奇异矩阵,中间奇异矩阵,右奇异矩阵
分解的左奇异矩阵,中间奇异矩阵,右奇异矩阵,左奇异矩阵表示词的一些特性,右奇异矩阵表示文档的一些特性,中间奇异矩阵表示左奇异矩阵的一行与右奇异矩阵的一列的重要程度,数字越大越重要
还可以看到,分解得到的左奇异矩阵虽然不是线性的,但是也有着这一定的关系,比如,book的0.15对应的出现了2次,dads的0.24对应的出现了2次,rich的0.36对应的出现了3次;再看右奇异矩阵,每一行代表每一篇文档中的单词的个数的近似
再看看,如果我们的吧左右奇异矩阵都取最后两维,投影到一个平面上,可以得到:
红色的表示单词,蓝色的表示文档,这样我们可以对单词和文档进行聚类,这样可以提取出文档中的近义词,这样当用户检索文档的时候可以使用语义级别(近义词集合)去检索了,而不是用词级别。这样可以减少我们的检索,存储量,压缩的文档和PCA有或者异曲同工之妙,而且通过近义词减少这是传统的索引无法做到的。
'''
这里的demo是使用的LDA,穿插了一小部分的LSI,使用的gensim的LDA模型 word-bow => tfidf => lsi(lda) => sim
'''
from gensim.models import LdaModel
from gensim.corpora import Dictionary
from gensim import corpora, models, similarities
train = []
with open('data/lda_logfile/stopwords.txt',encoding='utf8') as f:
stopwords = [i.strip() for i in f]
documents = ["Shipment of gold damaged in a fire",
"Delivery of silver arrived in a silver truck",
"Shipment of gold arrived in a truck"]
texts = [[word for word in document.lower().split()] for document in documents]
# 词袋
print('=====bag-of-words=====')
dictionary = corpora.Dictionary(texts) # 这里的输入的是句子切割后的列表形式存在的句子
print(dictionary)
print(dictionary.token2id) # {'a': 0, 'damaged': 1, 'fire': 2, 'gold': 3, 'in': 4, 'of': 5, 'shipment': 6, 'arrived': 7, 'delivery': 8, 'silver': 9, 'truck': 10}
print('====doc2bow====')
corpus = [dictionary.doc2bow(text) for text in texts]
print(corpus) # 这就是真正的词袋:[[(字典对应的value,本句出现次数) * count(setence's words)]] # [[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1)], [(0, 1), (4, 1), (5, 1), (7, 1), (8, 1), (9, 2), (10, 1)], [(0, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), (10, 1)]]
# 计算TF-IDF模型
print('====TF-IDF模型====')
tfidf = models.TfidfModel(corpus,)
corpus_tfidf = tfidf[corpus]
for doc in corpus_tfidf:
print(doc) # [(1, 0.6633689723434505), (2, 0.6633689723434505), (3, 0.2448297500958463), (6, 0.2448297500958463)][(7, 0.16073253746956623), (8, 0.4355066251613605), (9, 0.871013250322721), (10, 0.16073253746956623)]
# 发现有一些token丢失了,查看:
print(tfidf.dfs) #{0: 3, 1: 1, 2: 1, 3: 2, 4: 3, 5: 3, 6: 2, 7: 2, 8: 1, 9: 1, 10: 2}
print(tfidf.idfs) # {0: 0.0, 1: 1.5849625007211563, 2: 1.5849625007211563, 3: 0.5849625007211562, 4: 0.0, 5: 0.0, 6: 0.5849625007211562, 7: 0.5849625007211562, 8: 1.5849625007211563, 9: 1.5849625007211563, 10: 0.5849625007211562}
# 0, 4, 5这3个单词的文档数(df)为3,而文档总数也为3,所以idf被计算为0了,看来gensim没有对分子加1,做一个平滑。不过我们同时也发现这3个单词分别为a, in, of这样的介词,完全可以在预处理时作为停用词干掉,这也从另一个方面说明TF-IDF的有效性。
# 训练LSI模型 # lsi的物理意义不太好解释,不过最核心的意义是将训练文档向量组成的矩阵SVD分解,并做了一个秩为2的近似SVD分解
print('====LSI模型====')
lsi = models.LsiModel(corpus_tfidf,id2word=dictionary,num_topics=2)
print(lsi.print_topics(2)) # 这个做man解释 打印主题
corpus_lsi = lsi[corpus_tfidf]
for doc in corpus_lsi:
print(doc)
print('====LDA模型====')
lda = models.LdaModel(corpus_tfidf,id2word=dictionary,num_topics=3)
print(lda.print_topics(3))
corpus_lda = lda[corpus_tfidf]
for doc in corpus_lda:
print(doc)
# 计算(余弦)相似度
index = similarities.MatrixSimilarity(lsi[corpus])
############ 例子 #############
print('====例子====')
query = 'gold silver truck'
query_bow = dictionary.doc2bow(query.lower().split())
print(query_bow)
query_lsi = lsi[query_bow] # 判断属于哪类主题
print(query_lsi)
sims = index[query_lsi] # 得到的和三类的相似度
print(list(enumerate(sims))) # 得到的和三类的相似度