词向量之TF-IDF模型详解

目录

  • 0 前言
  • 1 TF-IDF模型
    • 1.1 TF-IDF数学形式
    • 1.2 举例
  • 2 TF-IDF的实现
    • 2.1 TF-IDF简单python实现
    • 2.2 TF-IDF的gesim实现:
    • 2.3 TF-IDF的sklearn实现

0 前言

前面介绍了词向量的One-Hot模型以及词袋模型,这都是为了将离散符号表示的文本转换成数字表示,以提供给后续机器学习算法的使用。
词向量之One-Hot编码详解
词向量之词袋模型(BOW)详解
本文主要介绍另一种词向量的表示方法:TF-IDF。

1 TF-IDF模型

  现有一个问题,怎么表示一个词在文档中的重要性?
  首先想的是文档中出现越多的词对文章来说就越重要,也就是说词在文档中的频率越大越重要,这里用TF(term frequency)来表示。但是又出现了另一个问题,“的”,“了”,“吗”这种词出现在文档中的频率一般也很高,但是它们又不带有实际意义,所以为了降低这些词的重要性,引入了IDF(inverse document frequency),若一个词在语料库中的很多文档中都出现,那么就让这个词的重要性降低一些。由此便产生了TF-IDF模型。

1.1 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中的词的总数。
    除此之外还有其他计算方式:

    • 二值:如果词出现在一篇文档中,则 N ( w ) N(w) N(w)为1,否则为0
    • 对数化: T F ( d , w ) = log ⁡ ( 1 + N ( w ) ) TF(d,w)=\log{(1+N(w))} TF(d,w)=log(1+N(w))
    • 双重标准化0.5:让词的频数除以该篇文档中最高频词的频数 T F ( d , w ) = 0.5 + 0.5 × N ( w ) max ⁡ w N ( w ) TF(d,w)=0.5+0.5\times\frac{N(w)}{\max\limits_wN(w)} TF(d,w)=0.5+0.5×wmaxN(w)N(w),这样做是为了避免偏向语料库中的长文档。
    • 双重标准化K:将上面方法中的0.5更换成 K ∈ [ 0 , 1 ] K\in [0,1] K[0,1],则 T F ( d , w ) = K + ( 1 − K ) × N ( w ) max ⁡ w N ( w ) TF(d,w)=K+(1-K)\times\frac{N(w)}{\max\limits_wN(w)} TF(d,w)=K+(1K)×wmaxN(w)N(w)
  • 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的文档的总数。
    除此之外还有其他计算方式:

    • 一元化:恒为1,不考虑IDF只考虑TF。
    • 平滑的IDF:为了避免由于词没有出现在语料库中而发生的除0错误 I D F ( w ) = log ⁡ ( N 1 + N ( w ) ) IDF(w)=\log{(\frac{N}{1+N(w)})} IDF(w)=log(1+N(w)N)
    • 最大逆文档频率(inverse document frequency max):遍历指定文档中的词,计算在语料库中包含该词的文档总数,取最大值,则 I D F ( w ) = log ⁡ ( max ⁡ t ∈ d N ( t ) 1 + N ( w ) ) IDF(w)=\log{(\frac{\max\limits_{t\in d}N(t)}{1+N(w)})} IDF(w)=log(1+N(w)tdmaxN(t))
    • 概率逆文档频率:将分母替换成 N − N ( w ) N-N(w) NN(w),则 I D F ( w ) = log ⁡ N − N ( w ) N ( w ) IDF(w)=\log{\frac{N-N(w)}{N(w)}} IDF(w)=logN(w)NN(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) TFIDF(w)=TF(d,w)×IDF(w)

1.2 举例

举例输入:
文档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是对整个语料库而言的。

2 TF-IDF的实现

2.1 TF-IDF简单python实现

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())

为了避免分词,直接采用这种形式。

2.2 TF-IDF的gesim实现:

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维向量表示,句子的维度是m
n
保存和加载模型的方法:

# 保存词典:
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')

2.3 TF-IDF的sklearn实现

  sklearn中计算TF-IDF的函数是TfidfTransformerTfidfVectorizer,严格来说后者等于CountVectorizer + TfidfTransformerTfidfTransformerTfidfVectorizer有一些共同的参数,这些参数的不同影响了tfidf的计算方式:

  • norm:归一化,L1、L2(默认值)或者 None。L1 范数是向量中每个值除以所有值的绝对值的和,L2范数是向量中每个值除以所有值的平方开根号,即对于 L1:
    x i = x i ∣ ∣ x ∣ ∣ 1 = x i ∑ j ∣ x j x_i=\frac{x_i}{∣∣\boldsymbol{x}∣∣_1}=\frac{x_i}{\sum\limits_j∣x_j} xi=x1xi=jxjxi
    对于 l2:
    x i = x i ∣ ∣ x ∣ ∣ 2 = x i ∑ j x j 2 x_i=\dfrac{x_i}{||\pmb x||_2} = \dfrac{x_i}{\sqrt{\sum\limits_j x^2_j}} xi=xxx2xi=jxj2 xi
  • 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())

你可能感兴趣的:(词向量)