Annoy(Fast Neighbor Vector Search Package)学習ノート-pipコマンドの学習と迷惑の基本的な使用法

1.前に書く

fun-recニュースレコメンデーションシステムのYouTubeDNNリコールを作成する場合、ユーザーベクトルとニュースベクトルが取得されます。ユーザーベクトルに基づいて、大規模なニュースから最も類似したTopKニュースを取得する必要があります。ベクトル検索技術を使用する必要があります。私が使用したツールの1つはfaissです。また、その使用方法を学ぶためにブログFaiss(Facebookのオープンソースの効率的な類似性検索ライブラリ)を記録しましたが、faissのインストールはそれほど簡単ではありません。 Windowsシステムでは、少し複雑に見えますが、今回は、ベクトル検索に役立つ別のツールキットに触れましたが、これは面倒です。この記事では、主に迷惑ツールキットを使用してベクトル検索を行う方法を記録します。

簡単な要約:迷惑パッケージは、ベクトルの最近傍検索に使用され、多数のアイテムから類似のTopKアイテムをすばやく検索します

迷惑パッケージの詳細な紹介については、https://github.com/spotify/annoyを参照してください。

2.迷惑をインストールします

まず、迷惑を最初にインストールする必要があります。直接インストールするpip install annoyか、ソースを指定できますpip install -i https://pypi.tuna.tsinghua.edu.cn/simple annoy

しかし、このコマンドを使用すると、Microsoft Visual C ++ 14.0が必要であると報告されます...私の側のシステムは現在Windowsであるため、LinuxまたはMacで簡単に使用できるはずです。
ここに画像の説明を挿入

以前にfaissまたはC++コンパイル環境を必要とする種類のパッケージをインストールしたときにこのエラーが発生したようです。これを完全に修正する方法は、C / C ++コンパイル環境をインストールすることですが、面倒で時間がかかります。たくさんのメモリ。

現時点ではこのメソッドを使用したくありませんが、別のメソッドを使用します。これがpythonユニバーサルパッケージライブラリです。その中の迷惑を検索し、指定されたpythonバージョンを見つけて、ダウンロードします。

次にpip install 文件的绝对路径、ローカルにインストールします。この方法は、ここで簡単に使用できます。インストールパッケージについて説明したので、もう少し知識を整理しましょう。

Pythonパッケージをインストールする場合、最も一般的なのはpipを使用してインストールすることです。ここでは、この機会にpipの一般的なコマンドを学習し、ここに記録します。詳細については、pipの必須のクイックチェックリストを参照してください。

# 安装python包
pip install 包名

# 指定版本号
pip install 包名==版本 
pip install 包名>=2.22, <3
pip install 包名!=2.22

# 指定镜像源安装
pip install -i url 包名  # 其中国内镜像源( url ) 可以是清华、中科大、豆瓣等
#清华:https://pypi.tuna.tsinghua.edu.cn/simple
#豆瓣:http://pypi.douban.com/simple/

# 本地wheel文件安装 whl文件可以去https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyhook离线下载到本地
pip install 包名.whl

# github仓库中安装
pip install git+包的github地址

# 更新python库
pip install 包名 -U

# 查看可提供安装的版本
pip install 包名==lemon

# 查看已经安装的python库
pip list
# 查询当前环境可升级的包
pip list -o

# 查看python库的信息
pip show 包名
pip show 包名 -f

# 卸载包
pip uninstall 包名

# 导出依赖包列表 freeze命令, 复用包很方便
pip freeze > requirements.txt  # 获取当前环境安装python库的版本信息,导入到txt文件中
# 从依赖包中安装
pip install -r requirements.txt

# 将python库制作成wheel文件,可以提供给他人用.whl文件,先安装wheel库
pip install wheel
# 特定python库制作成wheel文件
pip wheel --wheel-dir DIR some-package[==version] # 其中,DIR是保存文件的路径,比如users/lemon/Downloads

