vlambda博客
学习文章列表

以隐含狄利克雷分布,实现对亚马逊评论的主题建模

作者:Enes Gokce原文链接[1]

编译:IvyLee

原文标题:对亚马逊评论使用 NLP 进行主题建模——隐含狄利克雷分布的 Python 实现(Topic Modeling with NLP on Amazon Reviews Application of Latent Dirichlet Allocation (LDA) with Python)


主题建模(Topic modeling)是另一种流行的文本分析技术,最终目标是在评论中找到中心思想,并发现隐藏的主题。语料库中的文档都包含一个或多个主题。

主题建模有很多技术,在这里,我将介绍 隐含狄利克雷分布(Latent Dirichlet Allocation 或 LDA) 的结果,这是一种无监督分类方法。当文本量很大而又不知道从哪里开始时,可以应用主题建模,然后查看出现的组别。LDA 是专门为文本数据设计的。

要使用主题建模技术,你需要提供:

1.文档-术语矩阵(document-term matrix)2.你想要算法提取的主题数

在主题建模的过程中,我会创建不同的模型进行比较,最后选择最有意义的主题模型。

如何使用隐狄利克雷分布(LDA)

本文不会介绍 LDA 的数学基础知识,主要是讨论的是如何解释 LDA 主题模型的结果。

LDA 主题建模过程会创建很多不同的主题组,作为研究人员,我们需要决定输出中的组数,但是我们并不知道最好的组数是多少。因此,我们需要尝试不同的组数,检验并比较主题模型,确定哪个主题模型更有意义、最有意义,在模型中具有最明显的区别,然后在所有主题组中选择最有意义的组(模型)。

必须指出的是,LDA 的性质是主观的。在选择最有意义的主题组时,不同的人可能会得出不同的结论。我们寻找的是最合理的主题组,不同背景、不同领域专业知识的人可能会做出不同的选择。

LDA 是一种无监督聚类方法。提到无监督聚类方法,就不得不提一下 K-Means 聚类——最著名的无监督聚类方法之一。K-Means 聚类在很多情况下都非常实用且有用,已应用于文本挖掘多年。与每个单词只能属于一个集群(硬聚类)的 K-Means 聚类相反,LDA 允许“模糊”成员资格(软聚类)。软聚类允许集群重叠,而在硬聚类中,集群是互斥的。也就是说,在 LDA 中,一个单词可以属于多个组,而在 K-Means 聚类中则不可能。在 LDA 中,这种折衷使查找单词之间的相似性更加容易。然而,这种软聚类会导致很难划分组别,因为同一个单词可以出现在不同的组中。后续分析中我们会体会到这种影响。

应用了主题建模技术之后,研究人员的工作就是解释结果,查看每个主题中单词的混合是否有意义。如果没有意义,则可以尝试修改主题的数量、文档-术语矩阵中的术语、模型参数,甚至尝试使用其他模型。

数据准备

本文使用的数据简介: 数据下载自 Kaggle,由 Stanford Network Analysis Project 上传。原始数据来自于 J. McAuley 和 J. Leskovec 所做的研究“从业余爱好者到鉴赏家:通过网络评论对用户专业知识的发展进行建模[2]”(2013)。该数据集由亚马逊网站的美食评论构成,包括 1999 年至 2012 年的全部 568,454 条评论。每条评论包括产品和用户信息,评分以及纯文本评价。

在本次研究中,我将重点关注对亚马逊的“好评”。我对“好评”的定义是:具有 4 星或 5 星(最高 5 星)的评论。换句话说,如果某条评论为 4 星或 5 星,就属于本次研究中的“好评”;1 2 3 星则被标记为“差评”。

数据准备至关重要,如果出现失误就无法实施主题建模。不过在本次研究中,我们不会深入探讨如何准备数据,因为这不是本次研究的重点。但是,你需要做好心理准备,这一步如果出现问题就会浪费不少时间。如果你在处理自己的数据集时,需要对应调整本文提供的代码,希望一切顺利。


先检查数据的列行数:

df.shape

(392384, 19) —— 数据集有 392,384 条评论。

对于很多家用计算机(以及 Google Colab)来说,这样的数据量都是难以处理的。因此,我会只使用其中 10,000 条评论。非常遗憾,如果我们无法使用超级计算机,就无法使用所有评论数据。

# 从数据集中获取前 10000 条好评df_good_reviews= df.loc[df.Good_reviews ==1][0:10000]

下一步是计数向量化器(Count Vectorizer):

