使用Gensim进行主题建模(一)

5,737 阅读11分钟

主题建模是一种从大量文本中提取隐藏主题的技术。Latent Dirichlet Allocation(LDA)是一种流行的主题建模算法,在Python的Gensim包中具有出色的实现。然而,挑战在于如何提取清晰,隔离和有意义的高质量主题。这在很大程度上取决于文本预处理的质量以及找到最佳主题数量的策略。本教程试图解决这两个问题。

内容

1.简介
2.先决条件 - 下载nltk停用词和spacy模型
3.导入包
4. LDA做什么?
5.准备停用词
6.导入新闻组数据
7.删除电子邮件和换行符
8.标记单词和清理文本
9.创建Bigram和Trigram模型
10.删除停用词,制作双字母组合词和词形变换
11.创建所需的词典和语料库主题建模
12.构建主题模型
13.查看LDA模型中的主题
14.计算模型复杂度和一致性得分
15.可视化主题 - 关键字
16.构建LDA Mallet模型
17.如何找到LDA的最佳主题数?
18.在每个句子中找到主要主题
19.为每个主题找到最具代表性的文件
20.跨文件分配主题

1.简介

自然语言处理的主要应用之一是从大量文本中自动提取人们正在讨论的主题。大量文本的一些示例可以是来自社交媒体的馈送,酒店的客户评论,电影等,用户反馈,新闻报道,客户投诉的电子邮件等。

了解人们在谈论什么并理解他们的问题和意见对于企业,管理者和政治活动来说非常有价值。并且很难人工阅读如此大数据量的文本并识别主题。

因此,需要一种自动算法,该算法可以读取文本文档并自动输出所讨论的主题。

在本教程中,我们将采用'20新闻组'数据集的真实示例,并使用LDA提取自然讨论的主题。

我将使用Gensim包中的Latent Dirichlet Allocation(LDA)以及Mallet的实现(通过Gensim)。Mallet有效地实现了LDA。众所周知,它可以更快地运行并提供更好的主题隔离。

我们还将提取每个主题的数量和百分比贡献,以了解主题的重要性。

让我们开始!

使用Gensim在Python中进行主题建模。摄影:Jeremy Bishop。

2.先决条件 - 下载nltk停用词和spacy模型

我们需要来自NLTK的stopwords和spacy的en模型进行文本预处理。稍后,我们将使用spacy模型进行词形还原。

词形还原只不过是将一个词转换为词根。例如:“机器”这个词的引理是“机器”。同样,'走路' - >'走路','老鼠' - >'老鼠'等等。

# Run in python console
import nltk; nltk.download('stopwords')

# Run in terminal or command prompt
python3 -m spacy download en

3.导入包

在本教程中使用的核心包regensimspacypyLDAvis。除此之外,我们还将使用matplotlibnumpy以及pandas数据处

理和可视化。让我们导入它们。

import re
import numpy as np
import pandas as pd
from pprint import pprint

# Gensim
import gensim
import gensim.corpora as corpora
from gensim.utils import simple_preprocess
from gensim.models import CoherenceModel

# spacy for lemmatization
import spacy

# Plotting tools
import pyLDAvis
import pyLDAvis.gensim  # don't skip this
import matplotlib.pyplot as plt
%matplotlib inline

# Enable logging for gensim - optional
import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.ERROR)

import warnings
warnings.filterwarnings("ignore",category=DeprecationWarning)

4. LDA做什么?

LDA的主题建模方法是将每个文档视为一定比例的主题集合。并且每个主题作为关键字的集合,再次以一定比例构成主题。

一旦您为算法提供了主题数量,它就会重新排列文档中的主题分布和主题内的关键字分布,以获得主题 - 关键字分布的良好组合。

当我说主题时,它实际上是什么以及如何表示?

一个主题只不过是典型代表的主导关键词集合。只需查看关键字,您就可以确定主题的内容。

以下是获得良好隔离主题的关键因素:

  1. 文本处理的质量。

  2. 文本谈论的各种主题。

  3. 主题建模算法的选择。

  4. 提供给算法的主题数量。

  5. 算法参数调整。

5.准备关键词

我们已经下载了停用词。让我们导入它们并使其可用stop_words

# NLTK Stop words
from nltk.corpus import stopwords
stop_words = stopwords.words('english')
stop_words.extend(['from', 'subject', 're', 'edu', 'use'])

6.导入新闻组数据

我们将使用20-Newsgroups数据集进行此练习。此版本的数据集包含来自20个不同主题的大约11k个新闻组帖子。这可以作为newsgroups.json使用。