# 根据依赖包信息,制作wheel文件
pip wheel --wheel-dir DIR -r requirements.txt

もう1つの方法は、パッケージを直接ダウンロードしてから、対応する環境のパッケージディレクトリにオフラインでコピーすることです。

  • Windows環境:Anaconda-> Lib->site-packages
  • Linux環境:anaconda->lib->pythonバージョン->site-packages
  • Mac環境:anaconda-> pkgs

このようにして、対応するフォルダーに移動して、特定のパッケージ実装の基礎となるソースコードを確認することもできます。

3.迷惑の基本的な使用

ここでは主に迷惑なGitHubの例を参照し、それを書き留めます

from annoy import AnnoyIndex
import random

f = 40
t = AnnoyIndex(f, 'angular')  # Length of item vector that will be indexed
for i in range(1000):
    v = [random.gauss(0, 1) for z in range(f)]
    t.add_item(i, v)

t.build(10)
#t.save('test.ann')

# ...

u = AnnoyIndex(f, 'angular')
u.load('test.ann') # super fast, will just mmap the file
print(u.get_nns_by_item(0, 1000)) # will find the 1000 nearest neighbors

この例は実際には非常に理解しやすいです。迷惑な役割は、大規模なベクトル内の隣接するベクトルをすばやく検索することです。まず、大規模なベクトルの効率的な検索インデックスを作成する必要があります。ここでは、ツリーメソッドを使用します。したがって、コードの最初の7行は、主にインデックスを作成しています。実際の検索は実際には最後の文です。この文の機能は、0位置のベクトルに最も類似している1000個のベクトルを検索することです。ここで返される結果にはそれ自体が含まれます。

以下は、迷惑に関して一般的に使用される関数のリストです。

  • インデックス関連の関数を作成する

    • AnnoyIndex(f, metric):f次元ベクトルを格納するための新しい読み取り可能および書き込み可能なインデックスを返します。ここで、メトリックは「角度」、「ユークリッド」、「マンハッタン」、「ハミング」、または「ドット」です。ここで、角距離コサイン類似度は正規化式sqrt(2(1-cos(u、v)))
    • a.add_item(i, v):i位置にベクトルを追加します(負でない整数)。このディクショナリの最大値はmax(i)+1アイテムです。たとえば、10000アイテム、ディクショナリサイズは0〜9999の位置で、各位置にiが格納されます。アイテムに対応するベクトル、この関数を介して辞書を構築できます
    • a.build(n_trees, n_jobs=-1):n_treesのフォレストを構築します。ツリーが多いほど、精度が高くなります。作成後、追加のアイテムを追加することはできません。n_jobsは、ツリーを構築するスレッドの数を指定するために使用されます。-1は、すべての追加のCPUコアを使用することを意味します。
    • a.save(fn, prefault=False):インデックスをディスクに保存してロードします(次の関数を参照)。保存後、アイテムを追加することはできません。
    • a.load(fn, prefault=False):ディスクからインデックスをロード(mmaps)します。デフォルトがTrueに設定されている場合、ファイル全体がメモリに事前に読み込まれます(mmapおよびMAP POPULATEを使用)。デフォルトはfalseです。


    上記は、迷惑パッケージを使用して優れたベクトル辞書を作成する方法と、ベクトル(ツリーの方法)を整理して保存する方法です。以下は、TopKの関数を使用する方法です。

  • ベクトル検索で使用される関数

    • a.get_nns_by_item(i, n, search_k=-1, include_distances=False):最も近いn個のアイテムを返します。クエリ中に、search_kノードがチェックされます。デフォルトはn_trees*nです。serarch_kは、精度と速度の間の実行時のトレードオフを実装します。include_distancesがTrueの場合、2つのリストを含む2要素のタプルが返されます。2番目のタプルには、対応するすべての距離が含まれます。
    • a.get_nns_by_vector(v, n, search_k=-1, include_distances=False):上記のアイテムに基づくクエリと同じですが、クエリベクトルvがここに指定されている点が異なります。たとえば、ユーザーが埋め込まれている場合、n個の最近傍アイテムが返されます。通常、このように使用すると、次の距離になります。運ばれ、細い列の強い側として使用される可能性があります
    • a.get_item_vector(i):インデックスiに対応するベクトルを返します
    • a.get_distance(i, j):item_iとitem_jの間の距離の2乗を返します
  • インデックスプロパティ関数

    • a.get_n_items():インデックス内のアイテムの数、つまり辞書のサイズを返します
    • a.get_n_trees():インデックスツリーの数