# 创建文档-术语矩阵from sklearn.feature_extraction import textfrom sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()data_cv = cv.fit_transform(df_good_reviews.Text)data_cv.shape

Pickle 存储数据,为后续创建文档-术语矩阵做准备。

# Pickle itimport picklepickle.dump(cv, open("cv_stop.pkl", "wb"))data_stop.to_pickle("dtm_stop.pkl") #saving the pickled data
data = pd.read_pickle('dtm_stop.pkl')data

Pickle 数据后,我们得到一个宽格式的词典:

以隐含狄利克雷分布,实现对亚马逊评论的主题建模

现在到了创建术语-文档矩阵的时候了,该术语-文档矩阵数据将用于生成主题模型,所以对此次研究至关重要。

# 所需的输入之一就是术语-文档矩阵tdm = data.transpose()tdm.head()

以隐含狄利克雷分布,实现对亚马逊评论的主题建模

# 通过从 df -> 稀疏矩阵 -> Gensim 语料库,将术语-文档矩阵转换为新的 Gensim 格式
sparse_counts = scipy.sparse.csr_matrix(tdm)corpus = matutils.Sparse2Corpus(sparse_counts)
# Gensim 还需要一个字典,表示所有术语及其在术语-文档矩阵中的对应位置
cv = pickle.load(open("cv_stop.pkl", "rb"))id2word = dict((v, k) for k, v in cv.vocabulary_.items())

现在开始创建主题模型!


使用 LDA 构建主题模型

构建主题模型的方法有很多。根据特定条件筛选文本,可以获得不同的主题组。在本次研究中,我们会使用以下内容创建主题模型:

1.所有文本2.仅包含文本中的所有名词3.文本中的所有名词和形容词

主题建模 - 尝试 1(所有文本)

首先,先尝试使用所有评论数据,在此过程中不进行文本筛选。

lda = models.LdaModel(corpus=corpus, id2word=id2word, num_topics=2, passes=10)lda.print_topics()

发现了两组主题:

[(0, ‘0.020”coffee” + 0.011”like” + 0.010”tea” + 0.010”flavor” + 0.009”good” + 0.009”taste” + 0.007”one” + 0.007”great” + 0.006”use” + 0.006”cup”’),

(1, ‘0.010”great” + 0.009”like” + 0.009”good” + 0.007”love” + 0.007”food” + 0.007”one” + 0.007”product” + 0.005”taste” + 0.005”get” + 0.005”amazon”’)]

如何处理这一结果? 此时,我们可以检查这两个主题组,确定它们是否是具有差异性的组。我们不是预期一个组中的所有单词都必须相关,而是查看主题组的整体趋势。具体到以上两组,很难看到明显的差异。这是我的个人看法,如果你认为它们确实是两个不同的组,并且能够证明其合理性,也可以放心使用这一结果。

注意,之前提到过 LDA 是一种软聚类模型。在这里,我们看到“like”一词包含在两组中,这是正常现象,因为在软聚类模型中,一个单词可以同时出现在不同的组。

不需要强迫自己努力使上述模型有意义,我们可以继续,创建更多的主题模型。

与创建 2 组主题模型的操作类似,创建 3 组和 4 组。

# LDA for num_topics = 3lda = models.LdaModel(corpus=corpus, id2word=id2word, num_topics=3, passes=10)lda.print_topics()
# LDA for num_topics = 4lda = models.LdaModel(corpus=corpus, id2word=id2word, num_topics=4, passes=10)lda.print_topics()

所有结果可在表 1(下表)中看到。此时,我们应该检查对比,尝试找到最有意义的组。通过查看表 1,可以说“两组主题的模型”是最有意义的,第一组与饮料有关,第二组与反应有关。当然,你完全可以得出与我不同的结论!现在,这些结果先保留在这里,不会再进一步调整,因为我们会再创建 6 个主题模型。得到所有结果后,我可以更仔细地检查输出。在生成进一步的主题模型之后,我们会重新考虑表 1。

以隐含狄利克雷分布,实现对亚马逊评论的主题建模

主题建模 - 尝试 2(仅名词): 在此步骤中,通过 LDA 方法仅使用名词创建主题。同样,我们的目的是在评论中找到隐藏的模式。现在,只需要使用不同的条件进行筛选。

与上一步类似,我们将运行带有 2、3、4 个主题组的 LDA。

import nltkfrom nltk.corpus import stopwordsnltk.download('punkt')nltk.download('averaged_perceptron_tagger')import stringfrom nltk import word_tokenize, pos_tag

