RS推荐系统-LSH最近邻查找+MiniHash

2023-11-07

什么是最近邻查找?

在推荐系统中,主要分为召回跟排序两个阶段。

  • 召回阶段,基于用户画像及场景数据从海量的视频库(百万级别)中将相关度最高的资源检索出来,作为候选集,召回阶段可以通过“粗糙”的方式召回候选item。
  • 排序阶段,基于更加精细的特征对候选集(百级别)进行排序,最终呈现给用户的是很少的一部分数据。在这个ranking阶段,采用更精细的特征计算user-item之间的排序score,作为最终输出推荐结果的依据。

随着机器学习的发展,越来越多问题转移到deep learning上面解决,而系统实际部署在基于Tensorflow的Google Brain上,模型有接近10亿级参数,训练样本千亿级别。
所以,如此之大数量级的数据量,如果要通过常规方法,计算item两两之间的相似度,那么计算量级要到(n^2)级别,实在是太久了。针对这一现象,聪明的人类提出了NN的方法:
在这里插入图片描述

  • NN,Nearest Neighbor Search,最近邻查找问题。
  • KNN,K-Nearest Neighbor,k最近邻,查找离目标数据最近的前k个数据项。
  • ANN,Approximate Nearest Neighbor,近似最近邻检索,在牺牲可接受范围内的精度的情况下提高检索效率。
  • ANN,Approximate Nearest Neighbor,近似最近邻检索,在牺牲可接受范围内的精度的情况下提高检索效率。
  • LSH,局部敏感哈希是ANN的一种。

什么是Hash?

目前主流的索引技术有:

  • 基于树的索引技术(二叉树,B-Tree,B+Tree)
  • 基于哈希的索引技术
  • 基于词的倒排索引

海量数据的检索方式,Hash是重要的索引技术。所谓hash,简而言之,就是将原始数据通过一个函数(hash)映射到新的数值。比如自定义一个hash 2 x + 1 2x+1 2x+1,原始数据有[2,4,6,8],通过hash可以映射到[5,9,13,17]。

针对海量且高维的数据如何进行查找:

  • 如果数据是低维 and 小数据 => 通过线性的方式查找。
  • 数据不仅海量,而且高维 => 需要降维,采用索引方式查找

LSH,Locality-Sensitive Hashing,局部敏感哈希

  • 需要查找与某个数据1个或多个相似的数据。
  • 最近邻查找方法(ANN,Approximate Nearest Neighbor)
    在这里插入图片描述
  • 对于传统的HashTable用于检索数据,无法将相似的数据放到同一个Bucket中,比如h=x mod w
  • LSH将相邻的数据,通过映射后依然保持相邻的关系,即保持局部的敏感度Locality-Sensitive。
  • 通过Hash Function,每个Bucket会落入一些原始数据,属于同一个桶内的数据有很大可能是相邻的(也存在不相邻的数据被hash到了同一个桶内)。
  • 将原始数据集合分成了多个子集合(即每个bucket),每个子集合中的数据大概率是相邻的,而且子集合中的元素个数较少。
  • 方便进行近邻查找 => 在一个很小的集合里查找相邻元素

上面提到即使是相邻的元素,也有可能分到不同的bucket,下面举个栗子~:
$$
Hash(127) = 127%8 = 7;
Hash(123) = 123%8 = 3;
Hash(1023) = 1023%8 = 7;

$$
从上面的例子可以看出通过hash把127和1023分到了同一个桶7,而与127更加相邻的123却分到了桶3。所以LSH一定程度上会丢失精度,但是精度换时间,这种买卖的交易是值得的。

MiniHash算法

