NLP自然语言处理实战(三):词频背后的语义--4.隐性狄利克雷分布(LDiA)

对大多数主题建模,语义搜索或基于内容的推荐引擎来说,LSA应该是我们的首选方法。它的数学机理直观、有效,它会产生一个线性变换,,可以应用于新来的自然语言文本而不需要训练过程,并几乎不会损失精确率。但是,在某些情况下,LDiA可以给出稍好的结果。
LDiA假设每篇文档都由某个任意数量的主题混合(线性组合)而成,该数量是在开始训练LDiA模型时选择的。LDiA还假设每个主题都可以用词的分布(词项频率)来表示。文档中每个主题的概率或权重,以及某个词被分配到一个主题的概率,都假定一开始满足狄利克雷概率分布(如果还记得统计学,则应该知道这个概率称为先验)。这就是该算法得名的来历。

1.隐性狄利克雷分布(LDiA)

1.1LDiA思想

设想有一台机器,该机器只有两个选项来开始生成特定文档的词混合结果。设想文档生成器会以某种概率分布来随机选择这些词,就像选择骰子的遍数然后将骰子的组合情况加在一起创建一个D&D人物卡。我们的文档“人物卡”只需要骰子的两轮投掷过程。但是骰子本身很大,而且有好几个,关于如何组合它们来为不同的值生成所需要的概率,有十分复杂的规则。我们希望词的数量和主题的数量有特定的概率分布,这样它们就可以匹配待分析的真实文档中的词和主题的分布。
骰子的两轮投掷过程分布代表:

  1. 生成文档的词的数量(泊松分布)
  2. 文档中混合的主题的数量(狄利克雷分布)

有了上面两个数值后,就会遇到比较难的部分,也就是要为文档选择词。设想的词袋生成器会在这些主题上迭代,并随机选择适合该主题的词,直到达到文档应该包含的词数量(见步骤1)为止。确定这些词对应主题的概率(每个主题的词的适宜度)是比较困难的。但是一旦确定,“机器人”就会从一个词项-主题概率矩阵中查找每个主题的词的概率。

因此,这台机器只需要一个泊松分布的参数来告诉它文档的平均长度应该有多长,以及两个另外的参数来定义设置主题数的狄利克雷分布。然后,文档生成算法需要文档喜欢使用的所有词和主题组成的词项-主题矩阵,并且,它还需要喜欢讨论的一个主题混合。

下面我们将文档生成(编写)问题转回到最初的问题,即从现有文档中估算主题和词。我们需要为前两个步骤测算和计算关于词和主题的那些参数。然后需要从一组文档中计算出词项-主题矩阵。这就是LDiA所做的事情。

对于步骤1,可以计算出语料库中文档的所有词袋中的平均词(或n-gram)数量,就像下面这样:

from nlpia.data.loaders import get_data
import pandas as pd
from nltk.tokenize.casual import casual_tokenize
sms = get_data('sms-spam')
index = ['sms{}{}'.format(i, "!"*j) for (i, j)in zip(range(len(sms)), sms.spam)]  # 我们向短消息的索引号后面添加一个感叹号,以使它们更容易被发现
sms.index = index
total_corpus_len= 0
for document_text in sms.text:
    total_corpus_len += len(casual_tokenize(document_text))
mean_document_len = total_corpus_len / len(sms)
print(total_corpus_len)
print(len(sms))
print(round(mean_document_len, 2))

Out[1]:103260
Out[2]:4837
Out[3]:21.35

设定LDiA模型所需要的第二个参数即主题的数量更加棘手。在一组特定的文档中,只有在为这些主题分配了词之后,才能直接得到主题的数量。就像KNN,k均值以及其他聚类算法一样,我们必须提前设定k的值。我们可以猜测主题的数量,然后检查这是否适用于这组文档。一旦设定好LDiA要寻找的主题数量,它就会找到要放入每个主题中的词的混合结果,从而优化其目标函数。

我们可以通过调整这个“超参数”(k,即主题的数量)来对其进行优化,直到它适合我们的应用为止。如果能够度量表示文档含义的LDiA语言模型的质量,就可以自动化上述优化过程。可以用于此优化的一个代价函数(cost function)是,LDiA模型在某些分类或回归问题(如情绪分析、文档关键词标注或主题分析)中的表现如何(好或差)。我们只需要一些带标签的文档来测试主题模型或分类器。


1.2基于LDiA主题模型的短消息语义分析

LDiA生成的主题对人类来说更容易理解和解释。这是因为经常一起出现的词被分配给相同的主题,而人类的期望也是如此。LSA(PCA)试图将原本分散的东西分散开来,而LDiA试图将原本接近的东西接近在一起。

下面我们来看看,对于一个包含数千条短消息的数据集(按照是否垃圾来标记)。首先计算TF-IDF向量,然后为每个短消息(文档)计算一些主题向量。和以前一样,我们假设只使用16个主题(成分)来对垃圾消息进行分类,保持主题(维度)的数量较低有助于减少过拟合的可能性。
LDiA使用原始词袋词频向量,而不是归一化的TF-IDF向量。这里有一个简单的方法在scikit-learn中计算词袋向量:

