GENSIM官方文档(4.0.0beta最新版)-LDA模型
原文链接
概述
这一章节介绍Gensim的LDA模型,并演示其在NIPS语料库上的用法。
本教程的目的是演示如何训练和调整LDA模型。
在本教程中,我们将:
- 加载输入数据。
- 预处理该数据。
- 将文档转换成单词袋向量。
- 训练LDA模型。
本教程不会:
- 解释潜在的狄利克雷分配方式
- 说明LDA模型如何执行推理
- 教您如何调参
如果您不熟悉LDA模型或如何在Gensim中使用它,我(Olavur Mortensen)建议您在继续学习本教程之前先进行阅读下面材料,以对LDA模型有足够的了解。
- Introduction to Latent Dirichlet Allocation
- Gensim tutorial: Topics and Transformations
- Gensim的LDA模型官方文档: gensim.models.LdaModel
我还奉劝你在将模型应用于数据时考虑每个步骤,而不仅仅是盲目地应用我的解决方案。不同的步骤取决于您的数据以及模型的目标。
数据集
我在本教程中使用了NIPS论文的数据集,但是如果您只是为了学习LDA而关注本教程,那么我鼓励您考虑选择一个熟悉的主题的论文集。LDA模型的效果仅从输出上看没那么直观,所以需要您充分了解数据集的主题。
NIPS(Neural Information Processing Systems,神经信息处理系统)是一个机器学习会议,因此该主题应该非常适合本教程的大多数目标受众。您可以从Sam Roweis的网站下载原始数据 。下面的代码也将为您完成此操作。
注意!语料库包含1740个文档,不是特别长。因此请记住,本教程并非针对效率而设计,在将代码应用于大型数据集之前要格外小心。
import io
import os.path
import re
import tarfile
import smart_open
def extract_documents(url='https://cs.nyu.edu/~roweis/data/nips12raw_str602.tgz'):
fname = url.split('/')[-1]
# Download the file to local storage first.
# We can't read it on the fly because of
# https://github.com/RaRe-Technologies/smart_open/issues/331
if not os.path.isfile(fname):
with smart_open.open(url, "rb") as fin:
with smart_open.open(fname, 'wb') as fout:
while True:
buf = fin.read(io.DEFAULT_BUFFER_SIZE)
if not buf:
break
fout.write(buf)
with tarfile.open(fname, mode='r:gz') as tar:
# Ignore directory entries, as well as files like README, etc.
files = [
m for m in tar.getmembers()
if m.isfile() and re.search(r'nipstxt/nips\d+/\d+\.txt', m.name)
]
for member in sorted(files, key=lambda x: x.name):
member_bytes = tar.extractfile(member).read()
yield member_bytes.decode('utf-8', errors='replace')
docs = list(extract_documents())
至此,我们有一个1740个文档的列表,其中每个文档都是一个Unicode字符串。如果您正在考虑使用自己的语料库,那么在继续本教程的其余部分之前,需要确保它具有相同的格式(Unicode字符串列表)。
(译者注:这些内容看个热闹就行,我们想了解的是LDA的实际应用步骤,至于数据的导入以及预处理,只需关注输出格式即可)
print(len(docs))
print(docs[0][:500])
#输出
'''
1740
1
CONNECTIVITY VERSUS ENTROPY
Yaser S. Abu-Mostafa
California Institute of Technology
Pasadena, CA 91125
ABSTRACT
How does the connectivity of a neural network (number of synapses per
neuron) relate to the complexity of the problems it can handle (measured by
the entropy)? Switching theory would suggest no relation at all, since all Boolean
functions can be implemented using a circuit with very low connectivity (e.g.,
using two-input NAND gates). However, for a network that learns a pr
'''
文档预处理以及向量化
下面将依次进行以下预处理操作:
- 分词
- 数据清理(包括小写化)
- 计算词汇二元组
- 计算数据的词袋模型
首先,我们使用来自NLTK的正则表达式进行分词。由于数字和仅出现一两次的单词没有用处,因此我们将其删除。
注意!本教程使用nltk库进行预处理,您可以根据需要将其替换为其他内容。
# Tokenize the documents.
from nltk.tokenize import RegexpTokenizer
# Split the documents into tokens.
tokenizer = RegexpTokenizer(r'\w+')
for idx in range(len(docs)):
docs[idx] = docs[idx].lower() # Convert to lowercase.
docs[idx] = tokenizer.tokenize(docs[idx]) # Split into words.
# Remove numbers, but not words that contain numbers.
docs = [[token for token in doc if not token.isnumeric()] for doc in docs]
# Remove words that are only one character.
docs = [[token for token in doc if len(token) > 1] for doc in docs]
我们使用NLTK的WordNet lemmatizer。在这种情况下,词干分析器比词干分析器更可取,因为它会产生更多可读的单词。易于阅读的输出在主题建模中非常理想。
# Lemmatize the documents.
from nltk.stem.wordnet import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
docs = [[lemmatizer.lemmatize(token) for token in doc] for doc in docs]
我们下一步将在文件中找二元组。双字母组是两个相邻单词的集合。使用bigrams,我们可以在输出中获得诸如“ machine_learning”之类的短语(空格由下划线代替);没有二元组,我们只会得到“machine”和“learning”。
请注意,在下面的代码中,我们找到了二元组,然后将它们添加到原始数据中,因为我们希望保留单词“ machine”和“ learning”以及二元组“ machine_learning”。(译者注:就是进行二元分词)
注意!计算大型数据集的n元分词可能需要大量的计算和内存。
# Compute bigrams.
from gensim.models import Phrases
# Add bigrams and trigrams to docs (only ones that appear 20 times or more).
bigram = Phrases(docs, min_count=20)
for idx in range(len(docs)):
for token in bigram[docs[idx]]:
if '_' in token:
# Token is a bigram, add to document.
docs[idx].append(token)
我们根据稀有词和常见词的文档出现频率来删除它们。在下面,我们删除出现少于20个文档的单词或在50%以上文档中出现的单词。也可以考虑仅根据单词的出现频率尝试删除单词,或者将其与这种方法结合使用。
from gensim.corpora import Dictionary
dictionary = Dictionary(docs)
dictionary.filter_extremes(no_below=20, no_above=0.5)
最后,我们将文档转换为向量形式。我们只计算每个单词的频率,包括两个单词构成的词组。再看看处理后的词典大小和文档数。
# Bag-of-words representation of the documents.
corpus = [dictionary.doc2bow(doc) for doc in docs]
print('Number of unique tokens: %d' % len(dictionary))
print('Number of documents: %d' % len(corpus))
#输出
'''
Number of unique tokens: 8644
Number of documents: 1740
'''
训练LDA
现在准备训练LDA模型。我们将首先讨论如何设置一些训练参数。
首先,一个最重要的问题是:要设定几个主题?对此确实没有简单的答案,这将取决于您的数据和应用场景。我在这里使用了10个主题,因为我想拥有一些可以解释并打标签的主题,这也给了我相当不错的效果。您可能不需要解释所有主题,因此可以把主题数设高一点,例如100。
chunksize参数控制在训练算法中一次处理多少个文档。chunksize大小的增加将加快训练速度,只要你内存够大。我设置chunksize = 2000,它比文档的数量还要多,因此我可以一次性处理所有数据。Hoffman和他同事说chunksize大小可以影响模型的质量,但在此例中差异并不大。
passes参数控制我们在整个语料库上训练模型的频次,其实就是epochs。iteration的设定有点技术性,但从本质上讲,它控制着我们对每个文档重复执行特定循环的频率。重要的是将“passes”和“iteration”的数量设置得足够高。
如果你设置passes=20,你将会看到这行20次,确保在最后一次之前文档已经收敛。所以你设高点就没事了。
至于参数alpha和eta,设置他们='auto’就行了,尽管这两个参数也不好设置,但是gensim可以在模型中自动学习这两个参数,你只需要在设置时设成‘auto’就行。
# Train LDA model.
from gensim.models import LdaModel
# Set training parameters.
num_topics = 10
chunksize = 2000
passes = 20
iterations = 400
eval_every = None #不去计算模型复杂度,因为很费时
# Make a index to word dictionary.
temp = dictionary[0] # This is only to "load" the dictionary.
id2word = dictionary.id2token
model = LdaModel(
corpus=corpus,
id2word=id2word,
chunksize=chunksize,
alpha='auto',
eta='auto',
iterations=iterations,
num_topics=num_topics,
passes=passes,
eval_every=eval_every
)
我们可以计算主题一致性,并将其打印。
Gensim使用“ Umass”主题相关性度量通过如下函数实现:gensim.models.ldamodel.LdaModel.top_topics()
原理见此文:“ AKSW”主题相关性度
如果您熟悉此数据集中的文章主题,则可以看到以下主题很有意义。但是,它们并非没有缺陷。我们可以看到,某些主题之间存在实质性的重叠,而另一些主题则难以解释,而且大多数主题中都会存在一些看起来很离谱的词。
top_topics = model.top_topics(corpus) #, num_words=20)
# Average topic coherence is the sum of topic coherences of all topics, divided by the number of topics.
avg_topic_coherence = sum([t[1] for t in top_topics]) / num_topics
print('Average topic coherence: %.4f.' % avg_topic_coherence)
from pprint import pprint
pprint(top_topics)
#输出:
'''
Average topic coherence: -1.1379.
[([(0.0081748655, 'bound'),
(0.007108706, 'let'),
(0.006066193, 'theorem'),
(0.005790631, 'optimal'),
(0.0051151128, 'approximation'),
(0.004763562, 'convergence'),
(0.0043320647, 'class'),
(0.00422147, 'generalization'),
(0.0037292794, 'proof'),
(0.0036608914, 'threshold'),
(0.0034258896, 'sample'
...
'''
需要调试的东西
- filter_extremes方法中的no_above和no_below参数。
- 增加trigrams或更高阶的n-gram。
- 考虑使用保留集和交叉验证。
- 尝试不同的数据集
尝试其他数据集
(译者注:下面是我自己跑的新冠数据集结果节选)
([(0.10162235, 'china'),
(0.037809063, 'wuhan'),
(0.020990247, 'chinese'),
(0.0153133245, 'coronavirusupdates'),
(0.015247746, 'korea'),
(0.013852675, 'infection'),
(0.013832916, 'hospital'),
(0.013138459, 'south'),
(0.012854813, 'chinacoronavirus'),
(0.012447124, 'wuhancoronavirus'),
(0.01102423, 'prepare'),
(0.009864287, 'epidemic'),
(0.00982727, 'control'),
(0.009546414, 'ncov2019'),
(0.008987158, 'south_korea'),
(0.008922475, 'rate'),
(0.008764917, 'chinavirus'),
(0.008691075, 'via'),
(0.0076270592, 'already'),
(0.00709006, 'wuhanvirus')],
-4.4179077239650955),
([(0.123386666, 'case'),
(0.058519185, 'new'),
(0.057235483, 'death'),
(0.039975904, 'report'),
(0.031473, 'first'),
(0.030759191, 'number'),
(0.0291138, 'confirm'),
(0.019362163, 'total'),
(0.013847279, 'confirmed'),
(0.010727949, 'rise'),
(0.009543617, 'hubei'),
(0.009087212, 'increase'),
(0.008469053, 'cases'),
(0.007537852, 'italy'),
(0.00707604, 'province'),
(0.0068858326, 'u.s.'),
(0.0068255165, 'update'),
(0.0063575087, 'ministry'),
(0.0060308618, 'coronavirusitalia'),
(0.005972879, '__twitter_impression=true')],
-5.086231316639932),
([(0.044851582, 'mask'),
(0.030441472, 'hand'),
(0.022144383, 'face'),
(0.021630984, 'safe'),
(0.019572152, 'stock'),
(0.014211622, 'wear'),
(0.012957984, 'wash'),
(0.011640941, 'avoid'),
(0.010301863, 'coronaviruscanada'),
(0.010279973, 'social'),
(0.009920472, 'wash_hand'),
(0.009902607, 'safety'),
(0.009480398, 'auspol'),
(0.009426605, 'trade'),
(0.009395943, 'sell'),
(0.008988872, 'na'),
(0.008853211, 'price'),
(0.00875052, 'gold'),
(0.008310639, 'press'),
(0.008298634, 'milan')],
-8.587770182651068),