在计算文本相似度中,有:

  • k-shingle,也称为k-gram,文档中任意长度为k的字符串。将每篇文档可以表示成文档中出现一次或者多次的k-shingle的集合
  • 比如document=“abcdabd”,当2-shingle组成的集合为 {ab,bc,cd,da,bd}
  • 如果两个文档相似,那么他们会有很多的shingles也是相同的
  • 文本越长,K取值越大。K的经验值参考,短文本K=5,长文本K=10
    在这里插入图片描述
    由上图所示,假设我们有四篇文档(4列),每篇文档的shingles如果出现则表示为1,否则为0。
    有了这个输入矩阵input_metrix,我们可以通过Jaccard方法计算文本的相似度:
    SIM ⁡ ( C i , C j ) = ∣ C i ∩ C j ∣ ∣ C i ∪ C j ∣ \operatorname{SIM}\left(C_{i}, C_{j}\right)=\frac{\left|C_{i} \cap C_{j}\right|}{\left|C_{i} \cup C_{j}\right|} SIM(Ci,Cj)=CiCjCiCj
    上式分子为两个文档之间交集,分母为两个文档的并集。拿上图举个例子,我们取文档1和文档2(列1和列2),他们之间的交集为2(即两列都为1的行数),并集为5(总共出现的nunique)。看到这里可能有人会提问为啥两列都为0的情况不计算?如果两列都为0,就表示这两篇文章都没有这个shingles,计不计算其实意义都不大。

有了Jaccard相似度,但是面对海量数据,高维 => 矩阵非常大的时候,我们的目标是需要找到一个Hash函数,将原来的Jaccard相似度计算,等同于降维后的相似度矩阵计算(Input Matrix => Signature Matrix)。

  • 如果原来文档的Jaccard相似度高,那么它们的hash值相同的概率高。
  • 如果原来文档的Jaccard相似度低,那么它们的hash值不相同的概率高。

针对这个hash函数,聪明的人类提出了MiniHash。MiniHash需要准备两个东西:

  • Input Metrix
  • 随机置换表

下面上图:
在这里插入图片描述
上图中红黄蓝表示随机置换的顺序表,而右边的是输入矩阵Input Metrix。
假设我们取蓝色的置换顺序表,那么我们通过置换可以得到以下的Metrix(如下图所示)。可以看到原本的第3行(0101)置换到了第1行,原本的第7行(1010)置换到了第3行。
在这里插入图片描述

同样的,我们根据红色,黄色置换表,也可以得到不一样的Metrix。如下图所示的黄色置换表得到的Metrix。
在这里插入图片描述
有了三个置换的得到的Metrix后,重点来了。==对于每个metrix,我们取出每列首个为1的行数。==拿蓝色置换得到的矩阵为例(如下图),第1列首个1的行数为2,第2列首个1的行数为1,第3列为2,第4列为1,所以最终可以得到(2121)这个hash后的数值。
在这里插入图片描述
如此类推,由黄色+红色+蓝色的置换+hash后,可以得到以下的Signature Metrix。
在这里插入图片描述
有了MiniHash得到的Signature Metrix,我们就可以计算Jaccard相似度了。如下图所示,第1行为原输入矩阵的Jaccard相似度,第2行为通过MiniHash降维后得到的Signature Metrix的Jaccard相似度。可以看出降维前后的相似度差距不是很大,但是两者之间的计算时间+空间量级得到了大大压缩。精度换时间,何乐而不为~
在这里插入图片描述

MiniHash总结

  • 先将原矩阵Input Metrix分别经过n次随机置换(n取决于降维后的维度大小)。
  • 每次置换后,采用MinHash得到Signature Metrix。
  • 使用Sig矩阵相似度用来近似估计原始矩阵Input Matrix的Jaccard相似度。

MiniHash+LSH