创建一个从文本字符串中提取名词的函数:

from nltk import word_tokenize, pos_tag
def nouns(text): '''给定一个文本字符串,对其进行分词,只提取出其中的名词''' is_noun = lambda pos: pos[:2] == 'NN' tokenized = word_tokenize(text) all_nouns = [word for (word, pos) in pos_tag(tokenized) if is_noun(pos)] return ' '.join(all_nouns)
# 如果此代码不能运行,可能是由于页面格式导致的缩进错误。

将定义好的 nouns 函数应用于评论文本数据:

data_nouns = pd.DataFrame(df_good_reviews.Text.apply(nouns))data_nouns

以隐含狄利克雷分布,实现对亚马逊评论的主题建模

仅使用名词创建一个新的文档-术语矩阵:

from sklearn.feature_extraction import textfrom sklearn.feature_extraction.text import CountVectorizer
# 重新添加其他停用词,因为我们正在重新创建文档-术语矩阵
add_stop_words = ['like', 'im', 'know', 'just', 'dont', 'thats', 'right', 'people','youre', 'got', 'gonna', 'time', 'think', 'yeah', 'said']
stop_words = text.ENGLISH_STOP_WORDS.union(add_stop_words)
# 重新创建仅包含名词的文档-术语矩阵cvn = CountVectorizer(stop_words=stop_words)data_cvn = cvn.fit_transform(data_nouns.Text)
data_dtmn = pd.DataFrame(data_cvn.toarray(), columns=cvn.get_feature_names())
data_dtmn.index = data_nouns.indexdata_dtmn

创建 Gensim 语料库:

corpusn=matutils.Sparse2Corpus(scipy.sparse.csr_matrix(data_dtmn.transpose()))
# 创建词汇字典
id2wordn = dict((v, k) for k, v in cvn.vocabulary_.items())

⚫ 从 2 组主题开始

ldan = models.LdaModel(corpus=corpusn, num_topics=2, id2word=id2wordn, passes=10)
ldan.print_topics()

⚫ 3 组主题的 LDA

ldan = models.LdaModel(corpus=corpusn, num_topics=3, id2word=id2wordn, passes=10)
ldan.print_topics()

⚫ 4 组主题的 LDA

ldan = models.LdaModel(corpus=corpusn, num_topics=4, id2word=id2wordn, passes=10)
ldan.print_topics()

表 2 是仅使用名词尝试的 LDA 主题模型输出。现在,我们需要再次检查主题,尝试找到具有不同主题组的模型。同样,仍然不需要花费很多时间,因为我们还会生成更多的主题模型。

以隐含狄利克雷分布,实现对亚马逊评论的主题建模

对我来说,其中具有三个小组的主题模型很有意义。它包含以下组:

1.宠物食品2.饼干和零食3.饮品

当然,得出不同的结论也是完全正常的。

主题建模-尝试 3(名词和形容词): 在此步骤中,仅使用名词和形容词通过 LDA 方法创建主题模型。

准备数据:

from nltk.corpus import stopwordsimport nltknltk.download('punkt')nltk.download('averaged_perceptron_tagger')import stringfrom nltk import word_tokenize, pos_tag

自定义获取名词或形容词的函数:

def nouns_adj(text): '''给定一个文本字符串,对其进行分词,获取其中的名词和形容词'''
is_noun_adj = lambda pos: pos[:2] == 'NN' or pos[:2] == 'JJ' tokenized = word_tokenize(text) nouns_adj = [word for (word, pos) in pos_tag(tokenized) if is_noun_adj(pos)] return ' '.join(nouns_adj)
# 如果此代码不能运行,可能是由于页面格式导致的缩进错误。

对评论数据使用 nouns_adj 进行筛选:

data_nouns_adj = pd.DataFrame(df_good_reviews.Text.apply(nouns_adj))data_nouns_adj

以隐含狄利克雷分布,实现对亚马逊评论的主题建模

如你所见,这里仅包含名词和形容词。现在,我们要求 LDA 使用筛选后的这一个数据集版本创建主题模型。

为 LDA 准备数据:

# 仅使用名词和形容词创建一个新的文档-术语矩阵,同时使用 max_df 删除常见单词cvna = CountVectorizer(max_df=.8) # max_df 用于删除出现频率过高的数据值,也称为“特定语料库的停用词(corpus-specific stop words)”# 例如,max_df=.8 表示会忽略文档中出现频率大于 80% 的词。
data_cvna = cvna.fit_transform(data_nouns_adj.Text)data_dtmna = pd.DataFrame(data_cvna.toarray(), columns=cvna.get_feature_names())
data_dtmna.index = data_nouns_adj.indexdata_dtmna