考慮すべき2つのハイパーパラメータ:ツリーの数n_treesと検索中にチェックされたノードの数search_k

  • n_trees:ビルド中に提供され、ビルド時間とインデックスサイズに影響します。値が大きいほど、結果は正確になりますが、インデックスは大きくなります。
  • search_k:実行時に提供され、検索パフォーマンスに影響します。値が大きいほど、結果は正確になりますが、返されるまでに時間がかかります。指定しない場合は、n_trees * nです。ここで、nは最近傍の数です。

ここで私のいくつかの例をチェックしてください:

ここに画像の説明を挿入

4.YoutubeDNNでのアプリケーション

YoutubeDNNがリコールを行うと、モデルに応じてユーザーの埋め込みとアイテムの埋め込みを取得できます。次に、ユーザーの埋め込みを取得し、大規模なアイテムに移動して、最も類似したTopKを取得し、ユーザーの候補アイテムとして返します。 。では、すでにuser_embsとitem_embsがあると仮定すると、迷惑を介して最近傍検索を高速に実行するにはどうすればよいでしょうか。

ここに関数を書きました:

def get_youtube_recall_res(user_embs, doc_embs, user_idx_2_rawid, doc_idx_2_rawid, topk):
    """近邻检索,这里用annoy tree"""
    # 把doc_embs构建成索引树
    f = user_embs.shape[1]
    t = AnnoyIndex(f, 'angular')
    for i, v in enumerate(doc_embs):
        t.add_item(i, v)
    t.build(10)
    # 可以保存该索引树 t.save('annoy.ann')
    
    # 每个用户向量, 返回最近的TopK个item
    user_recall_items_dict = collections.defaultdict(dict)
    for i, u in enumerate(user_embs):
        recall_doc_scores = t.get_nns_by_vector(u, topk, include_distances=True)
        # recall_doc_scores是(([doc_idx], [scores])), 这里需要转成原始doc的id
        raw_doc_scores = list(recall_doc_scores)
        raw_doc_scores[0] = [doc_idx_2_rawid[i] for i in raw_doc_scores[0]]
        # 转换成实际用户id
        user_recall_items_dict[user_idx_2_rawid[i]] = dict(zip(*raw_doc_scores))
    
    # 默认是分数从小到大排的序, 这里要从大到小
    user_recall_items_dict = {
    
    k: sorted(v.items(), key=lambda x: x[1], reverse=True) for k, v in user_recall_items_dict.items()}
    
    # 保存一份
    pickle.dump(user_recall_doc_dict, open('youtube_u2i_dict.pkl', 'wb'))
    
    return user_recall_items_dict

ここには2つの追加パラメーターがありますuser_idx_2_rawid, doc_idx_2_rawid。これら2つは辞書であり、ユーザーベクトルの位置インデックスとユーザーの元のIDの間のマッピング、およびアイテムベクトルのインデックスと元のitem_idの間のマッピングを保存します。辞書、ユーザーの元のID、およびアイテムの元のIDを保存する必要があります。この関数を実行すると、結果は次のようになります。

ここに画像の説明を挿入
さて、迷惑の探求が最初にここにあります、そしてあなたが後で新しい知識を学ぶならば、あなたはそれを補うでしょう。

おすすめ

転載: blog.csdn.net/wuzhongqiang/article/details/122516942