当数据量非常大的时候,计算量随之也会成倍的增加。我们利用LSH+MiniHash的算法,将可能相似的用户以较大概率分到同一个桶内,这样每一个用户的“相似用户候选集”就会少很多,大大降低计算复杂度。其中主要的思想就是:

  • 在MiniHash降维的基础上,将得到的Signature向量分成多段(band)。

  • 将Signiture矩阵分成b组(即b个bands),每组由r行组成。(如下图所示)
    在这里插入图片描述

  • 对每一组进行hash,各个组设置不同的桶空间。只要两列有一组的MinHash相同,那么这两列就会hash到同一个桶而成为候选相似项.(如下图所示)
    在这里插入图片描述
    对于以上分组,我们假设对于某行,两列MinHash的值相同的概率为s(两列的相似度),有:

  • 在某个band,值都相等的概率是 S r S^r Sr

  • 在某个band,值不相同的概率是 1 − S r 1-S^r 1Sr

  • 两个文档存在b个band,这b个band都不相同的概率是 ( 1 − S r ) b (1-S^r)^{b} (1Sr)b

  • b个band里,至少有一个相同的概率是 1 − ( 1 − S r ) b 1-(1-S^r)^{b} 1(1Sr)b,即两列成为候选相似对的概率是 1 − ( 1 − S r ) b 1-(1-S^r)^{b} 1(1Sr)b

以上我们将其称之为And Then Or方法,先要求每个band的所有对应元素必须都相同,再要求多个band中至少有一个相同。符合这两条,才能发生hash碰撞。下面我们举个例子用以理解:假设两列MinHash的值相同的概率s=0.8,20个band,每个band 5行,即b=20, r=5。有

  • 在某个band,值都相等的概率: 0. 8 5 = 0.328 0.8^5 = 0.328 0.85=0.328
  • 在某个band,值不相同的概率: 1 − 0. 8 5 = 0.672 1- 0.8^5 = 0.672 10.85=0.672
  • b个band都不相同的概率: 0.67 2 2 0 = 0.00035 0.672^20 = 0.00035 0.67220=0.00035
  • b个band里,至少有一个相同的概率: 1 − 0.67 2 2 0 = 0.9996 1- 0.672^20 = 0.9996 10.67220=0.9996
  • 相应地,保持b和r不变,我们也可以调节不同的s,有:
    在这里插入图片描述
    从上图可以看出,当我们的s超过某个阈值后,两个用户成为candidate用户的概率会迅速增加并接近于1。这个阈值,也就是概率变化最陡的地方,近似为 t = ( 1 b ) 1 r t=\left(\frac{1}{b}\right)^{\frac{1}{r}} t=(b1)r1
    在这里插入图片描述
    那么实际使用的过程中,我们需要确定以下几点:
  • Smin,相似用户的阈值定义(近邻定义)
  • Signature向量的长度,降到k维embedding,即随机置换表的个数。

而针对b和r的取值,我们需要考虑:

  • 如果想要尽可能少的出现false negative,需要选择b和r使得概率变化最陡的地方小于Smin(比如s在0.5以上才属于相似用户,选择b和r使得S曲线的最陡处小于0.5)。
    在这里插入图片描述
  • 如果想要保证计算速度较快,并且尽可能少出现false positive,那么最好选择b和r使得概率变化最陡的地方较大(比如b=20,r=6)这样,s较小的两个用户就很难成为candidate用户,但同时也会有一些“潜在”的相似用户不会被划分到同一个桶内。
    在这里插入图片描述

对于LSH的一般定义:
Locality-Sensitive Hashing是满足一定条件的Hash函数簇
d 1 d_1 d1< d 2 d_2 d2是定义在距离测定d下的两个距离值,如果一个函数族的每一个函数f满足:

  • 如果d(x,y)<= d 1 d_1 d1,则f(x)=f(y)的概率至少为 p 1 p_1 p1,即P(f(x)=f(y)) >= p 1 p_1 p1
  • 如果d(x,y)>= d 2 d_2 d2,则f(x)=f(y)的概率至多为 p 2 p_2 p2,即p(f(x)=f(y)) <= p 2 p_2 p2
    那么称F为( d 1 d_1 d1, d 2 d_2 d2, p 1 p_1 p1, p 2 p_2 p2)-sensitive的函数族。
  • Jaccard相似性对应的LSH为MinHash,是( d 1 d_1 d1, d 2 d_2 d2, 1 − d 1 1-d_1 1d1, 1 − d 2 1-d_2 1d2)-sensitive.

LSH算法工具

part1
首先我们看一下用MiniHash前后,jaccard相似度的对比。话不多说,先上代码:

