TF-IDF计算过程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TF-IDF计算过程相关的知识,希望对你有一定的参考价值。

参考技术A 本文内容主要摘自python  machine  learning  2nd   edition

1、假设我们有以下三个文本

• 'The sun is shining'

•  'The weather is sweet'

•  'The sun is shining, the weather is sweet, and one and one is  two

2、利用CountVectorizer类得到如下字典

'and': 0,'two': 7,'shining': 3,'one': 2,'sun': 4,'weather': 8,'the': 6,'sweet': 5, 'is': 1

3、将步骤1的文档转换为矩阵

[[0 1 0 1 1 0 1 0 0]

[0 1 0 0 0 1 1 0 1]

[2 3 2 1 1 1 2 1 1]]

4.计算tf-idf值

我们以is为例进行计算,is对应的是矩阵第二列。

tf值,表示term在该文本中出现的次数,这里即is在文本3出现的次数,很容易看出是3.

idf值,sklearn做了小小的改动,公式是 (1+log ).  的意思就是文本总数(number of  document),df(d,t)表示包含is 的文件数目,很明显,这里也是3.这样,计算的结果为3*(1+log )=3.

需要注意的是,sklearn对结果进行了正则化处理。

最终得到的结果为

[[ 0.   0.43    0. 0.56 0.56  0.    0.43    0.    0. ]

[ 0.   0.43    0.   0.   0.    0.56 0.43  0.   0.56]

[ 0.5 0.45    0.5 0.19 0.19 0.19 0.3 0.25 0.19]]

每一行的平方和均为1,这是l2正则化处理的结果。

另外可以看出,原先is的词频是 1 1 3,最终tf-idf值是0.43 0.43 0.45 。

TF-IDF算法介绍,简单模拟,以及在图数据中应用

目录

一、百度百科关于TF-IDF的算法介绍

二、简单模拟

2.1、创建表以及数据

2.2、模拟实现TF-IDF的统计算分

三、图数据中应用

3.1、图数据模型

3.2、计算文档中关键词的TF-IDF的分值

3.2.1、cypher实现TF-IDF 分数计算算法

3.2.2、存储过程实现

3.3、优化3.2的查询

3.3.1、cypher优化去除ybCount计算的实现

3.3.2、优化去除ybCount计算的存储过程实现


一、百度百科关于TF-IDF的算法介绍

https://baike.baidu.com/item/tf-idf/8816134?fr=aladdin

二、简单模拟

既然是简单模拟,我们就用人们最常用的一种工具MySQL去模拟一下这个算法可以实现的效果

 

2.1、创建表以及数据

article_keywords.sql

