Gensim是在做自然语言处理时较为经常用到的一个工具库,主要用来以无监督的方式从原始的非结构化文本当中来学习到文本隐藏层的主题向量表达。
主要包括TF-IDF,LSA,LDA,word2vec,doc2vec等多种模型。
在gensim中有一些核心的概念,这里简要介绍一下:
1、Document(文档):主要是指一些文本
2、Corpus(语料库):文档的一个集合
3、Vector(向量):一种文档在数学上的表示形式,将文档以一串数字来表示
4、Model(模型):一种用来把向量从一种表现形式转换为另一种表现形式
在Gensim中,所谓的文档在python3中实际上是一种文本序列类型的对象(str),一个文档可以是一个140个字符的微博,一个单独的段落,或者一本书,都可以称为文档
document = "Human machine interface for lab abc computer applications"
语料库是一组文档组成的对象,在Gensim中,语料库通常有两种功能:
1、做为训练一个模型的输入,在训练的过程中,模型通过使用训练语料库来去找到公共主题或主题,初始化模型的内部参数。
Gensim主要是包含一些无监督模型,所以不需要人类的干预,不需要人类来进行注释或者标记文档
2、做为一种文档的组织形式。在训练过后,一个主题模型可以用来去从新的文档中抽取主题。
这里举一个例子,创建一个包含9个文档的语料库:
text_corpus = [
"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",
]
注意:在实际中的语料库可能相当的大,一次性把它加载到内存中是不可能的,Gensim采用一种流(streaming)的方式来每一次加载一篇文档。
上面的例子是一个简单的事例,在实际中可能是莎士比亚写的所有的文本,或者所有维基百科的文章的集合,或者一个人感兴趣的推特的集合等等。
收集了语料库之后,通常需要进行一些典型的预处理步骤,比如删除一些常见的单词(比如the等等),或者删除掉在语料库中只出现过一次的词,这个过程往往是使用标记器Tokenization来把文档以空格的方式分割成单个的单词。
注意⚠️:为了去更好的对文档进行预处理,而不是简单的使用转小写或者用空格分割,可以查看gensim.tuils.simple_preprocess()函数的相关介绍
创建一个停止词表:
stop_list=set('for a of the and to in'.split(' '))
print(stop_list)
{'a', 'and', 'the', 'for', 'of', 'to', 'in'}
对每篇文档进行转小写,并且以空格分割,同时过滤掉所有的停止词
texts = [[word for word in document.lower().split() if word not in stop_list]for document in text_corpus]
print(texts)
[['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
print(frequency)
defaultdict(<class '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})
#只保留出现一次以上的单词
processed_corpus = [[token for token in text if frequency[token]>1]for text in texts]
print(processed_corpus)
可以得到以下的结果:
[['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']]
在处理语料库之前,我们可能希望每一个在语料库中的词都有一个独一无二的ID,那么我们可以使用gensim.corpora.Dictionary类,这个字典可以定义一个词表,词表中包含所有的单词
from gensim import corpora
dictionary = corpora.Dictionary(processed_corpus)
print(dictionary)
结果为
Dictionary(12 unique tokens: ['computer', 'human', 'interface', 'response', 'survey']...)
由于上面定义的语料库很小,所以在字典中只有12个不同的单词,在很大的语料库当中,这个字典通常包含成百上千个tokens
为了去得到语料库当中的潜在的结构,需要一种方法来用数学的方法去表示这个文档。
一种方法是把每一个文档表示为一个由特征组成的向量。
举个例子,一个特征可能看作一个问答对:
1.How many times does the word splinter appear in the document?
Zero
2.How many paragraphs does the document consist of?
Two
3.How many fonts does the document use?
Five
问题是一般用来表示的方式好是使用一个int类型的ID,就比如说:(1,0.0) (2,2.0)(3,5.0)
这就是经常说的密集向量,由于它包含了上述问题的明确的答案。
如果我们提前知道所有的问题,那么我们可能就把它隐式的表达成(0,2,5)而对应的答案序列的表示就是3维的密集向量。
在Gensim中,答案只允许是一个单独的浮点数。
在实际的应用中,向量可能包含许多的0值,为了节省内存,Gensim忽略了所有包含O.O.的与向量值为0的值
所以上面的例子会是(2,2.0)(3,5.0)
这就被大家称为稀疏向量或者词袋向量。在稀疏表示中,所有的缺失的特征可以被明确的表示为0
现在假设有这么一种情况,如果所有的问题都是相同的,我们可以对两个不同的文档的向量进行比较:
假设有两个向量(0.0,2.0,5.0)和(0.1,1.9,4.9)
这两个向量非常的像,对应的值可能只相差0.1,所以我们可以得出结论,这两个向量所代表的文档也就十分的相似。
当然,这个结论的正确性取决于我们对于问题的选择
另一种方法去表示一个文档,是词袋模型。在词袋模型当中,每一个文档是使用一个包含了所有的词频的向量来表示。比如说,这里有一个字典包含['coffee','milk','sugar','spoon'],然后有这么一篇文档"coffee milk coffee",可以使用向量[2,1,0,0]来表示这篇文档,其中的数字分别表示"coffee"、"milk","sugar","spoon"出现的次数。
向量的长度取决于整个字典中包含的词的数量。
词袋模型的一个主要的问题就是,它忽略了token的顺序!
现在假设有一个语料库,经过处理以后有12个不同的词,那么在词袋模型中,就可以使用一个12纬的向量来表示器中的文档,可以通过这个字典来把一个经过了token化的文档来表示成向量。
print(dictionary.token2id)
{'computer': 0,
'eps': 8,
'graph': 10,
'human': 1,
'interface': 2,
'minors': 11,
'response': 3,
'survey': 4,
'system': 5,
'time': 6,
'trees': 9,
'user': 7}
举个例子来说,假如说有这么一个新的短语:
"Human computer interaction",这是一个新的短语(在一开始的语料库中不存在),这是我们可以使用doc2bow方法,可以根据单词统计得到一个稀疏向量:
new_doc = "Human computer interaction"
new_vec = dictionary.doc2bow(new_doc.lower().split())
print(new_vec)
[(0, 1), (1, 1)]
结果是一个由元组组成的列表,在输出中的元组,第一个数字表示这个token在字典中的ID,第二个数字代表出现的次数。
注意"interaction"在一开始的语料库当中没有出现过,所以这个单词并没有出现在结果中。
结果中只出现出现在该文档中的单词!
由于一篇文档出现的单词的数量一般很少,所以会有大量的频率为0的项,为了节省内存,在gensim里面所有频率为0的单词都会被省略
bow_corpus = [dictionary.doc2bow(text) for text in processed_corpus]
print(bow_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)]]
注意,这个列表会完整的保存在内存当中,在大多数应用中,你可能希望不要一次性全部加载。
所以gensim允许去使用一个迭代器,一次只返回一篇文档的向量。
在将语料库进行向量化之后,就可以把向量传递到模型中。
模型:将一种文档表示转换成另一种表示。
在gensim中,文档都会被转换成向量,模型就可以看作两个向量空间之间的变换。
模型在训练的过程中会学习到这个"变换"的细节。
一个简单的模型——TF-IDF,这个模型是将词袋向量转换成另一种向量空间,在这个新的向量空间中,频率根据每一个单词在语料库中真实出现的情况而记录下来。
下面是一个简单的例子:
from gensim import models
tfidf = models.TfidfModel(bow_corpus)
words = "system minors".lower().split()
print(tfidf[dictionary.doc2bow(words)])
[(5, 0.5898341626740045), (11, 0.8075244024440723)]
tf-idf模型返回一个由元组组成的列表,第一个数字是token的ID,第二个数字是这个单词的Tf-idf值。
在训练完成后,你可以把这个模型保存起来,之后使用的时候再重新加载回来。
一旦创建好了模型,你就可以接着做一些很酷的事情,比如说,可以将整个语料库都进行转换,可以做相似性查询:
from gensim import similarities
index = similarities.SparseMatrixSimilarity(tfidf[bow_corpus],num_features=12)
为了去查询文档与语料库中每一个文档的相似性:
query_document = "system engineering".split()
query_bow = dictionary.doc2bow(query_document)
sims = index[tfidf[query_bow]]
print(list(enumerate(sims)))
[(0, 0.0), (1, 0.32448703), (2, 0.41707572), (3, 0.7184812), (4, 0.0), (5, 0.0), (6, 0.0), (7, 0.0), (8, 0.0)]
这个输出怎么去理解呢?文档三具有一个相似性分数:0.718,文档二具有一个相似性分数:42%
可以通过对这个结果进行一个排序,方便观察
for document_num,score in sorted(enumerate(sims),key=lambda x:x[1],reverse=True):
print(document_num,score)
3 0.7184812
2 0.41707572
1 0.32448703
0 0.0
4 0.0
5 0.0
6 0.0
7 0.0
8 0.0