【问题标题】:PostgreSQL, trigrams and similarityPostgreSQL,三元组和相似性
【发布时间】:2017-04-01 12:40:16
【问题描述】:

只是在我的 Mac 上测试 PostgreSQL 9.6.2 并使用 Ngrams。 假设酒厂领域有一个 GIN trigram 索引。

相似性的限制(我知道这已被弃用):

SELECT set_limit(0.5);

我正在 2,3M 行表上构建三元组搜索。

我的选择代码:

SELECT winery, similarity(winery, 'chateau chevla blanc') AS similarity 
FROM usr_wines 
WHERE status=1 AND winery % 'chateau chevla blanc'  
ORDER BY similarity DESC;

我的结果(在我的 Mac 上为 329 毫秒):

Chateau ChevL Blanc 0,85
Chateau Blanc   0,736842
Chateau Blanc   0,736842
Chateau Blanc   0,736842
Chateau Blanc   0,736842
Chateau Blanc,  0,736842
Chateau Blanc   0,736842
Chateau Cheval Blanc    0,727273
Chateau Cheval Blanc    0,727273
Chateau Cheval Blanc    0,727273
Chateau Cheval Blanc (7)    0,666667
Chateau Cheval Blanc Cbo    0,64
Chateau Du Cheval Blanc 0,64
Chateau Du Cheval Blanc 0,64

好吧,我不明白在这种情况下,“Chateau blanc”如何与“Chateau Cheval Blanc”有相似之处?我知道这两个词是完全相同的“chateau”和“blanc”,但没有其他词“cheval”。

还有为什么“Chateau ChevL Blanc”是第一名?缺少一个字母“a”!

嗯,我的目标是在我给出酒厂名称时匹配所有可能的重复项,即使它拼写错误。我错过了什么?

【问题讨论】:

  • 对于“Chateau ChevL Blanc”得分高于“Chateau Cheval Blanc”,pg_trgm 将相似度定义为相对于联合体的交集大小,对拼写错误的定义要严格得多而不是遗漏或添加,因为它们既缩小了交集,又扩大了联合。在实践中,如果您正在实施自己的评分方法,则可能值得考虑诸如相对于两组中较大者的交集的大小。

标签: postgresql similarity trigram


【解决方案1】:

trigram 相似性的概念依赖于将任何句子划分为“trigrams”(三个连续字母的序列),并将结果视为 SET(即:顺序无关紧要,并且您没有重复值)。在考虑句子之前,在开头添加两个空格,在结尾添加一个空格,并将单个空格替换为两个空格。

TrigramsN-grams的特例。

“Chateau blanc”对应的三元组是通过查找其上出现的三个字母的所有序列来找到的:

  chateau  blanc
---                 => '  c'
 ---                => ' ch'
  ---               => 'cha'
   ---              => 'hat'
    ---             => 'ate'
     ---            => 'tea'
      ---           => 'eau'
       ---          => 'au '
        ---         => 'u  '
         ---        => '  b'
          ---       => ' bl'
           ---      => 'bla'
            ---     => 'lan'
             ---    => 'anc'
              ---   => 'nc '

对它们进行排序,去掉重复项即可:

'  b'
'  c'
' bl'
' ch'
'anc'
'ate'
'au '
'bla'
'cha'
'eau'
'hat'
'lan'
'nc '
'tea'

这可以由 PostgreSQL 通过函数show_trgm 计算:

SELECT show_trgm('Chateau blanc') AS A

A = [  b,  c, bl, ch,anc,ate,au ,bla,cha,eau,hat,lan,nc ,tea]

... 它有 14 个三元组。 (查看pg_trgm)。

而“Chateau Cheval Blanc”对应的卦集是:

SELECT show_trgm('Chateau Cheval Blanc') AS B 

B = [  b,  c, bl, ch,anc,ate,au ,bla,cha,che,eau,evl,hat,hev,la ,lan,nc ,tea,vla]

... 有 19 个三元组

如果你计算有多少三元组有两个相同的集合,你会发现它们有以下几个:

A intersect B = 
    [  b,  c, bl, ch,anc,ate,au ,bla,cha,eau,hat,lan,nc ,tea]

