前言
最近在重刷李航老师的《统计机器学习方法》尝试将其与NLP结合,通过具体的NLP应用场景,强化对书中公式的理解,最终形成「统计机器学习方法 for NLP」的系列。这篇将介绍潜在语义分析LSA(绝对给你一次讲明白)并基于LSA完成一个主题模型提取的任务。
潜在语义分析是什么
潜在语义分析(Latent Semantic Analysis, LSA)是一种无监督的机器学习方来,通过分析文本内容来获取文本「隐藏主题」,也就是文章的「表示」,一旦能获取文本的「表示」就可以进行文本之间的相似度的计算,进而实现文本聚类等应用。
单词向量空间模型
在统计机器学习里面获取一个文章的「表示」最基础的方法就是单词向量空间模型。基本的想法是用一个向量来表示文章的语义,向量的每一个维度表示词表中的一个单词,这个维度的数值就表示该单词在文本中出现的频次或者权重(例如tf-idf)。这样通过计算两个向量的相似度(例如余弦相似度)就可以来获得两个文本之间的相似度。这种模型非常简单而且计算效率很高,至今在信息检索等场景广泛应用。
![a7acfa1642641a64d54a756230e9bab1.jpeg](https://i-blog.csdnimg.cn/blog_migrate/967861f92ee0f1dcc5d43da2f96625a2.jpeg)
但也会存在两个问题:(1) 无法解决一词多义或者多词一义的问题,因为是最简单的「字面匹配」。例如“很开心”和“好快乐” 这两句其实是一个意思,但是没有一个字是一样的,按照上面的方法这两句话的语义相似度就是0 (2) 存储开销较大,因为词表的大小一般是非常大的(例如几万维),所以存一个N*V的矩阵会是很大的开销,这里N表示文章个数,V表示词表大小。(其实现在来看这点不算什么问题)
正是因为最基础的「单词向量空间模型」存在上述的问题,所以潜在语义分析LSA就被提出了。
话题向量空间模型
我们认为一个文本一般包含多个话题,两个文本语义的相似可以等价于两个文本话题的相似。这里的话题就可以用一组相似的单词构成,比如{开心,快乐,高兴}等。所以我们可以构建一个话题向量来对一个文本进行表示,向量的每一个维度表示一个话题,这个维度的数值就表示文本在这个话题上的权重,并且一般话题的个数是远小于词表的个数,存储空间大幅降低。所以通过这种方式就可以解决「单词向量空间模型」遇到了两个问题。
![9b719a1f7bcd22c6525fd86b6edbdf2a.jpeg](https://i-blog.csdnimg.cn/blog_migrate/b1bcb8d409e247fb2c3420e58ac2c9ef.jpeg)
如上图所示,LSA就是通过将「单词-文本矩阵」(单词向量空间)转化成 「单词-话题矩阵」和 「话题-文本矩阵」(话题向量空间模型)乘积的形式,从而挖掘出文章中潜在的语义。
训练阶段:首先基于单词向量空间模型从训练文本中构建出「单词-文本」矩阵,然后通过矩阵的奇异值分解对「单词-文本矩阵」进行分解,将分解结果的左矩阵作为话题的表示,右矩阵作为文本的表示(在话题维度下)。
预测阶段:给定一个文本,先获取单词向量空间(1*m),然后与空间表示(m*k)相乘,就获得了话题空间向量(1*k),作为这个文本的表示。
基于LSA的主题模型
![a04a1de82041f9e12071c084e4574115.jpeg](https://i-blog.csdnimg.cn/blog_migrate/615cd5c64305edaa2fbdf41334bbcdac.jpeg)
主题模型(Topic Model)是一种无监督的机器学习方法(例如本文介绍的LSA),来获取一个文档/文章所表示的主题是什么。这里的「主题」不一定是我们常见的真实「主题」,例如体育,历史,娱乐等;也可以表示这篇文章一组最佳的关键词,例如奥运会,游泳,夺冠等;甚至是这篇文章的一个向量表示(人类不可读)。
通过主题模型可以实现文章的聚类。例如新闻网站就可以对同一个新闻事件的不同文章进行自动的聚合,或者从一系列的文章中自动挖掘出几个大的类目。
下面我们通过简单的例子来进行实际的应用。假设我们有下面5个句子构成的文档集,并希望挖掘出2个主题。
0 He is a good dog.
1 The dog is too lazy.
2 That is a brown cat.
3 The cat is very active.
4 I have brown cat and dog.
首先我们对句子进行预处理,移除停用词,得到下面清洗后的结果
0 good dog
1 dog lazy
2 brown cat
3 cat active
4 brown cat dog
接下来我们用sklearn的TfidfVectorizer来构建词表和「单词-文本」矩阵
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(stop_words='english', smooth_idf=True)
X = vectorizer.fit_transform(df['clean_documents'])
我们看一下构建的词表和原始的「单词-文本矩阵」
print(vectorizer.get_feature_names())
"""['active', 'brown', 'cat', 'dog', 'good', 'lazy']"""
print(X.to_array())
"""
array([[0. , 0. , 0. , 0.55645052, 0.83088075, 0. ],
[0. , 0. , 0. , 0.55645052, 0. , 0.83088075],
[0. , 0.76944707, 0.63871058, 0. , 0. , 0. ],
[0.83088075, 0. , 0.55645052, 0. , 0. , 0. ],
[0. , 0.64846263, 0.53828256, 0.53828256, 0. , 0. ]])
"""
下面继续进行SVD分解,这里我们假设有2个主题
from sklearn.decomposition import TruncatedSVD
# SVD represent documents and terms in vectors
svd_model = TruncatedSVD(n_components=2, algorithm='randomized', n_iter=100)
lsa = svd_model.fit_transform(X)
我们将每个主题的表示先打印出来
dictionary = vectorizer.get_feature_names()
encoding_matrix = pd.DataFrame(svd_model.components_, index = ["topic_1","topic_2"], columns = (dictionary)).T
![f3f2f7430b1f4a22d5a819e588f0669b.jpeg](https://i-blog.csdnimg.cn/blog_migrate/264b4327c59aa583a05087aa1a5b3b62.jpeg)
可以看到第一个主题主要关于猫 ,第二个主题主要关于狗 。
接着我们将每个文本对应的话题向量打印出来
pd.options.display.float_format = '{:,.16f}'.format
topic_encoded_df = pd.DataFrame(lsa, columns = ["topic_1", "topic_2"])
topic_encoded_df["documents"] = df['clean_documents']
display(topic_encoded_df[["documents", "topic_1", "topic_2"]])
![0a5255d1f79ce6fa883470b2d4f35e9b.jpeg](https://i-blog.csdnimg.cn/blog_migrate/852940f2060ca076b0652a237b6894c4.jpeg)
可以看到文本0,1提到了狗所以被分到了主题1;文本2,3提到了猫所以被分到了主题2;文本4中同时提到了狗和猫但是主题1中还包括“brown”棕色这个概念,所以文本4也被分到了主题1。