NLP文档集数据处理 gensim corpora.Dictionary 的简单使用
Posted ZSYL
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NLP文档集数据处理 gensim corpora.Dictionary 的简单使用相关的知识,希望对你有一定的参考价值。
【NLP】文档集数据处理 gensim corpora.Dictionary
gensim是一个python的自然语言处理库,能够将文档根据TF-IDF, LDA, LSI 等模型转化成向量模式,以便进行进一步的处理。此外,gensim还实现了word2vec功能,能够将单词转化为词向量。
1. corpora 和 dictionary
基本概念和用法:
corpora是gensim中的一个基本概念,是文档集的表现形式,也是后续进一步处理的基础。从本质上来说,corpora其实是一种格式或者说约定,其实就是一个二维矩阵。在实际运行中,因为单词数量极多(上万甚至10万级别),而一篇文档的单词数是有限的,所以如果还是采用密集矩阵来表示的话,会造成极大的内存浪费,所以gensim内部是用稀疏矩阵的形式来表示的。
导包:
from gensim import corpora
from collections import defaultdict
2. 词典操作
将文档分割成词语之后,使用dictionary = corpora.Dictionary(texts)生成词典。并可以使用save函数将词典持久化。
dictionary.save('/tmp/deerwester.dict')
生成词典以后corpus = [dictionary.doc2bow(text) for text in texts]档转化为向量形式。
from gensim import corpora
from collections import defaultdict
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"]
# 去掉停用词
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]
# 去掉只出现一次的单词
frequency = defaultdict(int)
for text in texts:
for token in text:
frequency[token] += 1
texts = [[token for token in text if frequency[token] > 1]
for text in texts]
dictionary = corpora.Dictionary(texts) # 生成词典
# 将文档存入字典,字典有很多功能,比如
# diction.token2id 存放的是单词-id key-value对
# diction.dfs 存放的是单词的出现频率
dictionary.save('/tmp/deerwester.dict') # store the dictionary, for future reference
corpus = [dictionary.doc2bow(text) for text in texts]
corpora.MmCorpus.serialize('/tmp/deerwester.mm', corpus) # store to disk, for later use
3. 存储
存储文件:
dictionary.save('/tmp/deerwester.dict')
序列化:
corpora.MmCorpus.serialize
将corpus持久化到磁盘中。
corpora.MmCorpus.serialize('/tmp/deerwester.mm', corpus)
反序列化:
corpus = corpora.MmCorpus('/tmp/deerwester.mm')
除了MmCorpus以外,还有其他的格式,例如SvmLightCorpus, BleiCorpus, LowCorpus等等,用法类似。
4. 其他操作
# 过滤掉出现频率最高的N个单词
dictionary.filter_n_most_frequent(N)
# 1.去掉出现次数低于no_below的
# 2.去掉出现次数高于no_above的。注意这个小数指的是百分数
# 3.在1和2的基础上,保留出现频率前keep_n的单词
dictionary.filter_extremes(no_below=5, no_above=0.5, keep_n=100000)
# 有两种用法,一种是去掉bad_id对应的词,另一种是保留good_id对应的词而去掉其他词。注意这里bad_ids和good_ids都是列表形式
dictionary.filter_tokens(bad_ids=None, good_ids=None)
# 在执行完前面的过滤操作以后,可能会造成单词的序号之间有空隙,这时就可以使用该函数来对词典来进行重新排序,去掉这些空隙。
dictionary.compacity()
5. 分批处理和分布式计算
当文本的规模很大时,也许会造成内存不足以容纳文本的情况,这就需要将所有文本分批处理,最后再将各批次计算得到的结果进行汇总。分布式计算时也有类似的需求。
这里假设在两个批次中,分别生成了dict1,corpus1以及dict2,corpus2.
第一步,首先将两个词典合并。当然,如果是先统一生成词典再分批生成词向量的话,可以跳过这一步,因为词典是一样的。
dict2_to_dict1 = dict1.merge_with(dict2)
要注意的是,得到的dict2_to_dict1并不是生成后的词典,而是dict2中的单词序号到这些词在合并后词典新序号的映射表。而dict1本身成为合并后的新词典。
第二步,合并corpus
如果之前跳过了第一步,即dict1就是dict2的话,可以直接进行合并。合并有两种方式,一种是
merged_corpus = [x for x in corpus1] + [x for x in corpus2]
另外一种,则需使用内置的itertools类:
merged_corpus = itertools.chain(corpus1, corpus2)
merged_corpus = [x for x in merged_corpus]
如果之前的词典也是分批生成的话,则需要对corpus2进行一定的处理:
new_corpus2 = dict2_to_dict1[corpus2]
merged_corpus = itertools.chain(corpus1, new_corpus2)
merged_corpus = [x for x in merged_corpus]
这样,就把分批处理得到的dict和corpus都合并起来了。
6. models
在models中,可以对corpus进行进一步的处理,比如使用tf-idf模型,lsi模型,lda模型等,非常强大。
在按照之前的方法生成了corpus和dictionary以后,就可以生成模型了。
tfidf_model = models.TfidfModel(corpus)
注意,目前只是生成了一个模型,但这是类似于生成器,并不是将对应的corpus转化后的结果。对tf-idf模型而言,里面存储有各个单词的词频,文频等信息。想要将文档转化成tf-idf模式表示的向量,还要使用如下命令:
corpus_tfidf = tfidf_model[corpus]
对于lda和lsi模型,用法有所不同:
lsi_model = models.LsiModel(corpus_tfidf, id2word=dictionary, num_topics=2)
corpus_lsi = lsi_model[corpus_tfidf]
可以看到,这里除了corpus以外,还多了num_topic的选项。这是指的潜在主题(topic)的数目,也等于转成lsi模型以后每个文档对应的向量长度。转化以后的向量在各项的值,即为该文档在该潜在主题的权重。因此lsi和lda的结果也可以看做该文档的文档向量,用于后续的分类,聚类等算法。值得注意的是,id2word是所有模型都有的选项,可以指定使用的词典。
代码示例:
import os
from gensim import corpora, models, similarities
from pprint import pprint
from matplotlib import pyplot as plt
import logging
# logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
def PrintDictionary(dictionary):
token2id = dictionary.token2id
dfs = dictionary.dfs
token_info = {}
for word in token2id:
token_info[word] = dict(
word = word,
id = token2id[word],
freq = dfs[token2id[word]]
)
token_items = token_info.values()
token_items = sorted(token_items, key = lambda x:x['id'])
print('The info of dictionary: ')
pprint(token_items)
print('--------------------------')
def Show2dCorpora(corpus):
nodes = list(corpus)
ax0 = [x[0][1] for x in nodes] # 绘制各个doc代表的点
ax1 = [x[1][1] for x in nodes]
# print(ax0)
# print(ax1)
plt.plot(ax0,ax1,'o')
plt.show()
if (os.path.exists("/tmp/deerwester.dict")):
dictionary = corpora.Dictionary.load('/tmp/deerwester.dict')
corpus = corpora.MmCorpus('/tmp/deerwester.mm')
print("Used files generated from first tutorial")
else:
print("Please run first tutorial to generate data set")
PrintDictionary(dictionary)
# 尝试将corpus(bow形式) 转化成tf-idf形式
tfidf_model = models.TfidfModel(corpus) # step 1 -- initialize a model 将文档由按照词频表示 转变为按照tf-idf格式表示
doc_bow = [(0, 1), (1, 1),[4,3]]
doc_tfidf = tfidf_model[doc_bow]
# 将整个corpus转为tf-idf格式
corpus_tfidf = tfidf_model[corpus]
# pprint(list(corpus_tfidf))
# pprint(list(corpus))
## LSI模型 **************************************************
# 转化为lsi模型, 可用作聚类或分类
lsi_model = models.LsiModel(corpus_tfidf, id2word=dictionary, num_topics=2)
corpus_lsi = lsi_model[corpus_tfidf]
nodes = list(corpus_lsi)
# pprint(nodes)
lsi_model.print_topics(2) # 打印各topic的含义
# ax0 = [x[0][1] for x in nodes] # 绘制各个doc代表的点
# ax1 = [x[1][1] for x in nodes]
# print(ax0)
# print(ax1)
# plt.plot(ax0,ax1,'o')
# plt.show()
lsi_model.save('/tmp/model.lsi') # same for tfidf, lda, ...
lsi_model = models.LsiModel.load('/tmp/model.lsi')
# *********************************************************
## LDA模型 **************************************************
lda_model = models.LdaModel(corpus_tfidf, id2word=dictionary, num_topics=2)
corpus_lda = lda_model[corpus_tfidf]
Show2dCorpora(corpus_lsi)
# nodes = list(corpus_lda)
# pprint(list(corpus_lda))
# 此外,还有Random Projections, Hierarchical Dirichlet Process等模型
7. similarities
这一部分主要负责计算文档间的相似度。与向量的相似度计算方式一样,采用余弦方法计算得到。一般来讲,使用lsi模型得到的向量进行计算效果比较好。
代码示例:
corpus_simi_matrix = similarities.MatrixSimilarity(corpus_lsi)
# 计算一个新的文本与既有文本的相关度
test_text = "Human computer interaction".split()
test_bow = dictionary.doc2bow(test_text)
test_tfidf = tfidf_model[test_bow]
test_lsi = lsi_model[test_tfidf]
test_simi = corpus_simi_matrix[test_lsi]
print(list(enumerate(test_simi)))
得到结果[(0, 0.99916452), (1, 0.99632162), (2, 0.9990505), (3, 0.99886364), (4, 0.99996823), (5, -0.058117405), (6, -0.021589279), (7, 0.013524055), (8, 0.25163394)]。可以看到显然属于第一类。
8. 实例
使用gensim做自然语言处理的一般思路是:使用(处理)字典 ----> 生成(处理)语料库 ----> 自然语言处理(tf-idf的计算等)
利用列表生成字典(dict):
#导入模块
from gensim import corpora
from pprint import pprint #格式化输出
#三个存放分好词的文本列表
word_list1 = ['我','来自','中国','我']
word_list2 = ['我们','来自','火星']
word_list3 = ['你','来自','何方']
#利用list1和list2生成一个词典
dict = corpora.Dictionary([word_list1,word_list2])
print('由list1和list2生成的词典:')
print(dict)
dict.add_documents([word_list3])
print('由list3拓展生成的词典:')
print(dict)
dict.save('test.dict') #保存字典
dict = corpora.Dictionary.load('test.dict') #加载字典
需要注意的是,corpora.Dictionary()方法,括号里面的对象的形式是这样的:[ [list1] , [list2] ]。
运行结果如下:
由list1和list2生成的词典:
Dictionary(5 unique tokens: ['中国', '我', '来自', '我们', '火星'])
由list3拓展生成的词典:
Dictionary(7 unique tokens: ['中国', '我', '来自', '我们', '火星']...)
表明已经生成了字典对象。
8.1 属性
接下来,通过一些方法来查看字典对象的一些属性:
dict.dfs 返回字典:{ 单词id:在多少文档中出现 }
dict.num_docs 返回文档数目
dict.num_pos 返回词的总数(不去重)
dict.token2id 返回字典:{ 单词:id }
dict.id2token 返回字典:{ id: 单词 }
dict.items() 返回对象:[(id,词)],查看需要循环
token:百度的意思是标记,但实际情况是表示字典中的词;id:则是每一个词在字典中独一无二的编号。(词袋中的索引值)
下面用代码来演示以下dict的属性:
print ('dfs:', dict.dfs) # 字典词频,{单词id,在多少文档中出现}
print ('num_docs:', dict.num_docs) # 文档数目
print ('num_pos:', dict.num_pos ) # 所有词的个数(不去重)
word_id_dict = dict.token2id # 生成{词:id}这样一个字典
id_word_dict = dict.id2token # 生成{id:词}这样一个字典
print('word_id_dict:',word_id_dict)
print('id_word_dict:',id_word_dict)
id_word_list = dict.items()
print ('id_word_dict:',id_word_list)
for itm in id_word_list:
print (itm[0], itm[1])
运行结果如下:
dfs: {1: 1, 2: 3, 0: 1, 3: 1, 4: 1, 6: 1, 5: 1}
num_docs: 3
num_pos: 10
word_id_dict: {'中国': 0, '我': 1, '来自': 2, '我们': 3, '火星': 4, '何方': 5, '你': 6}
id_word_dict: {} # 这里本应是与上一行相反的结果,未找出原因,后面更正:为了节约内存,id2token里的元素不是现成提供的,需要访问它才给你,如果你要读取id2token,提前运行下面的代码即可: for itm in dict.items(): print (itm[0], itm[1])
# 理论上结果为:
# id_word_dict: { 0 :'中国', 1 :'我', 2 :'来自', 3 :'我们', 4 :'火星', 5 :'何方', 6:'你' }
id_word_dict: ItemsView(<gensim.corpora.dictionary.Dictionary object at 0x00000177B6D0FE48>)
0 中国
1 我
2 来自
3 我们
4 火星
5 何方
6 你
8.2 过滤
得到一个字典后,如果想要进行过滤(或者说去除处理),那么可以使用以下方法:
print(dict) #未过滤前的字典
dict.filter_n_most_frequent(1) #过滤掉出现频率最高的1个单词(参数决定)
print(dict) #过滤后的字典
运行结果:少了‘来自’这个词
Dictionary(7 unique tokens: ['中国', '我', '来自', '我们', '火星']...)
Dictionary(6 unique tokens: ['中国', '我', '我们', '火星', '何方']...)
过滤词汇还可以使用 :dict.filter_extremes()
参数:
- no_below,
- no_above,
- keep_n,
- keep_tokens。
遵循以下原则:
-
去掉少于在no_below篇文档中出现的词,比如说‘火星’这个词只在一篇文档中出现,你想要去掉它,那么no_below=2便可以实现,其他只在一篇文档出现的也会去除。
-
去掉高于no_above*num_docs篇文档中出现的词,no_above理解为总文档数的百分数,比如你想去掉一半以上文档出现的词,那么no_above应当取0.5(这个数字一定是小于1的)。
在1和2的基础上,保留出现频率前keep_n的单词;
keep_tokens:强制保留的词,即无视以上规则,保留下来的词。
下面用代码来演示一下:
#以下是我们的字典及其在文档出现的文档数
#{'中国': 1, '我': 1, '来自': 3, '我们': 1, '火星': 1, '何方': 1, '你': 1}
print(dict)
dict.filter_extremes(no_below=2, no_above=0.5, keep_n=1000, keep_tokens=['中国','我'])
#no_below:不要少于多少文档;no_above:不要超过多少(百分数)文档
#keep_n:受前面约束保留的前几个词;keep_tokens:不受约束保留的具体的词
print(dict)
#运行结果
Dictionary(7 unique tokens: ['中国', '我', '来自', '我们', '火星']...)
Dictionary(2 unique tokens: ['中国', '我'])
以上代码去除的是:只在一篇文档出现的,多于一半文档出现的。理论上所有的会过滤掉。但是我们保留了’中国’,'我’两个词,最终结果也只剩下这两个。
8.3 语料库
接下来的步骤进行语料库的处理:
word_bow1 = dict.doc2bow(word_list1, allow_update=False) # 词袋[(id,num)],稀疏向量
word_bow2 = dict.doc2bow(word_list2) # 将词列表转换成稀疏词袋向量
word_bow3 = dict.doc2bow(word_list3) #将词列表转换成稀疏词袋向量
print ('word_bow1:', word_bow1)
print ('word_bow2:', word_bow2)
print ('word_bow3:', word_bow3)
corpus = [word_bow1, word_bow2, word_bow3] # 由词袋向量组成的列表构成语料
print(corpus)
# 运行结果
word_bow1: [(0, 1), (1, 2), (2, 1)]
word_bow2: [(2, 1), (3, 1), (4, 1)]
word_bow3: [(2, 1), (5, 1), (6, 1)]
[[(0, 1), (1, 2), (2, 1)], [(2, 1), (3, 1), (4, 1)], [(2, 1), (5, 1), (6, 1)]]
词袋形象地解释便是:将文本中的词放入一个袋子里,在袋子中,词拥有两个属性编号和数目(id,num)。
一个词袋对应一篇文档。由元组(id, num)构成的 [(0, 1), (1, 2), (2, 1)] 则称作稀疏向量。gensim中一般语料库都是用这种方式来表示。
接下来,便是使用模型来计算tf-idf:
from gensim import models
#根据语料库和字典生成模型
tfidf_model = models.TfidfModel(corpus=corpus, dictionary=dict)
tfidf_model.save('test_tfidf.model') #保存模型到本地
tfidf_model = models.TfidfModel.load('test_tfidf.model') #载入模型
corpus_tfidf = [tfidf_model[doc] for doc in corpus]
print ('TF-IDF:')
for tfidf in corpus_tfidf:
print (tfidf)
#运行结果
TF-IDF:
[(0, 0.4472135954999579), (1gensim.corpora wikiCorpus 是不是仅适用于 bz2 文件?