from datasketch import MinHash
import jieba

#创建两个句子
sentense_1 = '今天我和朋友去打球,他说我打得很好。'
sentense_2 = '昨天我和朋友去打球,他说我打得没他好。'

#文本清洗,去标点符号,分词。
def clean(x):
    symbol = ',。'
    for i in symbol:
        x = x.replace(i,'')
    res = jieba.lcut(x)
    return res
sentense_1 = clean(sentense_1)
sentense_2 = clean(sentense_2)
#查看一下清洗后的样式
sentense_1,sentense_2

在这里插入图片描述

#开始调用MiniHash方法
m1 = MinHash()
m2 = MinHash()

#对两个句子进行MiniHash操作
for d in sentense_1:
    m1.update(d.encode('utf8'))
for d in sentense_2:
    m2.update(d.encode('utf8'))
#打印MiniHash后,两个句子的相似度    
print("使用MinHash预估的Jaccard相似度", m1.jaccard(m2))

#
s1 = set(sentense_1)
s2 = set(sentense_2)
actual_jaccard = float(len(s1.intersection(s2)))/float(len(s1.union(s2)))
print("未使用MinHash预估的Jaccard相似度实际值", actual_jaccard)

在这里插入图片描述

从上图结果可以看出,用了MiniHash的结果未必比没用的差(当然这可能存在偶然性)。这里只是想表明,计算相似度里面,MiniHash的效果跟原数据的差不多,但是时间跟计算复杂度上能压缩很多倍!

在datasketch中的MinHash(),有一些可调参数:

  • num_perm参数,Hash置换函数设定个数,默认为128,如果需要提高精度,可以提高该数值,比如设置num_perm=256
  • update函数,内容Hash化m1.update(content)
  • merge函数,Hash合并,比如m1.merge(m2)

part2
了解了MiniHash的简单使用,再讲讲MiniHash+LSH的结合体。话不多说,线上代码:

from datasketch import MinHash,MinHashLSH
import jieba

sentense_1 = '今天我和朋友去打球,他说我打得很好。'
sentense_2 = '昨天我和朋友去打球,他说我打得没他好。'
test = '昨天我和朋友没去打球,他说我打得太好'

def clean(x):
    symbol = ',。'
    for i in symbol:
        x = x.replace(i,'')
    res = jieba.lcut(x)
    return res
sentense_1 = clean(sentense_1)
sentense_2 = clean(sentense_2)
test = clean(test)

sentense_1,sentense_2,test

在这里插入图片描述

m1 = MinHash()
m2 = MinHash()
m3 = MinHash()

for d in sentense_1:
    m1.update(d.encode('utf8'))
for d in sentense_2:
    m2.update(d.encode('utf8'))
for d in test:
    m3.update(d.encode('utf8'))

lsh = MinHashLSH(threshold=0.5, num_perm=128)
lsh.insert("m1", m1)
lsh.insert("m2", m2)
result = lsh.query(m3)
print("近似的邻居(Jaccard相似度>0.5)", result)

在这里插入图片描述
由上图可以得到与test相似度最高的sentense_1

part3
除了MiniHash+LSH,还可以用MinHashLSHForest,针对新闻中某句话Query进行TOP-K相似度输出。话不多说,先上代码(数据集链接):

from datasketch import MinHash, MinHashLSH, MinHashLSHForest
from sklearn.feature_extraction.text import TfidfVectorizer
import jieba.posseg as pseg
import re

# 读取文件
f = open('./sentences.txt', 'r', encoding='UTF-8')
text = f.read()

# 以句号,叹号,问号作为分隔,去掉\n换行符号
sentences = re.split('[。!?]', text.replace('\n', ''))

# 最后一行如果为空,则删除
if sentences[len(sentences)-1] == '':
    sentences.pop()
#查看简单清洗效果
sentences

在这里插入图片描述

# 将文本进行分词
def get_item_str(item_text):
    item_str = "" 
    item=(pseg.cut(item_text)) 
    for i in list(item):
        #去掉停用词
        if i.word not in list(stop):  
            item_str += i.word
            #tfidf_vectorizer.fit_transform的输入需要空格分隔的单词
            item_str += " "
    return item_str