这是使用导入的pandas.read_json,结果数据集有3列,如图所示。

# Import Dataset
df = pd.read_json('https://raw.githubusercontent.com/selva86/datasets/master/newsgroups.json')
print(df.target_names.unique())
df.head()
['rec.autos' 'comp.sys.mac.hardware' 'rec.motorcycles' 'misc.forsale'
 'comp.os.ms-windows.misc' 'alt.atheism' 'comp.graphics'
 'rec.sport.baseball' 'rec.sport.hockey' 'sci.electronics' 'sci.space'
 'talk.politics.misc' 'sci.med' 'talk.politics.mideast'
 'soc.religion.christian' 'comp.windows.x' 'comp.sys.ibm.pc.hardware'
 'talk.politics.guns' 'talk.religion.misc' 'sci.crypt']

20个新闻组数据集

7.删除电子邮件和换行符

正如您所看到的那样,有许多电子邮件,换行符和额外空间非常分散注意力。让我们使用正则表达式摆脱它们。

# Convert to list
data = df.content.values.tolist()

# Remove Emails
data = [re.sub('\S*@\S*\s?', '', sent) for sent in data]

# Remove new line characters
data = [re.sub('\s+', ' ', sent) for sent in data]

# Remove distracting single quotes
data = [re.sub("\'", "", sent) for sent in data]

pprint(data[:1])
['From: (wheres my thing) Subject: WHAT car is this!? Nntp-Posting-Host: '
 'rac3.wam.umd.edu Organization: University of Maryland, College Park Lines: '
 '15 I was wondering if anyone out there could enlighten me on this car I saw '
 'the other day. It was a 2-door sports car, looked to be from the late 60s/ '
 'early 70s. It was called a Bricklin. The doors were really small. In '
 'addition, the front bumper was separate from the rest of the body. This is '
 'all I know.  (..truncated..)]

删除电子邮件和额外空格后,文本仍然看起来很乱。它尚未准备好让LDA消费。您需要通过标记化将每个句子分解为单词列表,同时清除过程中的所有杂乱文本。

Gensim对此很有帮助simple_preprocess

8.标记单词和清理文本

让我们将每个句子标记为一个单词列表,完全删除标点符号和不必要的字符。

Gensim对此很有帮助simple_preprocess()。此外,我已经设置deacc=True删除标点符号。

def sent_to_words(sentences):
    for sentence in sentences:
        yield(gensim.utils.simple_preprocess(str(sentence), deacc=True))  # deacc=True removes punctuations

data_words = list(sent_to_words(data))

print(data_words[:1])
[['from', 'wheres', 'my', 'thing', 'subject', 'what', 'car', 'is', 'this', 'nntp', 'posting', 'host', 'rac', 'wam', 'umd', 'edu', 'organization', 'university', 'of', 'maryland', 'college', 'park', 'lines', 'was', 'wondering', 'if', 'anyone', 'out', 'there', 'could', 'enlighten', 'me', 'on', 'this', 'car', 'saw', 'the', 'other', 'day', (..truncated..))]]

9.创建Bigram和Trigram模型

Bigrams是文档中经常出现的两个词。Trigrams是经常出现3个单词。

我们示例中的一些示例是:'front_bumper','oil_leak','maryland_college_park'等。

Gensim的Phrases模型可以构建和实现bigrams,trigrams,quadgrams等。两个重要的论点Phrasesmin_countthreshold。这些参数的值越高,将单词组合成双字母组的难度就越大。

# Build the bigram and trigram models
bigram = gensim.models.Phrases(data_words, min_count=5, threshold=100) # higher threshold fewer phrases.
trigram = gensim.models.Phrases(bigram[data_words], threshold=100)  

# Faster way to get a sentence clubbed as a trigram/bigram
bigram_mod = gensim.models.phrases.Phraser(bigram)
trigram_mod = gensim.models.phrases.Phraser(trigram)

# See trigram example
print(trigram_mod[bigram_mod[data_words[0]]])
['from', 'wheres', 'my', 'thing', 'subject', 'what', 'car', 'is', 'this', 'nntp_posting_host', 'rac_wam_umd_edu', 'organization', 'university', 'of', 'maryland_college_park', 'lines', 'was', 'wondering', 'if', 'anyone', 'out', 'there', 'could', 'enlighten', 'me', 'on', 'this', 'car', 'saw', 'the', 'other', 'day', 'it', 'was', 'door', 'sports', 'car', 'looked', 'to', 'be', 'from', 'the', 'late', 'early', 'it', 'was', 'called', 'bricklin', 'the', 'doors', 'were', 'really', 'small', 'in', 'addition', 'the', 'front_bumper' (..truncated..)]

