Gensim
是 Python
的一个三方库,旨在从文档中自动提取语义主题,以及处理原始的非结构化数字文本(纯文本
)
在Gensim
的算法,比如Word2Vec
,FastText
,潜在语义分析(LSI
,LSA
,SEE
,LSIModel
),隐含狄利克雷分布(LDA
)等,自动训练文档,检查统计共生模式发现的文件的语义结构,这些算法是无监督的,这意味着不需要人工输入。
一旦找到这些统计模式,任何纯文本文档(句子,短语,单词……)都可以在新的语义中简单的表达,并查询与其他文档的主题相似性。
从表示为字符串的文档开始:
documents = [
"Human machine interface for lab abc computer applications",
"A survey of user opinion of computer system response time",
"The EPS user interface management system",
"System and human system engineering testing of EPS",
"Relation of user perceived response time to error measurement",
"The generation of random binary unordered trees",
"The intersection graph of paths in trees",
"Graph minors IV Widths of trees and well quasi ordering",
"Graph minors A survey",
]
这是一个由九个文档组成的小型语料库,每个文档只包含一个句子。
首先,对文档进行标记,删除常用单词以及在语料库中仅出现一次的单词:
from gensim import corpora
# 指定停止词集合
stoplist = set('for a of the and to in'.split(" "))
# 判断每个单词是否在停止词集合中,如果不是,则将其加入到新闻本中
texts = [[word for word in document.lower().split() if word not in stoplist]
for document in documents]
print(stoplist, texts)
{'for', 'of', 'the', 'a', 'and', 'in', 'to'} [['human', 'machine', 'interface', 'lab', 'abc', 'computer', 'applications'], ['survey', 'user', 'opinion', 'computer', 'system', 'response', 'time'], ['eps', 'user', 'interface', 'management', 'system'], ['system', 'human', 'system', 'engineering', 'testing', 'eps'], ['relation', 'user', 'perceived', 'response', 'time', 'error', 'measurement'], ['generation', 'random', 'binary', 'unordered', 'trees'], ['intersection', 'graph', 'paths', 'trees'], ['graph', 'minors', 'iv', 'widths', 'trees', 'well', 'quasi', 'ordering'], ['graph', 'minors', 'survey']]
from collections import defaultdict
# 得到每个单词出现的次数
frequency = defaultdict(int)
for text in texts:
for token in text:
frequency[token] += 1
frequency
defaultdict(int,
{'human': 2,
'machine': 1,
'interface': 2,
'lab': 1,
'abc': 1,
'computer': 2,
'applications': 1,
'survey': 2,
'user': 3,
'opinion': 1,
'system': 4,
'response': 2,
'time': 2,
'eps': 2,
'management': 1,
'engineering': 1,
'testing': 1,
'relation': 1,
'perceived': 1,
'error': 1,
'measurement': 1,
'generation': 1,
'random': 1,
'binary': 1,
'unordered': 1,
'trees': 3,
'intersection': 1,
'graph': 3,
'paths': 1,
'minors': 2,
'iv': 1,
'widths': 1,
'well': 1,
'quasi': 1,
'ordering': 1})
# 去掉仅出现一次的单词
texts = [[token for token in text if frequency[token] > 1]
for text in texts]
print(texts)
[['human', 'interface', 'computer'],
['survey', 'user', 'computer', 'system', 'response', 'time'],
['eps', 'user', 'interface', 'system'],
['system', 'human', 'system', 'eps'],
['user', 'response', 'time'],
['trees'],
['graph', 'trees'],
['graph', 'minors', 'trees'],
['graph', 'minors', 'survey']]
接下来,调用API建立预料特征的索引字典,并将文本特征的原始表达转化为词袋模型对应的稀疏向量的表达:
import os
dictionary = corpora.Dictionary(texts)
if not os.path.exists('./tmp'):
os.mkdir('./tmp')
dictionary.save('./tmp/deerwester.dict')
print(dictionary)
Dictionary(12 unique tokens: ['computer', 'human', 'interface', 'response', 'survey']...)
这里,为语料库中出现的所有单词分配了一个唯一的整数id gensim.corpora.dictionary.Dictionary
。这会扫描文本,收集字数和相关统计数据。最后,我们看到在处理过的语料库中由12个不同的单词,这意味着每个文档将有12个数字表示(即12-D向量),要查看单词及其ID之间的映射:
print(dictionary.token2id)
{'computer': 0,
'human': 1,
'interface': 2,
'response': 3,
'survey': 4,
'system': 5,
'time': 6,
'user': 7,
'eps': 8,
'trees': 9,
'graph': 10,
'minors': 11}
将标记化文档实际转换为向量:
new_doc = "Human computer interaction"
new_vec = dictionary.doc2bow(new_doc.lower().split())
print(new_vec)
[(0, 1), (1, 1)]
因为interaction
没有出现在字典中,所以没有返回它的值。
doc2bow()
只计算每个不同单词的出现次数,将单词转化为整数单词id,并将结果作为稀疏向量返回。因此稀疏向量[(0, 1), (1, 1)]
读取:在文档**“Human computer interaction”**中,单词computer
(id 0) 和 human
(id 1) 出现一次,其他十个字典单词隐含的出现零次。
corpus = [dictionary.doc2bow(text) for text in texts]
corpora.MmCorpus.serialize('./tmp/deerwester.mm', corpus)
corpus
[[(0, 1), (1, 1), (2, 1)],
[(0, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1)],
[(2, 1), (5, 1), (7, 1), (8, 1)],
[(1, 1), (5, 2), (8, 1)],
[(3, 1), (6, 1), (7, 1)],
[(9, 1)],
[(9, 1), (10, 1)],
[(9, 1), (10, 1), (11, 1)],
[(4, 1), (10, 1), (11, 1)]]
from gensim import models, corpora, similarities
dictionary = corpora.Dictionary.load('./tmp/deerwester.dict')
corpus = corpora.MmCorpus('./tmp/deerwester.mm')
对文本向量的变换是Gensim
的核心。通过挖掘语料中隐藏的语义结构特征,我们最终可以变换出一个简洁高效的文本向量。
在Gensim
中,每一个向量变换的操作都对应着一个主题模型,例如上一小节提到的对应着词袋模型的doc2bow
变换。每一个模型又都是一个标准的Python对象。下面以TF-IDF
模型为例,介绍Gensim
模型的一般使用方法。
首先是模型对象的初始化。通常,Gensim
模型都接受一段训练语料(注意在Gensim
中,语料对应着一个稀疏向量的迭代器)作为初始化的参数。显然,越复杂的模型需要配置的参数越多。
tfidf = models.TfidfModel(corpus)
corpus
是一个返回bow
向量的迭代器。这两行代码将完成对corpus
中出现的每一个特征的IDF
值的统计工作。
接下来,我们可以调用这个模型将任意一段预料(依然是bow
向量的迭代器)转化成TFIDF
向量的迭代器,这里的bow
向量必须与训练语料的bow
向量共享同一个特征字典(即共享同一个向量空间)
doc_bow = [(0, 1), (1, 1)]
tfidf[doc_bow]
[(0, 0.7071067811865476), (1, 0.7071067811865476)]
或者将转换应用于整个语料库
corpus_tfidf = tfidf[corpus]
for doc in corpus_tfidf:
print(doc)
[(0, 0.5773502691896257), (1, 0.5773502691896257), (2, 0.5773502691896257)]
[(0, 0.44424552527467476), (3, 0.44424552527467476), (4, 0.44424552527467476), (5, 0.3244870206138555), (6, 0.44424552527467476), (7, 0.3244870206138555)]
[(2, 0.5710059809418182), (5, 0.4170757362022777), (7, 0.4170757362022777), (8, 0.5710059809418182)]
[(1, 0.49182558987264147), (5, 0.7184811607083769), (8, 0.49182558987264147)]
[(3, 0.6282580468670046), (6, 0.6282580468670046), (7, 0.45889394536615247)]
[(9, 1.0)]
[(9, 0.7071067811865475), (10, 0.7071067811865475)]
[(9, 0.5080429008916749), (10, 0.5080429008916749), (11, 0.695546419520037)]
[(4, 0.6282580468670046), (10, 0.45889394536615247), (11, 0.6282580468670046)]
Gensim
内置了多种主题模型的向量变换,包括LDA
,LSI
,RP
,HDP
等。这些模型通常以bow
向量或tfidf
向量的语料为输入,生成相应的主题向量。所有的模型都支持流式计算。
在得到每一篇文档对应的主题向量后,我们就可以计算文档之间的相似度,进而完成如文本聚类,信息检索之类的任务。在Gensim
中,也提供了这一类任务的API接口
以信息检索为例。对于一篇待检索的query
,我们的目标是从文本集合中检索出主题相似度最高的文档。
首先,我们需要将待检索的query
和文本放在同一个向量空间里表达(以LSI
向量空间为例):
lsi = models.LsiModel(corpus_tfidf, id2word=dictionary, num_topics=2)
corpus_lsi = lsi[corpus_tfidf]
用待检索的文档向量初始化一个相似度计算的对象:
index = similarities.MatrixSimilarity(lsi[corpus])
如果待检索的目标文档过多,使用
similarities.MatrixSlimilarity
类往往会带来内存不够的问题。此时,可以改用similarities.Similarity
类。二者接口基本一致。
通过潜在语义索引将我们的TF-IDF
语料库转换为潜在的2D
空间(设置了num_topics=2
)。这两个潜在的维度代表为:
lsi.print_topics(2)
[(0,
'0.703*"trees" + 0.538*"graph" + 0.402*"minors" + 0.187*"survey" + 0.061*"system" + 0.060*"time" + 0.060*"response" + 0.058*"user" + 0.049*"computer" + 0.035*"interface"'),
(1,
'-0.460*"system" + -0.373*"user" + -0.332*"eps" + -0.328*"interface" + -0.320*"response" + -0.320*"time" + -0.293*"computer" + -0.280*"human" + -0.171*"survey" + 0.161*"trees"')]
根据LSI
的说法,trees
,graph
和minors
都是相关词,且对第一个主题方向贡献最大。前五个文件与第二个主题关联性更强,而剩下四个文件与第一个主题相关:
for doc in corpus_lsi:
print(doc)
[(0, 0.06600783396090293), (1, -0.5200703306361845)]
[(0, 0.19667592859142477), (1, -0.7609563167700054)]
[(0, 0.08992639972446376), (1, -0.7241860626752504)]
[(0, 0.07585847652178108), (1, -0.6320551586003421)]
[(0, 0.10150299184980141), (1, -0.5737308483002963)]
[(0, 0.7032108939378311), (1, 0.16115180214025757)]
[(0, 0.877478767311983), (1, 0.16758906864659354)]
[(0, 0.9098624686818575), (1, 0.1408655362871895)]
[(0, 0.6165825350569278), (1, -0.05392907566389435)]
1, -0.6320551586003421)]
[(0, 0.10150299184980141), (1, -0.5737308483002963)]
[(0, 0.7032108939378311), (1, 0.16115180214025757)]
[(0, 0.877478767311983), (1, 0.16758906864659354)]
[(0, 0.9098624686818575), (1, 0.1408655362871895)]
[(0, 0.6165825350569278), (1, -0.05392907566389435)]