前面介绍了词向量的One-Hot模型以及词袋模型,这都是为了将离散符号表示的文本转换成数字表示,以提供给后续机器学习算法的使用。
词向量之One-Hot编码详解
词向量之词袋模型(BOW)详解
本文主要介绍另一种词向量的表示方法:TF-IDF。
现有一个问题,怎么表示一个词在文档中的重要性?
首先想的是文档中出现越多的词对文章来说就越重要,也就是说词在文档中的频率越大越重要,这里用TF(term frequency)来表示。但是又出现了另一个问题,“的”,“了”,“吗”这种词出现在文档中的频率一般也很高,但是它们又不带有实际意义,所以为了降低这些词的重要性,引入了IDF(inverse document frequency),若一个词在语料库中的很多文档中都出现,那么就让这个词的重要性降低一些。由此便产生了TF-IDF模型。
TF:单词在某一篇文章中的词频,一个词在文章中出现的频率,此处文章可以是一个句子,也可以是一整篇文章。
T F ( d , w ) = N ( w ) N ( d ) TF(d,w)=\frac{N(w)}{N(d)} TF(d,w)=N(d)N(w)
其中 N ( w ) N(w) N(w)表示 d d d这篇文章中词 w w w的总数, N ( d ) N(d) N(d)表示文档 d d d中的词的总数。
除此之外还有其他计算方式:
IDF:逆文档频率,指的是含有这个单词的文章占语料库中所有文章总数的比例的倒数。
I D F ( w ) = log N N ( w ) IDF(w)=\log{\frac{N}{N(w)}} IDF(w)=logN(w)N
其中, N N N指的是语料库中所有文档的总数, N ( w ) N(w) N(w)指的是语料库中含有词 w w w的文档的总数。
除此之外还有其他计算方式:
TF-IDF是TF乘以IDF:
T F − I D F ( w ) = T F ( d , w ) × I D F ( w ) TF-IDF(w)=TF(d,w)\times IDF(w) TF−IDF(w)=TF(d,w)×IDF(w)
举例输入:
文档1:我 爱 技术 人民。
文档2:我 爱 电脑。
文档3:我 爱 加班 的 人民。
计算TF:每篇文档的词频,例如文档1,“我”的词频就是 1 / 4 = 0.5 1/4=0.5 1/4=0.5
计算DF:文档频率,“人民”在三篇文档中出现了两次,所以 D F = 2 / 3 = 0.66667 DF=2/3=0.66667 DF=2/3=0.66667
计算IDF:逆文档频率,"人民” I D F = 1 D F = 3 / 2 = 1.5 IDF=\frac{1}{DF}=3/2=1.5 IDF=DF1=3/2=1.5
注意:
TF是针对一篇文档而言;IDF是对整个语料库而言的。
import numpy as np
class TFIDF(object):
"""
手写一个TFIDF统计类,只写最简单的一个实现
"""
def __init__(self, corpus):
"""
初始化
self.vob:词汇个数统计,dict格式
self.word_id:词汇编码id,dict格式
self.smooth_idf:平滑系数,关于平滑不多解释了
:param corpus:输入的语料
"""
self.word_id = {}
self.vob = {}
self.corpus = corpus
self.smooth_idf = 0.01
def fit_transform(self, corpus):
pass
def get_vob_fre(self):
"""
计算文本特特征的出现次数,也就是文本频率term frequency,但是没有除token总数,因为后面bincount计算不支持float
:return: 修改self.vob也就是修改词频统计字典
"""
# 统计各词出现个数
id = 0
for single_corpus in self.corpus:
if isinstance(single_corpus, list):
pass
if isinstance(single_corpus, str):
single_corpus = single_corpus.strip("\n").split(" ")
for word in single_corpus:
if word not in self.vob:
self.vob[word] = 1
self.word_id[word] = id
id += 1
else:
self.vob[word] += 1
# 生成矩阵
X = np.zeros((len(self.corpus), len(self.vob)))
for i in range(len(self.corpus)):
if isinstance(self.corpus[i], str):
single_corpus = self.corpus[i].strip("\n").split(" ")
else:
single_corpus = self.corpus[i]
for j in range(len(single_corpus)):
feature = single_corpus[j]
feature_id = self.word_id[feature]
X[i, feature_id] = self.vob[feature]
return X.astype(int) # 需要转化成int
def get_tf_idf(self):
"""
计算idf并生成最后的TFIDF矩阵
:return:
"""
X = self.get_vob_fre()
n_samples, n_features = X.shape
df = []
for i in range(n_features):
"""
这里是统计每个特征的非0的数量,也就是逆文档频率指数的分式中的分母,是为了计算idf
"""
df.append(n_samples - np.bincount(X[:,i])[0])
df = np.array(df)
# perform idf smoothing if required
df += int(self.smooth_idf)
n_samples += int(self.smooth_idf)
idf = np.log(n_samples / df) + 1
return X*idf/len(self.vob)
if __name__ == '__main__':
corpus = [["我", "a", "e"], ["我", "a", "c"], ["我", "a", "b"]]
test = TFIDF(corpus)
print(test.get_tf_idf())
为了避免分词,直接采用这种形式。
from gensim import corpora,similarities,models
import jieba
#第一步:确定语料库的语料和要进行判断的句子:
#wordlist作为语料库,语料库中有三句话,相当于三篇文章.比较sentences和wordlist中三句话的相似度
wordlist=['我喜欢编程','我想变漂亮','今天吃午饭了吗']
#注意这里是个字符串
sentenses='我喜欢什么'
#第二步:使用语料库建立词典,也就是给预料库中的每个单词标上序号,类似:{'我':1,'喜欢':2,'编程':3,....}首先进行中文分词
text=[[word for word in jieba.cut(words) ]for words in wordlist]
dictionary=corpora.Dictionary(text)
#第三步,对语料中的每个词进行词频统计,doc2bow是对每一句话进行词频统计,传入的是一个list
#corpus得到的是一个二维数组[[(0, 1), (1, 1), (2, 1)], [(3, 1), (4, 1)], [(5, 1), (6, 1), (7, 1), (8, 1), (9, 1)]],意思是编号为0的词出现的频率是1次,编号为2的词出现的频率是1次
corpus=[dictionary.doc2bow(word) for word in text]
#第四步:使用corpus训练tfidf模型
model=models.TfidfModel(corpus)
#要是想要看tfidf的值的话可以:
tfidf=model[corpus]
'''
tfidf的结果是语料库中每个词的tfidf值
[(0, 0.5773502691896258), (1, 0.5773502691896258), (2, 0.5773502691896258)]
[(3, 0.7071067811865475), (4, 0.7071067811865475)]
[(5, 0.4472135954999579), (6, 0.4472135954999579), (7, 0.4472135954999579), (8, 0.4472135954999579), (9, 0.4472135954999579)]
'''
#第五步:为tfidf模型中的每个句子建立索引,便于进行相似度查询,传入的时候语料库的tfidf值
similarity=similarities.MatrixSimilarity(tfidf)
#第六步,处理要比较的句子,首先分词,其次获得词频,jieba只能传入字符串
sen=[word for word in jieba.cut(sentenses)]
//doc2传入的是一个列表
sen2=dictionary.doc2bow(sen)
#然后计算它的tfidf值
sen_tfidf=model[sen2]
#获得与所有句子的相似度,sim输出的是一个数组
sim=similarity[sen_tfidf]
#获得最大值,转化成list进行操作,使用list的max函数
max_sim=max(list(sim))
#获得最大值索引,使用list的index函数
max_index=sim.index(max)
tfidf如何表示一个句子:
加入一个句子有n个单词,每个单词计算出它的tfidf值,即每个单词用一个标量表示,则句子的维度是1n
如果是用embedding表示法,每个单词用m维向量表示,句子的维度是mn
保存和加载模型的方法:
# 保存词典:
dictionary.save(DICT_PATH)
# 保存tfidf模型
model.save(MODEL_PATH)
# 保存相似度
similarity.save(SIMILARITY_PATH)
# 加载词典:
dictionary = corpora.Dictionary.load('require_files/dictionary.dict')
# 加载模型
tfidf = models.TfidfModel.load("require_files/my_model.tfidf")
# 加载相似度
index=similarities.MatrixSimilarity.load('require_files/similarities.0')
sklearn中计算TF-IDF的函数是TfidfTransformer
和TfidfVectorizer
,严格来说后者等于CountVectorizer + TfidfTransformer
。TfidfTransformer
和TfidfVectorizer
有一些共同的参数,这些参数的不同影响了tfidf的计算方式:
norm
:归一化,L1、L2(默认值)或者 None。L1 范数是向量中每个值除以所有值的绝对值的和,L2范数是向量中每个值除以所有值的平方开根号,即对于 L1:use_idf
:bool,默认 True,是否使用 IDF;smooth_idf
:bool,默认True,是否平滑IDF,默认分子和分母都+1,和上述任何一种都不一样,防止除零错误;sublinear_tf
:bool,默认False,是否对TF使用 sublinear,即使用 1 + log ( T F ) 1 + \log(TF) 1+log(TF)来替换原始的TF。所以,默认参数下(norm='l2', use_idf=True, smooth_idf=True, sublinear_tf=False)
,sklearn 是这么计算TF-IDF的:
t f i d f ( t , d , D ) = t f ( t , d ) × i d f ( t , D ) = t f ( t , d ) × ( log ( 1 + N 1 + N ( w ) ) + 1 ) \begin{aligned} tfidf(t,d,D)&=tf(t,d)\times idf(t,D)\\ &=tf(t,d)\times (\log{(\frac{1+N}{1+N(w)})}+1) \end{aligned} tfidf(t,d,D)=tf(t,d)×idf(t,D)=tf(t,d)×(log(1+N(w)1+N)+1)
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer, TfidfTransformer
corpus = ['This is the first document.',
'This is the second second document.',
'And the third one.',
'Is this the first document?']
# CountVectorizer是通过fit_transform函数将文本中的词语转换为词频矩阵
# get_feature_names()可看到所有文本的关键字
# vocabulary_可看到所有文本的关键字和其位置
# toarray()可看到词频矩阵的结果
vectorizer = CountVectorizer()
count = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names())
print(vectorizer.vocabulary_)
print(count.toarray())
# TfidfTransformer是统计CountVectorizer中每个词语的tf-idf权值
transformer = TfidfTransformer()
tfidf_matrix = transformer.fit_transform(count)
print(tfidf_matrix.toarray())
# TfidfVectorizer可以把CountVectorizer, TfidfTransformer合并起来,直接生成tfidf值
# TfidfVectorizer的关键参数:
# max_df:这个给定特征可以应用在 tf-idf 矩阵中,用以描述单词在文档中的最高出现率。假设一个词(term)在 80% 的文档中都出现过了,那它也许(在剧情简介的语境里)只携带非常少信息。
# min_df:可以是一个整数(例如5)。意味着单词必须在 5 个以上的文档中出现才会被纳入考虑。设置为 0.2;即单词至少在 20% 的文档中出现 。
# ngram_range:这个参数将用来观察一元模型(unigrams),二元模型( bigrams) 和三元模型(trigrams)。
tfidf_vec = TfidfVectorizer()
tfidf_matrix = tfidf_vec.fit_transform(corpus)
print(tfidf_vec.get_feature_names())
print(tfidf_vec.vocabulary_)
print(tfidf_matrix.toarray())