10.删除停用词,制作Bigrams和Lemmatize

双词模型准备就绪。让我们定义函数来删除停用词,制作双字母组合和词形还原并按顺序调用它们。

# Define functions for stopwords, bigrams, trigrams and lemmatization
def remove_stopwords(texts):
    return [[word for word in simple_preprocess(str(doc)) if word not in stop_words] for doc in texts]

def make_bigrams(texts):
    return [bigram_mod[doc] for doc in texts]

def make_trigrams(texts):
    return [trigram_mod[bigram_mod[doc]] for doc in texts]

def lemmatization(texts, allowed_postags=['NOUN', 'ADJ', 'VERB', 'ADV']):
    """https://spacy.io/api/annotation"""
    texts_out = []
    for sent in texts:
        doc = nlp(" ".join(sent)) 
        texts_out.append([token.lemma_ for token in doc if token.pos_ in allowed_postags])
    return texts_out

我们按顺序调用这些函数。

# Remove Stop Words
data_words_nostops = remove_stopwords(data_words)

# Form Bigrams
data_words_bigrams = make_bigrams(data_words_nostops)

# Initialize spacy 'en' model, keeping only tagger component (for efficiency)
# python3 -m spacy download en
nlp = spacy.load('en', disable=['parser', 'ner'])

# Do lemmatization keeping only noun, adj, vb, adv
data_lemmatized = lemmatization(data_words_bigrams, allowed_postags=['NOUN', 'ADJ', 'VERB', 'ADV'])

print(data_lemmatized[:1])
[['where', 's', 'thing', 'car', 'nntp_post', 'host', 'rac_wam', 'umd', 'organization', 'university', 'maryland_college', 'park', 'line', 'wonder', 'anyone', 'could', 'enlighten', 'car', 'see', 'day', 'door', 'sport', 'car', 'look', 'late', 'early', 'call', 'bricklin', 'door', 'really', 'small', 'addition', 'front_bumper', 'separate', 'rest', 'body', 'know', 'anyone', 'tellme', 'model', 'name', 'engine', 'spec', 'year', 'production', 'car', 'make', 'history', 'whatev', 'info', 'funky', 'look', 'car', 'mail', 'thank', 'bring', 'neighborhood', 'lerxst']]

11.创建主题建模所需的词典和语料库

LDA主题模型的两个主要输入是字典(id2word)和语料库。让我们创造它们。

# Create Dictionary
id2word = corpora.Dictionary(data_lemmatized)

# Create Corpus
texts = data_lemmatized

# Term Document Frequency
corpus = [id2word.doc2bow(text) for text in texts]

# View
print(corpus[:1])
[[(0, 1), (1, 2), (2, 1), (3, 1), (4, 1), (5, 1), (6, 5), (7, 1), (8, 1), (9, 2), (10, 1), (11, 1), (12, 1), (13, 1), (14, 1), (15, 1), (16, 1), (17, 1), (18, 1), (19, 1), (20, 1), (21, 1), (22, 2), (23, 1), (24, 1), (25, 1), (26, 1), (27, 1), (28, 1), (29, 1), (30, 1), (31, 1), (32, 1), (33, 1), (34, 1), (35, 1), (36, 1), (37, 1), (38, 1), (39, 1), (40, 1), (41, 1), (42, 1), (43, 1), (44, 1), (45, 1), (46, 1), (47, 1), (48, 1), (49, 1), (50, 1)]]

Gensim为文档中的每个单词创建一个唯一的ID。上面显示的产生的语料库是(word_id,word_frequency)的映射。

例如,上面的(0,1)暗示,单词id 0在第一个文档中出现一次。同样,单词id 1出现两次,依此类推。

这用作LDA模型的输入。

如果要查看给定id对应的单词,请将id作为键传递给字典。

id2word[0]
'addition'

或者,您可以看到语料库本身的人类可读形式。

# Human readable format of corpus (term-frequency)
[[(id2word[id], freq) for id, freq in cp] for cp in corpus[:1]]
[[('addition', 1),  ('anyone', 2),  ('body', 1),  ('bricklin', 1),  ('bring', 1),  ('call', 1),  ('car', 5),  ('could', 1),  ('day', 1),  ('door', 2),  ('early', 1),  ('engine', 1),  ('enlighten', 1),  ('front_bumper', 1),  ('maryland_college', 1),  (..truncated..)]]

好吧,让我们重新回到正轨,进行下一步:构建主题模型。

12.构建主题模型

我们拥有培训LDA模型所需的一切。除语料库和字典外,您还需要提供主题数量。