CREATE TABLE `article_keywords` (
  `id` int NOT NULL AUTO_INCREMENT,
  `article` varchar(255) NOT NULL,
  `keyword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=126 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


INSERT INTO `article_keywords` VALUES ('113', '悟空悟空芭蕉扇牛魔王', '悟空');
INSERT INTO `article_keywords` VALUES ('114', '悟空悟空芭蕉扇牛魔王', '悟空');
INSERT INTO `article_keywords` VALUES ('115', '悟空悟空芭蕉扇牛魔王', '芭蕉扇');
INSERT INTO `article_keywords` VALUES ('116', '悟空悟空芭蕉扇牛魔王', '牛魔王');
INSERT INTO `article_keywords` VALUES ('117', '悟空红孩儿红孩儿三昧真火三昧真火', '悟空');
INSERT INTO `article_keywords` VALUES ('118', '悟空红孩儿红孩儿三昧真火三昧真火', '红孩儿');
INSERT INTO `article_keywords` VALUES ('119', '悟空红孩儿红孩儿三昧真火三昧真火', '红孩儿');
INSERT INTO `article_keywords` VALUES ('120', '悟空红孩儿红孩儿三昧真火三昧真火', '三昧真火');
INSERT INTO `article_keywords` VALUES ('121', '悟空红孩儿红孩儿三昧真火三昧真火', '三昧真火');
INSERT INTO `article_keywords` VALUES ('122', '悟空三昧真火火眼金睛', '三昧真火');
INSERT INTO `article_keywords` VALUES ('123', '悟空三昧真火火眼金睛', '火眼金睛');
INSERT INTO `article_keywords` VALUES ('124', '悟空三昧真火火眼金睛', '悟空');

2.2、模拟实现TF-IDF的统计算分

实现sql

#实现TF-IDF的统计算分值
SELECT * FROM (
	#计算TF-IDF的值
	SELECT *,(TF * IDF)as 'TF-IDF' FROM (
		#分别计算TF和IDF的值
		SELECT *,(keyword_in_current_article_count / current_article_keyword_count) as TF,(article_count / keyword_in_all_article_count) as IDF FROM(
			#查询表中数据以及联查计算TF-IDF所需的各种聚合值以及直接聚合文章总数量的值
			SELECT article_keywords.*,keyword_in_current_article_count.keyword_in_current_article_count,current_article_keyword_count.current_article_keyword_count,keyword_in_all_article_count.keyword_in_all_article_count,(SELECT COUNT(1) FROM (SELECT article FROM article_keywords GROUP BY article) as a)as article_count FROM article_keywords as article_keywords 
			#联查关键词在当前文章中出现的频率的聚合表
			LEFT JOIN (SELECT article,keyword,COUNT(1) as keyword_in_current_article_count FROM article_keywords GROUP BY article,keyword)as keyword_in_current_article_count
			ON article_keywords.article = keyword_in_current_article_count.article AND article_keywords.keyword = keyword_in_current_article_count.keyword
			#联查当前文章中所有关键词的个数的聚合表
			LEFT JOIN (SELECT article,COUNT(keyword) as current_article_keyword_count FROM article_keywords GROUP BY article) as current_article_keyword_count
			ON article_keywords.article = current_article_keyword_count.article
			#联查出现此关键词的文章个数的聚合表
			LEFT JOIN (SELECT a.keyword as keyword,COUNT(article) keyword_in_all_article_count FROM (
				SELECT article,keyword FROM article_keywords GROUP BY article,keyword
			)as a GROUP BY a.keyword)as keyword_in_all_article_count
			ON article_keywords.keyword = keyword_in_all_article_count.keyword
		) as article_keywords_tf_idf
	) as article_keywords_tfidf
)as article_keywords_tfidf_order 
#WHERE keyword = '三昧真火'
GROUP BY article,keyword ORDER BY article_keywords_tfidf_order.`TF-IDF` DESC

结果样例

这只是用大家都熟悉的一个MySQL做一个简单的模拟实现的效果

当然,在实际应用中的场景比这要复杂的多,而且实际应用也不会使用MySQL来实现

下面我们来看看在图数据中的一个实际应用场景

 

三、图数据中应用

像我们常见的文档检索,比如ES的分词+倒排索引,通过对搜索内容的分词,对应找到包含这些关键词的文档,并且对结果进行一个计分排序,返回给用户。

这里我们用图数据库来做这样的事。

数据大概是这样的:

我们有文档节点,关键词节点(通过对文档分词的结果),以及他们的包含关系。

 

3.1、图数据模型

(文档详细属性)-[包含词频,TF-IDF分值]->(关键词)

 

3.2、计算文档中关键词的TF-IDF的分值

3.2.1、cypher实现TF-IDF 分数计算算法

指定研报以及研报中的一个关键词计算TF-IDF分数
// 获取研报以及关键词,计算该词在这篇研报的TF-IDF分数
// 获取研报`HDOC6a0250e61f91a856cd5dd6c39327fa57`和关键词`HCEPT69305efd9563834a4e7b67d6d5ac4674`,以及该关键词在研报中出现的次数count
MATCH (yb:研报)-[r:包含]->(kw:关键词) WHERE yb.hcode='HDOC6a0250e61f91a856cd5dd6c39327fa57' AND kw.hcode='HCEPT69305efd9563834a4e7b67d6d5ac4674'
WITH ID(yb) AS ybId,r.count AS count,ID(kw) AS kwId
// 获取该研报中关键词总数
MATCH (yb)-[r:包含]->(kw:关键词) WHERE ID(yb)=ybId
WITH ybId,SUM(r.count) AS kwCount,count,kwId
// 计算TF-词频(Term Frequency)
WITH ybId,1.0*count/kwCount AS tf,kwId
// 获取研报总数
MATCH (yb:研报) WITH COUNT(*) AS ybCount,ybId,tf,kwId
// 该关键词出现在多少篇研报中
MATCH (yb:研报)-[:包含]->(kw:关键词) WHERE ID(kw)=kwId WITH COUNT(yb) AS ybKwCount,ybCount,ybId,tf,kwId
// 计算IDF-逆文本频率指数(Inverse Document Frequency)
WITH tf,log10(ybCount/ybKwCount) AS idf,ybId,kwId
RETURN ybId,kwId,tf*idf AS `TF-IDF`

3.2.2、存储过程实现

CALL apoc.custom.asProcedure(
'yanbao.kw.tfidf',
'// 获取研报以及关键词,计算该词在这篇研报的TF-IDF分数
// 获取研报`HDOC6a0250e61f91a856cd5dd6c39327fa57`和关键词`HCEPT69305efd9563834a4e7b67d6d5ac4674`,以及该关键词在研报中出现的次数count
MATCH (yb:研报)-[r:包含]->(kw:关键词) WHERE yb.hcode=$yanbaoHcode AND kw.hcode=$kwHcode 
WITH ID(yb) AS ybId,r.count AS count,ID(kw) AS kwId 
// 获取该研报中关键词总数
MATCH (yb)-[r:包含]->(kw:关键词) WHERE ID(yb)=ybId 
WITH ybId,SUM(r.count) AS kwCount,count,kwId 
// 计算TF-词频(Term Frequency)
WITH ybId,1.0*count/kwCount AS tf,kwId 
// 获取研报总数
MATCH (yb:研报) WITH COUNT(*) AS ybCount,ybId,tf,kwId 
// 该关键词出现在多少篇研报中
MATCH (yb:研报)-[:包含]->(kw:关键词) WHERE ID(kw)=kwId WITH COUNT(yb) AS ybKwCount,ybCount,ybId,tf,kwId 
// 计算IDF-逆文本频率指数(Inverse Document Frequency)
WITH tf,log10(ybCount/ybKwCount) AS idf,ybId,kwId 
RETURN ybId,kwId,tf*idf AS tfidf,$yanbaoHcode as yanbaoHcode,$kwHcode as kwHcode',
'READ',
[['yanbaoHcode','STRING'],['kwHcode','STRING'],['ybId','LONG'],['kwId','LONG'],['tfidf','DOUBLE']],
[['yanbaoHcode','STRING'],['kwHcode','STRING']],
'计算研报中某关键词TF-IDF分数'
);

调用过程结果

CALL custom.yanbao.kw.tfidf('HDOC6a0250e61f91a856cd5dd6c39327fa57','HCEPT69305efd9563834a4e7b67d6d5ac4674') YIELD yanbaoHcode,kwHcode,ybId,kwId,tfidf RETURN yanbaoHcode,kwHcode,ybId,kwId,tfidf

结果是实现了,但是可以发现,一次计算耗时400ms~700ms

我们的模型数据集大概有一亿左右,这个效率要计算到猴年马月

 

3.3、优化3.2的查询

对于大批量使用这个过程的情况下的优化

这个存储过程的算法的计算,需要用到的值为4个【当前关键词在当前研报中包含的数量,当前研报中所包含的关键词的个数,研报的总数量,包含当前关键词的研报数量】

  1. 当前关键词在当前研报中包含的数量:(yb:研报)-[r:包含]->(kw:关键词),可以直接获取,无需消耗
  2. 当前研报中所包含的关键词的个数:存量计算时,可以直接聚合计算每个研报包含的关键词总数,加载至内存(耗时巨大,不采取)
  3. 研报的总数量:存量计算时,可以直接聚合研报总数量的值加载至内存(采用)
  4. 包含当前关键词的研报数量:存量计算时,可以直接聚合关键词的研报数量加载至内存(耗时巨大,不采取)

3.3.1、cypher优化去除ybCount计算的实现

//直接获取研报数量,免去在过程中每次都要获取研报数量
MATCH (n:研报) RETURN COUNt(n) as ybCount//1081720

// 获取研报以及关键词,计算该词在这篇研报的TF-IDF分数
// 获取研报`HDOC6a0250e61f91a856cd5dd6c39327fa57`和关键词`HCEPT69305efd9563834a4e7b67d6d5ac4674`,以及该关键词在研报中出现的次数count
MATCH (yb:研报)-[r:包含]->(kw:关键词) WHERE yb.hcode='HDOC6a0250e61f91a856cd5dd6c39327fa57' AND kw.hcode='HCEPT69305efd9563834a4e7b67d6d5ac4674'
WITH ID(yb) AS ybId,r.count AS count,ID(kw) AS kwId,1081720 as ybCount 
// 获取该研报中关键词总数
MATCH (yb)-[r:包含]->(kw:关键词) WHERE ID(yb)=ybId
WITH ybId,SUM(r.count) AS kwCount,count,kwId,ybCount
// 计算TF-词频(Term Frequency)
WITH ybId,1.0*count/kwCount AS tf,kwId,ybCount
// 该关键词出现在多少篇研报中
MATCH (yb:研报)-[:包含]->(kw:关键词) WHERE ID(kw)=kwId WITH COUNT(yb) AS ybKwCount,ybCount,ybId,tf,kwId
// 计算IDF-逆文本频率指数(Inverse Document Frequency)
WITH tf,log10(ybCount/ybKwCount) AS idf,ybId,kwId
RETURN ybId,kwId,tf*idf AS `TF-IDF`

3.3.2、优化去除ybCount计算的存储过程实现

CALL apoc.custom.asProcedure(
'yanbao.kw.tfidf.withYbCount',
'// 获取研报以及关键词,计算该词在这篇研报的TF-IDF分数
// 获取研报`HDOC6a0250e61f91a856cd5dd6c39327fa57`和关键词`HCEPT69305efd9563834a4e7b67d6d5ac4674`,以及该关键词在研报中出现的次数count
MATCH (yb:研报)-[r:包含]->(kw:关键词) WHERE yb.hcode=$yanbaoHcode AND kw.hcode=$kwHcode 
WITH ID(yb) AS ybId,r.count AS count,ID(kw) AS kwId,$ybCount as ybCount 
// 获取该研报中关键词总数
MATCH (yb)-[r:包含]->(kw:关键词) WHERE ID(yb)=ybId 
WITH ybId,SUM(r.count) AS kwCount,count,kwId,ybCount 
// 计算TF-词频(Term Frequency)
WITH ybId,1.0*count/kwCount AS tf,kwId,ybCount 
// 该关键词出现在多少篇研报中
MATCH (yb:研报)-[:包含]->(kw:关键词) WHERE ID(kw)=kwId WITH COUNT(yb) AS ybKwCount,ybCount,ybId,tf,kwId 
// 计算IDF-逆文本频率指数(Inverse Document Frequency)
WITH tf,log10(ybCount/ybKwCount) AS idf,ybId,kwId 
RETURN ybId,kwId,tf*idf AS tfidf,$yanbaoHcode as yanbaoHcode,$kwHcode as kwHcode',
'READ',
[['yanbaoHcode','STRING'],['kwHcode','STRING'],['ybId','LONG'],['kwId','LONG'],['tfidf','DOUBLE']],
[['yanbaoHcode','STRING'],['kwHcode','STRING'],['ybCount','LONG']],
'计算研报中某关键词TF-IDF分数,增加存量数据时传入研报数量参数'
);

调用过程结果

CALL custom.yanbao.kw.tfidf.withYbCount('HDOC6a0250e61f91a856cd5dd6c39327fa57','HCEPT69305efd9563834a4e7b67d6d5ac4674',1081720) YIELD yanbaoHcode,kwHcode,ybId,kwId,tfidf RETURN yanbaoHcode,kwHcode,ybId,kwId,tfidf

可以看到,我们去除了算法中最耗时的一步操作,COUNT()聚合是很耗性能的,现在的结果是一次计算耗时1ms,500倍的提升。

以上是关于TF-IDF计算过程的主要内容,如果未能解决你的问题,请参考以下文章

TF-IDF算法介绍,简单模拟,以及在图数据中应用

TF-IDF算法介绍,简单模拟,以及在图数据中应用

Alink漫谈 : TF-IDF算法的实现

Alink漫谈 : TF-IDF算法的实现

如何计算查询的 TF-IDF?

从 tf-idf 计算余弦相似度