从相似度算法谈起 - Effective similarity search in PostgreSQL

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 标签 PostgreSQL , 数组 , 相似度 背景 相似度分析是一个非常普遍的需求,例如根据用户提供的线索,从一堆文本数据、图片数据、视频数据中筛选一段与用户的描述相近的。 我之前写过一系列的文章来介绍,文本、图片相似度搜索的技术和使用场景。 《PostgreSQL 在视频、图片去

标签

PostgreSQL , 数组 , 相似度 , 文本分析 , 图像分析 , 字符串分析 , 婚姻介绍 , 精确配对


背景

相似度分析是一个非常普遍的需求,例如根据用户提供的线索,从一堆文本数据、图片数据、视频数据中筛选一段与用户的描述相近的。

我之前写过一系列的文章来介绍,文本、图片相似度搜索的技术和使用场景。

《PostgreSQL 在视频、图片去重,图像搜索业务中的应用》

《弱水三千,只取一瓢,当图像搜索遇见PostgreSQL(Haar wavelet)》

《聊一聊双十一背后的技术 - 毫秒分词算啥, 试试正则和相似度》

《PostgreSQL 全文检索加速 快到没有朋友 - RUM索引接口(潘多拉魔盒)》

《PostgreSQL 文本数据分析实践之 - 相似度分析》

为什么还要写本文呢?

本文提到的技术实际上是很早以前的相似度计算的技术,现在已经改进了很多,但是旧的东西比较简单,也容易理解,了解一下初心未尝不可,还是挺有意思的。

从最简单的说起 - 如何计算两个数组的相似度

假设有两个数组,里面分别有一些元素,这些元素是用来表示用户的画像的。

那么通过计算不同用户之间的数组的相似度,就可以知道他们是否有共同的癖好,有没有话题可聊。

好像又扯到一些婚介网站啦,没错,确实可以用来配对呢。

pic

pic

那么怎么计算这两个数组的相似度呢?

算法介绍

首先了解几个数组相关的术语。

Na, Nb – the number of unique elements in the arrays

Nu – the number of unique elements in the union of sets

Ni – the number of unique elements in the intersection of arrays

1. 最简单的相似度算法如下

pic

pic

好处

  • 容易理解

  • 速度=N*log(N)

  • 当Nb, Na很大时,也可以很好的支持

2. 另一种相似度算法

pic

好处

  • 速度=N*log(N)

  • 当Nb, Na很大时,也可以很好的支持

注意以上两种方法都存在一定的问题

  • Few elements -> large scatter of similarity (当元素很少时,相似度可能会很分散)

  • Frequent elements -> weight below (当元素频繁出现时,没有词频的权重,无法得到合理的相似度)

3. TF/IDF系数,解决以上问题

http://en.wikipedia.org/wiki/Tf*idf

pic

其中

pic

pic

有了理论基础,就可以来实现相似度了的运算了,PostgreSQL很容易扩展,所以不需要担心大改PG内核,加个插件就行了。

下面提到的smlar插件是一个古老的插件,但是它支持相似度公式,也就是说,你可以自定义相似度的算法公式,进行运算,同时还支持GiST和GIN的索引哦。

smlar相似度插件

部署

git clone git://sigaev.ru/smlar  
cd smlar  
USE_PGXS=1 make   
USE_PGXS=1 make install  

设置参数,相似度阈值(大于阈值返回TRUE,小于阈值返回FALSE)

smlar.threshold = 0.8  # or any other value >0 and <1  

使用方法

psql  

test=# CREATE EXTENSION smlar;  
CREATE EXTENSION  

计算相似度

test=# SELECT smlar('{1,4,6}'::int[], '{5,4,6}' );  
  smlar    
----------  
 0.666667  
(1 row)  

test=# SELECT smlar('{1,4,6}'::int[], '{5,4,6}', 'N.i / sqrt(N.a * N.b)' );  
  smlar    
----------  
 0.666667  
