TFIDF代码实现与sklearn库代码分析

本文介绍了TF-IDF的概念,包括词频(tf)和逆文档频率(idf)的计算方式,以及它们如何结合以衡量词语在文档中的重要性。通过sklearn库展示了如何实现TF-IDF向量化,并探讨了不同参数如use_idf、smooth_idf和norm的影响。此外,还提供了一个简单的个人实现示例,用于计算文档的TF-IDF向量。
摘要由CSDN通过智能技术生成

理论讲解

tf-idf可以帮助我们了解文档中每个词的重要程度。tf代表词频,表示当前文档每个词出现的次数;idf代表逆文档频率,表示在整个文档集中,该词在几个文档中出现过。其公式如下所示:
t f i d f ( w , d , D ) = t f ( w , d ) ∗ i d f ( w , D ) tfidf(w, d, D) = tf(w, d) * idf(w, D) tfidf(w,d,D)=tf(w,d)idf(w,D)
其中:

  • tf(w, d) = (词w在文档d中出现的次数) / (文档d中所有词的总数)
  • idf(w, D) = log(文档集D的文档总数 / 包含词w的文档数) + 1

如果只考虑tf,很多频繁出现并且无意义的词会变得很重要,加入idf后可以缓解这种情况,因为那些无意义的词通常会出现在多个文档中,idf值就会很小,相乘后总值会变小,但是tfidf通常还是会倾向于那些出现次数高的词。

注1:idf值后面加1是防止某个词在所有文档中都出现过,导致分子分母相等,log值为0,从而无法与tf值加权。

注2:有些idf计算公式会在分母处加1,以防止某个词出现次数为0而导致分母为0报错。在首次输入文档集计算idf值时,会首先遍历文档集,得到文档集出现过的词汇表,然后计算这些词的idf值,所以每个词至少在一个文档中出现过,不存在分母为0的情况发生。而建立好idf表之后,对新的文本计算tfidf向量的时候,这时候就有可能存在超出词表之外的情况,这时候就需要分母+1平滑处理了。但对于词表之外的词可以直接略过,或取一个常值代替idf值,视不同情况而定。(个人认为,请求指正)

注2补充:分母加1后,如果某个词在所有文档都出现过,那分母就会比分子大1,分子/分母<1,取log后会小于0,乘上tf值就无法反映出该词真实的重要程度了。

注3:有些idf计算公式会在分子和分母处同时加1,为了平滑idf值,看起来还较为合理。

sklearn库实现

引入sklearn的tfidf模块并初始化

from sklearn.feature_extraction.text import TfidfVectorizer

tfidf_vec = TfidfVectorizer()

初始化时可以指定一些参数,这里只介绍一些常用并且关键的参数

  • use_idf:默认为True,指定是否计算idf值,若为False,返回值只是tf值,若为True,返回的是tf * idf;
  • smooth_idf:默认为True,平滑idf值,指定计算idf时是否需要分子分母同时加1;
  • norm:默认为l2标准化,指定标准化的方式。tfidf计算后,会针对每个文档返回一个向量,向量的维度是整个文档集词汇表的大小,向量包含该文档中每个词的tfidf值,为了更好的计算不同文档之间的相似度,这里需要将其标准化。l2标准化代表每个值的平方和为1,若指定为l1则代表绝对值和为1,若指定为None则不进行标准化。

下面将sklearn的结果与手动计算的结果进行对比:

  • 首先设置use_idf=False,不计算idf,并取消标准化
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
  "查下 明天 天气",
  "查下 今天 天气",
]
tfidf_vec = TfidfVectorizer(use_idf=False, smooth_idf=False, norm=None)
tfidf_matrix = tfidf_vec.fit_transform(corpus)
print('输出每个单词对应的 id 值:', tfidf_vec.vocabulary_)
# 输出每个单词对应的 id 值: {'查下': 3, '明天': 2, '天气': 1, '今天': 0}
print('返回tf值:', tfidf_matrix.toarray())
# 返回tf值: [[0. 1. 1. 1.],  [1. 1. 0. 1.]]
词汇表文档1文档2
查看11
明天10
今天01
天气11

tfidf_matrix.toarray()返回的维度为(2,4),2代表有2个文档,4对应词汇表的大小,例如第一行[0, 1, 1, 1]代表文档1中没有出现 ‘今天’,‘查下’、‘明天’、'天气’各出现了一次。

这里返回的tf值并没有除以该文档的总词数,因为默认是需要标准化的,而不管除不除以该文档的总词数,标准化后的结果都是一样的。

将参数设置为norm='l2'后结果变为

