preface
这一篇我们做文本相似度计算主要采用jieba,Gensim模块来做。文本相似度有什么用呢?它能够计算出文本内容相似的文章,可以把相似的文章推送给读者,也可以去计算几篇文章是否存在抄袭的嫌疑。好那么下面就开始开车,请坐稳扶好。
windows下大型文本读取如何处理字符编码问题:
我们首先看下代码,采用最基本的Open方法:
f=open('F:\Learnning\daomubiji.txt','r+')
此时报错了:
- Traceback (most recent call last):
- File "H:/PythonProject/untitled/DataParse/Day2/002.py", line 32, in <module>
- f=open('F:\Learnning\daomubiji.txt','r',).read()
- UnicodeDecodeError: 'gbk' codec can't decode byte 0xac in position 512518: illegal multibyte sequence
对于这样的方法,我们可以采用http的方法来读取,如下代码:
- import
- hf = urllib.request.urlopen('http://127.0.0.1/txt.html').read().decode('utf8','ignore')
jieba模块介绍
jieba模块主要对中文做分词,分析句子和文章都是以词语为文章,所以我们对此处理的时候需要进行分词,与英文不一样的,英文分词靠空格就可以解决。下面就看看jieba的使用:
安装jieba
pip install jieba
cut方法之全模式:
- setence = '温柔易淡在北京东方之音工作'
- w1 = jieba.cut(setence,cut_all=True) # cut是分词方法,cut_all = True表示全模式
- print(w1)
- for w in w1:
- print(w)
打印结果如下:
- 温柔
- 易
- 淡
- 在
- 北京
- 北京东方
- 京东
- 京东方
- 东方
- 之
- 音
- 工作
如上所示,全模式是说把所有词都弄出来。包含重复的。
cut方法之精准模式:
只需要把上面的代码小改下就行,cut_all=False。默认是精准模式
w1 = jieba.cut(setence,cut_all=False) # cut是分词方法,cut_all = False精准模式
输出结果如下:
- 温柔
- 易淡
- 在
- 北京东方
- 之音
- 工作
很明显,没有任何重复的词了,by the way,词语是具有权重性的,所以这里的分词依照词语权重性来分词
cut_for_search方法:
这个方法是倒排索引的方法,代码如下:
- w2 = jieba.cut_for_search(setence)
- for i in w2:
- print(i)
打印结果如下:
- 温柔
- 易淡
- 在
- 北京
- 京东
- 东方
- 京东方
- 北京东方
- 之音
- 工作
jieba的字典使用
jieba默认有自己的字典,有时候我们需要使用自己自定义的字典,那么我们可以在jieba模块的目录下编写自己的字典,如下所示:
我在我的电脑搜索python,然后在lib下面找到了jieba的模块:
C:\Users\Leo\AppData\Local\Programs\Python\Python35\Lib\site-packages\jieba
在这下面有个dict.txt的文件,这是自带的,我们看看dict.txt的内容格式:
词语 | 权重 | 词性 |
---|---|---|
AT&T | 3 | nz |
第一列为词语,第二列为词语的权重,第三列为词性。
此时我们了解jieba字典格式后,我们就可以自定义字典了,我这里使用window系统自带的记事本打开编辑的,下面请看:
我自己自定义自己的字典
词语 | 权重 | 词性 |
---|---|---|
ljf | 3 | n |
Leo | 3 | n |
温柔易淡 | 3 | n |
然后我们在导入自己定义的字典:
- import jieba.posseg
- # 加载字典
- jieba.load_userdict("C:\dict1.txt")
- sentence3='ljfLeo温柔易淡都是同一个人'
- w4 = jieba.posseg.cut(sentence3)
- for i in w4:
- print(i.word,'---',i.flag)
打印结果如下:
- ljf --- n # 使用了自定义字典的,能够把ljf,Leo给拆分开来。
- Leo --- n
- 温柔易淡 --- n
- 都 --- d
- 是 --- v
- 同一个 --- b
- 人 --- n
我们在回头看看没有使用自定义字典的打印结果:
- ljfLeo --- eng # 没有使用自定义字典的,把ljfLeo都视为英文单词了。
- 温柔 --- a
- 易 --- a
- 淡 --- a
- 都 --- d
- 是 --- v
- 同一个 --- b
- 人 --- n
词性说明
- a形容词
- b连词
- d 副词
- e 叹词
- f 方位词
- i 成语
- m 数词
- n 名词
- nr 人名
- ns 地名
- nt 机构团体
- nz 其他专有名词
- p 介词
- r 代词
- t 时间
- u 助词
- v 动词
- vn 动名词
- w 标点符号
- un 未知词语
利用posseg来判断词性:
- from jieba import posseg
- w5 = posseg.cut(sentence)
- for i in w5:
- print(i.word,'=',i.flag)
输出结果如下:
- 温柔 = a
- 易 = a
- 淡 = a
- 在 = p
- 北京东方 = ns
- 之 = u
- 音 = n
- 工作 = vn
analyse关键词提取
通过analyse.extract_tags来做,代码如下
- import jieba.analyse
- tag = jieba.analyse.extract_tags(sentence2,3)
- print(tag)
打印结果如下
['北京东方', '易淡', '之音']
tokenize 获取词语下标
和元组,列表一样,默认下标都是从0开始的。代码如下:
- w9 = jieba.tokenize(sentence2)
- for i in w9:
- print(i)
打印结果如下
- ('温柔', 0, 2)
- ('易淡', 2, 4)
- ('在', 4, 5)
- ('北京东方', 5, 9)
- ('之音', 9, 11)
- ('工作', 11, 13)
tokenize 获取词语下标之search模式
search模式等同于cut下的全模式,把所有的单词都给找出来。
- w9 = jieba.tokenize(sentence2,mode='search')
- for i in w9:
- print(i)
打印结果如下
- ('温柔', 0, 2)
- ('易淡', 2, 4)
- ('在', 4, 5)
- ('北京', 5, 7)
- ('京东', 6, 8)
- ('东方', 7, 9)
- ('京东方', 6, 9)
- ('北京东方', 5, 9)
- ('之音', 9, 11)
- ('工作', 11, 13)
提取盗墓笔记的关键字
提取关键字的方法是通过analyse.extract_tags
,第二个参数3表示提取三个关键字。
文本内容从网上下载的,然后用记事本删除几个句子(象征性删除)再保存下,这样在open 的时候能够最大程度避免编码问题导致读取不了文件。万恶的字符编码
- f=open('C:\Program Files\phpstudy\WWW\daomubiji.txt','rb').read()
- tag = jieba.analyse.extract_tags(f,3)
- print(tag)
- # 打印结果如下:
- ['三叔', '我们', '老痒']
Gensim模块简介
Gensim是一款开源的第三方Python工具包,用于从原始的非结构化的文本中,无监督地学习到文本隐层的主题向量表达。它支持包括TF-IDF,LSA,LDA,和word2vec在内的多种主题模型算法,支持流式训练,并提供了诸如相似度计算,信息检索等一些常用任务的API接口。
下面聊聊Gensim四个基本概念:
- 语料(Corpus):一组原始文本的集合,用于无监督地训练文本主题的隐层结构。语料中不需要人工标注的附加信息。在Gensim中,Corpus通常是一个可迭代的对象(比如列表)。每一次迭代返回一个可用于表达文本对象的稀疏向量。
- 向量(Vector):由一组文本特征构成的列表。是一段文本在Gensim中的内部表达。
- 稀疏向量(Sparse Vector):我们可以略去向量中多余的0元素或空元素。此时,向量中的每一个元素是一个(key, value)的tuple。
- 模型(Model):是一个抽象的术语。定义了两个向量空间的变换(即从文本的一种向量表达变换为另一种向量表达)。
简单的使用
- #!/usr/bin/env python
- # __author__:Leo
- from gensim import corpora
- from collections import defaultdict
- from pprint import pprint
- # 建立简单的文档内容
- 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"]
-
- # remove common words and tokenize
- 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] # 像这样的2层嵌套循环,遵循从右往左看,if word not in stoplist 剔除无用的单词。
-
- # remove words that appear only once
- frequency = defaultdict(int) # 默认字典使用int类型
- for text in texts: # 下面2行代码是统计词频
- for token in text:
- frequency[token] += 1
- texts = [[token for token in text if frequency[token] > 1]
- for text in texts] # 提取出来大于等于1的单词。
-
- dictionary = corpora.Dictionary(texts) # 把刚才生成的文本做成字典
- dictionary.save('D:\deerwester.dict') # store the dictionary, for future reference
- print(dictionary.token2id) # 打印每个单词的ID
-
- #To actually convert tokenized documents to vectors:
- new_doc = "Human computer interaction"
-
- # doc2bow 方法的用途:The function doc2bow() simply counts the number of occurrences of each distinct word,converts the word to its integer
- # word id and returns the result as a sparse vector
-
- new_vec = dictionary.doc2bow(new_doc.lower().split())
- print(new_vec) # the word "interaction" does not appear in the dictionary and is ignored
入门使用可以参考官网地址:http://radimrehurek.com/gensim/tut1.html
具体应用和详细的资料可以查看Gensim官网:http://radimrehurek.com/gensim/tutorial.html
文本相似度匹配
在经过上面的简单知识学习后,我们知道了jieba这个模块的分词,提取关键字,使用自定义字典等等基本方法。下面就聊聊通过TFIDF算法与jieba做文本相似度匹配。
tfidf(term frequency–inverse document frequency)是一种用于信息检索与数据挖掘的常用加权技术,说白了就是一种统计方法,,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度,但同时会随着它在语料库中出现的频率成反比下降。
tfidf具体内容与应用可以查看百度百科
操作步骤:
- 读取文档
- 对要计算的多篇文档进行分词
- 对文档进行整理成指定格式,方便后续进行计算
- 计算出词语的频率
- [可选]-对频率低的词语进行过滤
- 通过语料库建立词典
- 加载要对比的文档
- 将要对比的文档通过doc2bow转化为稀疏向量
- 对稀疏向量进行进一步处理,得到新语料库
- 将新语料库通过tfidfmodel进行处理,得到tfidf
- 通过token2id得到特征数
- 稀疏矩阵相似度,从而建立索引
- 得到最终相似度结果
代码操作:
在简单的了解上面文本相似度匹配步骤以后,下面就看看怎么用代码去实现:
- shezhao.txt 是盗墓笔记4 蛇沼鬼城.txt
- daomubiji.txt 是随便从一个网站上下载下来 的全集(我也不知道是不是真的全集)
- mihaiguichao.txt 是盗墓笔记5 谜海归巢.txt,和 盗墓笔记4 蛇沼鬼城.txt 是一套的。
盗墓笔记自己上网上找吧,我这里就不提供下载地址了。
老规矩,如果open文件遇到字符集编码的文本,我是用记事本打开对应的文件,删除一些文字后保存,或者删除 程序报错时提示哪个位置的内容 后保存即可。
- #!/usr/bin/env python
- # __author__:Leo
- from gensim import corpora,models,similarities
- import jieba
- from collections import defaultdict
- f1=open('F:\Learnning\shezhao.txt','r+').read()
- f2=open('F:\Learnning\daomubiji.txt','r+').read()
- data1 = jieba.cut(f1)
- data2 = jieba.cut(f2)
- data1_words = ''
- data2_words = ''
- for word in data1:
- data1_words += word+" "
- for word in data2:
- data2_words += word+" "
- documents = [data1_words,data2_words]
- texts = [[word for word in document.split()]
- for document in documents] # 遇到这样的代码,我们阅读的规则是遵循从右往左,先看最外面的for循环,z再看里面的for循环
-
- #print('texts',texts)
- frequency = defaultdict(int) # 使用默认字典
- for text in texts: # 下面2行代码是计算每个词的频数。方便下面的代码去除频数少的单词
- for token in text:
- frequency[token] = +1
-
- dictionary = corpora.Dictionary(texts)
- dictionary.save('F:\Learnning\dictionary.txt')
-
- # texts = [[word for word in text if frequency[token] > 1]
- # for text in texts] # 去除频数少于1的词语, 内循环是先走if判断,再走循环
- texts = [[word for word in text ]
- for text in texts]
- f3=open('F:\Learnning\mihaiguichao.txt','r+').read()
- data3=jieba.cut(f3)
- data3_words =''
- for item in data3:
- data3_words += item+' '
- new_vec = dictionary.doc2bow(data3_words.split()) # 建立向量
- corpus = [dictionary.doc2bow(text) for text in texts] # 建立新的语料库
- corpora.MmCorpus.serialize("F:\Learnning\CSDN-python大数据\Day2\XinYU.mm",corpus) # 存新的语料库
- tfidf = models.TfidfModel(corpus) # 建立tfidf模型
- featureNum = len(dictionary.token2id.keys()) # 通过token2id得到特征数
- index=similarities.SparseMatrixSimilarity(tfidf[corpus],num_features=featureNum) #稀疏矩阵相似度,从而建立索引
- sim = index[tfidf[new_vec]] # 计算最终相似度结果
- print(sim)from gensim import corpora,models,similarities
- import jieba
- from collections import defaultdict
- f1=open('F:\Learnning\CSDN-python大数据\Day2\shezhao.txt','r+').read()
- f2=open('F:\Learnning\CSDN-python大数据\Day2\daomubiji.txt','r+').read()
- data1 = jieba.cut(f1)
- data2 = jieba.cut(f2)
- data1_words = ''
- data2_words = ''
- for word in data1:
- data1_words += word+" "
- for word in data2:
- data2_words += word+" "
- documents = [data1_words,data2_words]
- texts = [[word for word in document.split()]
- for document in documents] # 遇到这样的代码,我们阅读的规则是遵循从右往左,先看最外面的for循环,z再看里面的for循环
-
- #print('texts',texts)
- frequency = defaultdict(int) # 使用默认字典
- for text in texts: # 下面2行代码是计算每个词的频数。方便下面的代码去除频数少的单词
- for token in text:
- frequency[token] = +1
-
- dictionary = corpora.Dictionary(texts)
- dictionary.save('F:\Learnning\CSDN-python大数据\Day2\dictionary.txt')
-
- # texts = [[word for word in text if frequency[token] > 1]
- # for text in texts] # 去除频数少于1的词语, 内循环是先走if判断,再走循环
- texts = [[word for word in text ]
- for text in texts]
- f3=open('F:\Learnning\CSDN-python大数据\Day2\mihaiguichao.txt','r+').read()
- data3=jieba.cut(f3)
- data3_words =''
- for item in data3:
- data3_words += item+' '
- new_vec = dictionary.doc2bow(data3_words.split()) # 建立向量
- corpus = [dictionary.doc2bow(text) for text in texts] # 建立新的语料库
- corpora.MmCorpus.serialize("F:\Learnning\CSDN-python大数据\Day2\XinYU.mm",corpus) # 存新的语料库
- tfidf = models.TfidfModel(corpus) # 建立tfidf模型
- featureNum = len(dictionary.token2id.keys()) # 通过token2id得到特征数
- index=similarities.SparseMatrixSimilarity(tfidf[corpus],num_features=featureNum) #稀疏矩阵相似度,从而建立索引
- sim = index[tfidf[new_vec]] # 计算最终相似度结果
- print(sim)
打印出来的相似度都在0-1之间,越接近1表示相似度速度越高。
如果数据量特别大,可以考虑采用多进程或者集群来做。