(1 row)  

根据相似度阈值,判断两者是否相似

test=# SELECT '{1,4,6,5,7,9}'::int[] % '{1,5,4,6,7,8,9}'::int[] as similar;  
 similar  
---------  
 t  
(1 row)  

索引支持,% 操作符支持索引检索,可以快速的得到你要查询的数据

GiST/GIN support for % operation.

The parameter "similar.type" allows you to specify what kind of formula used to calculate the similarity: cosine (default), overlap or tfidf.

For "tfidf" need to make additional configuration, but I will not consider this in the article (all can be found in the README file).

Now let's consider an example of using this extension.

前面讲了,相似度的计算算法,有3个公式可以使用,所以这里也一样,用户可以自定义公式来计算相似度

计算相似度时,用户可以提供计算公式。

test=# SELECT smlar('{1,4,6}'::int[], '{5,4,6}', 'N.i / sqrt(N.a * N.b)' );  
  smlar    
----------  
 0.666667  
(1 row)  

由数组的相似度运算到字符串、图片、..... 的相似度运算

前面分析了一同数组的相似度运算,马上会问了,字符串 怎么搞,图片,或者其他的特殊类型 怎么算相似度呢?

字符串相似度

字符串与字符串的相似度运算,其实也有思路的,比如PostgreSQL pg_trgm插件,将字符串打成很多的token,对tokens进行运算。(其实又回到了数组与数组的相似度计算)

https://www.postgresql.org/docs/9.6/static/pgtrgm.html

postgres=# select similarity('hello digoal','hell digoal');  
 similarity   
------------  
   0.785714  
(1 row)  

pg_trgm很好用,有很多的索引检索,排序的支持。

包括对正则表达式的索引支持,有更详细的文本请参考。

《聊一聊双十一背后的技术 - 毫秒分词算啥, 试试正则和相似度》

图片相似度

说完文本,该说说图片了,其实图片也可以数字化,比如有一张大图,

pic

首先压缩为1515 pixel的小图,1515一共225个小格子,每个小格子里面由RGB三原色组成。

可以将每个格子的三原色计算成一个值,这样就组成了一个15*15的矩阵数组。

例如某个格子的值为 0.299 * red + 0,587 * green + 0,114 * blue

那么又回到了数组与数组的相似度计算上面了。

pic

以下是使用以上方法完成的,对两张图片的近似度运算

pic

pic

是不是很神奇呢?

例子

CREATE TABLE images (  
 id serial PRIMARY KEY,  
 name varchar(50),  
 image_array integer[]  
);  

INSERT into images(image_array) VALUES ('{1010257,...,2424257}');  

test=# SELECT count(*) from images;  
 count   
--------  
 200000  
(1 row)  

test=# EXPLAIN ANALYZE SELECT id FROM images WHERE images.image_array % '{1010259,...,2424252}'::int[];  

Aggregate  (cost=14.58..14.59 rows=1 width=0) (actual time=1.785..1.785 rows=1 loops=1)  
   -&gt;  Seq Scan on images  (cost=0.00..14.50 rows=33 width=0) (actual time=0.115..1.772 rows=20 loops=1)  
         Filter: (image_array % '{1010259,1011253,...,2423253,2424252}'::integer[])  
 Total runtime: 5152.819 ms  
(4 rows)  

CREATE INDEX image_array_gin ON images USING GIN(image_array _int4_sml_ops);  

or  

CREATE INDEX image_array_gist ON images USING GIST(image_array _int4_sml_ops);  

索引的使用测试