# 对文本创建MinHash
def get_minhash(item_str):
    temp = MinHash()
    for d in item_str:
        temp.update(d.encode('utf8'))
    return temp

# 设置停用词
stop = [line.strip().decode('utf-8') for line in open('stopword.txt').readlines()]
# 得到分词后的documents
documents = []
for item_text in sentences:
    # 将item_text进行分词
    item_str = get_item_str(item_text)
    documents.append(item_str)
    
# 创建LSH Forest及MinHash对象
minhash_list = []
forest = MinHashLSHForest()
for i in range(len(documents)):
    #得到train_documents[i]的MinHash
    temp = get_minhash(documents[i])
    minhash_list.append(temp)
    forest.add(i, temp)
# index所有key,以便可以进行检索
forest.index()
query = '00:01:36,2019天猫双11总成交额超100亿元'
# 将query的内容进行分词
item_str = get_item_str(query)
# 得到query的MinHash
minhash_query = get_minhash(item_str)

# 查询forest中与m1相似的Top-K个邻居,此处K取3
result = forest.query(minhash_query, 3)
for i in range(len(result)):
    print(result[i], minhash_query.jaccard(minhash_list[result[i]]), documents[result[i]].replace(' ', ''))
print("Top 3 邻居", result)

在这里插入图片描述

MinHashLSHForest流程总结

  • 对新闻中的句子进行检索
  • 使用MinHashLSHForest,针对某句话Query进行TOP-K相似度输出
  • Step1、分词
  • Step2、创建MinHash
  • Step3、使用MinHashLSHForest进行Index
  • Step4、使用MinHashLSHForest进行Query,查询Top-K相似句子

走过路过的朋友们,记得点个关注或者点赞哦,谢谢,我是阿民,会努力保持更新!!!谢谢!!

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

RS推荐系统-LSH最近邻查找+MiniHash 的相关文章

