看着gensim这个标题都觉得霸气,从文本预处理,特征提出到主题聚类等,基本上包含了文本处理的所有功能。
语料与向量空间,这一节主要讲如何把文本从词变为bag-of-word。中文的话无非是中间加入了分词这一步骤。之前用过jieba,觉得效果还不错。
停用词git上有一个中文预料预处理
documents = ["做人要谦虚,但做事不要谦虚,毛遂自荐,让别人看到你,知道你的存在,知道你的能力,这样你才有机会,别人才会把重任交给你。",
"天下没有好赚的钱,千万不要一口吃个胖子,先从小钱赚起吧",
"不管从事什么行业,一定要相同行业或不同行业的人吸收新知识,而且要用请教的态度。",
"犯错,就诚实地认错,并立刻改错,不要狡辩。",
".要想成功,就要以失败为老师,在失败中汲取教训。",
".一定要守时!守时是对别人的尊重。",
".用真心诚意打动别人,让任何人为你做事。",
"全力以赴迎接种种挑战,不要把困难看成是在整你。",
"这一句没什么意思"]
f=open('stopwords.txt', encoding='gbk')
lines=f.readline()
stoplst=list(map(lambda x:x.strip('\n'), lines))
texts = [[word for word in jieba.lcut(document) if word not in stop lst] for document in documents]
但是发现停用词完全没有被过滤。后来发现,在stoplst中每个元素后面都有一个\n。
分词结果如下
[['做人','谦虚','做事','不要','谦虚','毛遂自荐','看到','知道','存在','知道','能力','机会','会','重任','交给'],
['天下', '没有', '好赚', '钱', '千万', '不要', '一口', '吃个', '胖子', '先', '从小', '钱', '赚起'],
['从事', '行业', '一定', '相同', '行业', '不同', '行业', '吸收', '新', '知识', '请教', '态度'],
['犯错', '诚实', '认错', '立刻', '改错', '不要', '狡辩'],
['.', '想', '成功', '失败', '老师', '失败', '中', '汲取', '教训'],
['.', '一定', '守时', '守时', '尊重'],
['.', '真心诚意', '打动', '任何人', '做事'],
['全力以赴', '迎接', '种种', '挑战', '不要', '困难', '看成', '整'],
['一句', '没什么', '意思']]
文中说了,文本的特征提取方法有很多,并且很重要,这里提到了一个garbage in, garbage out意思就是你算法再牛逼,你的输入有问题,输出还是不行,所以,怎么选取一个能够有效表述文本的特征非常重要。然后这里选择的最简单的bag-of-words,并且,为了方便表示,使用corpora.Dictionary 用id来替代文本,这里是构建一个字典,下面的doc2bow是该对象的一个方法,即将文本使用这个字典描述,字典中不存在的直接忽略。
from collections import defaultdict
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)
dictionary.save('/tmp/deerwester.dict') # store the dictionary, for future reference
print(dictionary)
print(dictionary.token2id)
{'做人': 0, '谦虚': 1, '做事': 2, '不要': 3, '毛遂自荐': 4, '看到': 5, '知道': 6, '存在': 7, '能力': 8, '机会': 9, '会': 10, '重任': 11, '交给': 12, '天下': 13, '没有': 14} #截取了一部分
由于都是短文本,我把频次小于1去掉那句注释了。
new_doc = "我的存在对你是真心诚意的"#新文本的转化
new_vec = dictionary.doc2bow(jieba.lcut(new_doc))
print(new_vec)
[(7, 1), (50, 1)]
corpus = [dictionary.doc2bow(text) for text in texts] #都转化为ID表示
corpora.MmCorpus.serialize('/tmp/deerwester.mm', corpus) # 保存到硬盘
print(corpus)
这一节主要是针对大文本文件,构建一个对象使其成为一个生产器。
>>> class MyCorpus(object):
>>> def __iter__(self):
>>> for line in open('mycorpus.txt'):
>>> # assume there's one document per line, tokens separated by whitespace
>>> yield dictionary.doc2bow(line.lower().split())
>>> from six import iteritems
>>> # collect statistics about all tokens
>>> dictionary = corpora.Dictionary(line.lower().split() for line in open('mycorpus.txt'))
>>> # remove stop words and words that appear only once
>>> stop_ids = [dictionary.token2id[stopword] for stopword in stoplist if stopword in dictionary.token2id]
>>> once_ids = [tokenid for tokenid, docfreq in iteritems(dictionary.dfs) if docfreq == 1]
>>> dictionary.filter_tokens(stop_ids + once_ids) # remove stop words and words that appear only once
>>> dictionary.compactify() # remove gaps in id sequence after words that were removed
>>> print(dictionary)
虽然结果同样是list,但是corpus对内存更加友好,因为作为生成器,内存中每次只处理一行。
gensim有很多将空间向量语义存到磁盘的方法
>>> corpora.MmCorpus.serialize('/tmp/corpus.mm', corpus)
>>> corpora.SvmLightCorpus.serialize('/tmp/corpus.svmlight', corpus)
>>> corpora.BleiCorpus.serialize('/tmp/corpus.lda-c', corpus)
>>> corpora.LowCorpus.serialize('/tmp/corpus.low', corpus)
load的话也很简单,但是corpus是一个文件流
>>> corpus = corpora.MmCorpus('/tmp/corpus.mm')
与numpy和scipy之间矩阵的转换,也是,现在python哪个机器学习相关的库不跟这两个打交道
>>> import gensim
>>> import numpy as np
>>> numpy_matrix = np.random.randint(10, size=[5,2]) # random matrix as an example
>>> corpus = gensim.matutils.Dense2Corpus(numpy_matrix)
>>> numpy_matrix = gensim.matutils.corpus2dense(corpus, num_terms=number_of_corpus_features)
>>> import scipy.sparse
>>> scipy_sparse_matrix = scipy.sparse.random(5,2) # random sparse matrix as example
>>> corpus = gensim.matutils.Sparse2Corpus(scipy_sparse_matrix)
>>> scipy_csc_matrix = gensim.matutils.corpus2csc(corpus)
from gensim import corpora, models, similarities
tfidf = models.TfidfModel(corpus) # step 1 -- initialize a model
不同的转换需要不同的初始化参数,对于tfidf,就只是遍历所有文本一遍建立文本频率信息。训练譬如LDA和LSA,则需要更多的参数和时间。
doc_bow = [(0, 1), (1, 1)]
print(tfidf[doc_bow]) # step 2 -- use the model to transform vectors
corpus_tfidf = tfidf[corpus]
利用已经建立的tfidf模型,得到新的文本的tfidf向量。并将原文本全部转化为tfidf。之前以为tfidf = models.TfidfModel(corpus)后得到的tfidf就是由corpus得到的tfidf矩阵,后来才知道,与其他拟合模型一样,它返回的相当于一个带参数的模型。
我们知道,corpus如果从文件中读取的话是一个stream,model[corpus] only creates a wrapper around the old corpus document stream,如果要对transform后的对象多次迭代的话,建议先序列化到磁盘。
并且transform可以序列化,即对上一个transformed的对象继续使用。
lsi = models.LsiModel(corpus_tfidf, id2word=dictionary, num_topics=2) # initialize an LSI transformation
corpus_lsi = lsi[corpus_tfidf] # create a double wrapper over the original corpus: bow->tfidf->fold-in-lsi
for doc in corpus_lsi: # both bow->tfidf and tfidf->lsi 转化实际上在这里才开始执行
print(doc)
lsi.save('/tmp/model.lsi') # same for tfidf, lda, ...
lsi = models.LsiModel.load('/tmp/model.lsi')
训练得到的模型可以保存,避免每次重复训练。
gensim中包含的转换算法,感觉每个都够研究一壶的
tfidf
LSI
Random Projections, RP
LDA
Hierarchical Dirichlet Process, HDP
我们之前bag-of-words, transform都是为了有一个能够衡量文本间距离的标准, (such as a user query vs. indexed documents).
index = similarities.MatrixSimilarity(lsi[corpus]) # transform corpus to LSI space and index it
首先,建立自己的语料库
similarities.MatrixSimilarity 会读入所有数据,因此对内存要求较大。
当内存大小不满足时,可以使用 similarities.Similarity class,这个类在固定的内存中运行,通过将索引分散到磁盘上的多个文件中,称为碎片。它内部调用similarities.MatrixSimilarity and similarities.SparseMatrixSimilarity,速度很快,就是有点复杂(这一部分后面使用后会加上一些内容)
index.save('/tmp/deerwester.index')
index = similarities.MatrixSimilarity.load('/tmp/deerwester.index')
这么不好计算,那就肯定可以把模型存下来咯