test=# EXPLAIN ANALYZE SELECT id FROM images WHERE images.image_array % '{1010259,1011253,...,2423253,2424252}'::int[];  

 Aggregate  (cost=815.75..815.76 rows=1 width=0) (actual time=320.428..320.428 rows=1 loops=1)  
   -&gt;  Bitmap Heap Scan on images  (cost=66.42..815.25 rows=200 width=0) (actual time=108.127..304.524 rows=40000 loops=1)  
         Recheck Cond: (image_array % '{1010259,1011253,...,2424252}'::integer[])  
         -&gt;  Bitmap Index Scan on image_array_gist  (cost=0.00..66.37 rows=200 width=0) (actual time=90.814..90.814 rows=40000 loops=1)  
               Index Cond: (image_array % '{1010259,1011253,...,2424252}'::integer[])  
 Total runtime: 320.487 ms  
(6 rows)  

test=# SELECT count(*) from images;  
  count   
---------  
 1000000  
(1 row)  

test=# EXPLAIN ANALYZE SELECT count(*) FROM images WHERE images.image_array % '{1010259,1011253,...,2423253,2424252}'::int[];  

 Bitmap Heap Scan on images  (cost=286.64..3969.45 rows=986 width=4) (actual time=504.312..2047.533 rows=200000 loops=1)  
   Recheck Cond: (image_array % '{1010259,1011253,...,2423253,2424252}'::integer[])  
   -&gt;  Bitmap Index Scan on image_array_gist  (cost=0.00..286.39 rows=986 width=0) (actual time=446.109..446.109 rows=200000 loops=1)  
         Index Cond: (image_array % '{1010259,1011253,...,2423253,2424252}'::integer[])  
 Total runtime: 2152.411 ms  
(5 rows)  

EXPLAIN ANALYZE SELECT smlar(images.image_array, '{1010259,...,2424252}'::int[]) as similarity FROM images WHERE images.image_array % '{1010259,1011253, ...,2423253,2424252}'::int[] ORDER BY similarity DESC;   

 Sort  (cost=4020.94..4023.41 rows=986 width=924) (actual time=2888.472..2901.977 rows=200000 loops=1)  
   Sort Key: (smlar(image_array, '{...,2424252}'::integer[]))  
   Sort Method: quicksort  Memory: 15520kB  
   -&gt;  Bitmap Heap Scan on images  (cost=286.64..3971.91 rows=986 width=924) (actual time=474.436..2729.638 rows=200000 loops=1)  
         Recheck Cond: (image_array % '{...,2424252}'::integer[])  
         -&gt;  Bitmap Index Scan on image_array_gist  (cost=0.00..286.39 rows=986 width=0) (actual time=421.140..421.140 rows=200000 loops=1)  
               Index Cond: (image_array % '{...,2424252}'::integer[])  
 Total runtime: 2912.207 ms  
(8 rows)  

文本的相似度分析

文本的分析,是指将文本使用全文检索的方式,转换为ts_vector数据类型,然后对FTS进行相似度分析,详见我写的如下文章

《PostgreSQL 全文检索加速 快到没有朋友 - RUM索引接口(潘多拉魔盒)》

《PostgreSQL 文本数据分析实践之 - 相似度分析》

更优秀的图片相似度分析方法

其实图像搜索有更好的技术,相比前面简单粗暴的pixel 矩阵的运算更合理,Haar wavelet的算法,一样是嫁接到PostgreSQL里面,详见我写的如下文章

《PostgreSQL 在视频、图片去重,图像搜索业务中的应用》

《弱水三千,只取一瓢,当图像搜索遇见PostgreSQL(Haar wavelet)》

smlar readme

float4 smlar(anyarray, anyarray)  
        - computes similary of two arrays. Arrays should be the same type.  

float4 smlar(anyarray, anyarray, bool useIntersect)  
        -  computes similary of two arrays of composite types. Composite type looks like:  
                CREATE TYPE type_name AS (element_name anytype, weight_name FLOAT4);  
           useIntersect option points to use only intersected elements in denominator  
           see an exmaples in sql/composite_int4.sql or sql/composite_text.sql  

