获取相关文章的高级方法(语义)

时间:2014-04-23 07:38:32

标签: sql semantics

我正面临一个挑战,即编写一些更聪明/更先进的“相关内容”算法,并且不知道从哪里开始所以我决定发布一个问题,如果有人会指出我正确的方向。

我们的数据库包含很多文章,到目前为止我们使用关键字/标签查询相关文章,但发现通常我们没有得到非常相关的结果,因为大多数关键字过于笼统(例如政府,税收,...... )。

最大的想法是,我们会以某种方式查询整个内容,并尝试匹配与当前显示的主题最相关的内容。但与此同时,该算法还应该“知道”匹配的内容是否具有某种负面含义。

例如,让我们看看3篇虚构的文章:

  • 一篇文章,说明如果通过互联网预订门票,如何能够更便宜地飞行
  • 一篇文章说,由于......,机票的价格正在下降。
  • 一篇文章说,有300多人在飞机失事中丧生

在这种情况下,所有三篇文章(它们的全部内容)都与飞行和飞机有某种关系,但第三篇文章具有负面含义。所以前两个应该相互关联,但第三个不应该以任何方式与前两个相关。

所以我的问题是 - 如何在具有超过百万篇文章的数据库上以编程方式完成这样的事情?我知道这不能仅仅通过SQL查询来完成 - 你会以某种方式需要字典或其他东西,但我不知道从哪里开始探索这个主题。那么,有人能指出我正确的方向吗?

1 个答案:

答案 0 :(得分:3)

<强> TL,DR
检查wiki上的TF*IDF,然后查看Cosine similarity

长答案(附例子)

什么是TF * IDF

TF * IDF代表期限频率*反向文件频率 它是为大型组内的文档创建良好标记的方法之一 其背后的想法是提取单个文档中使用的单词对同一文档的描述。 为此,它使用两种不同的统计数据,第一种是术语频率。

术语频率表示单个文档或句子中单词的重要性 例如句子

SQL Post. Asking about semantic in SQL with generic document example, SQL generic

获取单个单词,并删除杂乱(噪音词)我们将

Word     | Count | Frequency
----------------------------
SQL      |   3   |  0.231
generic  |   2   |  0.154
Post     |   1   |  0.077
Asking   |   1   |  0.077
semantic |   1   |  0.077
document |   1   |  0.077
example  |   1   |  0.077

术语频率可以通过多种方式计算,表中有简单的一个,原始计数,一个标准化的,一个是频率,第二个是更好的选择,让TF * IDF归一化介于0和1之间 这个例子说明了&#34; SQL&#34;表征样本句子,因此它是标记的良好候选者。

反向文档频率是所有文档语料库中单词重要性的指标:每个文档中出现的单词对搜索没有帮助,少数文档中出现的单词会带来更多信息。<登记/> 反文档频率计算为

IDF = ln(Document count / Document with the word)

例如,我们使用三个句子作为我们的语料库,加上我们之前的语句

SQL Post. Asking about semantic in SQL with generic document example, SQL generic
C# Post. This is a C# answer with an example
SQL Post. Asking a good SQL question with an example
Math Post. This is a Math answer with an example of equation

如果我们计算前一句最佳指标的IDF,我们总共有四份文件和四份文件,其中&#34;示例&#34;出现

IDF = ln(4/4) -> ln(1) -> 0

单词&#34;示例&#34;在每个句子中,所以它对我们的文档来说不是一个好的搜索项目。 使用&#34;问题&#34;或者&#34;回答&#34;相反,我们有

IDF = ln(4/1) -> ln(4) -> 1.386 for "question"
IDF = ln(4/2) -> ln(2) -> 0.693 for "answer"

在我们的文档中搜索它们是更好的选择。

然后,我们可以单独指出一个单词对文档的重要性,并且是文档语料库中的一个很好的选择。
使用IDF更新上表

Word     | Frequency|  IDF  | TF*IDF
-------------------------------------
SQL      |  0.231   | 0.693 | 0.160
generic  |  0.154   | 1.386 | 0.213
Post     |  0.077   | 0     | 0
Asking   |  0.077   | 0.693 | 0.053
semantic |  0.077   | 1.386 | 0.107
document |  0.077   | 1.386 | 0.107
example  |  0.077   | 0     | 0

