文章目录
参考文章
1、TF-IDF算法介绍
~~~~ TF-IDF(term frequency-inverse document frequency,词频-逆向文件频率)是一种用于信息检索(infomation retrieval)与文本挖掘(test mining)的常用加权技术。
~~~~ TF-IDF是一种统计方法,用来评估一个字词对于一个文件集或语料库中的其中一份文件的重要程度。
~~~~ 字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。
主要思想
~~~~ 若是某个词在一篇文章中经常出现的频率高 ( ( (即 T F ( TF( TF(词频 ) ) )高 ) ) ),在其他文章中的TF却很低,那么可以认为这个词或者短语具有很好的类别区分能力,适合用来分类
TF词频(Term Frequency)
词频
(
T
F
)
(TF)
(TF)表示词条(关键字)在文本中出现频率。
一般会将数字归一化(一般是词频除以文章总词数),防止它偏向字数长的文件
t f i j = n i , j ∑ k n k , j tf_{ij}= \frac{n_{i,j}}{\sum_k n_{k,j}} tfij=∑knk,jni,j
即: T F w = w 词 条 的 出 现 次 数 所 有 词 条 数 目 TF_w = \frac{w词条的出现次数}{所有词条数目} TFw=所有词条数目w词条的出现次数
- n i , j n_{i,j} ni,j表示该词在文件 d j d_j dj中出现的次数
- ∑ k n k , j \sum_kn_{k,j} ∑knk,j表示文件 d j d_j dj中所有词汇出现的次数总和
- N N N表示该类中所有的词条数目, N w N_w Nw表示某一类中词条 w w w出现的次数
IDF是逆向文件频率(Inverse Document Frequency)
逆向文件频率(IDF):某一特定词语的IDF,可以由总文件数目除以包含该词语的文件的数目,再将结果取对数。
如果包含词条 i i i的文档越少,说明词条具有很好的类别区分能力
I D F i = log ∣ D ∣ ∣ { j : t i ∈ d j } ∣ IDF_i = \log{\frac{|D|}{|\lbrace j:t_i \in d_j \rbrace|}} IDFi=log∣{j:ti∈dj}∣∣D∣
- |D|是语料库中的文件总数
- ∣ { j : t i ∈ d j } ∣ |\lbrace j:t_i \in d_j \rbrace| ∣{j:ti∈dj}∣表示包含词语 t i t_i ti的文件数目(即 n i , j ≠ 0 n_{i,j \not = 0} ni,j=0)
- 但是分母可能为零,一般会在分母上 + 1 +1 +1
ps
若是某个词条在每个文章中都出现,那么
I
D
F
IDF
IDF就会计算为
0
0
0,这显然并不是我们想看到的,故我们可以改为以下公式
I
D
F
i
=
log
∣
D
∣
∣
{
j
:
t
i
∈
d
j
}
∣
+
1
IDF_i = \log{\frac{|D|}{|\lbrace j:t_i \in d_j \rbrace |}}+1
IDFi=log∣{j:ti∈dj}∣∣D∣+1
更平滑版本
I
D
F
i
=
log
1
+
∣
D
∣
1
+
∣
{
j
:
t
i
∈
d
j
}
∣
+
1
IDF_i = \log{\frac{1+|D|}{1+| \lbrace j:t_i \in d_j \rbrace |}}+1
IDFi=log1+∣{j:ti∈dj}∣1+∣D∣+1
推荐使用平滑版本: I D F = log 语 料 库 文 档 总 数 + 1 包 含 词 条 w 的 文 档 数 + 1 + 1 IDF = \log{\frac{语料库文档总数+1}{包含词条w的文档数+1}}+1 IDF=log包含词条w的文档数+1语料库文档总数+1+1
TF-IDF
~~~~ 某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此其倾向于过滤常见的词语,保留重要的词语。
T F − I D F = T F ∗ I D F TF-IDF = TF * IDF TF−IDF=TF∗IDF
2、 TF-IDF应用
- 搜索引擎
- 关键字提取
- 文本相似性
- 文本摘要
3、 算法实现
import numpy as np
from collections import Counter
import itertools
import matplotlib.pyplot as plt
import pickle
from matplotlib.pyplot import cm
import os
import utils
docs = [
"it is a good day, I like to stay here",
"I am happy to be here",
"I am bob",
"it is sunny today",
"I have a party today",
"it is a dog and that is a cat",
"there are dog and cat on the tree",
"I study hard this morning",
"today is a good day",
"tomorrow will be a good day",
"I like coffee, I like book and I like apple",
"I do not like it",
"I am kitty, I like bob",
"I do not care who like bob, but I like kitty",
"It is coffee time, bring your cup",
]
docs_words = [d.replace(",", "").split(" ") for d in docs]
vocab = set(itertools.chain(*docs_words))
v2i = {v: i for i, v in enumerate(vocab)}
i2v = {i: v for v, i in v2i.items()}
def show_tfidf(tfidf, vocb):
# [n_vocab, n_doc]
plt.imshow(tfidf, cmap="YlGn", vmin=tfidf.min(), vmax=tfidf.max())
plt.xticks(np.arange(tfidf.shape[1]+1), vocb, fontsize=6, rotation=90)
plt.yticks(np.arange(tfidf.shape[0]), np.arange(1, tfidf.shape[0]+1), fontsize=6)
plt.tight_layout()
plt.savefig("1.png")
def safe_log(x):
mask = x!=0
x[mask] = np.log(x[mask])
return x
#计算tf的多种不同方法
tf_methods = {
"log":lambda x:np.log(1+x),
"augmented":lambda x:0.5 + 0.5 * x/np.max(x,axis=1,keepdims=True),
"boolean":lambda x:np.minimum(x,1),
"log_avg":lambda x:(1+safe_log(x))/(1+safe_log(np.mean(x,axis=1,keepdims=True))),
}
def get_tf(method="log"):
'''
计算词频
:param method:
:return:
'''
_tf = np.zeros((len(vocab),len(docs)),dtype=np.float64)#shape(总词个数,文本docs数)
for i,d in enumerate(docs_words):
#计算每一句的单词个数
counter = Counter(d)
for v in counter.keys():
print(counter.most_common())
#v2i[v]单词在第i个词条中的出现次数/此词条中出现单词个数最多的单词的个数
_tf[v2i[v],i] = counter[v]/counter.most_common(1)[0][1]
weighted_tf = tf_methods.get(method,None)
if weighted_tf is None:
raise ValueError
return weighted_tf(_tf)
idf_methods = {
"log" : lambda x:np.log(len(docs)/(1+x)),
"prob":lambda x:np.maximum(0,np.log((len(docs)-x)/(x+1))),
"len_norm":lambda x:x/(np.sum(np.square(x))+1),
}
def get_idf(method="log"):
'''
IDF:IDF越低代表区分能力越弱
:param method:
:return:
'''
#词个数
df = np.zeros((len(i2v),1))
# 统计单词出现的词条个数,遍历每个单词
for i in range(len(i2v)):
d_count = 0
for d in docs_words:
#计算每个单词i2v[i]出现的词条个数,即docs中每句话中含有这个单词的个数
d_count += 1 if i2v[i] in d else 0
df[i,0] =d_count
print(df)
#选择idf的计算方式,从上面的字典中
idf_fn = idf_methods.get(method,None)
if idf_fn is None:
raise ValueError
return idf_fn(df)
def cosine_similarity(q,_tf_idf):
'''
:param q:
:param _tf_idf:
:return:
'''
unit_q = q/np.sqrt(np.sum(np.square(q),axis=0,keepdims=True))
unit_ds = _tf_idf/np.sqrt(np.sum(np.square(_tf_idf),axis=0,keepdims=True))
#Return a flattened array.
similarity = unit_ds.T.dot(unit_q).ravel()
return similarity
def docs_score(q,len_norm=False):
'''
:param q:
:param len_norm:
:return:
'''
q_words = q.replace(",","").split(" ")
#计算q中没有出现在语料库中的单词个数
unknown_v = 0
for v in set(q_words):
#此单词没有出现在v2i和i2v中,则将其加入进去
if v not in v2i:
v2i[v] = len(v2i)
i2v[len(v2i)-1] = v
unknown_v += 1
#若是有未知单词,更新idf和tf_idf
if unknown_v > 0:
#将未出现的单词加入到idf中,为新的_idf,_idf的shape为(len(v2i),1),添加到最后为(len(v2i)+unkown_v,1)
_idf = np.concatenate((idf,np.zeros((unknown_v,1),dtype=np.float)),axis=0)
_tf_idf = np.concatenate((tf_idf,np.zeros((unknown_v,tf_idf.shape[1]),dtype=np.float)),axis=0)
else:
_idf,_tf_idf = idf,tf_idf
#输入语句的字数统计
counter = Counter(q_words)
# 计算输入语句的词频
q_tf = np.zeros((len(_idf),1),dtype=np.float)
for v in counter.keys():
q_tf[v2i[v], 0] = counter[v]
q_vec = q_tf * _idf
q_scores = cosine_similarity(q_vec,_tf_idf)
if len_norm:
len_docs = [len(d) for d in docs_words]
q_scores = q_scores/np.array(len_docs)
return q_scores
def get_keywords(n=2):
for c in range(3):
col = tf_idf[:, c]
idx = np.argsort(col)[-n:]
print("doc{}, top{} keywords {}".format(c, n, [i2v[i] for i in idx]))
tf = get_tf() # [n_vocab, n_doc]
idf = get_idf() # [n_vocab, 1]
tf_idf = tf * idf # [n_vocab, n_doc]
print("tf shape(vecb in each docs): ", tf.shape)
print("\ntf samples:\n", tf[:2])
print("\nidf shape(vecb in all docs): ", idf.shape)
print("\nidf samples:\n", idf[:2])
print("\ntf_idf shape: ", tf_idf.shape)
print("\ntf_idf sample:\n", tf_idf[:2])
# test
get_keywords()
q = "I get a coffee cup"
scores = docs_score(q)
d_ids = scores.argsort()[-3:][::-1]
print("\ntop 3 docs for '{}':\n{}".format(q, [docs[i] for i in d_ids]))
show_tfidf(tf_idf.T, [i2v[i] for i in range(len(i2v))])
4、 不足之处
~~~~ IDF的简单结构并布恩那个有效地反映单词的重要程度和特征词的分布情况,使其无法很好地完成对权值调整的功能,所以TF-IDF的算法精度并不是很高,尤其是当文本集已经分类的情况下。
~~~~ 在本质上IDF是一种试图抑制噪声的加权,并且单纯地认为文本频率小的单词就越重要,文本频率的单词就越无用,这对于大部分文本信息,并不是完全正确的。IDF 的简单结构并不能使提取的关键词, 十分有效地反映单词的重要程度和特征词的分布情 况,使其无法很好地完成对权值调整的功能。尤其是在同类语料库中,这一方法有很大弊端,往往一些同类文本的关键词被盖。
TF-IDF算法实现简单快速,但是仍有许多不足之处:
(1)没有考虑特征词的位置因素对文本的区分度,词条出现在文档的不同位置时,对区分度的贡献大小是不一样的。
(2)按照传统TF-IDF,往往一些生僻词的IDF(反文档频率)会比较高、因此这些生僻词常会被误认为是文档关键词。
(3)传统TF-IDF中的IDF部分只考虑了特征词与它出现的文本数之间的关系,而忽略了特征项在一个类别中不同的类别间的分布情况。
(4)对于文档中出现次数较少的重要人名、地名信息提取效果不佳。