from sklearn.feature_extraction.text import CountVectorizer
np.random.seed(42)
counter = CountVectorizer(tokenizer=casual_tokenize)
bow_docs = pd.DataFrame(counter.fit_transform(raw_documents=sms.text).toarray(), index=index)
column_nums, terms = zip(*sorted(zip(counter.vocabulary_.values(), counter.vocabulary_.keys())))  # key:word value:num
bow_docs.columns = terms
print(bow_docs)

在这里插入图片描述
我们再次检查一下,看看这里的词频是否对标记为“sms0”的第一条短消息有意义:

print(sms.loc['sms0'].text)
print(bow_docs.loc['sms0'][bow_docs.loc['sms0'] > 0].head())

在这里插入图片描述
下面给出了如何使用LDiA为短消息语料库创建主题向量的过程:

from sklearn.decomposition import LatentDirichletAllocation as LDiA
ldia = LDiA(n_components=16, learning_method='batch')
ldia = ldia.fit(bow_docs)  # LDiA要比PCA或SVD花费更长的时间,特别是在语料库包含大量主题和大量词的情况下更是如此
print(ldia.components_.shape)

Out[1]:(16, 9232)
至此,上述模型已经将9232个词分配给16个主题。下面来看看开通的几个词,我们了解一下它们是如何分配到16个主题中的。记住,大家的词和主题将不同于这里的例子。LDiA是一种随机算法,它依赖随机数生成器做出一些统计决策来为主题分配词。因此,大家自己的主题词权重将不同于上面给出的结果,但它们应该大小类似。

columns = ['topic{}'.format(i) for i in range(16)]
components = pd.DataFrame(ldia.components_.T, index=terms, columns=columns)
print(components.round(2).head(3))

在这里插入图片描述
可以看出,感叹号(!)被分配到大多数主题中,但它其实是topic3中一个特别重要的部分,在该主题中引号(")几乎不起作用。或许topic3关注情感的强度或强调,并不太在意数值或引用。我们来看看:

print(components['topic3'].sort_values(ascending=False)[:10])

在这里插入图片描述
该主题的前十个词项似乎是在要求某人做某事的强调指令中可能使用的词类型。我们可以看到,即使这样粗略浏览一下,也可以对主题的词分配进行合理化解释或推理。
在拟合LDA分类器之前,需要为所有文档(短消息)计算出LDiA主题向量。下面我们看看,这些向量与SVD及PCA为相同文档生成的主题向量有什么不同:

ldia16_topic_vectors = ldia.transform(bow_docs)
ldia16_topic_vectors = pd.DataFrame(ldia16_topic_vectors,index=index, columns=columns)
print(ldia16_topic_vectors.round(2).head(6))

在这里插入图片描述
我们可以看到,上述主题之间分隔的更加清晰。在为消息分配主题时,会出现很多0.在基于NLP流水线结果做出业务决策是,这是使LDiA主题更容易解释的做法之一。


1.3LDiA+LDA=垃圾消息过滤器

下面我们看看这些LDiA主题在预测(如消息的垃圾性)时的有效性。我们将再次使用LDiA主题向量来训练LDA模型:

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(ldia16_topic_vectors, sms.spam, test_size=0.5, random_state=271828)
lda = LDA(n_components=1)
lda = lda.fit(X_train, y_train)
sms['ldia16_spam'] = lda.predict(ldia16_topic_vectors)
print(round(float(lda.score(X_test, y_test)), 2))

Out[1]:0.94
ldia16_topic_vectors矩阵的行列式接近于0,所以很可能会得到“变量是共线的”这类警告。这种情况可能发生在小型语料库上使用LDiA的场景,因为这时的主题向量中有很多0,并且一些消息可以被重新生成为其他消息主题的线性组合。另一种可能的场景是语料库中有一些具有相似(或相同)主题混合的短消息。

共线警告可能发生的一种情况是,如果文本包含一些2-gram或3-gram,其中组成它们的词只同时出现在这些2-gram或3-gram中。因此,最终的LDiA模型必须在这些相等的词项频率之间任意分配权重。大家能在短消息中找到导致共线性(零行列式)的词吗?大家寻找的这种词,当它出现时,另一个词(它的配对)总是在相同的消息中。

我们可以使用Python而不是手工进行搜索。首先,我们可能只想在语料库中寻找任何相同的词袋向量。这些向量可能出现在不完全相同的短消息中,如“Hi there Bob!”或"Bob, Hi there",因为它们有相同的出现频率。我们可以遍历所有词袋对,以寻找相同的向量。这些向量肯定会在LDiA或LSA中引发共线警告。

我们看看这里的LDiA模型与基于TF-IDF向量的高维模型相比结果如何:

from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(tokenizer=casual_tokenize)
tfidf_docs = tfidf.fit_transform(raw_documents=sms.text).toarray()
tfidf_docs = tfidf_docs - tfidf_docs.mean(axis=0)
X_train, X_test, y_train, y_test = train_test_split(tfidf_docs, sms.spam.values, test_size=0.5, random_state=271828)
lda = LDA(n_components=1)
lda = lda.fit(X_train, y_train)
print(round(float(lda.score(X_train, y_train)), 3))
print(round(float(lda.score(X_test, y_test)), 3))

Out[1]:1.0
Out[2]:0.748
在训练集上基于TF-IDF的模型的精确率是完美的!但是,当使用低维主题向量而不是TF-IDF向量训练时,测试集上的精确率要更高。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值