LSH(Locality Sensitive Hashing)原理与实现

2023-11-10

LSH(Locality Sensitive Hashing)翻译成中文,叫做“局部敏感哈希”,它是一种针对海量高维数据的快速最近邻查找算法。

在信息检索,数据挖掘以及推荐系统等应用中,我们经常会遇到的一个问题就是面临着海量的高维数据,查找最近邻。如果使用线性查找,那么对于低维数据效率尚可,而对于高维数据,就显得非常耗时了。为了解决这样的问题,人们设计了一种特殊的hash函数,使得2个相似度很高的数据以较高的概率映射成同一个hash值,而令2个相似度很低的数据以极低的概率映射成同一个hash值。我们把这样的函数,叫做LSH(局部敏感哈希)。LSH最根本的作用,就是能高效处理海量高维数据的最近邻问题

定义

我们将这样的一族hash函数 H={ h:SU} H = { h : S → U } 称为是 (r1,r2,p1,p2) ( r 1 , r 2 , p 1 , p 2 ) 敏感的,如果对于任意 H H 中的函数 h h ,满足以下2个条件:

  1. 如果 d(O1,O2)<r1 d ( O 1 , O 2 ) < r 1 ,那么 Pr[h(O1)=h(O2)]p1 P r [ h ( O 1 ) = h ( O 2 ) ] ≥ p 1
  2. 如果 d(O1,O2)>r2 d ( O 1 , O 2 ) > r 2 ,那么 Pr[h(O1)=h(O2)]p2 P r [ h ( O 1 ) = h ( O 2 ) ] ≤ p 2

其中, O1,O2S O 1 , O 2 ∈ S ,表示两个具有多维属性的数据对象, d(O1,O2) d ( O 1 , O 2 ) 为2个对象的相异程度,也就是1 - 相似度。其实上面的这两个条件说得直白一点,就是当足够相似时,映射为同一hash值的概率足够大;而足够不相似时,映射为同一hash值的概率足够小。

相似度的定义根据实际情况自己决定(有关数据对象相似度的比较,详情可以参考我的另一篇博文:数据相似性的度量方法总结),后面我们可以看到,针对不同的相似度测量方法,局部敏感哈希的算法设计也不同,我们主要看看在两种最常用的相似度下,两种不同的LSH:

  1. 使用Jaccard系数度量数据相似度时的min-hash
  2. 使用欧氏距离度量数据相似度时的P-stable hash

当然,无论是哪种LSH,其实说白了,都是将高维数据降维到低维数据,同时,还能在一定程度上,保持原始数据的相似度不变。LSH不是确定性的,而是概率性的,也就是说有一定的概率导致原本很相似的数据映射成2个不同的hash值,或者原本不相似的数据映射成同一hash值。这是高维数据降维过程中所不能避免的(因为降维势必会造成某种程度上数据的失真),不过好在LSH的设计能够通过相应的参数控制出现这种错误的概率,这也是LSH为什么被广泛应用的原因。

min-hash

hash函数的选择

了解min-hash之前,首先普及一下Jaccard系数的概念。Jaccard系数主要用来解决的是非对称二元属性相似度的度量问题,常用的场景是度量2个集合之间的相似度,公式这里我不写了,就是2个集合的交比2个集合的并。

比如,我在底下的表格中写出了4个对象(你可以看做是4个文档)的集合情况,每个文档有相应的词项,用词典 { w1,w2,,w7} { w 1 , w 2 , … , w 7 } 表示。若某个文档存在这个词项,则标为1,否则标0.

word D1 D 1 D2 D 2 D3 D 3 D4 D 4
w1 w 1 1 0 1 0
w2 w 2 1 1 0 1
w3 w 3 0 1 0 1
w4 w 4 0 0 0 1
w5 w 5 0 0 0 1
w6 w 6 1 1 1 0
w7 w 7 1 0 1 0

首先,我们现在将上面这个word-document的矩阵按行置换,比如可以置换成以下的形式:

word D1 D 1 D2 D 2 D3 D 3 D4 D 4
w2 w 2 1 1 0 1
w1 w 1 1 0 1 0
w4 w 4 0 0 0 1
w3 w 3 0 1 0 1
w7 w 7 1 0 1 0
w6 w 6 1 1 1 0
w5 w 5 0 0 0 1

可以确定的是,这没有改变文档与词项的关系。现在做这样一件事:对这个矩阵按行进行多次置换,每次置换之后,统计每一列(其实对应的就是每个文档)第一个不为0的位置(行号),这样每次统计的结果能构成一个与文档数等大的向量,这个向量,我们称之为签名向量。

比如,如果对最上面的矩阵做这样的统计,得到 [1,2,1,2] [ 1 , 2 , 1 , 2 ] ,对于下面的矩阵做统计,得到 [1,1,2,1] [ 1 , 1 , 2 , 1 ] .

简单来想这个问题,就拿上面的文档来说,如果两个文档足够相似,那也就是说这两个文档中有很多元素是共有的,换句话说,这样置换之后统计出来的签名向量,如果其中有一些文档的相似度很高,那么这些文档所对应的签名向量的相应的元素,值相同的概率就很高。

我们把最初始时的矩阵叫做input matrix, m m 个文档, n n 个词项组成。而把由 t t 次置换后得到的一个 t×m t × m 的矩阵叫做signature matrix.

下面是我盗的一张图,能够很清晰的展现出这一套流程:

这里写图片描述

图中,4个文档,做了3次置换,得到了一个3 x 4的签名矩阵。感谢提供图的这篇博文的作者:http://blog.sina.com.cn/s/blog_4ff49c7e0102vl52.html

需要注意的是,置换矩阵的行,在代码实现的时候,可以用这样的算法实现:

  1. 在当下剩余的行中(初始时,剩余的行为全部行),随机选取任意一行,看看这一行哪些位置(这里的位置其实是列号)的元素是1,如果签名向量中这个位置的元素还未被写入,则在这个位置写入随机选取的这个行的行号。并将这一行排除。

  2. 持续进行1步的工作,直到签名向量全部被写满为止。

以上2步的意义跟对整个矩阵置换、再统计,结果是一样的。这么说可能有点抽象,我把函数放在下面:

def sigGen(matrix):
    """
    * generate the signature vector

    :param matrix: a ndarray var
    :return a signature vector: a list var
    """

    # the row sequence set
    seqSet = [i for i in range(matrix.shape[0])]

    # initialize the sig vector as [-1, -1, ..., -1]
    result = [-1 for i in range(matrix.shape[1])]

    count = 0

    while len(seqSet) > 0:

        # choose a row of matrix randomly
        randomSeq = random.choice(seqSet)

        for i in range(matrix.shape[1]):

            if matrix[randomSeq][i] != 0 and result[i] == -1:
                result[i] = randomSeq
                count += 1
        if count == matrix.shape[1]:
            break

        seqSet.remove(randomSeq)

    # return a list
    return result

现在给出一个定理。

定理:对于签名矩阵的任意一行,它的两列元素相同的概率是 xn x n ,其中 x x 代表这两列所对应的文档所拥有的公共词项的数目。而 xn x n 也就是这两个文档的Jaccard系数。

这个定理我想不用证明了。实际上,置换input matrix的行,取每列第一个非0元的做法,就是一个hash函数。这个hash函数成功地将多维数据映射成了一维数据。而从这个定理我们发现,这样的映射没有改变数据相似度。

需要注意的一点是,这里的hash函数只能对Jaccard系数定义数据相似度的情况起作用。不同的相似度模型,LSH是不同的,目前,还不存在一种通用的LSH。

构造LSH函数族

为了能实现前面LSH定义中的2个条件的要求,我们通过多次置换,求取向量,构建了一组hash函数。也就是最终得到了一个signature matrix. 为了控制相似度与映射概率之间的关系,我们需要按下面的操作进行,一共三步。