返回tf值:
 [[0.         0.57735027 0.57735027 0.57735027]
 [0.57735027 0.57735027 0.         0.57735027]]
  • 设置use_idf=True,将idf参与计算
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
  "查下 明天 天气",
  "查下 今天 天气",
]
tfidf_vec = TfidfVectorizer(use_idf=True, smooth_idf=False, norm=None)
tfidf_matrix = tfidf_vec.fit_transform(corpus)
print('输出每个单词对应的 id 值:', tfidf_vec.vocabulary_)
# 输出每个单词对应的 id 值: {'查下': 3, '明天': 2, '天气': 1, '今天': 0}
print('返回idf值:', tfidf_vec.idf_)
# 返回idf值: [1.69314718 1.         1.69314718 1.        ]
print('返回tf值:', tfidf_matrix.toarray())
# 返回tfidf值: [[0.         1.         1.69314718 1.        ] [1.69314718 1.         0.         1.        ]]

根据公式log(文档集D的文档总数 / 包含词w的文档数) + 1得到下表结果,这里log以e为底。

如 ‘查看’:ln(2/2) + 1=1;‘明天’:ln(2/1) + 1=1.693;

词汇表出现文档次数idf
查看21
明天11.693
今天11.693
天气21

最后得到的tfidf结果也是两者相乘,若设置标准化,也同样会对每个文档的tfidf向量进行标准化。

将参数设置为norm='l2'后结果变为

返回tf值:
[[0.         0.45329466 0.76749457 0.45329466]
 [0.76749457 0.45329466 0.         0.45329466]]

个人简易实现

这里首先遍历文档集建立词汇表,然后建立一个大小为(文档数,词汇表长度)的数组,用来存储各文档的tfidf值。首先遍历各文档得到tf值,然后计算每个词对应的idf值,将二者相乘得到文档的tfidf向量,最后再将其标准化,得到的输出与sklearn库中相同。
对于新文档,还可以用类中的方法快速计算出该文档的向量,以便后续计算相似度。

class Tfidf:
    def __init__(self, documents, use_idf=True, norm='l2'):
        self.documents = documents
        self.vocab = defaultdict(int)
        # 获取词表
        # defaultdict(<class 'int'>, {'查下': 0, '明天': 1, '天气': 2, '今天': 3})
        for text in documents:
            for t in text.split():
                self.vocab[t] = self.vocab.get(t, len(self.vocab))

        # 获得每个文档的tf向量
        # [[1. 1. 1. 0.]
        #  [1. 0. 1. 1.]]
        self.docment_vector = np.zeros((len(documents), len(self.vocab)))
        for i in range(len(documents)):
            for word in documents[i].split():
                self.docment_vector[i][self.vocab[word]] += 1

        if use_idf:
            self.idf = defaultdict(int)
            # 计算所有词汇的idf值, idf值在不同文档中是相等的
            self.idf_vector = np.zeros((len(self.vocab)))
            for k in self.vocab.keys():
                for document in documents:
                    if k in document.split():
                        self.idf[k] += 1
            # 先计算出现在文档集中的次数, 再取log
            # idf_vector: [1.         0.         1.         1.69314718]]
            for k, v in self.idf.items():
                self.idf[k] = np.log(len(documents) / v) + 1
                self.idf_vector[self.vocab[k]] = self.idf[k]
            # 得到最终的tf * idf
            self.docment_vector *= self.idf_vector

        if norm:
            # 这里只实现了l2标准化
            for i in range(len(self.docment_vector)):
                norm = np.sqrt(sum(self.docment_vector[i] ** 2))
                self.docment_vector[i] /= norm

    def search_similar(self, text):
        vec = np.zeros((len(self.vocab)))
        # 这里略过了词表之外的词
        for t in text.split():
            if t in self.vocab:
                vec[self.vocab[t]] += 1
        vec *= self.idf_vector
        vec /= np.sqrt(sum(vec ** 2))
        return vec

corpus = [
  "查下 明天 天气",
  "查下 今天 天气",
]
target = '今天 天气 怎么样'
my_tfidf = Tfidf(corpus)
print('输出每个单词对应的 id 值:', my_tfidf.vocab)
# 输出每个单词对应的 id 值: defaultdict(<class 'int'>, {'查下': 0, '明天': 1, '天气': 2, '今天': 3})
print('返回idf值:', my_tfidf.idf)
# 返回idf值: defaultdict(<class 'int'>, {'查下': 1.0, '明天': 1.6931471805599454, '天气': 1.0, '今天': 1.6931471805599454})
print('返回各文档的向量:', my_tfidf.docment_vector)
# 返回各文档的向量: [[0.45329466 0.76749457 0.45329466 0.        ] [0.45329466 0.         0.45329466 0.76749457]]
print('指定文本的向量:', my_tfidf.search_similar(target))
# 指定文本的向量: [0.         0.         0.50854232 0.861037  ]
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值