除此之外,alpha还有eta影响主题稀疏性的超参数。根据Gensim文档,默认为1.0 / num_topics之前。

chunksize是每个训练块中使用的文档数。update_every确定应更新模型参数的频率,以及passes培训通过的总数。

# Build LDA model
lda_model = gensim.models.ldamodel.LdaModel(corpus=corpus,
                                           id2word=id2word,
                                           num_topics=20, 
                                           random_state=100,
                                           update_every=1,
                                           chunksize=100,
                                           passes=10,
                                           alpha='auto',
                                           per_word_topics=True)

13.查看LDA模型中的主题

上述LDA模型由20个不同的主题构建,其中每个主题是关键字的组合,并且每个关键字对主题贡献一定的权重。

您可以看到每个主题的关键字以及每个关键字的权重(重要性),lda_model.print_topics()如下所示。

# Print the Keyword in the 10 topics
pprint(lda_model.print_topics())
doc_lda = lda_model[corpus]
[(0,
  '0.016*"car" + 0.014*"power" + 0.010*"light" + 0.009*"drive" + 0.007*"mount" '
  '+ 0.007*"controller" + 0.007*"cool" + 0.007*"engine" + 0.007*"back" + '
  '0.006*"turn"'),
 (1,
  '0.072*"line" + 0.066*"organization" + 0.037*"write" + 0.032*"article" + '
  '0.028*"university" + 0.027*"nntp_post" + 0.026*"host" + 0.016*"reply" + '
  '0.014*"get" + 0.013*"thank"'),
 (2,
  '0.017*"patient" + 0.011*"study" + 0.010*"slave" + 0.009*"wing" + '
  '0.009*"disease" + 0.008*"food" + 0.008*"eat" + 0.008*"pain" + '
  '0.007*"treatment" + 0.007*"syndrome"'),
 (3,
  '0.013*"key" + 0.009*"use" + 0.009*"may" + 0.007*"public" + 0.007*"system" + '
  '0.007*"order" + 0.007*"government" + 0.006*"state" + 0.006*"provide" + '
  '0.006*"law"'),
 (4,
  '0.568*"ax" + 0.007*"rlk" + 0.005*"tufts_university" + 0.004*"ei" + '
  '0.004*"m" + 0.004*"vesa" + 0.004*"differential" + 0.004*"chz" + 0.004*"lk" '
  '+ 0.003*"weekly"'),
 (5,
  '0.029*"player" + 0.015*"master" + 0.015*"steven" + 0.009*"tor" + '
  '0.009*"van" + 0.008*"king" + 0.008*"scripture" + 0.007*"cal" + '
  '0.007*"helmet" + 0.007*"det"'),
 (6,
  '0.028*"system" + 0.020*"problem" + 0.019*"run" + 0.018*"use" + 0.016*"work" '
  '+ 0.015*"do" + 0.013*"window" + 0.013*"driver" + 0.013*"bit" + 0.012*"set"'),
 (7,
  '0.017*"israel" + 0.011*"israeli" + 0.010*"war" + 0.010*"armenian" + '
  '0.008*"kill" + 0.008*"soldier" + 0.008*"attack" + 0.008*"government" + '
  '0.007*"lebanese" + 0.007*"greek"'),
 (8,
  '0.018*"money" + 0.018*"year" + 0.016*"pay" + 0.012*"car" + 0.010*"drug" + '
  '0.010*"president" + 0.009*"rate" + 0.008*"face" + 0.007*"license" + '
  '0.007*"american"'),
 (9,
  '0.028*"god" + 0.020*"evidence" + 0.018*"christian" + 0.012*"believe" + '
  '0.012*"reason" + 0.011*"faith" + 0.009*"exist" + 0.008*"bible" + '
  '0.008*"religion" + 0.007*"claim"'),
 (10,
  '0.030*"physical" + 0.028*"science" + 0.012*"direct" + 0.012*"st" + '
  '0.012*"scientific" + 0.009*"waste" + 0.009*"jeff" + 0.008*"cub" + '
  '0.008*"brown" + 0.008*"msg"'),
 (11,
  '0.016*"wire" + 0.011*"keyboard" + 0.011*"md" + 0.009*"pm" + 0.008*"air" + '
  '0.008*"input" + 0.008*"fbi" + 0.007*"listen" + 0.007*"tube" + '
  '0.007*"koresh"'),
 (12,
  '0.016*"motif" + 0.014*"serial_number" + 0.013*"son" + 0.013*"father" + '
  '0.011*"choose" + 0.009*"server" + 0.009*"event" + 0.009*"value" + '
  '0.007*"collin" + 0.007*"prediction"'),
 (13,
  '0.098*"_" + 0.043*"max" + 0.015*"dn" + 0.011*"cx" + 0.009*"eeg" + '
  '0.008*"gateway" + 0.008*"c" + 0.005*"mu" + 0.005*"mr" + 0.005*"eg"'),
 (14,
  '0.024*"book" + 0.009*"april" + 0.007*"group" + 0.007*"page" + '
  '0.007*"new_york" + 0.007*"iran" + 0.006*"united_state" + 0.006*"author" + '
  '0.006*"include" + 0.006*"club"'),
 (15,
  '0.020*"would" + 0.017*"say" + 0.016*"people" + 0.016*"think" + 0.014*"make" '
  '+ 0.014*"go" + 0.013*"know" + 0.012*"see" + 0.011*"time" + 0.011*"get"'),
 (16,
  '0.026*"file" + 0.017*"program" + 0.012*"window" + 0.012*"version" + '
  '0.011*"entry" + 0.011*"software" + 0.011*"image" + 0.011*"color" + '
  '0.010*"source" + 0.010*"available"'),
 (17,
  '0.027*"game" + 0.027*"team" + 0.020*"year" + 0.017*"play" + 0.016*"win" + '
  '0.010*"good" + 0.009*"season" + 0.008*"fan" + 0.007*"run" + 0.007*"score"'),
 (18,
  '0.036*"drive" + 0.024*"card" + 0.020*"mac" + 0.017*"sale" + 0.014*"cpu" + '
  '0.010*"price" + 0.010*"disk" + 0.010*"board" + 0.010*"pin" + 0.010*"chip"'),
 (19,
  '0.030*"space" + 0.010*"sphere" + 0.010*"earth" + 0.009*"item" + '
  '0.008*"launch" + 0.007*"moon" + 0.007*"mission" + 0.007*"nasa" + '
  '0.007*"orbit" + 0.006*"research"')]怎么解释这个?