(1) 将signature matrix水平分割成一些区块(记为band),每个band包含了signature matrix中的 r r 行。需要注意的是,同一列的每个band都是属于同一个文档的。如下图所示。这个图我还是盗的上面链接中的博文,特此说明。

这里写图片描述

(2) 对每个band计算hash值,这里的hash算法没有特殊要求,MD5,SHA1等等均可。一般情况下,我们需要将这些hash值做处理,使之成为事先设定好的hash桶的tag,然后把这些band“扔”进hash桶中。如下图所示。但是这里,我们只是关注算法原理,不考虑实际操作的效率问题。所以,省略处理hash值得这一项,得到每个band的hash值就OK了,这个hash值也就作为每个hash bucket的 tag

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LSH(Locality Sensitive Hashing)原理与实现 的相关文章

  • CentOS 7升级gcc/CentOS 7 yum 安装gcc

    centos7自带的gcc版本是4 8 手动升级安装很锻炼 毕竟已经0202年了 devtoolset 7 Developer Toolset is designed for developers working on CentOS or
  • phpmyadmin打不开500错误

    phpmyadmin打不开500错误 提示500错误 可以去掉友好提示 显示出具体错误 我处理的时候显示的具体错误是白页现象 解决方案 给上级目录一个users写入权限即可 如直接在某个磁盘下 可以给它新建一个上级目录放进去 ps 很多ph
  • c语言指令 mul bl,MUL是进行无符号乘法的指令

    MUL 无符号乘法 指令有三种格式 第一种是将8位的操作数于al相乘 第二种是将16位的操作数与ax相乘 第三种是将32位的操作数与eax进行相乘 乘数和被乘数大小必须相同 乘积的尺寸是乘数 被乘数大小的两倍 三种格式都既接受寄存器操作数
  • 【C语言实现简单数据结构】单链表

    C语言实现简单数据结构 单链表 心有所向 日复一日 必有精进 文章目录 C语言实现简单数据结构 单链表 前言 链表 1 1链表的概念和结构 1 2链表的分类 1 3单链表的实现 1 3 1接口实现 1 3 2动态申请一个结点 1 3 3单链

