最近参加了个长文本分类的比赛,然后开始使用gensim,一个很强大的NLP神器,用于从原始的非结构化的文本中,无监督地学习到文本隐层的主题向量表达,在此记录一下使用心得。
目录
基本概念
1.语料的处理
2.生成词典和向量转化
3.主题向量的转化
1)TFIDF(词频逆文档频率)
2)LSI(潜在语义索引)
3)LDA(隐含狄利克雷分配)
4)RP(随即映射)
4.相似度匹配
5.转化为sklearn和scripy格式
对语料进行切分、去除停用词等基本处理,得到每一篇文档的特征列表。比如下面的列表
postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him', 'my'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
from gensim import corpora
dictionary = corpora.Dictionary(texts)
print(dictionary)
显示如下:
Dictionary(32 unique tokens: ['dog', 'flea', 'has', 'help', 'my']...)
其中字典还有很多实用的方法
print(dictionary.token2id)
# id2word print(dictionary.token2id)
# word2id print(dictionary.dfs) # 词频
结果如下,词频的key值是字典中单词的索引值:
{'dog': 0, 'flea': 1, 'has': 2, 'help': 3, 'my': 4, 'please': 5, 'problems': 6, 'him': 7, 'maybe': 8, 'not': 9, 'park': 10, 'stupid': 11, 'take': 12, 'to': 13, 'I': 14, 'cute': 15, 'dalmation': 16, 'is': 17, 'love': 18, 'so': 19, 'garbage': 20, 'posting': 21, 'stop': 22, 'worthless': 23, 'ate': 24, 'how': 25, 'licks': 26, 'mr': 27, 'steak': 28, 'buying': 29, 'food': 30, 'quit': 31}
{4: 3, 0: 3, 2: 1, 1: 1, 6: 1, 3: 1, 5: 1, 8: 1, 9: 1, 12: 1, 7: 3, 13: 2, 10: 1, 11: 3, 16: 1, 17: 1, 19: 1, 15: 1, 14: 1, 18: 1, 22: 2, 21: 1, 23: 2, 20: 1, 27: 1, 26: 1, 24: 1, 28: 1, 25: 1, 31: 1, 29: 1, 30: 1}
其他方法:
dictionary.filter_n_most_frequent(N) 过滤掉出现频率最高的N个单词
dictionary.filter_extremes(no_below=5, no_above=0.5, keep_n=100000)
1.去掉出现次数低于no_below的
2.去掉出现次数高于no_above的。注意这个小数指的是百分数
3.在1和2的基础上,保留出现频率前keep_n的单词
dictionary.filter_tokens(bad_ids=None, good_ids=None)
有两种用法,一种是去掉bad_id对应的词,另一种是保留good_id对应的词而去掉其他词。
dictionary.compacity() 在执行完前面的过滤操作以后,可能会造成单词的序号之间有空隙,这时就可以使用该函数来对词典来进行重新排序,去掉这些空隙。
dictionary.merge_with(other)和其他字典合并
字典持久化和加载
dictionary.save("data/mydict.dic")
dictionary = corpora.Dictionary.load('data/mydict.dic')
将文本特征的原始表达转为词袋模型对应的稀疏向量的表达。
corpus = [dictionary.doc2bow(text) for text in postingList]
print(corpus)
显示如下,其中(9,1)表示在字典中索引为9对应的单词,出现了1次:
[[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1)], [(0, 1), (7, 1), (8, 1), (9, 1), (10, 1), (11, 1), (12, 1), (13, 1)], [(4, 2), (7, 1), (14, 1), (15, 1), (16, 1), (17, 1), (18, 1), (19, 1)], [(11, 1), (20, 1), (21, 1), (22, 1), (23, 1)], [(4, 1), (7, 1), (13, 1), (22, 1), (24, 1), (25, 1), (26, 1), (27, 1), (28, 1)], [(0, 1), (11, 1), (23, 1), (29, 1), (30, 1), (31, 1)]]
语料的持久化和加载
corpora.MmCorpus.serialize("data/corpus.mm", corpus)
corpus = corpora.MmCorpus("data/corpus.mm")
(1)首先是模型对象的初始化。通常,Gensim模型都接受一段训练语料(注意在Gensim中,语料对应着一个稀疏向量的迭代器)作为初始化的参数。
(2)利用初始化的模型将语料转化为对象的向量
from gensim import models
tfidf = models.TfidfModel(corpus)
tfidf.save("./model.tfidf")
tfidf = models.TfidfModel.load("./model.tfidf")
将词袋模型或TFIDF空间映射到低维度的潜在空间,推荐200-500为金标准,在达观数据的长文本分类中,尝试350的维度分数得分优于其他维度。LSI可以进行增量训练,只要有新文档可以一直输入模型当中,通过add_document方法。如果python报memoryerror,那就是内存不够了,需要降低维度。
lsi_model = models.LsiModel(corpus, id2word=dictionary, num_topics=2)
#这是指的潜在主题(topic)的数目,也等于转成lsi模型以后每个文档对应的向量长度。转化以后的向量在各项的值,即为该文档 在该潜在主题的权重。
documents = lsi_model[corpus]
lsi_model.save("data/model1.lsi")
lsi_model = models.LsiModel.load("data/model2.lsi")
LDA是LSA的概率扩展,也是向低纬度转化的方式
lda_model = LdaModel(corpus=corpus, id2word=dictionary, num_topics=100)
documents = lda_model[corpus]
lda_model .save("data/model1.lsi")
lda_model = models.LsiModel.load("data/model2.lsi")
目的在于减小空维度,通过随机性,近似的到文档之间的TFIDF距离,不过对于大数据量确实很慢,反正10万篇文档的数据我的小电脑是受不了
model=rpmodel.RpModel(corpus, num_topics=2)
在得到文章对应的主题向量以后,就可以进行相似性匹配,我第一次是用在了长文本分类上,将多个文档遍历进行匹配,然后排序选择相似度最大的文章,取其在训练集中对应的分类编号,作为测试文档的类别。在单纯使用LSI向量,不加入TFIDF的情况下,准确率不高。
如果单纯将corpus转化为LSI向量,主需要将测试文章用LSI模型转化一次:
lsi_model = models.LsiModel(corpus, id2word=dictionary, num_topics=2)
documents = lsi_model[corpus] #训练数据转化为LSI向量
query_vec = lsi_model[query] #测试数据转化为LSI向量
相似度匹配:
index = similarities.MatrixSimilarity(documents)
#网上说这个会占用大量的内存,如果内存不足可以改用similarities.Similarity
sims = index[query_vec]
predclass = listClasses[np.where(sims == np.max(sims))[0][0]] #取对应训练数据的分类
相似度矩阵的持久化(会生成多个文件,千万不要删除其中任何一个):
index.save('/data/deerwester.index')
index = similarities.MatrixSimilarity.load('/data/deerwester.index')
如果加入TFIDF,在使用LSI,需要先转化为TFIDF向量,在转化为LSI向量
tfidf_query = models.TfidfModel(query)
query_vec = lsi_model[tfidf_query]
gensim的相似度匹配,用于文本分类时准确率不高,而在sklearn中有很多机器学习和深度学习算法,两者的数据格式不符。sklearn的输入和numpy和scripy相关,如果是密集矩阵,使用numpy.array格式,如果是稀疏矩阵,使用scipy.sparse.csr_matrix格式,可以去 scipy的官网查看相关文档,其中第四种方式比较简单:
def sparse2dense(lsi_corpus_total):
data = []
rows = []
cols = []
line_count = 0
for line in lsi_corpus_total: # lsi_corpus_total 是之前由gensim生成的lsi向量
for elem in line:
rows.append(line_count)
cols.append(elem[0])
data.append(elem[1])
line_count += 1
lsi_sparse_matrix = csr_matrix(data, (rows,cols))
其他方式
import gensim
import numpy as np
corpus = gensim.matutils.Dense2Corpus(numpy_matrix)
numpy_matrix = gensim.matutils.corpus2dense(corpus, num_terms=number_of_corpus_features)
import scipy.sparse
corpus = gensim.matutils.Sparse2Corpus(scipy_sparse_matrix)
scipy_csc_matrix = gensim.matutils.corpus2csc(corpus)