随机推荐

  • Qt5.12.3移植rk3399pro笔记

    Qt5 12 3移植到rk3399pro笔记 环境 主机 Ubuntu16 04 目标机 rk3399pro板 x11平台 交叉编译toolchain linux aarch64 gnu 问题描述 我的目标机是debian系统 带lxde桌
  • 【大数据】HiveQL的数据操作

    HiveQL的数据操作 因为 Hive 没有行级别的数据插入 数据更新和删除操作 那么往表中装载数据的唯一途径就是使用一种 大量 的数据装载操作 或者通过其他方式仅仅将文件写入到正确的目录下 1 向管理表中装载数据 LOAD DATA LO
  • SpringFactoriesLoader ServiceLoader区别

    内容简介 IoC 并不仅限于解决模块内类与类之间的依赖耦合问题 其同样适用于模块与模块之间 OSGi 一直致力于这方面的工作 但其实 Java 和 Spring 都提供了对 IoC 的支持 Java 本身提供了一种很简便的方式来支持 IoC
  • 报告

    来源 Prophet 2019年 战略数字化转型的重要性已经不止于IT领域 而影响着全公司的竞争力 企业的相关预算直线攀升 利益相关方所关注的颠覆性技术数量急剧增加 数字化项目开始由首席高管主导 并由相互协作的跨职能团队管理 数字化是整个企
  • 爬虫项目五:最详细的京东商品、评价爬虫、词云展示

    文章目录 前言 一 京东商品信息爬虫 1 分析URL 2 实例化chrome 3 加载完整数据 4 实现翻页 5 解析数据 二 京东商品评价爬虫 1 找到接口 2 分析url 3 解析数据 4 词云 前言 本文内容包含京东商品列表爬虫的详细
  • pycharm启动报错

    1 点击pycharm 报错 2 打开cmd 输入gpedit msc 点击 确定 3 在本地组策略编辑器 选择 Windows设置 安全设置 本地策略 安全选项 用户帐户控制 用于内置管理员帐户的管理员批准模式 4 设置 用户帐户控制 用
  • cuda历史版本和cudnn的下载地址

    cuda历史版本下载地址 https developer nvidia com cuda toolkit archive cudnn下载地址 https developer nvidia com rdp cudnn archive 欢迎大家
  • pthread_mutex_init线程互斥锁的使用

    pthread mutex init 头文件 include
  • Springboot项目中@JsonProperty不生效-如何处理呢?

    转自 Springboot项目中 JsonProperty不生效 如何处理呢 下文笔者讲述SpringBoot中 JsonProperty不生效的相关简介说明 首先笔者将讲述JsonProperty注解的功能简介说明 JsonPropert
  • googlecloud谷歌云的初学体会(1)

    googlecloud谷歌云入门 1 一 纯小白自述 二 云是个什么云 三 装一个软件 资源 服务 四 服务器 爷爷提供服务的电脑 五 PGSQL的安装 六 总结 一 纯小白自述 自己是个小白 仅仅懂得几句sql查询和编程的基础语法 云是啥
  • 第八十七题 UVa12166 Equilibrium Mobile

    A mobile is a type of kinetic sculpture constructed to take advantage of the principle of equilibrium It consists of a n
  • 傻瓜攻略(七)——MATLAB神经网络的保存和调用

    作为科研领域十分重要的计算工具 MATLAB在深度学习方面也一直与时俱进 每一个版本的更新都会引进许多新的机器学习和深度学习案例 下面介绍将训练好的网络进行保存的方法 当再次调用网络时 可以在前一次训练的基础上进一步训练或者直接处理新数据
  • NIPS 2017

    Attention is all you need Author Unit Google Brain Google Research University of Toronto Authors Ashish Vaswani Noam Sha
  • 什么副业可以月赚1万元?做什么副业可以月入上万?

    在当前经济形势下 对于一个普通上班族而言 指望工资生活 日子肯定是过得紧巴巴的 许多人想谋求一份副业收入很正常 那么 在当前社会上 有哪些副业项目 能一个月收入一万多呢 我这里给大家推荐几个 仅供用于调研参考 1 自媒体 也许很多人会说这个
  • 决策树详解(一)

    1 决策树的概念 决策树算法以树状结构表示数据分类的结果 每个决策点实现一个具有离散输出的测试函数 记为分支 决策树的元素有 根节点 非叶子节点 分支 叶节点四种元素 其代表的含义如下图所示 决策树的工作分为两个阶段 1 训练阶段 给定训练
  • 生成以太坊系地址

    代码解析 要首先生成一个新的钱包地址 我们需要导入go ethereum crypto包 该包提供用于生成随机私钥的GenerateKey方法 privateKey err crypto GenerateKey if err nil log
  • 再论KVM超量使用

    转载自 http www sohu com a 111248295 251444 KVM超量使用一直是热门话题 前段时间发的文章 群讨论 虚拟机能否使用32个CPU 又引去了群友的激烈讨论 本文为群友根据自己的经验总结投稿 感谢这位热心的群
  • 华为 ospf 报文及邻居状态有限机

    华为 IP 路由基础 ospf 报文及邻居状态有限机 ospf协议邻居建立 一 ospf的工作机制 建立邻居 发送数据库信息 计算出最短路径 hello报文 用来建立邻居和维护邻居 邻居发现 是自动发现邻居路由器 使用的组播的地址224 0
  • docker 安装

    前提 开发环境为虚拟机Ubuntu 16 04 更换国内镜像 虚拟机是刚刚安装的 需要先更换成国内镜像源 1 首先备份原始源文件 sudo cp etc apt sources list etc apt sources list bak 2
  • RS推荐系统-LSH最近邻查找+MiniHash

    什么是最近邻查找 在推荐系统中 主要分为召回跟排序两个阶段 召回阶段 基于用户画像及场景数据从海量的视频库 百万级别 中将相关度最高的资源检索出来 作为候选集 召回阶段可以通过 粗糙 的方式召回候选item 排序阶段 基于更加精细的特征对候