随机推荐

  • 【Linux】文件目录操作指令(pwd、ls、cd、mkdir、rmdir、touch、cp、rm、mv、cat、echo、tail等)

    目录 1 指定运行级别 1 1 基本介绍 1 2 应用实例 2 帮助指令 2 1 man获得帮助信息 2 2 help指令 3 文件目录类 3 1 pwd指令 3 2 ls指令 3 3 cd指令 3 4 mkdir指令 3 5 rmdir指
  • sort自定义排序方式

    2022 05 14 sort 方式1 结构体内重载运算符 方式2 cmp参数 与优先队列类比 Java和python的处理方式 Java python sort sort a begin a end sort a a n n为数组长度 通
  • C/C++Unix网络编程-IPC简介

    IPC是进程间通信的简称 进程 线程与信息共享 Unix进程间的信息共享的方式 1 左边的两个进程共享存留于文件系统中某个文件上的某些信息 为访问这些信息 每个进程都得穿越内核 例如read write lseek等 当一个文件有待更新时
  • Hadoop MapReduce执行过程详解(带hadoop例子)

    为什么80 的码农都做不了架构师 gt gt gt 分析MapReduce执行过程 MapReduce运行的时候 会通过Mapper运行的任务读取HDFS中的数据文件 然后调用自己的方法 处理数据 最后输出 Reducer任务会接收Mapp
  • UE4_模型_Bound(边界)

    放置在关卡中的每个Actor都有一个bound bound由两部分组成 蓝色的长方体和黄色的球体 Bound是专门用来测试一个物体是否可见的 边界球用于通过简单的距离测试进行快速碰撞检测 而且 通常情况下 它的大小大于它包含的对象 另一方面
  • 逻辑左移、逻辑右移、算术左移、算术右移、循环左移、循环右移的学习

    逻辑左移时 最高位丢失 最低位补0 逻辑右移时 最高位补0 最低位丢失 算术左移时 依次左移一位 尾部补0 最高的符号位保持不变 算术右移时 依次右移一位 尾部丢失 符号位右移后 原位置上复制一个符号位 循环左移时 将最高位重新放置最低位
  • Vue promise的用法

    1 promise是什么 它可以说是异步编程的一种解决方法 就拿传统的ajax发请求来说 单个还好 如果是一个请求回来的数据还要被其他请求调用 不断地嵌套 可想而知 代码看起来是很乱的 promise主要是为了解决这种情景而出现的 2 简单
  • 山东科技大学oj题库1165 打印金字塔 之一

    include
  • vs查看宏展开

    宏在我们的代码中能经常给我们带来很大的便利 但是有些宏会造成意向不到的错误 能够查看宏展开就能够查看宏错误的根源 VS2008对编译是不保存预处理的文件信息的 而宏展开的信息就是在预编译阶段 如下图 工程属性 配置属性 c c 预处理器 生
  • 【数据分析】数据分析方法(七):AARRR 模型分析 & 漏斗分析

    数据分析方法 七 AARRR 模型分析 漏斗分析 1 AARRR 模型分析方法 如果把产品看作一个鱼塘 使用产品的用户看作鱼塘里的鱼 AARRR 模型的五个环节可以描述如下 获取用户 Acquisition 想办法给鱼塘里添加新的鱼 从而扩
  • vue注册自定义指令

    src 新建文件夹directive directive下新建modules文件夹 里面存放指令js文件 新建prevetMultiClick jk 防按钮多次重复点击的指令 export default inserted el bindi
  • python open3d点云可视化(本节会根据实际所用持续更新)

    本文为博主原创文章 未经博主允许不得转载 本文为专栏 python三维点云从基础到深度学习 系列文章 地址为 https blog csdn net suiyingy article details 124017716 为了便于加强对点云数
  • 基于cocos2d-x的2D空间中的OBB(Orient Bounding Box)碰撞检测算法

    引言 最近在与好友聊天的过程中 好友问我如何实现类似这样的游戏 它主要想知道 如何检测旋转过后的物体与其他物体之间的碰撞 我们知道 在没有旋转的情况下 对于这样的方块 比较规则的物体 我们完全可以使用AABB Axie Align Bond
  • Docker容器安装启动以及基本指令

    Docker使用步骤 一 ubuntu安装Docker 更新ubuntu的apt源索引 sudo apt get update 安装包允许apt通过HTTPS使用仓库 sudo apt get install apt transport h
  • 《零基础入门学习python》学习过程(四)

    学习时间 2017 09 19 第14课 元组 知识点汇总 元组和列表最本质的区别就是元组是封闭的列表 它一旦定义就不可修改 不可插入或删除任意一个元素等操作 创建和访问一个元组 gt gt gt tuple2 创建一个空元组 gt gt
  • vue里的$attrs

    attrs官网介绍 关于 attrs vue官网如是介绍 包含了父作用域中不作为 prop 被识别 且获取 的特性绑定 class 和 style 除外 当一个组件没有声明任何 prop时 这里会包含所有父作用域的绑定 class 和 st
  • CSS transform变换(一)

    1 transiate x y 设置盒子位移
  • Vue 中既可以输入也可以下拉选择(改进版)

    项目场景 项目场景 由于前些天 弄了一个下拉选择 也可以输入的 input输入框 但我感觉效果并不是很优美 所以今天对以前的代码 改进并进行优化 达到一个完美的效果 代码CV就可以用前提是你得将数据参数改为你的 问题描述 昨天的代码 参照网
  • Decode函数的语法

    Decode函数的语法结构如下 decode expression search 1 result 1 decode expression search 1 result 1 search 2 result 2 decode express
  • LSH(Locality Sensitive Hashing)原理与实现

    LSH Locality Sensitive Hashing 翻译成中文 叫做 局部敏感哈希 它是一种针对海量高维数据的快速最近邻查找算法 在信息检索 数据挖掘以及推荐系统等应用中 我们经常会遇到的一个问题就是面临着海量的高维数据 查找最近