一、说明
本文是对主题建模及其相关技术的更新全面概述。在自然语言理解(NLU)任务中,有一个镜头层次结构,通过它我们可以提取含义 - 从单词到句子到段落到文档。在文档级别,理解文本的最有用方法之一是分析其主题(另请参阅 NER)。在文档集合中学习、识别和提取这些主题的过程称为主题建模。
在这篇文章中,我们将通过当今最流行的4种技术来探索主题建模:LSA,pLSA,LDA和更新的基于深度学习的lda2vec。
二、概述
所有主题模型都基于相同的基本假设:
- 每个文档都由主题的混合组成,并且
- 每个主题都由一组单词组成。
换句话说,主题模型是围绕这样一种想法构建的,即我们文档的语义实际上由一些我们没有观察到的隐藏或“潜在”变量控制。因此,主题建模的目标是揭示这些潜在的变量 - 主题 - 塑造我们的文档和语料库的含义。这篇博文的其余部分将建立对不同主题模型如何发现这些潜在主题的理解。
三、模型LSA
3.1 原理简述
潜在语义分析 (LSA) 是主题建模的基础技术之一。核心思想是获取我们拥有的内容(文档和术语)的矩阵,并将其分解为单独的文档-主题矩阵和主题-术语矩阵。
第一步是生成我们的文档术语矩阵。给定词汇表中的 m 个文档和 n 个单词,我们可以构造一个 m × n 个矩阵 A,其中每行代表一个文档,每列代表一个单词。在最简单的 LSA 版本中,每个条目可以只是第 j 个单词在第 i 个文档中出现的次数的原始计数。然而,在实践中,原始计数的效果不是特别好,因为它们没有考虑文档中每个单词的重要性。例如,“核”一词可能比“试验”一词更多地告诉我们有关给定文件的主题的信息。
因此,LSA 模型通常用 tf-idf 分数替换文档术语矩阵中的原始计数。Tf-idf,或术语频率-反文档频率,为文档 i 中的术语 j 分配权重,如下所示:
直观地说,当一个术语在文档中频繁出现但很少出现在语料库中时,它有很大的权重。“build”一词可能经常出现在文档中,但由于它在语料库的其余部分可能相当常见,因此它的tf-idf 分数不高。但是,如果“绅士化”一词经常出现在文档中,因为它在语料库的其余部分更罕见,它将具有更高的 tf-idf 分数。
一旦我们有了文档术语矩阵 A,我们就可以开始思考我们的潜在主题了。事情是这样的:A很可能非常稀疏,非常嘈杂,并且在其许多维度上都非常冗余。因此,为了找到捕获单词和文档之间关系的少数潜在主题,我们希望对 A 执行降维。
这种降维可以使用截断的 SVD 执行。SVD 或奇异值分解是线性代数中的一种技术,它将任何矩阵 M 分解为 3 个独立矩阵的乘积:M=U*S*V,其中 S 是 M 奇异值的对角矩阵。至关重要的是,截断的 SVD 通过仅选择 t 个最大奇异值并仅保留 U 和 V 的前 t 列来降低维数。在这种情况下,t 是一个超参数,我们可以选择和调整以反映我们想要查找的主题数量。
直观地说,这只认为在我们变换后的空间中保留了最重要的t个维度。
在这种情况下,U ∈ R^(m ⨉ t) 成为我们的文档主题矩阵,V ∈ R^(n ⨉ t) 成为我们的术语主题矩阵。在 U 和 V 中,列对应于我们的 t 主题之一。在 U 中,行表示以主题表示的文档向量;在 V 中,行表示以主题表示的术语向量。
使用这些文档向量和项向量,我们现在可以轻松地应用余弦相似性等度量来评估:
- 不同文档的相似性
- 不同单词的相似性
- 术语(或“查询”)和文档的相似性(当我们想要检索与搜索查询最相关的段落时,这在信息检索中很有用)。
3.2 代码
在 sklearn 中,LSA 的简单实现可能如下所示:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import TruncatedSVD
from sklearn.pipeline import Pipeline
documents = ["doc1.txt", "doc2.txt", "doc3.txt"] # raw documents to tf-idf matrix:
vectorizer = TfidfVectorizer(stop_words='english', use_idf=True, smooth_idf=True)
# SVD to reduce dimensionality:
svd_model = TruncatedSVD(n_components=100, // num dimensionsalgorithm='randomized',n_iter=10)
# pipeline of tf-idf + SVD, fit to and applied to documents:
svd_transformer = Pipeline([('tfidf', vectorizer), ('svd', svd_model)])
svd_matrix = svd_transformer.fit_transform(documents)# svd_matrix can later be used to compare documents, compare words, or compare queries with documents
LSA使用起来既快速又高效,但它确实有一些主要缺点:
- 缺乏可解释的嵌入(我们不知道主题是什么,组件可能是任意的正/负)
- 需要非常大的文档和词汇集才能获得准确的结果
- 表示效率较低
四、模型PLSA
4.1 模型概述
pLSA,或概率潜在语义分析,使用概率方法而不是SVD来解决问题。核心思想是找到一个具有潜在主题的概率模型,该模型可以生成我们在文档术语矩阵中观察到的数据。特别是,我们想要一个模型 P(D,W),对于任何文档 d 和单词 w,P(d,w) 对应于文档术语矩阵中的该条目。
回想一下主题模型的基本假设:每个文档由主题的混合组成,每个主题由一组单词组成。pLSA为这些假设增加了概率自旋:
- 给定文档 d,主题 z 存在于该文档中,概率为 P(z|d)
- 给定一个主题 z,单词 w 从 z 中抽取,概率为 P(w|z)
从形式上讲,同时看到给定文档和单词的共同概率为:
直观地说,这个等式的右侧告诉我们看到某个文档的可能性有多大,然后根据该文档的主题分布,在该文档中找到某个单词的可能性有多大。
在本例中,P(D), P(Z|D) 和 P(W|Z) 是我们模型的参数。P(D)可以直接从我们的语料库中确定。P(Z|D) 和 P(W|Z) 建模为多项式分布,可以使用期望最大化算法 (EM) 进行训练。在不对算法进行全面数学处理的情况下,EM 是一种为模型找到最可能的参数估计的方法,该方法依赖于未观察到的潜在变量(在我们的例子中是主题)。
有趣的是,P(D,W) 可以使用一组不同的 3 个参数等效地参数化:
我们可以通过将模型视为生成过程来理解这种等价性。在我们的第一个参数化中,我们从带有 P(d) 的文档开始,然后使用 P(z|d) 生成主题,然后使用 P(w|z) 生成单词。在这个参数化中,我们从 P(z) 的主题开始,然后使用 P(d|z) 独立生成文档,使用 P(w|z) 独立生成单词。
https://www.slideshare.net/NYCPredictiveAnalytics/introduction-to-probabilistic-latent-semantic-analysis
这种新的参数化之所以如此有趣,是因为我们可以看到我们的pLSA模型与LSA模型之间的直接平行:
其中主题 P(Z) 的概率对应于奇异主题概率的对角矩阵,给定主题 P(D|Z) 的文档的概率对应于我们的文档主题矩阵 U,而给定主题 P(W|Z) 对应于我们的术语-主题矩阵 V。
那么这告诉我们什么呢?尽管它看起来完全不同,并且以非常不同的方式处理问题,但pLSA实际上只是在LSA之上添加了对主题和单词的概率处理。这是一个更加灵活的模型,但仍存在一些问题。特别:
- 因为我们没有参数来建模 P(D),所以我们不知道如何为新文档分配概率
- pLSA的参数数量随着我们拥有的文档数量线性增长,因此容易过度拟合
我们不会查看 pLSA 的任何代码,因为它很少单独使用。一般来说,当人们正在寻找超出LSA提供的基线性能的主题模型时,他们会转向LDA。LDA 是最常见的主题模型类型,它扩展了 PLSA 以解决这些问题。
五、LDA
5.1 LDA原理概述
LDA 代表 潜在狄利克雷分配。LDA是pLSA的贝叶斯版本。特别是,它使用狄利克雷先验来表示文档主题和单词主题分布,从而有助于更好地泛化。
我不打算深入讨论狄利克雷分布,因为这里和这里有非常好的直观解释。然而,作为简要概述,我们可以将狄利克雷视为“分布之上的分布”。从本质上讲,它回答了这个问题:“给定这种类型的分布,我可能会看到哪些实际概率分布?
考虑比较主题混合物的概率分布的非常相关的示例。假设我们正在查看的语料库包含来自 3 个非常不同的主题领域的文档。如果我们想对此进行建模,我们想要的分布类型将是一个非常重的权重一个特定主题,而根本不给其余主题太多权重。如果我们有 3 个主题,那么我们可能会看到的一些特定概率分布是:
- 混合物 X:90% 主题 A、5% 主题 B、5% 主题 C
- 混合物Y:5%主题A,90%主题B,5%主题C
- 混合物 Z:5% 主题 A、5% 主题 B、90% 主题 C
如果我们从这个狄利克雷分布中得出一个随机概率分布,由单个主题的大权重参数化,我们可能会得到一个非常类似于混合物 X、混合物 Y 或混合物 Z 的分布。我们不太可能对 33% 的主题 A、33% 的主题 B 和 33% 的主题 C 的分布进行抽样。
这本质上就是狄利克雷分布所提供的:一种对特定类型的概率分布进行采样的方法。回想一下pLSA的模型:
在pLSA中,我们对一个文档进行采样,然后是一个基于该文档的主题,然后是一个基于该主题的单词。以下是 LDA 的模型:
从狄利克雷分布 Dir(α),我们绘制一个随机样本,表示特定文档的主题分布或主题混合。此主题分布为 θ。从 θ 中,我们根据分布选择一个特定的主题 Z。
接下来,从另一个狄利克雷分布 Dir(β),我们选择一个表示主题 Z 的单词分布的随机样本。这个词分布是φ。从φ中,我们选择w这个词。
从形式上讲,从文档生成每个单词的过程如下(请注意,此算法使用 c 而不是 z 来表示主题):
https://cs.stanford.edu/~ppasupat/a9online/1140.html
LDA 通常比 pLSA 效果更好,因为它可以轻松地推广到新文档。在 pLSA 中,文档概率是数据集中的固定点。如果我们没有看到文档,我们就没有该数据点。在LDA中,数据集用作文档主题分布的狄利克雷分布的训练数据。如果我们还没有看到文档,我们可以轻松地从狄利克雷分布中采样并从那里继续前进。
5.2 法典
LDA很容易成为最流行(通常是最有效的)主题建模技术。它在 gensim 中可用,易于使用:
from gensim.corpora.Dictionary import load_from_text, doc2bow
from gensim.corpora import MmCorpus
from gensim.models.ldamodel import LdaModel
document = "This is some document..."
# load id->word mapping (the dictionary)
id2word = load_from_text('wiki_en_wordids.txt')
# load corpus iterator
mm = MmCorpus('wiki_en_tfidf.mm')
# extract 100 LDA topics, updating once every 10,000
lda = LdaModel(corpus=mm, id2word=id2word, num_topics=100, update_every=1, chunksize=10000, passes=1)
# use LDA model: transform new doc to bag-of-words, then apply lda
doc_bow = doc2bow(document.split())
doc_lda = lda[doc_bow]
# doc_lda is vector of length num_topics representing weighted presence of each topic in the doc
使用 LDA,我们可以从文档语料库中提取人类可解释的主题,其中每个主题都以与它们最密切相关的单词为特征。例如,主题 2 可以用“石油、天然气、钻井、管道、梯形石、能源”等术语来描述。此外,给定一个新文档,我们可以得到一个表示其主题混合的向量,例如 5% 主题 1、70% 主题 2、10% 主题 3 等。这些载体通常对下游应用非常有用。
六、深度学习中的LDA:lda2vec
那么,这些主题模型在更复杂的自然语言处理问题中考虑在哪里呢?
在这篇文章的开头,我们谈到了能够从各个层面的文本中提取意义是多么重要——单词、段落、文档。在文档级别,我们现在知道如何将文本表示为主题的混合。在单词级别,我们通常使用类似word2vec的东西来获取向量表示。lda2vec 是 word2vec 和 LDA 的扩展,它共同学习单词、文档和主题向量。
这是它的工作原理。
LDA2VEC专门建立在word2vec的skip-gram模型之上,以生成词向量。如果你不熟悉skip-gram和word2vec,你可以在这里阅读它,但本质上它是一个神经网络,通过尝试使用输入词来预测周围的上下文词来学习词嵌入。
使用 lda2vec,我们不是直接使用词向量来预测上下文词,而是利用上下文向量进行预测。此上下文向量创建为另外两个向量的总和:单词向量和文档向量。
词向量是由前面讨论的相同的skip-gram word2vec模型生成的。文档矢量更有趣。它实际上是另外两个组件的加权组合:
- 文档权重向量,表示文档中每个主题的“权重”(稍后转换为百分比)
- 主题矩阵,表示每个主题及其相应的向量嵌入
文档向量和单词向量一起为文档中的每个单词生成“上下文”向量。lda2vec 的强大之处在于,它不仅可以学习单词的词嵌入(和上下文向量嵌入),还可以同时学习主题表示和文档表示。
https://multithreaded.stitchfix.com/blog/2016/05/27/lda2vec
有关该模型的更详细概述,请查看Chris Moody的原始博客文章(Moody在2年创建了lda2016vec)。代码可以在穆迪的github存储库和这个Jupyter Notebook示例中找到。
七、结论
很多时候,我们将主题模型视为“正常工作”的黑盒算法。幸运的是,与许多神经网络不同,主题模型实际上非常可解释,并且诊断、调整和评估更加简单。希望这篇博文能够解释您需要的基本数学、动机和直觉,并为您提供足够的高级代码来入门。请在评论中留下您的想法,祝您黑客愉快!