LSH局部敏感哈希:原理姓建,Python手撕LSH實(shí)現(xiàn)embedding近鄰檢索

摘要:局部敏感哈希Python洒沦,矢量檢索推薦系統(tǒng)

單獨(dú)記錄一下LSH算法的原理价淌,結(jié)合代碼深入理解一下申眼,因?yàn)檫@個(gè)算法的調(diào)參對(duì)結(jié)果影響極大,不懂原理就不會(huì)調(diào)參蝉衣,導(dǎo)致最終效果不理想

LSH概述

知識(shí)準(zhǔn)備

重點(diǎn)概念

算法流程

其他策略

Python代碼實(shí)現(xiàn)

這個(gè)代碼參考了https://blog.csdn.net/sgyuanshi/article/details/108132214
我在他的基礎(chǔ)上增加了idMap的映射括尸,在模型中把向量的標(biāo)識(shí)也灌了進(jìn)去

import numpy as np
from typing import List, Union


class EuclideanLSH:
    def __init__(self, num_hash_tables: int, bucket_len: int, embedding_size: int):
        """
        LSH
        :param num_hash_tables:
        :param bucket_len:
        :param embedding_size:
        """
        self.num_hash_tables = num_hash_tables
        self.bucket_len = bucket_len
        # 同一個(gè)hash_table采用同一個(gè)hash函數(shù),k和hash函數(shù)相同病毡,R相同
        self.R = np.random.random([embedding_size, num_hash_tables])
        # 一個(gè)hash——table一個(gè)隨機(jī)的b
        self.b = np.random.uniform(0, bucket_len, [1, num_hash_tables])
        # 初始化空的hash_table
        self.hash_tables = [dict() for i in range(num_hash_tables)]
        # ids和vector的對(duì)應(yīng)關(guān)系
        self.ids_map = {}

    def _hash(self, inputs: Union[List[List], np.ndarray]):
        """
        將向量映射到對(duì)應(yīng)的hash_table的索引
        :param inputs: 輸入的單個(gè)或多個(gè)向量
        :return: 每一行代表一個(gè)向量輸出的所有索引濒翻,每一列代表位于一個(gè)hash_table中的索引
        """
        # H(V) = |V·R + b| / a,R是一個(gè)隨機(jī)向量啦膜,a是桶寬有送,b是一個(gè)在[0,a]之間均勻分布的隨機(jī)變量,這個(gè)乘10是為了hash值分布地更開
        hash_val = np.floor(np.abs(np.matmul(inputs, self.R) + self.b) * 10 / self.bucket_len)

        return hash_val

    def insert(self, inputs, ids):  # 增加id和向量的對(duì)應(yīng)關(guān)系
        """
        將向量映射到對(duì)應(yīng)的hash_table的索引僧家,并插入到所有hash_table中
        :param inputs:
        :return:
        """
        self.ids_map = dict(zip(ids, inputs))
        # 將inputs轉(zhuǎn)化為二維向量
        inputs = np.array(inputs)
        if len(inputs.shape) == 1:
            inputs = inputs.reshape([1, -1])

        hash_index = self._hash(inputs)
        # 一條輸入向量雀摘,一條映射到所有hash_table的索引值
        for id_one, inputs_one, indexs in zip(ids, inputs, hash_index):
            # i代表第i個(gè)hash_table,key則為當(dāng)前hash_table的索引位置
            for i, key in enumerate(indexs):
                # 第n個(gè)hash_table的第k個(gè)桶位置八拱,將這條數(shù)據(jù)灌進(jìn)去
                # 還可以這樣寫, 這個(gè)地方用元組是因?yàn)楹竺嫘枰尤雜et阵赠,不可變可以hash
                self.hash_tables[i].setdefault(key, []).append(id_one)

    def query(self, id_one, nums=20):
        """
        查詢與id_one的inputs相似的向量,并輸出相似度最高的nums個(gè)
        :param id_one:
        :param nums:
        :return:
        """
        assert id_one in self.ids_map, "元素不存在在"
        id_vector = self.ids_map[id_one]
        hash_val = self._hash(id_vector).ravel()  # 計(jì)算新輸入的在所有hash_table的值肌稻,然后拉平為一維向量
        candidates = set()

        # 每一張hash table中相同桶位置的向量全部加入候選集豌注,去重
        for i, key in enumerate(hash_val):
            # 后面是一個(gè)集合,所以用update灯萍,將集合拆碎加入,集合內(nèi)部元素必須是不可變的轧铁,所以提前轉(zhuǎn)了元組
            candidates.update(self.hash_tables[i][key])
        candidates = [x for x in candidates if x != id_one]
        print("LSH之后所有hash_table一個(gè)桶下的候選集總計(jì){}".format(len(candidates)))

        # 根據(jù)向量距離進(jìn)行排序
        # 候選集暴力求解
        res = [(x, self.euclidean_dis(self.ids_map[x], id_vector)) for x in candidates]
        return sorted(res, key=lambda x: x[1])[:nums]

    @staticmethod
    def euclidean_dis(x, y):
        """
        計(jì)算歐式距離
        :param x:
        :param y:
        :return:
        """
        x = np.array(x)
        y = np.array(y)

        return np.sqrt(np.sum(np.power(x - y, 2)))

