Annoy (paquete de búsqueda de vector de vecino rápido) Notas de aprendizaje: aprendizaje del comando pip y uso básico de annoy

1. Escribe al frente

Al escribir el retiro de YouTubeDNN del sistema de recomendación de noticias fun-rec, se obtienen el vector de usuario y el vector de noticias. En función del vector de usuario, se necesita obtener las noticias TopK más similares de las noticias masivas. En este momento, el rápido Se necesita usar tecnología de recuperación de vectores. Una de las herramientas que he usado es faiss. También grabé un blog Faiss (biblioteca de búsqueda de similitud eficiente de código abierto de Facebook) para aprender a usarla . Sin embargo, faiss no es muy fácil de instalar en el sistema de Windows, y parece un poco complicado.Esta vez, entré en contacto con otro conjunto de herramientas útil para la recuperación de vectores, que es molesto. Este artículo registra principalmente cómo usar el kit de herramientas molesto para recuperar vectores.

Resumen simple: el paquete molesto se usa para la recuperación de vectores vecinos más cercanos para encontrar rápidamente elementos TopK similares de una gran cantidad de elementos

Para obtener una introducción detallada al paquete annoy, consulte https://github.com/spotify/annoy

2. Instalar molestar

En primer lugar, primero debemos instalar Annoy, podemos instalarlo directamente pip install annoyo especificar la fuente pip install -i https://pypi.tuna.tsinghua.edu.cn/simple annoy.

Pero cuando uso este comando, informará que se requiere Microsoft visual c ++ 14.0 ..., debido a que el sistema de mi lado actualmente es Windows, debería ser fácil de usar en Linux o Mac.
inserte la descripción de la imagen aquí

Parece que encontré este error cuando instalé faiss o el tipo de paquete que requiere un entorno de compilación de C++ antes. La forma de solucionarlo de una vez por todas es instalar un entorno de compilación de C/C++, pero es problemático y requiere mucha memoria

No quiero usar este método en este momento, pero use otro método. Aquí hay una biblioteca de paquetes universales de python , busque molestar en ella, encuentre la versión de python especificada y luego descárguela.

Luego pip install 文件的绝对路径instálelo localmente Este método es fácil de usar para mí aquí. Ahora que se menciona el paquete de instalación, organicemos un poco más el conocimiento.

Cuando instalamos paquetes de python, lo más común es usar pip para instalar. Aquí, también aprovechamos esta oportunidad para aprender los comandos comunes de pip y registrarlos aquí. Para obtener más detalles, consulte la lista de verificación rápida imprescindible de 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

Otra forma es descargar el paquete directamente y luego copiarlo sin conexión al directorio del paquete del entorno correspondiente.

  • Entorno de Windows: Anaconda -> Lib -> paquetes de sitio
  • Entorno Linux: anaconda -> lib -> versión de python -> paquetes de sitio
  • Entorno Mac: anaconda -> paquetes

De esta forma, también puede ir a la carpeta correspondiente para ver el código fuente subyacente de la implementación del paquete específico.

3. Uso básico de molestar

Aquí se refiere principalmente al ejemplo de GitHub de Annoy, escríbalo

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

Este ejemplo es realmente muy fácil de entender. El papel de molestar es buscar rápidamente vectores vecinos en vectores masivos. Primero, debemos construir un índice de búsqueda eficiente para los vectores masivos. El método de árbol se usa aquí. Entonces, las primeras 7 líneas de código están construyendo principalmente el índice. La recuperación real es en realidad la última oración. La función de esta oración es recuperar los 1000 vectores que son más similares al vector de posición 0. El resultado devuelto aquí será el mismo.