创建 Gensim 语料库:

corpusna=matutils.Sparse2Corpus(scipy.sparse.csr_matrix(data_dtmna.transpose()))
# 创建词汇字典id2wordna = dict((v, k) for k, v in cvna.vocabulary_.items())

现在可以运行 LDA 了。

⚫ 从 2 组主题开始

ldana = models.LdaModel(corpus=corpusna, num_topics=2, id2word=id2wordna, passes=10)ldana.print_topics()

⚫ 尝试 3 组主题

ldana = models.LdaModel(corpus=corpusna, num_topics=3, id2word=id2wordna, passes=10)ldana.print_topics()

⚫ 尝试 4 组主题

ldana = models.LdaModel(corpus=corpusna, num_topics=4, id2word=id2wordna, passes=10)ldana.print_topics()

表 3 是仅使用名词和形容词的 LDA 主题模型输出。我们再次检查主题,确定是否具有有意义的主题组。

以隐含狄利克雷分布,实现对亚马逊评论的主题建模

评估结果

现在到了最后阶段,表 1、表 2 和表 3 的结果必须一起评估。我们一共创建了 9 个主题模型,问一下自己:“哪一组更有意义?”现在,需要集中精力仔细检查各个主题。哪一组有意义?如果都没有意义,我们需要返回到数据清理步骤,更改参数或使用其他筛选条件和模型,这是一个递归过程。

在 9 个主题模型中,对我来说最有意义的是:仅名词具有 3 个主题组的模型(表 4)。我在这里看到三个不同的类别:(1)宠物食品、(2)饼干和零食、(3)饮品。同样,找到其他更有意义的主题模型是完全可以的。

以隐含狄利克雷分布,实现对亚马逊评论的主题建模

记住一点,该数据集仅包含食品评论。因此,看到这些组都与食品有关是很正常的。

找出最有意义的主题模型后,通过更多迭代,获得微调模型。3 组主题(仅名词)对我来说最有意义,所以我将微调这一主题模型,把迭代数从 10 调整到 80。

# Fine-tuned LDA with topics = 3ldan = models.LdaModel(corpus=corpusn, num_topics=3, id2word=id2wordn, passes=80)
ldan.print_topics()

在上表中,我们看到与表 4 类似的组,只是顺序不同。通过以上使用 LDA 方法进行主题分析的所有步骤,可以得出结论,亚马逊评论的好评可分为三个主要主题:(1)饮品、(2)宠物食品、(3)饼干和零食。

最后需要注意的是,要了解哪个主题组更有意义,我们可能需要对应领域的专业知识。作为一名研究人员,有责任证明我们对主题组的选择是正确的。只要理由充足,我们可以将任何主题组确定为最终主题模型。

感谢阅读!分析愉快!

特别感谢我的朋友 Bibor Szabo[3] 在撰写本文时提供的宝贵建议。


提示: 此次研究所使用的 Python 代码,可以在我的 GitHub 文件夹[4] 中查找。另外,这次的主题模型研究是另一个更大项目的一部分。如果你对前面的步骤感兴趣,可以查看我之前的文章:数据清洗[5] 和 情绪分析[6]

References

[1] 原文链接: https://towardsdatascience.com/topic-modeling-with-nlp-on-amazon-reviews-an-application-of-latent-dirichlet-allocation-lda-ae42a4c8b369
[2] 从业余爱好者到鉴赏家:通过网络评论对用户专业知识的发展进行建模: http://i.stanford.edu/~julian/pdfs/www13.pdf
[3] Bibor Szabo: https://www.linkedin.com/in/bibor-szabo/
[4] GitHub 文件夹: https://colab.research.google.com/drive/1JUL32S3r1rShFY4BLK54w5GSwOKqJUe1?usp=sharing
[5] 数据清洗: https://towardsdatascience.com/beginners-guide-for-data-cleaning-and-feature-extraction-in-nlp-756f311d8083
[6] 情绪分析: https://towardsdatascience.com/sentiment-analysis-on-amazon-reviews-45cd169447ac


关于biendata

旨在以人工智能竞赛为基础打造全方位的数据科学爱好者社区。成立至今与多家国内外顶级学术机构、科技企业合作,赛题领域涵盖机器视觉、自然语言处理、知识图谱、推荐系统等机器学习领域;吸引了 10 万名机器学习领域的学者、学生和工程师参加。