現(xiàn)在我們了來(lái)調(diào)用一發(fā),數(shù)據(jù)采用離線訓(xùn)練好的170萬(wàn)的實(shí)體embedding向量旦棉,向量維度為16齿风,大概長(zhǎng)這個(gè)樣子



下一步初始化模型參數(shù),向量維度16固定绑洛,設(shè)置num_hash_table和bucket_len都是8



最后輸入一個(gè)實(shí)體尋找Top10救斑,對(duì)比一下LSH近似搜索和全表暴力搜索的性能和準(zhǔn)確度
LSH近似搜索

暴力搜索

結(jié)果是LSH從170萬(wàn)實(shí)體中先召回了所有和輸入實(shí)體共同桶的7.1萬(wàn)個(gè)候選,最終在桶下暴力搜索耗時(shí)3.6秒真屯,而全表暴力搜索耗時(shí)60秒脸候,從準(zhǔn)確率來(lái)看全表掃描更準(zhǔn)備,但是LSH的Top1也是全表掃描的第三名,整體來(lái)看LSH準(zhǔn)確率表現(xiàn)也不錯(cuò)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末运沦,一起剝皮案震驚了整個(gè)濱河市泵额,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌携添,老刑警劉巖嫁盲,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異烈掠,居然都是意外死亡羞秤,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門左敌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瘾蛋,“玉大人,你說(shuō)我怎么就攤上這事矫限〔负撸” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵奇唤,是天一觀的道長(zhǎng)幸斥。 經(jīng)常有香客問我匹摇,道長(zhǎng)咬扇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任廊勃,我火速辦了婚禮懈贺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘坡垫。我一直安慰自己梭灿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布冰悠。 她就那樣靜靜地躺著堡妒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪溉卓。 梳的紋絲不亂的頭發(fā)上皮迟,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音桑寨,去河邊找鬼伏尼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛尉尾,可吹牛的內(nèi)容都是我干的爆阶。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼辨图!你這毒婦竟也來(lái)了班套?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤徒役,失蹤者是張志新(化名)和其女友劉穎孽尽,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忧勿,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡杉女,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸳吸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熏挎。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖晌砾,靈堂內(nèi)的尸體忽然破棺而出坎拐,到底是詐尸還是另有隱情,我是刑警寧澤养匈,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布哼勇,位于F島的核電站,受9級(jí)特大地震影響呕乎,放射性物質(zhì)發(fā)生泄漏积担。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一猬仁、第九天 我趴在偏房一處隱蔽的房頂上張望帝璧。 院中可真熱鬧,春花似錦湿刽、人聲如沸的烁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)渴庆。三九已至,卻和暖如春雅镊,著一層夾襖步出監(jiān)牢的瞬間襟雷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工漓穿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嗤军,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓晃危,卻偏偏與公主長(zhǎng)得像叙赚,于是被迫代替她去往敵國(guó)和親老客。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 背景 在最近鄰的搜索算法中震叮,數(shù)據(jù)的維度不同胧砰,適用的算法也不同,一般來(lái)說(shuō)苇瓣,準(zhǔn)確的暴力計(jì)算只適用于在維度較低的時(shí)候尉间,在...
    yxwithu閱讀 3,608評(píng)論 1 1
  • 寫在前面的話 醬醬,又到了程序媛拯救世界的時(shí)間击罪,程序猿們解決不了的問題看來(lái)又只能靠我們女程序員來(lái)幫大家解決了哲嘲。不好...
    塔克樹閱讀 2,385評(píng)論 0 2
  • LSH局部敏感哈希 問題場(chǎng)景: 快速的從海量高維數(shù)據(jù)集合中找到與某個(gè)數(shù)據(jù)最相似(距離最近)的一個(gè)數(shù)據(jù)或多個(gè)數(shù)據(jù) 局...
    囧囧俠道閱讀 889評(píng)論 0 0
  • 參考/摘自:minHash(最小哈希)和LSH(局部敏感哈希)[https://blog.csdn.net/liu...
    井底蛙蛙呱呱呱閱讀 10,956評(píng)論 0 4
  • 閱讀目錄 基本思想 局部敏感哈希LSH 文檔相似度計(jì)算 局部敏感哈希(Locality Sensitive Has...
    Anaven閱讀 3,443評(píng)論 2 4