TF-IDF(Term Frequency - Inverse Document Frequency词频-逆文档频次)
由TF算法以及IDF算法组成。
TF算法:统计一个词在一篇文档内出现的频次,单词在文档中出现的次数越多,则其对文档的表达能力就越强。
IDF算法:统计一个单词在文档集合中的多少个文档内出现过,其基本思想是(若单词在越少的文档中出现,则其对文档的区分能力也就越强)
但是,TF仅衡量了单词出现的频次,但没有考虑到单词对文档的区分程度,而IDF强调的是单词的区分能力。
详情参考:https://blog.csdn.net/baimafujinji/article/details/51476117
TextRank算法
基本思想来源于Google的PageRank算法,类似于“一篇论文的价值有时会取决于被引用的次数”
PageRank算法
- 如果一个网页被很多其他网页链接到的话说明这个网页比较重要,也就是PageRank值会相对较高
- 如果一个PageRank值很高的网页链接到一个其他的网页,那么被链接到的网页的PageRank值会相应地因此而提高
用TextRank提取来提取关键词,用PageRank的思想来解释它:
TextRank算法
- 如果一个单词出现在很多单词后面的话,那么说明这个单词比较重要
- 一个TextRank值很高的单词后面跟着的一个单词,那么这个单词的TextRank值会相应地因此而提高
详细参考《【TextRank】关键词提取 算法原理 公式推导 源码分析》:https://blog.csdn.net/qq_41664845/article/details/82869596
LSA/LSI/LDA算法
在某些场景,基于文档本身的关键词提取还不是非常足够,有些关键词并不一定会显示地出现在文档当中,如一篇讲动物生存环境的科普文,通篇介绍狮子老虎等,但是文中并没有显示地出现动物二字。
主题模型认为在词与文档之间没有直接的联系,它们应当还有一个维度将它们串联起来,主题模型将这个维度称为主题。每个文档都应该对应着一个或多个的主题,而每个主题都会有对应的词分布,通过主题,就可以得到每个文档的词分布。
目前常用的方法有LSA(LSI)和LDA,其中LSA主要采用SVD(奇异值分解)方法进行暴力破解,LDA通过贝叶斯学派方法对待提取文本进行拟合。
TF-IDF算法需要基于一个现成的语料库,比如TF-IDF需要逆文档率(统计每个词在语料库中的多少个文档内有出现过)
主题模型的关键词提取算法需要通过对大规模文档的学习,来发现文档的隐含主题。
TextRank算法则可以脱离语料库,仅对单片文档进行分析就可以提取该文档的关键词。
实战提取文本关键词
主要使用jieba和gensim库。jieba库中主要使用其在analyse模块封装的TextRank算法。gensim用于从原始的非结构化的文本中,无监督地学习到文本隐藏的主题向量表达,在这里我们主要调用gensim中LSI和LDA模型的接口实现。
关键词提取算法的步骤:
1、加载已有的文档数据集
2、加载停用词表
3、对文档进行分词
4、根据停用词表。过滤干扰词
5、根据数据集训练算法新算法训练好后,对于新的文档进行一下步骤
1、新文档分词
2、根据停用词表过滤干扰词
3、根据训练好的算法提取关键词
TD-LDF训练:根据训练文本生成单词的对应IDF值字典,后续对于已经存在的单词直接读取即可。
LSI和LDA训练 :根据训练文本数据集生成“文档-主题分布矩阵”和“主题-单词分布矩阵”,gensim中有相应的接口方法,直接调用
# -*- coding: utf-8 -*-
import math
import jieba
import jieba.posseg as psg
from gensim import corpora, models
from jieba import analyse
import functools
# 导入functools库,主要使用cmp_to_key函数,python3中sorted函数废弃了cmp参数,
# 停用词表加载方法
def get_stopword_list():
# 停用词表存储路径,每一行为一个词,按行读取进行加载
# 进行编码转换确保匹配准确率
stop_word_path = './stopword.txt'
stopword_list = [sw.replace('\n', '') for sw in open(stop_word_path,encoding="utf-8").readlines()]
# 按行读取停用词表,并去除掉其中的“\n”
return stopword_list
# 最终返回一个列表
# 分词方法,调用结巴接口。pos是判断是否采用词性标注的参数
def seg_to_list(sentence, pos):
if not pos:
# 不进行词性标注的分词方法
seg_list = jieba.cut(sentence)
else:
# 进行词性标注的分词方法
seg_list = psg.cut(sentence)
# 返回分词迭代器
return seg_list
# 去除干扰词
# 根据分词结果对干扰词进行过滤,根据pos判断是否过滤除名词外的其他词性,
# 再判断词是否在停用词表中+长度是否大于2
def word_filter(seg_list, pos):
# 得到停用词表,并根据是否过滤来筛选词性
stopword_list = get_stopword_list()
filter_list = []
# 根据POS参数选择是否词性过滤
## 不进行词性过滤,则将词性都标记为n,表示全部保留
for seg in seg_list:
if not pos:
word = seg # 词汇
flag = 'n' # 词性
else:
word = seg.word
flag = seg.flag
if not flag.startswith('n'):
continue
'''
str.startswith(str, beg=0,end=len(string));
str -- 这是要检查的字符串。
beg -- 这是可选的参数设置匹配边界的初始索引。
end -- 这是可选的参数设置匹配边界的结束索引
'''
# 过滤停用词表中的词,以及长度为<2的词
# 该词不在停用词内 + 词的长度>=2 才会选为备选词,否则不予理睬
if not word in stopword_list and len(word) > 1:
filter_list.append(word)
return filter_list
# 数据加载,pos为是否词性标注的参数,corpus_path为数据集路径
# 加载数据集并进行数据分词+过滤干扰词,原始数据集是一行行的文本,按行读取后对文本进行分词、过滤掉干扰词
# 最终返回一个非干扰词组成的词语列表
def load_data(pos, corpus_path='./corpus.txt'):
# 调用上面方式对数据集进行处理,处理后的每条数据仅保留非干扰词
doc_list = []
for line in open(corpus_path, 'r',encoding="utf-8"):
# 剔除文本行前后的空格
content = line.strip()
# 调用分词函数
seg_list = seg_to_list(content, pos)
# 调用过滤函数
filter_list = word_filter(seg_list, pos)
# 处理后的文本行加入 纯洁文本词列表中
doc_list.append(filter_list)
return doc_list
'''
'''
# idf值统计方法
def train_idf(doc_list):
# TF-IDF根据”训练文本“生成对应的IDF值字典,后续需要时,直接从字典读取即可
idf_dic = {}
# 总文档数
tt_count = len(doc_list)
# 统计“训练文本集合doc_list”中每个词出现的文档数量
for doc in doc_list:
# 训练文本doc
for word in set(doc):
# set集合去重,统计谋篇训练文本中单词出现的次数
idf_dic[word] = idf_dic.get(word, 0.0) + 1.0
# get(word, 0.0)获取word的次数,如果None,则设置为0.0;不为空,则得到真实值
# 按公式转换为idf值,分母加1进行平滑处理(拉普拉斯平滑,避免有些新词在语料库中未出现,导致分母为0的情况)
for k, v in idf_dic.items():
# 文档集中总文档数量tt_count/(词k出现的文档数量v+1)
idf_dic[k] = math.log(tt_count / (1.0 + v))
# 对于没有在字典中的词,默认其仅在一个文档出现,得到默认idf值
default_idf = math.log(tt_count / (1.0))
return idf_dic, default_idf
# 排序函数,用于topK关键词的按值排序,1、按关键词的计算分值排序;2、得分相同时,根据关键词排序
def cmp(e1, e2):
import numpy as np
# 得到相减的计算符号
res = np.sign(e1[1] - e2[1])
if res != 0:
return res
else:
# res==0,也就是得分相同的情况
a = e1[0] + e2[0]
b = e2[0] + e1[0]
if a > b:
return 1
elif a == b:
return 0
else:
return -1
# TF-IDF类
'''
1、根据要处理的文本,计算每个词的tf值
2、获取前面训练后的IDF数据,直接获取每个词的IDF值
3、综合计算每个词的IDF值
'''
class TfIdf(object):
# 四个参数分别是:训练好的idf字典,默认idf值,处理后的待提取文本,关键词数量
def __init__(self, idf_dic, default_idf, word_list, keyword_num):
self.word_list = word_list
self.idf_dic, self.default_idf = idf_dic, default_idf
self.tf_dic = self.get_tf_dic()
self.keyword_num = keyword_num
# 计算 待提取文本 的tf值,并得到tf字典
def get_tf_dic(self):
tf_dic = {}
for word in self.word_list:
tf_dic[word] = tf_dic.get(word, 0.0) + 1.0
tt_count = len(self.word_list)
for k, v in tf_dic.items():
# k为单词,v为该单词出现的频次
tf_dic[k] = float(v) / tt_count
return tf_dic
# 按公式计算tf-idf
def get_tfidf(self):
tfidf_dic = {}
for word in self.word_list:
# 没有在字典中的词,默认频次为1,称《default_idf》
idf = self.idf_dic.get(word, self.default_idf)
tf = self.tf_dic.get(word, 0)
tfidf = tf * idf
tfidf_dic[word] = tfidf
tfidf_dic.items()
# 根据tf-idf排序,去排名前keyword_num的词作为关键词
for k, v in sorted(tfidf_dic.items(), key=functools.cmp_to_key(cmp), reverse=True)[:self.keyword_num]:
print(k + " ", end='')
print()
# 主题模型
'''
LSA主要步骤:
1、BOW模型将每个文档表示为向量
2、文档词向量拼接称(词-文档矩阵(m*n))
3、词-文档矩阵进行奇异值分解(SVD)操作-->[m*r][r*r][r*n]
4、根据SVD结果,词-文档矩阵继续映射到更低维度k([m*k][k*k][k*n],0