La siguiente es una lista de funciones comúnmente utilizadas sobre molestar:

  • Crear funciones relacionadas con el índice

    • AnnoyIndex(f, metric): Devuelve un nuevo índice legible y escribible para almacenar el vector de dimensión f, donde la métrica puede ser "angular", "euclidiana", "manhattan", "hamming" o "punto". Aquí la similitud del coseno de la distancia angular es la Fórmula de normalización sqrt(2(1-cos(u,v)))
    • a.add_item(i, v):Agregue un vector en la posición i (entero no negativo). El máximo de este diccionario es max(i)+1 elementos, por ejemplo, tengo 10000 elementos, el tamaño del diccionario es 0-9999 posiciones, cada posición i almacena el vector correspondiente al elemento, a través de Esta función puede construir un diccionario
    • a.build(n_trees, n_jobs=-1): Cree un bosque de n_trees. Cuantos más árboles, mayor será la precisión. Después de la creación, no se pueden agregar elementos adicionales. n_jobs se usa para especificar la cantidad de subprocesos para construir el árbol, -1 significa usar todos los núcleos de CPU adicionales
    • a.save(fn, prefault=False): guarde el índice en el disco y cárguelo (vea la siguiente función). Después de guardar, no se pueden agregar más elementos.
    • a.load(fn, prefault=False): Carga (mmaps) un índice desde el disco. Si el valor predeterminado es Verdadero, leerá previamente todo el archivo en la memoria (usando mmap y MAP POPULATE). El valor predeterminado es falso.


    Lo anterior es cómo usar el paquete annoy para construir un buen diccionario de vectores y cómo organizar los vectores (la forma de los árboles) y luego guardarlos. Los siguientes son cómo obtener la función de TopK para usar.

  • Funciones utilizadas en la recuperación de vectores

    • a.get_nns_by_item(i, n, search_k=-1, include_distances=False): Devuelve los n elementos más cercanos. Durante la consulta, se comprobarán los nodos search_k, cuyo valor predeterminado es n_trees*n. serarch_k implementa un compromiso de tiempo de ejecución entre precisión y velocidad. Cuando include_distances es True, devolverá una tupla de 2 elementos que contiene dos listas: la segunda contiene todas las distancias correspondientes.
    • a.get_nns_by_vector(v, n, search_k=-1, include_distances=False): Lo mismo que la consulta anterior basada en el elemento, excepto que aquí se proporciona un vector de consulta v. Por ejemplo, dada la incrustación de un usuario, se devuelven los n elementos vecinos más cercanos. Generalmente, cuando se usa de esta manera, la siguiente distancia será llevado, que puede ser utilizado como El lado fuerte de la fila fina
    • a.get_item_vector(i): devuelve el vector correspondiente al índice i
    • a.get_distance(i, j): Devuelve la distancia al cuadrado entre item_i y item_j
  • función de propiedad de índice

    • a.get_n_items(): Devuelve el número de elementos del índice, es decir, el tamaño del diccionario
    • a.get_n_trees(): el número de árboles índice

Dos hiperparámetros a considerar: el número de árboles n_trees y el número de nodos verificados durante la búsqueda search_k

  • n_trees: proporcionado durante la compilación, afecta el tiempo de compilación y el tamaño del índice. Cuanto mayor sea el valor, más preciso será el resultado, pero mayor será el índice.
  • search_k: se proporciona en tiempo de ejecución y afecta el rendimiento de la búsqueda. Cuanto mayor sea el valor, más preciso será el resultado, pero más tiempo tardará en volver. Si no se proporciona, es n_trees * n, donde n es el número de vecinos más cercanos

Mira mis pocos ejemplos aquí:

inserte la descripción de la imagen aquí

4. Aplicaciones en YoutubeDNN

Cuando YoutubeDNN realiza el retiro, podemos obtener la incrustación del usuario y la incrustación del elemento de acuerdo con el modelo. Luego, tomamos la incrustación del usuario y luego vamos al elemento masivo, recuperamos el TopK más similar y lo devolvemos como el elemento candidato del usuario. . Entonces, suponiendo que ya tenemos user_embs y item_embs, ¿cómo realizamos una recuperación rápida del vecino más cercano a través de molestar?

Escribí una función aquí:

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

Hay dos parámetros adicionales aquí user_idx_2_rawid, doc_idx_2_rawid, estos dos son diccionarios, que guardan el mapeo entre el índice de posición del vector de usuario y la identificación original del usuario, y el mapeo entre el índice del vector del elemento y el item_id original, nuestro último In el diccionario, la identificación original del usuario y la identificación original del elemento deben guardarse.Después de ejecutar esta función, el resultado es así:

inserte la descripción de la imagen aquí
Ok, la exploración de la molestia está aquí primero, y si aprendes nuevos conocimientos más tarde, los complementarás.

Supongo que te gusta

Origin blog.csdn.net/wuzhongqiang/article/details/122516942
Recomendado
Clasificación