目录
- 关键词提取技术介绍概述
- 关键词提取算法TF-IDF
- TextRank算法
- LSA/LSI/LDA算法
- 实战提取文本关键词
1. 关键词提取技术概述
- 有监督
主要通过分类的方式进行,通过构建一个丰富和完善的词表,然后通过判断每个文档与词表中每个文档与词表中每个词的匹配程度,以类似打标签的方式,从而达到关键词提取的效果。能够获得较高精度,但是需要大批量的标注数据,人工成本较高;
- 无监督
不需人工生成、维护的词表,也不需要人工标注语料辅助进行训练,主要有TF-IDF算法、TextRank算法和主题模型算法(LSA、LSI、LDA等);
2. 关键词提取算法TF-IDF
TF-IDF(Term Frequency-Inverse Document Frequency,词频-逆文档频次算法):基于统计的计算方法,常用于评估一个文档集中一个词对某份文档的重要程度。
TF算法统计一个词在一篇文档中出现的频次,基本思想为:一个词在文档中出现的次数越多,则对文档的表达能力也越强。
t f i j = n i j ∑ k n k j tf_{ij}=\frac{n_{ij}}{\sum k n{kj}} tfij=∑knkjnij
IDF算法统计一个词在文档集中的多少个文档中出现,基本思想为:若一个词在越少的文档中出现,则对文档的区分能力越强。
i d f i = l o g ( ∣ D ∣ 1 + ∣ D i ∣ ) idf_i=log(\frac{|D|}{1+|D_i|}) idfi=log(1+∣Di∣∣D∣)
TF-IDF算法公式:
t f − i d f ( i , j ) = t f i j × i d f i = n i j ∑ k n k j × l o g ( ∣ D ∣ 1 + ∣ D i ∣ ) tf-idf(i,j) = tf_{ij} \times idf_i = \frac{n_{ij}}{\sum k n{kj}} \times log(\frac{|D|}{1+|D_i|}) tf−idf(i,j)=tfij×idfi=∑knkjnij×log(1+∣Di∣∣D∣)
∣ D ∣ |D| ∣D∣为文档集中总文档树, ∣ D i ∣ |D_i| ∣Di∣是文档集中出现词 i i i的文档数量,而分母 + 1 +1 +1则是采用拉普拉斯平滑,避免有部分新词在语料库中未出现而导致分母为零的情况;
关键词数量可以不止一个,可以根据tf-idf值由大到小排序取前n个作为关键词。
3. TextRank算法
TextRank算法可以脱离语料库,仅对单篇文档进行分析从而提取该文档的关键词。
最早用于文档自动摘要,基于句子维度的分析,利用TextRank对每个句子进行打分,挑出分数最高的 n n n个句子作为文档的关键句,从而达到自动摘要的效果,基本思想源于PageRank算法;
PageRank算法是一种网页排名算法,其基本思想有两条:
1):链接数量。一个网页被越多的其他网页链接,说明这个网页越重要。
2 )链接质量。 一个网页被一个越高权值的网页链接,也能表明这个网页越重要。
PageRank算法计算示意图
S ( V i ) = ( 1 − d ) + d × ∑ j ∈ l n ( V j ) ( 1 ∣ O u t ( V j ) ∣ × S ( V j ) ) S(V_i)=(1-d)+d\times \sum_{j \in ln(V_j)}(\frac {1}{|Out(V_j)|}\times S(V_j)) S(Vi)=(1−d)+d×j∈ln(Vj)∑(∣Out(Vj)∣1×S(Vj))
TextRank
W
S
(
V
i
)
=
(
1
−
d
)
+
d
×
∑
V
j
∈
l
n
(
V
i
)
(
w
j
i
∑
V
k
∈
O
u
t
(
V
j
)
w
j
k
×
W
S
(
V
j
)
)
WS(V_i)=(1-d)+d \times \sum_{V_j \in ln(V_i)}(\frac {w_{ji}}{\sum_{V_{k \in Out(V_j){w{jk}}}}}\times WS(V_j))
WS(Vi)=(1−d)+d×Vj∈ln(Vi)∑(∑Vk∈Out(Vj)wjkwji×WS(Vj))
PageRank是有向无权图,而TextRank进行自动摘要则是有权图。当TextRank应用到关键词提取时,与自动摘要中主要有两点不同:
1. 词与词之间的关联无权重
2. 每个词不是与文档中所有词都有链接
由于第1点,故TextRank中的分数计算公式就变为PageRank一直,通过将得分平均贡献给每个链接的词:
W S ( V i ) = ( 1 − d ) + d × ∑ j ∈ l n ( V i ) ( 1 ∣ O u t ( V j ) ∣ × W S ( V j ) ) WS(V_i)=(1-d)+d \times \sum_{j \in ln(V_i)}(\frac {1}{|Out(V_j)|}\times WS(V_j)) WS(Vi)=(1−d)+d×j∈ln(Vi)∑(∣Out(Vj)∣1×WS(Vj))
第二点,提出了一个窗口的概念,在窗口中的词相互间都有链接关系。
4. LSA/LSI/LDA算法
LSA/LSI算法
LSA(Latent Semantic Analysis,潜在语义分析)主要利用SVD(奇异值分解)的方法进行暴力破解,和LSI(Latent Semantic Index,潜在语义索引)通常被认为是同一种算法,只有应用场景略有不同;
LSA的主要步骤:
1)使用BOW模型将每个文档表示为向量;
2)将所有的文档词向量拼接构成词-文档矩阵(
m
×
n
m\times n
m×n);
3)对词-文档矩阵进行SVD操作(
[
m
×
r
]
⋅
[
r
×
r
]
⋅
[
r
×
n
]
[m \times r]\cdot[r\times r]\cdot[r\times n]
[m×r]⋅[r×r]⋅[r×n]);
4)根据SVD的结果,将词-文档矩阵进行奇异值分解到更低维度
k
k
k(
[
m
×
k
]
⋅
[
k
×
k
]
⋅
[
k
×
n
]
,
0
<
k
<
r
[m \times k]\cdot[k\times k]\cdot[k\times n],0<k<r
[m×k]⋅[k×k]⋅[k×n],0<k<r)的近似SVD结果中,每个词和文档均可表示为
k
k
k个主题构成的空间中的一个点,通过计算每个词和文档的相似度(余弦相似度或KL相似度),然后得到每个文档中对每个词的相似度结果,取相似度最高的一个词即为文档关键词。
优点:可映射到低维空间,在有限利用文本语义信息的同时,大幅度降低计算代价,提高分析质量;
缺点:SVD计算复杂度非常高,特征空间维度较大,因此计算效率十分低下。同时,LSA对词的频率分布不敏感,物理解释性薄弱;
LDA(Latent Dirichlet Allocation,隐含狄利克雷分布)算法
定义:基于贝叶斯理论,根据对词的共现信息的分析,拟合出词-文档-主题的分布,进而将词、文本都映射到一个语义空间中;
结合吉布斯采样的LDA模型函数训练过程:
1)随机初始化,对每篇文档中的每个词
w
w
w,随机赋予一个topic编号
z
z
z;
2)重新扫描语料库,对每个词
w
w
w按吉布斯采样公式重新采样其topic,然后在语料中进行更新;
3)重复上述语料库的重采样过程指导吉布斯采样收敛;
4)统计语料库中topic-word贡献频率矩阵,即为LDA模型;
经过以上的步骤,就得到一个训练好的LDA模型,接下来就可以按照一定的方式对新文档的topic进行预估,具体步骤如下:
1 )随机初始化,对当前文档中的每个词 w ,随机地赋一个 topic 编号 z。
2) 重新扫描当前文档,按照吉布斯采样公式,重新采样它的 topic。
3 )重复以上过程直到吉布斯采样收敛。
4) 统计文裆中的 topic 分布即为预估结果。
5. 实战提取文本关键词
训练关键词提取算法的几个步骤:
1)加载已有文档数据集;
2)加载停用词表;
3)对数据集中的文档进行分词;
4)根据停用词表,过滤干扰词;
5)根据数据集训练算法;
利用训练好的算法对新文档进行关键词提取的环节:
1)对新文档进行分词;
2)根据停用词表,过滤干扰词;
3)根据训练好的算法提取关键词;
实现代码
import math
import jieba
import jieba.posseg as psg
from jieba import analyse
import numpy as np
from gensim import corpora, models
import functools
def get_stopword_list(stopword_path = './stopword.txt'):
result = []
with open(stopword_path, encoding='utf-8') as f:
for line in f:
line = line.strip()
result.append(line)
return result
def seg_to_list(sentence, pos = False):
if not pos:
seg_list = jieba.cut(sentence)
else:
seg_list = psg.cut(sentence)
return seg_list
def word_filter(seg_list, pos = False):
for seg in seg_list:
if not pos:
word = seg
else:
if not seg.flag.startswith('n'):
continue
word = seg.word
if word not in stopword_list and len(word) > 1:
filter_list.append(word)
return filter_list
def load_data(pos = False,corpus_path = './corpus.txt'):
doc_list = []
with open(corpus_path, encoding='utf-8') as f:
for line in f:
line = line.strip()
seg_list = seg_to_list(line, pos)
filter_list = word_filter(seg_list, pos)
doc_list.append(filter_list)
return doc_list
def train_idf(doc_list):
idf_dic = {}
tt_count = len(doc_list)
for doc in doc_list:
for word in set(doc):
idf_dic[word] = idf_dic.get(word, 0.0) + 1.0
for k,v in idf_dic.items():
idf_dic[k] = math.log(tt_count / (1.0 + v))
default_idf = math.log(tt_count / (1.0))
return idf_dic, default_idf
def cmp(e1, e2):
res = np.sign(e1[1] - e2[1])
if res != 0:
return res
else:
a = e1[0] + e2[0]
b = e2[0] + e1[0]
if a > b:
return 1
elif a == b:
return 0
else:
return -1
class TfIdf:
def __init__(self, idf_dic, default_idf, word_list, keyword_num):
self.word_list = word_list
self.idf_dic = idf_dic
self.default_idf = default_idf
self.tf_dic = self.get_tf_dic()
self.keyword_num = keyword_num
def get_tf_dic(self):
tf_dic = {}
for word in self.word_list:
tf_dic[word] = tf_dic.get(word, 0) + 1.0
for k,v in tf_dic.items():
tf_dic[k] = float(v) / len(self.word_list)
return tf_dic
def get_tfidf(self):
tfidf_dic = {}
for word in self.word_list:
idf = self.idf_dic.get(word, self.default_idf)
tf = self.tf_dic.get(word, 0.0)
tfidf = tf * idf
tfidf_dic[word] = tfidf
for k,v in sorted(tfidf_dic.items(), key=functools.cmp_to_key(cmp),reverse=True)[:self.keyword_num]:
print(k + '/', end = '')
print()
class TopicModel:
def __init__(self, doc_list, keyword_num, model = 'LSI', num_topics = 4):
self.dictionary = corpora.Dictionary(doc_list)
corpus = [self.dictionary.doc2bow(doc) for doc in doc_list]
self.tfidf_model = models.TfidfModel(corpus)
self.corpus_tfidf = self.tfidf_model[corpus]
self.keyword_num = keyword_num
self.num_topics = num_topics
if model == 'LSI':
self.model = self.train_lsi()
else:
self.model = self.train_lda()
self.wordtopic_dic = self.get_wordtopic(doc_list)
def train_lsi(self):
return models.LsiModel(self.corpus_tfidf, id2word=self.dictionary,num_topics=self.num_topics)
def train_lda(self):
return models.LdaModel(self.corpus_tfidf, id2word=self.dictionary,num_topics=self.num_topics)
def get_wordtopic(self, input_doc_list):
def word_dictionary(doc_list):
dictionary = []
for doc in doc_list:
dictionary.extend(doc)
dictionary = list(set(dictionary))
return dictionary
word_dic = word_dictionary(input_doc_list)
wordtopic_dic = {}
for word in word_dic:
single_list = [word]
wordcorpus = self.tfidf_model[self.dictionary.doc2bow(single_list)]
wordtopic = self.model[wordcorpus]
wordtopic_dic[word] = wordtopic
return wordtopic_dic
def get_simword(self, word_list):
sentcorpus = self.tfidf_model[self.dictionary.doc2bow(word_list)]
senttopic = self.model[sentcorpus]
def cal_sim(A, B):
sum_a, sum_b, product = 0.0, 0.0, 0.0
for a, b in zip(A, B):
Ai = a[1]
Bi = b[1]
sum_a += Ai * Ai
sum_b += Bi * Bi
product += Ai * Bi
sim = 0.0
if sum_a * sum_b != 0.0:
sim = product / math.sqrt(sum_a * sum_b)
return sim
sim_dic = {}
for k, v in self.wordtopic_dic.items():
if k not in word_list:
continue
sim = cal_sim(v, senttopic)
sim_dic[k] = sim
for k, v in sorted(sim_dic.items(), key=functools.cmp_to_key(cmp),
reverse=True)[:self.keyword_num]:
print(k + '/ ', end='')
print()
def tfidf_extract(word_list, pos=False, keyword_num=10):
doc_list = load_data(pos)
idf_dic, default_idf = train_idf(doc_list)
train_model = TfIdf(idf_dic, default_idf, word_list, keyword_num)
train_model.get_tfidf()
def textrank_extract(text, pos=False, keyword_num=10):
textrank = analyse.textrank
keywords = textrank(text, keyword_num)
for keyword in keywords:
print(keyword + '/ ', end='')
print()
def topic_extract(word_list, model, pos=False, keyword_num=10):
doc_list = load_data(pos)
topic_model = TopicModel(doc_list, keyword_num, model=model)
topic_model.get_simword(word_list)
if __name__ == '__main__':
with open('./test.txt', encoding='utf8') as f:
text = f.read()
pos = True
seg_list = seg_to_list(text, pos)
filter_list = word_filter(seg_list, pos)
print("TF-IDF模型结果:")
tfidf_extract(filter_list)
print("TextRank模型结果:")
textrank_extract(text)
print("LSI模型结果:")
topic_extract(filter_list, 'LSI', pos)
print("LDA模型结果:")
topic_extract(filter_list, 'LDA', pos)