【算法】局部敏感哈希(LSH):高效解决相似性搜索问题的利器

news/2024/9/19 17:02:24 标签: 算法, 哈希算法

什么是局部敏感哈希?

简单来说,LSH是一种在海量数据中快速找到相似项的技术。与传统的哈希函数不同,LSH的特点是:相似的输入更可能被哈希到相同的"桶"中。

为什么要用LSH?

想象一下,你有一个包含数百万用户信息的数据库,你需要找出与某个特定用户最相似的人。传统方法可能需要遍历整个数据库,这在大数据时代是非常耗时的。而LSH可以大大加快这个过程。

LSH如何工作?一个简单的例子

让我们从一个简单的例子开始:假设我们只考虑用户的年龄。

def simple_lsh(age):
    return age // 10

users = [
    ("Alice", 22), ("Bob", 25), ("Charlie", 31),
    ("David", 23), ("Eve", 38), ("Frank", 41), ("Grace", 29)
]

hash_table = {}
for name, age in users:
    hash_value = simple_lsh(age)
    if hash_value not in hash_table:
        hash_table[hash_value] = []
    hash_table[hash_value].append((name, age))

# 打印结果
for hash_value, group in hash_table.items():
    print(f"年龄在 {hash_value * 10}{(hash_value + 1) * 10 - 1} 之间的人:")
    for name, age in group:
        print(f"  {name}: {age}")
    print()

这个简单的LSH将年龄相近的用户分到同一个桶中。但是,这种方法有一个问题:它无法处理跨越桶边界的情况,比如29岁和31岁的用户会被分到不同的桶中。

改进:多重哈希

为了解决这个问题,我们可以使用多重哈希:

def multi_hash(age):
    return [age // 10, (age + 5) // 10]

hash_table = {}
for name, age in users:
    for hash_value in multi_hash(age):
        if hash_value not in hash_table:
            hash_table[hash_value] = set()
        hash_table[hash_value].add((name, age))

# 查找相似用户
def find_similar(name, age):
    similar = set()
    for hash_value in multi_hash(age):
        similar.update(hash_table[hash_value])
    similar.remove((name, age))  # 移除自己
    return similar

# 测试
test_name, test_age = "Charlie", 31
similar = find_similar(test_name, test_age)
print(f"{test_name} ({test_age}) 的相似用户:")
for name, age in similar:
    print(f"  {name}: {age}")

这种方法增加了找到真正相似项的概率,即使它们跨越了原来的桶边界。

现实场景

在我给出的简化例子中,使用哈希的优势并不明显。让我解释一下为什么在实际应用中LSH仍然非常有用,并给出一个更贴近实际的例子来说明LSH的优势。

在小规模数据集或维度较低的情况下,LSH可能并不比直接比较更有优势。LSH的真正威力体现在处理大规模、高维度数据时。让我们考虑一个更现实的场景:

假设我们有一个包含100万用户的数据库,每个用户有100个特征(比如年龄、收入、兴趣爱好等)。我们想找出与某个特定用户最相似的前10个用户。

  1. 暴力搜索方法:

    • 需要比较100万个用户。
    • 每次比较需要计算100个特征的距离。
    • 总计算量:100万 * 100 = 1亿次特征比较。
  2. 使用LSH方法:

    • 预处理:为每个用户计算LSH值(假设使用10个哈希函数)。
    • 查询时:
      1. 计算目标用户的10个哈希值。
      2. 在哈希表中查找这10个哈希值对应的桶。
      3. 只对这些桶中的用户进行详细比较。

假设每个哈希桶平均包含100个用户:

  • 需要比较的用户数:约10 * 100 = 1000个(而不是100万)。
  • 总计算量:1000 * 100 = 10万次特征比较。

这比暴力搜索减少了约1000倍的计算量!

让我们用Python代码来模拟这个更realistic的场景:

import numpy as np
from collections import defaultdict

# 模拟100万用户,每个用户有100个特征
num_users = 1_000_000
num_features = 100
users = np.random.rand(num_users, num_features)

# LSH函数(这里使用一个简化的版本)
def lsh(vector, num_hashes=10):
    return tuple(np.dot(vector, np.random.randn(num_features)) > 0 for _ in range(num_hashes))

# 构建LSH索引
lsh_index = defaultdict(list)
for i, user in enumerate(users):
    for h in lsh(user):
        lsh_index[h].append(i)

# 查询函数
def query(target_user, top_k=10):
    candidate_set = set()
    for h in lsh(target_user):
        candidate_set.update(lsh_index[h])
    
    # 只对候选集中的用户计算实际距离
    distances = [(i, np.linalg.norm(users[i] - target_user)) for i in candidate_set]
    return sorted(distances, key=lambda x: x[1])[:top_k]

# 测试
target_user = np.random.rand(num_features)
result = query(target_user)

print(f"找到的前 {len(result)} 个最相似用户:")
for i, distance in result:
    print(f"用户ID: {i}, 距离: {distance}")

print(f"\n总用户数: {num_users}")
print(f"LSH候选集大小: {len(set().union(*[lsh_index[h] for h in lsh(target_user)]))}")

这个例子展示了LSH在大规模数据集上的优势:

  1. 大幅减少需要比较的对象数量:从100万减少到可能只有几千或更少。
  2. 查询速度快:不需要遍历整个数据集。
  3. 内存效率:可以只将哈希索引保存在内存中,而不是整个数据集。
  4. 可并行化:哈希计算和桶查询可以很容易地并行处理。

http://www.niftyadmin.cn/n/5665875.html

相关文章

c++基础入门三

文章目录 C基础入门(三)auto关键字auto简介使用细则一、可以和指针联合使用二、在一行定义多个变量 不能使用场景一、不能作为函数的参数二、不能用来声明数组 基于for的循环使用条件 指针空值nullptr C基础入门(三) 回顾上集,我们介绍了C的函数重载,引…

opencv实战项目二十四:棋盘格相机内参标定

文章目录 前言一、简介:二、使用步骤2.1制作标定板2.2 拍摄不同角度的标定板2.3计算棋盘格角点并优化2.4计算相机参数 三、整体代码实现: 前言 在数字图像处理和计算机视觉领域,相机标定是一个至关重要的步骤。它为相机提供了一个准确的数学…

Python 入门教程(4)数据类型 | 4.3、数字类型

文章目录 一、数字类型1、整数(Integer)2、浮点数(Float)3、数学计算 前言: 在Python中,数字类型是其基本数据类型之一,用于表示数学中的数值。Python提供了多种数字类型,每种类型都…

【Git】常见命令(仅笔记)

文章目录 创建/初始化本地仓库添加本地仓库配置项提交文件查看仓库状态回退仓库查看日志分支删除文件暂存工作区代码远程仓库使用 .gitigore 文件让 git 不追踪一些文件标签 创建/初始化本地仓库 git init添加本地仓库配置项 git config -l #以列表形式显示配置项git config …

机器学习-监督学习:朴素贝叶斯分类器

机器学习-监督学习:朴素贝叶斯分类器 一、引言 在机器学习的广阔领域中,监督学习占据着核心地位,它通过已知的数据集(包括输入和输出)来训练模型,以期对新的、未见过的数据做出准确的预测。朴素贝叶斯分类…

Python基础(六)——PyEcharts数据可视化初级版

案例 【前言:为了巩固之前的Python基础知识(一)到(五),并为后续使用Python作为数据处理的好帮手,我们一起来看几个例子】 使用工具:Echarts Echarts 是一个由百度开源的数据可视化…

JSON.parseArray 内存溢出

实际上我的JSON如下: 如果用以下代码:JVM的内存直接飙到内存溢出,报错OutOfMemoryError: Java heap space Object oo JSON.parseArray(json, TestVo.class) 如果我换成了这样,就没事: Object oo JSON.parseObject(…

闲鱼网页版开放,爬虫的难度指数级降低。

爬虫,可以说是程序员最基础的热手项目。 之前我也一直说阿里系的签名系统搞得太复杂,风控太高,很不利于正常的自动化工具开发,这对于需要阿里应用的客户来说,也是一个很难覆盖的成本支出不是。 当然,我做项…