float4 smlar( anyarray a, anyarray b, text formula );  
        - computes similary of two arrays by given formula, arrays should   
        be the same type.   
        Predefined variables in formula:  
          N.i   - number of common elements in both array (intersection)  
          N.a   - number of uniqueelements in first array  
          N.b   - number of uniqueelements in second array  
        Example:  
        smlar('{1,4,6}'::int[], '{5,4,6}' )  
        smlar('{1,4,6}'::int[], '{5,4,6}', 'N.i / sqrt(N.a * N.b)' )  
        That calls are equivalent.  

anyarray % anyarray  
        - returns true if similarity of that arrays is greater than limit  

float4 show_smlar_limit()  - deprecated  
        - shows the limit for % operation  

float4 set_smlar_limit(float4) - deprecated  
        - sets the limit for % operation  

Use instead of show_smlar_limit/set_smlar_limit GUC variable   
smlar.threshold (see below)  


text[] tsvector2textarray(tsvector)  
        - transforms tsvector type to text array  

anyarray array_unique(anyarray)  
        - sort and unique array  

float4 inarray(anyarray, anyelement)  
        - returns zero if second argument does not present in a first one  
          and 1.0 in opposite case  

float4 inarray(anyarray, anyelement, float4, float4)  
        - returns fourth argument if second argument does not present in   
          a first one and third argument in opposite case  

GUC configuration variables:  

smlar.threshold  FLOAT  
        Array's with similarity lower than threshold are not similar   
        by % operation  

smlar.persistent_cache BOOL  
        Cache of global stat is stored in transaction-independent memory  

smlar.type  STRING  
        Type of similarity formula: cosine(default), tfidf, overlap  