他们总共拥有的是:

A union B = 
    [  b,  c, bl, ch,anc,ate,au ,bla,cha,che,eau,evl,hat,hev,la ,lan,nc ,tea,vla]

也就是说,两个句子共有 14 个三元组,总共 19 个。
相似度计算为:

 similarity = 14 / 19

您可以通过以下方式进行检查:

SELECT 
    cast(14.0/19.0 as real) AS computed_result, 
    similarity('Chateau blanc', 'chateau cheval blanc') AS function_in_pg

你会看到你得到:0.736842

...它解释了如何计算相似度,以及为什么你会得到你得到的值。


注意:您可以通过以下方式计算交集和并集:

SELECT 
   array_agg(t) AS in_common
FROM
(
    SELECT unnest(show_trgm('Chateau blanc')) AS t 
    INTERSECT 
    SELECT unnest(show_trgm('chateau chevla blanc')) AS t
    ORDER BY t
) AS trigrams_in_common ;

SELECT 
   array_agg(t) AS in_total
FROM
(
    SELECT unnest(show_trgm('Chateau blanc')) AS t 
    UNION 
    SELECT unnest(show_trgm('chateau chevla blanc')) AS t
) AS trigrams_in_total ;

这是一种探索不同句子相似性的方法:

WITH p AS
(
    SELECT 
      'This is just a sentence I''ve invented'::text AS f1,
      'This is just a sentence I''ve also invented'::text AS f2
),
t1 AS
(
    SELECT unnest(show_trgm(f1)) FROM p
),
t2 AS
(
    SELECT unnest(show_trgm(f2)) FROM p
),
x AS
(
    SELECT
        (SELECT count(*) FROM 
            (SELECT * FROM t1 INTERSECT SELECT * FROM t2) AS s0)::integer AS same,
        (SELECT count(*) FROM 
            (SELECT * FROM t1 UNION     SELECT * FROM t2) AS s0)::integer AS total,
        similarity(f1, f2) AS sim_2
FROM
    p 
)
SELECT
    same, total, same::real/total::real AS sim_1, sim_2
FROM
    x ;

您可以在Rextester查看它

【讨论】:

  • 这是一个非常好的和详细的解释 joanolo。谢谢 !所以我将继续我的测试以匹配重复项。
  • 用向量进行全文搜索可以成为我匹配重复项的朋友还是我需要继续使用三元组?
  • 全文搜索可以帮助您逐字查找重复(不一定是相同的顺序);但不允许出现拼写错误。
  • 为什么 'u ' 从三元组中删除?
  • 斯蒂芬,我真的不知道。
【解决方案2】:

trigram 算法应该越准确,比较字符串的长度差异越小。您可以修改算法以补偿长度差异的影响。

以下示例函数将字符串长度相差 1 个字符的相似度降低 1%。这意味着它偏爱相同(相似)长度的字符串。

create or replace function corrected_similarity(str1 text, str2 text)
returns float4 language sql as $$
    select similarity(str1, str2)* (1- abs(length(str1)-length(str2))/100.0)::float4
$$;

select 
    winery, 
    similarity(winery, 'chateau chevla blanc') as similarity,
    corrected_similarity(winery, 'chateau chevla blanc') as corrected_similarity
from usr_wines 
where winery % 'chateau chevla blanc'  
order by corrected_similarity desc;

          winery          | similarity | corrected_similarity 
--------------------------+------------+----------------------
 Chateau ChevL Blanc      |       0.85 |               0.8415
 Chateau Cheval Blanc     |   0.727273 |             0.727273
 Chateau Cheval Blanc     |   0.727273 |             0.727273
 Chateau Cheval Blanc     |   0.727273 |             0.727273
 Chateau Blanc,           |   0.736842 |             0.692632
 Chateau Blanc            |   0.736842 |             0.685263
 Chateau Blanc            |   0.736842 |             0.685263
 Chateau Blanc            |   0.736842 |             0.685263
 Chateau Blanc            |   0.736842 |             0.685263
 Chateau Blanc            |   0.736842 |             0.685263
 Chateau Cheval Blanc (7) |   0.666667 |                 0.64
 Chateau Du Cheval Blanc  |       0.64 |               0.6208
 Chateau Du Cheval Blanc  |       0.64 |               0.6208
 Chateau Cheval Blanc Cbo |       0.64 |               0.6144