使用TF * IDF,即使&#34; SQL&#34;是句子中最突出的词,它被#34; generic&#34;如果我们考虑整个文档列表,那么&#34; SQL&#34;有两行。

TF * IDF可以为整个句子/文档库中每个有意义的句子提供相关单词列表。

计算每个文档的Word / TF * IDF列表是起始行,检查两个或多个文档的相似性我们可以使用余弦相似度

什么是余弦相似度

可以将余弦相似度视为度量:计算两点之间距离的方法。在我们的例子中,积分是我们的句子。 要测量距离,我们需要点的坐标,对于句子,坐标是他们的单词TF * IDF的列表。

余弦相似度的公式是

sim = (A*B)/(||A||*||B||)

其中A和B是句子坐标 从它的矢量形式中扩展公式它变成

sim = Sum(A[word] * B[word])/(Sqrt(Sum(A[word]^2)) * Sqrt(Sum(B[word]^2)))

sim = cross_product/(norm(A) * norm(B))

,其中

cross_product = Sum(A[word] * B[word])
norm(X) = Sqrt(Sum(X[word]^2))

例如,我们可以使用之前的第一个和第三个句子,第三个句子的单词vector是

Word     | Frequency|  IDF  | TF*IDF
-------------------------------------
SQL      |  0.2     | 0.693 | 0.139
Asking   |  0.1     | 0.693 | 0.069
good     |  0.1     | 1.386 | 0.139
question |  0.1     | 1.386 | 0.139

对于交叉积,我们只能对两个向量中出现的单词进行数学计算,对于其他单词,我们将0作为另一个值。

cross_product = 0.160*0.053 (SQL) + 0.023*0.069 (Asking) = 0,02587
norm(1) = sqrt(0.160^2 + 0.213^2 + 0.053^2 + 0.107^2 + 0.107^2) = 0.31090
norm(3) = sqrt(0.139^2 + 0.069^2 + 0.139^2 + 0.139^2) = 0.24992
sim = cross_product/(norm(1) * norm(3)) = 0.333

由于TD * IDF严格为正,因此余弦相似度值将严格为正。

由于使用的TD * IDF被归一化,余弦相似度将具有边界[0; 1] 0表示没有共享信息,1表示文档相同,如果不是噪声词。

SQLServer示例

使用全文搜索的SQLServer可以用来完成工作,执行它的基础是函数sys.dm_fts_parser,给定一个字符串,返回一个包含单个单词的表,一个值表示一个词是一个噪音和其他信息 要计算TD * IDF,最重要的是将文档分成单词,每个可以执行此操作的数据库,我选择的只是基于我自己的专业知识。

注意 sys.dm_fts_parser只能由具有sysadmin角色的用户执行。

我们首先创建一个包含测试数据的临时表

SELECT 1 AS Id, N'SQL Post. Asking about semantic in SQL with generic document'
              + N' example, SQL generic' AS txt 
INTO #testTable
UNION ALL SELECT 2, N'C# Post. This is a C# answer with an example' 
UNION ALL SELECT 3, N'SQL Post. Asking a good SQL question with an example' 
UNION ALL SELECT 4, N'Math Post. This is a Math answer with an example of'
                  + N' equation'

然后我们继续使用句子词TD * IDF

