目录:
本篇文章做的内容:
- 使用使用三种方式实现tf-idf(sklearn,gensim,python)
- 使用tf-idf结合余弦相似度计算文档之间的相似度
- 面试中相关问题
TF-IDF(Term Frequency–Inverse Document Frequency)
是一种用于信息检索与文本挖掘的常用加权技术。TF-IDF
是一种统计方法,用以评估一字词对于一个文档集或一个语料库中的其中一份文档的重要程度。字词的重要性随着它在文档中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。简单的解释为,一个单词在一个文档中出现次数很多,同时在其他文档中出现此时较少,那么我们认为这个单词对该文档是非常重要的。
在一份给定的文档中,词频(term frequency,tf)指的是某一个给定的词语在该文档中出现的频率。为了以防止它偏向长文档(同一个词语在长文档里可能会比短文档有更高的词数,而不管该词语重要与否),我们常对词数(term count)的归一化处理。对于在某一特定文档里的词语 $w$ 来说,它的重要性有一下三种表示方式: \[ tf = 词w在文章中的出现次数 \] \[ tf = \frac{词w在文章中的出现次数}{文章的总词数} \] \[ tf = \frac{词w在文章中的出现次数}{该文中出现次数最多的词的出现次数} \]
逆向文档频率(inverse document frequency,idf) 是一个词语普遍重要性的度量。某一特定词语的idf
,可以由总文档数目除以包含该词的文档的数目,再将得到的商取对数加1得到:
\[
idf = \log{\frac{N}{N(w)}+1}
\]
其中:
N
:语料库中的文档总数
N(w)
:包含词语 $w$ 的文档数目。
注意:
- log可以使以e为底、以10为底和以2为底。在参考文献[1]里面是以2为底,在参考文献[2]中是以e为底,在参考文献[3]中是以10为底。
- 如果单词$w$在所有的文档中都出现过,则idf值为0。为了避免这个状况,在尾部添加了1。
如果词语没有在文档中出现过,就导致分母为零,因此一般情况下使用常见的平滑方式(分子分母同时加1),原式变为
\[
idf = \log{\frac{N+1}{N(w)+1}+1}
\]
然后通过下面公式计算tf-idf
值:
\[
tfidf = tf * idf
\]
总的来说,在某一特定文档中,给定词在该文档中出现的频率高,并且该词在整个文档集合中的频率低,产生出高权重的tf-idf
。因此,tf-idf
倾向于过滤掉常见的词语,保留重要的词语。
举个栗子(只是一种计算方式): 词频(tf)是一词出现的次数除以该文档的总词语数。假如一篇文档的总词语数是
100
个,而词语“路飞”出现了3
次,那么“路飞”一词在该文档中的词频就是3/100=0.03
。而计算逆文档频率(IDF)的方法是以文档总数除以出现“路飞”一词的文档数。所以,如果“路飞”一词在1,000个文档出现过,而文档总数是10,000,000份的话,其逆向文档频率就是lg(10,000,000 / 1,000)=4。最后的tf-idf
的分数为0.03 * 4=0.12。
在实现时注意的两点:
- 相同单词在同一个文档中的TF-IDF值应该是一样的。
- 相同单词在不同文档中的TF-IDF值应该是不一定相同的,因为不同文档单词出现的频率不一定相同。
TfidfVectorizer
这个类,会将原始文档转化为TF-IDF特征矩阵。
类定义如下:class sklearn.feature_extraction.text.TfidfVectorizer(input=’content’, encoding=’utf-8’, decode_error=’strict’, strip_accents=None, lowercase=True, preprocessor=None, tokenizer=None, analyzer=’word’, stop_words=None, token_pattern=’(?u)\b\w\w+\b’, ngram_range=(1, 1), max_df=1.0, min_df=1, max_features=None, vocabulary=None, binary=False, dtype=<class ‘numpy.float64’>, norm=’l2’, use_idf=True, smooth_idf=True, sublinear_tf=False)
值得一提的几个参数:
lowercase: 在tokenizing之前,进行了转小写的处理,默认为True。
analyzer: 解析器,指导TfidfVectorizer以何种(n-gram)的方式去解析文档。提供的方式包括:‘word’,‘char’和‘char_wb’。如果使用‘char_wb’,无论是特征由词n-gram还是字符n-gram组成,都会创建字符的n-gram特征。
tokenizer: 当analyzer==’word’时,在保留预处理过程和n-gram生成步骤时会重写字符串的tokenization方法。默认为None。
stop_words: 默认情况下不去停用词。可以传入字符串’english‘,这是唯一支持的字符串,也可以传入停用词列表。默认值为None。
norm: 输出的每一行都有一个单元范式,l2范式表示向量元素的平方和为1,如果使用l2范式,两个向量的点积就是他们的余弦相似度。l1范式表示向量元素的绝对值之和。
use_idf: 使idf权重可以更新(一般在训练中为True),默认为True。
smooth_idf: 默认使用了平滑版的计算公式,默认为True。
四个属性:
vocabulary_:类会对训练中出现过的词进行去重后排序,因此每个单词又有自己的编号,该属性以字典的方式返回单词以及对应的编号。如果只想获取单词列表可以使用get_feature_names()方法获取。
fixed_vocabulary_: 如果用户提供索引单词到编号(索引)的固定词汇表,此属性返回True。
idf_: 当use_idf参数为True时,返回IDF向量。
stop_words_:返回停用词集合(set)。该方法仅用于自省,可以被安全删除。
训练代码:
from sklearn.feature_extraction.text import TfidfVectorizer
# 语料
documents_train = [
'An Apple orange Apple.',
'Chinese is an banana.',
'Chinese KongFu.',
'Apple is not pen.'
]
# 实例化
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(documents_train)
# 将语料库去重后,返回单词列表
print(vectorizer.get_feature_names())
运行结果:
['an', 'apple', 'banana', 'chinese', 'is', 'kongfu', 'not', 'orange', 'pen']
代码:
# 以字典的形式返回输入语料库中的单词和对应的序号
print(vectorizer.vocabulary_)
运行结果:
{'an': 0, 'apple': 1, 'orange': 7, 'chinese': 3, 'is': 4, 'banana': 2, 'kongfu': 5, 'not': 6, 'pen': 8}
代码:
# 输出每句话的TF-IDF权重矩阵。每句话权重向量都是和特征单词表对应。
print(tfidf_matrix.toarray())
运行结果:
[[0.3889911 0.7779822 0. 0. 0. 0.
0. 0.49338588 0. ]
[0.46580855 0. 0.59081908 0.46580855 0.46580855 0.
0. 0. 0. ]
[0. 0. 0. 0.6191303 0. 0.78528828
0. 0. 0. ]
[0. 0.43779123 0. 0. 0.43779123 0.
0.55528266 0. 0.55528266]]
测试代码:
documents_test = [
'Apple Store.',
'Chinese people is Lee.'
]
test_fit = vectorizer.transform(test)
print(vectorizer.get_feature_names())
print(vectorizer.vocabulary_)
print(test_fit.toarray())
运行结果:
['apple', 'chinese', 'is', 'lee', 'people', 'store']
{'apple': 0, 'store': 5, 'chinese': 1, 'people': 4, 'is': 2, 'lee': 3}
[[0.70710678 0. 0. 0. 0. 0.70710678]
[0. 0.5 0.5 0.5 0.5 0. ]]
步骤:
- 对语料进行分词
- 创建字典
- 训练模型并保存模型
- 加载模型并测试语料
训练代码:
# 对语料进行分词
docs_list = []
for document in documents:
document = document.lower()[:-1]
docs_list.append(document.split(" "))
# 语料库去重后生成 Dictionary 对象
dictionary = corpora.Dictionary(docs_list)
# 将文档表示成向量
new_corpus = [dictionary.doc2bow(text) for text in docs_list]
print(new_corpus)
print(dictionary.token2id)
运行结果:
Dictionary(9 unique tokens: ['an', 'apple', 'orange', 'banana', 'chinese']...)
[[(0, 1), (1, 2), (2, 1)], [(0, 1), (3, 1), (4, 1), (5, 1)], [(4, 1), (6, 1)], [(1, 1), (5, 1), (7, 1), (8, 1)]]
{'an': 0, 'apple': 1, 'orange': 2, 'banana': 3, 'chinese': 4, 'is': 5, 'kongfu': 6, 'not': 7, 'pen': 8}
注意:
- new_corpus的格式为元素为(index,frequence)元组的列表。
- new_corpus中,由于每句话相同词的TF-IDF值相同,所以进行了去重。
- dictionary.token2id 属性可以看到每个token对应的index。
查看训练结果并保存模型代码:
# 训练模型并保存
tfidf_model = models.TfidfModel(new_corpus)
tfidf_model.save("my_model.tfidf")
# 查看训练结果
tfidf_vec = []
for i in range(len(new_corpus)):
tfidf_vec.append(tfidf_model[new_corpus[i]])
print(tfidf_vec)
运行结果:
[[(0, 0.3333333333333333), (1, 0.6666666666666666), (2, 0.6666666666666666)], [(0, 0.3779644730092272), (3, 0.7559289460184544), (4, 0.3779644730092272), (5, 0.3779644730092272)], [(4, 0.4472135954999579), (6, 0.8944271909999159)], [(1, 0.31622776601683794), (5, 0.31622776601683794), (7, 0.6324555320336759), (8, 0.6324555320336759)]]
测试语料代码:
def gensim_tf_idf_test(dictionary, doc_list_test):
# 载入模型
tfidf_model = models.TfidfModel.load("my_model.tfidf")
# 使用这个训练好的模型得到单词的tfidf值
# 查看训练结果
tfidf_vec = []
for i in range(len(doc_list_test)):
corpus = dictionary.doc2bow(doc_list_test[i])
tfidf_vec.append(tfidf_model[corpus])
print(tfidf_vec)
运行结果:
[[(1, 1.0)], [(4, 0.7071067811865475), (5, 0.7071067811865475)]]
注意:
- 训练和测试时使用的词袋模型是相同的,所以传入训练时得到的dictionary即可。
- 测试时寻到训练时没有出现的词,Gensim会自动忽略。
这里使用Python实现训练阶段模型的搭建。
具体步骤如下:
- 遍历所有文档,因为不同文档中相同的单词tf-idf值也是不同的。
- 每篇文档中的单词去重,并计算tf-idf得分。
代码:
# 计算每个单词的tf-idf
for i, document in enumerate(docs_list_train):
print("第{}个文档中,共{}个单词".format(i+1, len(document)))
# 去重, 每篇文章对应的分数
# 键值存在无需重复计算
score = {}
for word in document:
if word not in score.keys():
score[word] = tf_idf(word, document, docs_list_train)
# 排序
word_list_sorted = sorted(score.items(), key=lambda x: x[1], reverse=True)
print(word_list_sorted)
具体TF-IDF值的计算:
def tf_idf(word, document, docs_list):
# 文档d中词w出现的频率
tf_val = document.count(word) / len(document)
# 含有该单词的句子的总数
count_sum = sum(1 for document in docs_list if word in document)
idf_val = math.log((len(docs_list)) / (count_sum + 1) + 1)
return tf_val * idf_val
运行结果:
第1个文档中,共4个单词
[('apple', 0.4236489301936017), ('orange', 0.27465307216702745), ('an', 0.21182446509680086)]
第2个文档中,共4个单词
[('banana', 0.27465307216702745), ('chinese', 0.21182446509680086), ('is', 0.21182446509680086), ('an', 0.21182446509680086)]
第3个文档中,共2个单词
[('kongfu', 0.5493061443340549), ('chinese', 0.4236489301936017)]
第4个文档中,共4个单词
[('not', 0.27465307216702745), ('pen', 0.27465307216702745), ('apple', 0.21182446509680086), ('is', 0.21182446509680086)]
注意:
- 计算TF-IDF需要传入的三个变量分别为:单词,单词所在文档,所有文档。
- Gensim 的 tfidfmodel.TfidfModel 模块使用的是以2为底的对数,这里使用的是以 e 为底的对数。
余弦相似度这里就不做介绍了,这里讲解我们既然了解了TF-IDF,如何使用的问题。
计算文章相似度的过程大致如下:
- 使用TF-IDF算法,找出两篇文章的关键词;
- 每篇文章各取出若干个关键词(比如20个),合并成一个集合,计算每篇文章对于这个集合中的词的词频(为了避免文章长度的差异,可以使用相对词频);
- 生成两篇文章各自的词频向量;
- 计算两个向量的余弦相似度,值越大就表示越相似。
- TF-IDF 原理
- 手写 TF-IDF 代码
- 项目中的 TF-IDF 特征是如何融入到模型的
[ 1 ] 数学之美