(14 rows)

以类似的方式,您可以通过例如有多少初始字符相同(认为函数会更复杂一些)来纠正标准相似度。

【讨论】:

  • 感谢您的建议!!!它做得很好。我也试过了,效果很好。然而,它目前比基本搜索慢 10 倍。我想 set_limit() 不能与自定义函数一起使用。
  • 当然,自定义函数是一个额外的成本,所以它似乎可以用来纠正早期获得的结果(以这样一种方式,它是针对一个小数据集执行的)。
  • 我偶尔玩过您的自定义函数的细微变化:我会更改校正因子,不使用/100.0,但有(1.0 - abs(length(str1) - length(str2))::float4 / (length(str1) + length(str2))::float4)::float4
【解决方案3】:

有时您想要与克林的答案相反。在某些应用程序中,字符串长度的较大差异不应导致如此显着的分数损失。

例如,想象一个自动完成的结果表单,其中包含随着您键入而改进的三元组匹配建议。

这是另一种评分匹配方法,它仍然使用三元组,但比平时更倾向于子字符串匹配。

相似度公式以

开头
the number of common trigrams
-------------------------------------------
the number of trigrams in the shortest word    <-- key difference

并且可以根据良好的标准相似度得分从那里上升。

CREATE OR REPLACE FUNCTION substring_similarity(string_a TEXT, string_b TEXT) RETURNS FLOAT4 AS $$
DECLARE
  a_trigrams TEXT[];
  b_trigrams TEXT[];
  a_tri_len INTEGER;
  b_tri_len INTEGER;
  common_trigrams TEXT[];
  max_common INTEGER;
BEGIN
  a_trigrams = SHOW_TRGM(string_a);
  b_trigrams = SHOW_TRGM(string_b);
  a_tri_len = ARRAY_LENGTH(a_trigrams, 1);
  b_tri_len = ARRAY_LENGTH(b_trigrams, 1);
  IF (NOT (a_tri_len > 0) OR NOT (b_tri_len > 0)) THEN
    IF (string_a = string_b) THEN
      RETURN 1;
    ELSE
      RETURN 0;
    END IF;
  END IF;
  common_trigrams := ARRAY(SELECT UNNEST(a_trigrams) INTERSECT SELECT UNNEST(b_trigrams));
  max_common = LEAST(a_tri_len, b_tri_len);
  RETURN COALESCE(ARRAY_LENGTH(common_trigrams, 1), 0)::FLOAT4 / max_common::FLOAT4;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION corrected_similarity(string_a TEXT, string_b TEXT) 
RETURNS FLOAT4 AS $$
DECLARE
  base_score FLOAT4;
BEGIN
  base_score := substring_similarity(string_a, string_b);
  -- a good standard similarity score can raise the base_score
  RETURN base_score + ((1.0 - base_score) * SIMILARITY(string_a, string_b));
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION is_minimally_substring_similar(string_a TEXT, string_b TEXT) RETURNS BOOLEAN AS $$
BEGIN
  RETURN corrected_similarity(string_a, string_b) >= 0.5;
END;
$$ LANGUAGE plpgsql;

CREATE OPERATOR %%% (
  leftarg = TEXT,
  rightarg = TEXT,
  procedure = is_minimally_substring_similar,
  commutator = %%%
);

现在您可以像标准相似度查询一样使用它:

SELECT * FROM table WHERE name %%% 'chateau'
ORDER BY corrected_similarity(name, 'chateau') DESC;

性能

性能对于 10 万条记录的搜索空间是可以接受的,但对于数百万条记录的搜索空间可能不会很好。为此,您可能需要使用modified build of the pg_trgm modulecode on github

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-27
    • 2013-03-30
    • 1970-01-01
    • 2020-10-18
    • 2015-05-15
    • 1970-01-01
    相关资源
    最近更新 更多