smlar.stattable STRING  
        Name of table stored set-wide statistic. Table should be   
        defined as  
        CREATE TABLE table_name (  
                value   data_type UNIQUE,  
                ndoc    int4 (or bigint)  NOT NULL CHECK (ndoc>0)  
        );  
        And row with null value means total number of documents.  
        See an examples in sql/*g.sql files  
        Note: used on for smlar.type = 'tfidf'  

smlar.tf_method STRING  
        Calculation method for term frequency. Values:  
                "n"     - simple counting of entries (default)  
                "log"   - 1 + log(n)  
                "const" - TF is equal to 1  
        Note: used on for smlar.type = 'tfidf'  

smlar.idf_plus_one BOOL  
        If false (default), calculate idf as log(d/df),  
        if true - as log(1+d/df)  
        Note: used on for smlar.type = 'tfidf'  

Module provides several GUC variables smlar.threshold, it's highly  
recommended to add to postgesql.conf:  
custom_variable_classes = 'smlar'       # list of custom variable class names  
smlar.threshold = 0.6  #or any other value > 0 and < 1  
and other smlar.* variables  

GiST/GIN support for % and  && operations for:  
  Array Type   |  GIN operator class  | GiST operator class    
---------------+----------------------+----------------------  
 bit[]         | _bit_sml_ops         |   
 bytea[]       | _bytea_sml_ops       | _bytea_sml_ops  
 char[]        | _char_sml_ops        | _char_sml_ops  
 cidr[]        | _cidr_sml_ops        | _cidr_sml_ops  
 date[]        | _date_sml_ops        | _date_sml_ops  
 float4[]      | _float4_sml_ops      | _float4_sml_ops  
 float8[]      | _float8_sml_ops      | _float8_sml_ops  
 inet[]        | _inet_sml_ops        | _inet_sml_ops  
 int2[]        | _int2_sml_ops        | _int2_sml_ops  
 int4[]        | _int4_sml_ops        | _int4_sml_ops  
 int8[]        | _int8_sml_ops        | _int8_sml_ops  
 interval[]    | _interval_sml_ops    | _interval_sml_ops  
 macaddr[]     | _macaddr_sml_ops     | _macaddr_sml_ops  
 money[]       | _money_sml_ops       |   
 numeric[]     | _numeric_sml_ops     | _numeric_sml_ops  
 oid[]         | _oid_sml_ops         | _oid_sml_ops  
 text[]        | _text_sml_ops        | _text_sml_ops  
 time[]        | _time_sml_ops        | _time_sml_ops  
 timestamp[]   | _timestamp_sml_ops   | _timestamp_sml_ops  
 timestamptz[] | _timestamptz_sml_ops | _timestamptz_sml_ops  
 timetz[]      | _timetz_sml_ops      | _timetz_sml_ops  
 varbit[]      | _varbit_sml_ops      |   
 varchar[]     | _varchar_sml_ops     | _varchar_sml_ops  

参考

https://github.com/postgrespro/imgsmlr

http://railsware.com/blog/2012/05/10/effective-similarity-search-in-postgresql/

https://github.com/postgrespro/pg_trgm_pro

https://www.postgresql.org/docs/9.6/static/pgtrgm.html

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
30天前
|
缓存 算法 C语言
【C++ 标准查找算法 】C++标准库查找算法深入解析(In-depth Analysis of C++ Standard Library Search Algorithms)
【C++ 标准查找算法 】C++标准库查找算法深入解析(In-depth Analysis of C++ Standard Library Search Algorithms)
46 0
|
10月前
|
机器学习/深度学习 传感器 算法
【麻雀算法】基于自适应螺旋飞行麻雀搜索算法求解单目标优化问题附matlab代码(Adaptive Spiral Flying Sparrow Search Algorithm,ASFSSA)
【麻雀算法】基于自适应螺旋飞行麻雀搜索算法求解单目标优化问题附matlab代码(Adaptive Spiral Flying Sparrow Search Algorithm,ASFSSA)
|
11月前
|
算法 搜索推荐 数据格式
基于用户的协同过滤算法(及3种计算用户相似度的方法)
本文参考《推荐系统实践》中基于用户的协同过滤算法内容。基于老师上课讲解,自己实现了其中的代码,了解了整个过程。
213 0
|
11月前
|
算法 数据库 索引
App Inventor 2 算法之二分算法(Binary Search)实现,快速查找定位
二分算法(Binary Search)是生活中非常常用的折半算法,能解决快速查找、快速定位的问题,主要用到数学和逻辑代码块。 本示例程序演示了采用普通遍历的方式和二分的方式分别需要几次能够猜中随机给出的数字。
104 0
|
机器学习/深度学习 算法 计算机视觉
【智能优化算法-圆圈搜索算法】基于圆圈搜索算法Circle Search Algorithm求解单目标优化问题附matlab代码
【智能优化算法-圆圈搜索算法】基于圆圈搜索算法Circle Search Algorithm求解单目标优化问题附matlab代码
【智能优化算法-圆圈搜索算法】基于圆圈搜索算法Circle Search Algorithm求解单目标优化问题附matlab代码
|
C++ 计算机视觉
CV之FC:人脸识别之判断相似度极高的国内外明星根据人工智能算法(AP云)预测判别是否为同一个人
CV之FC:人脸识别之判断相似度极高的国内外明星根据人工智能算法(AP云)预测判别是否为同一个人
CV之FC:人脸识别之判断相似度极高的国内外明星根据人工智能算法(AP云)预测判别是否为同一个人
|
机器学习/深度学习 算法 固态存储
选择性搜索算法(Selective Search)超详解(通俗易懂版)
选择性搜索算法(Selective Search)超详解(通俗易懂版)
选择性搜索算法(Selective Search)超详解(通俗易懂版)
|
机器学习/深度学习 传感器 算法
榛子树搜索算法(Hazelnut tree search algorithm,HTS)附matlab代码
榛子树搜索算法(Hazelnut tree search algorithm,HTS)附matlab代码
|
算法 Java
经典算法之折半查找(Binary Search)
经典算法之折半查找(Binary Search)
168 0
经典算法之折半查找(Binary Search)
|
算法
经典算法之顺序查找(Sequential Search)
经典算法之顺序查找(Sequential Search)
175 0

相关产品

  • 云原生数据库 PolarDB