主题0表示为_0.016

“car” + 0.014
“power” + 0.010
“light” + 0.009
“drive” + 0.007
“mount” + 0.007
“controller” + 0.007
“cool” + 0.007
“engine” + 0.007
“back” + ‘0.006
“turn”。

这意味着贡献这个主题的前10个关键词是:'car','power','light'等等,主题0上单词'car'的权重是0.016。

权重反映了关键字对该主题的重要程度。

看看这些关键词,您能猜出这个主题是什么吗?您可以将其概括为“汽车”或“汽车”。

同样,您是否可以浏览剩余的主题关键字并判断主题是什么?


从关键字推断主题

14.计算模型复杂度和一致性分数

模型复杂度和主题一致性提供了一种方便的方法来判断给定主题模型的好坏程度。根据我的经验,特别是主题一致性得分更有帮助。

# Compute Perplexity
print('\nPerplexity: ', lda_model.log_perplexity(corpus))  # a measure of how good the model is. lower the better.

# Compute Coherence Score
coherence_model_lda = CoherenceModel(model=lda_model, texts=data_lemmatized, dictionary=id2word, coherence='c_v')
coherence_lda = coherence_model_lda.get_coherence()
print('\nCoherence Score: ', coherence_lda)
Perplexity:  -8.86067503009
Coherence Score:  0.532947587081

你有一个0.53的一致性得分。

15.可视化主题 - 关键字

现在已经构建了LDA模型,下一步是检查生成的主题和关联的关键字。没有比pyLDAvis包的交互式图表更好的工具,并且设计为与jupyter notebook一起使用。

# Visualize the topicspyLDAvis.enable_notebook()vis = pyLDAvis.gensim.prepare(lda_model, corpus, id2word)vis


PYLDAVIS输出

那么如何推断pyLDAvis的输出呢?

左侧图中的每个气泡代表一个主题。气泡越大,该主题就越普遍。

一个好的主题模型将在整个图表中分散相当大的非重叠气泡,而不是聚集在一个象限中。

具有太多主题的模型通常会有许多重叠,小尺寸的气泡聚集在图表的一个区域中。

好吧,如果将光标移动到其中一个气泡上,右侧的单词和条形将会更新。这些单词是构成所选主题的显著关键字。

我们已经成功构建了一个好的主题模型。

鉴于我们之前对文档中自然主题数量的了解,找到最佳模型非常简单。

其余部分下篇继续。。。


查看英文原文:https://www.machinelearningplus.com/nlp/topic-modeling-gensim-python/

查看更多文章:www.apexyun.com

公众号:银河系1号

联系邮箱:public@space-explore.com

(未经同意,请勿转载)