我正面临一个挑战,即编写一些更聪明/更先进的“相关内容”算法,并且不知道从哪里开始所以我决定发布一个问题,如果有人会指出我正确的方向。
我们的数据库包含很多文章,到目前为止我们使用关键字/标签查询相关文章,但发现通常我们没有得到非常相关的结果,因为大多数关键字过于笼统(例如政府,税收,...... )。
最大的想法是,我们会以某种方式查询整个内容,并尝试匹配与当前显示的主题最相关的内容。但与此同时,该算法还应该“知道”匹配的内容是否具有某种负面含义。
例如,让我们看看3篇虚构的文章:
在这种情况下,所有三篇文章(它们的全部内容)都与飞行和飞机有某种关系,但第三篇文章具有负面含义。所以前两个应该相互关联,但第三个不应该以任何方式与前两个相关。
所以我的问题是 - 如何在具有超过百万篇文章的数据库上以编程方式完成这样的事情?我知道这不能仅仅通过SQL查询来完成 - 你会以某种方式需要字典或其他东西,但我不知道从哪里开始探索这个主题。那么,有人能指出我正确的方向吗?
答案 0 :(得分:3)
<强> TL,DR 强>
检查wiki上的TF*IDF,然后查看Cosine similarity。
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可以用来完成工作,执行它的基础是函数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值的向量表,以及每个文档的余弦相似度的多对多连接表相反,每个其他文档,以便只需要计算新文档或搜索查询的值。
所有这些还不够,OP要求一个想法,用不同的情感来区分相似的句子,这是好消息,坏消息永远不应该相关,即使相似性很高。
要做到这一点,我们必须欺骗余弦相似性,杠杆在定义
由于TD * IDF严格为正,因此余弦相似度值将严格为正。
可以对搜索错误关键字的文章进行分类,如果找到了某些内容,则整个文档的TD * IDF将更改为否定。
好消息和坏消息之间的余弦相似性将以正(对于好消息)和消极(对于坏消息)TD * IDF计算,这意味着如果有共同词,则交叉产品将为负数,并且负交叉积成为负相似度,因为无论TD_IDF的性质如何,SQRT(TD_IDF ^ 2)
都是正数。
对于具有相同&#34;情感的两个新闻的余弦相似性&#34;我们将使用相同符号的TD * IDF,它将以正交叉积和正相似性组合。
通过这种改变,余弦相似性的边界将变为-1 -1; 1]