创建临时表
With TF AS (
SELECT DISTINCT id, display_term, special_term
     , CAST(COUNT(display_term) 
            OVER (PARTITION BY id, display_term) AS DECIMAL(10, 8))
     / COUNT(occurrence) OVER (PARTITION BY id) TF
FROM   #testTable
       CROSS APPLY sys.dm_fts_parser('"'+REPLACE(txt,'"','""')+'"', 1033, 0,0)
WHERE  TXT IS NOT NULL
  AND  display_term NOT LIKE 'nn%'
  AND  special_term <> 'End Of Sentence'
), IDF AS (
SELECT display_term word
     , sentences = COUNT(DISTINCT tt.ID)
     , sentence_with_word 
     = COUNT(DISTINCT CASE WHEN tt.txt LIKE '%' + tf.display_term + '%' 
                           THEN tt.id 
                           ELSE NULL 
                      END)
   , IDF = LOG(CAST(COUNT(DISTINCT tt.ID) AS DECIMAL (10, 8)) 
             / COUNT(DISTINCT CASE WHEN tt.txt LIKE '%' + tf.display_term + '%' 
                                   THEN tt.id 
                                   ELSE NULL 
                              END))
FROM   #testTable tt
       CROSS JOIN TF
WHERE  TF.special_term = 'Exact Match'
group by display_term
)
SELECT tf.Id sentence, word
     , TD = TF.TF, IDF.IDF
     , TD_IDF = TF.TF * IDF.IDF
INTO   #sentence_word_TD_IDF
FROM   TF
       INNER JOIN IDF ON tf.display_term = IDF.word

每个句子中的每个单词都有TD * IDF,我们可以继续使用句子1和3之间的余弦相似性

WITH S1 AS (
SELECT word, TD_IDF 
FROM   #sentence_word_TD_IDF 
WHERE  sentence = 1
), S2 AS (
SELECT word, TD_IDF 
FROM   #sentence_word_TD_IDF 
WHERE  sentence = 3
), cat AS (
SELECT word = COALESCE(S1.word, S2.word)
     , word_S1_TD_IDF = COALESCE(S1.TD_IDF, 0)
     , word_S2_TD_IDF = COALESCE(S2.TD_IDF, 0)
FROM   S1
       FULL JOIN S2 ON S1.word = S2.word
)
SELECT cross_product = SUM(word_S1_TD_IDF * word_S2_TD_IDF)
     , norm_1 = SQRT(SUM(word_S1_TD_IDF * word_S1_TD_IDF))
     , norm_2 = SQRT(SUM(word_S2_TD_IDF * word_S2_TD_IDF))
     , co_sim = SUM(word_S1_TD_IDF * word_S2_TD_IDF)
              / (SQRT(SUM(word_S1_TD_IDF * word_S1_TD_IDF)) 
               * SQRT(SUM(word_S2_TD_IDF * word_S2_TD_IDF)))
FROM   cat

查询有一些部分步骤可以更容易检查,例如在CTE IDF中有列&#39;句子&#39;和&#39; sentence_with_word&#39;而不仅仅是IDF。

这些查询可以在没有全文索引的情况下在表上运行 使用全文索引,可以获得词干,使用词的根而不是句子中的屈折形式。

为了更快地响应查询,最好为每个文档的每个单词创建一个TF * IDF值的向量表,以及每个文档的余弦相似度的多对多连接表相反,每个其他文档,以便只需要计算新文档或搜索查询的值。

进一步的步骤:Polarize

所有这些还不够,OP要求一个想法,用不同的情感来区分相似的句子,这是好消息,坏消息永远不应该相关,即使相似性很高。
要做到这一点,我们必须欺骗余弦相似性,杠杆在定义

由于TD * IDF严格为正,因此余弦相似度值将严格为正。

可以对搜索错误关键字的文章进行分类,如果找到了某些内容,则整个文档的TD * IDF将更改为否定。
好消息和坏消息之间的余弦相似性将以正(对于好消息)和消极(对于坏消息)TD * IDF计算,这意味着如果有共同词,则交叉产品将为负数,并且负交叉积成为负相似度,因为无论TD_IDF的性质如何,SQRT(TD_IDF ^ 2)都是正数。
对于具有相同&#34;情感的两个新闻的余弦相似性&#34;我们将使用相同符号的TD * IDF,它将以正交叉积和正相似性组合。

通过这种改变,余弦相似性的边界将变为-1 -1; 1]

  • -1永远无法达到,因为至少有一个词需要不同来承载不同的情感
  • 负数表示具有相反情况的类似文件&#34;情感&#34;
  • 0表示文件之间没有相关性
  • 正数表示具有相同&#34;情感&#34;
  • 的类似文件
  • 1个相同的文件,如果不是噪音词(并且它们必须是相同的数字)