登录  | 加入社区

黑狼游客您好!登录后享受更多精彩

只需一步,快速开始

新浪微博登陆

只需一步, 快速开始

查看: 348|回复: 0

教程 | 如安在Python中快速举行语料库搜刮:近似近来邻算法 ...

[复制链接]

377

主题

13

帖子

0

现金

黑狼菜鸟

Rank: 1

积分
0
发表于 2018-10-5 09:14:57 | 显示全部楼层 |阅读模式 来自 广东中山

选自Medium

作者:Kevin Yang

呆板之心编译

到场:路雪



近来,我不停在研究在 GloVe 词嵌入中做加减法。比方,我们可以把「king」的词嵌入向量减去「man」的词嵌入向量,随后参加「woman」的词嵌入得到一个效果向量。随后,假如我们有这些词嵌入对应的语料库,那么我们可以通过搜刮找到最相似的嵌入并检索相应的词。假如我们做了如许的查询,我们会得到:



  • King + (Woman - Man) = Queen


我们有许多方法来搜刮语料库中词嵌入对作为近来邻查询方式。绝对可以确保找到最优向量的方式是遍历你的语料库,比力每个对与查询需求的相似水平——这固然是泯灭时间且不保举的。一个更好的技能是利用向量化余弦间隔方式,如下所示:




  • vectors = np.array(embeddingmodel.embeddings)
  • ranks = np.dot(query,vectors.T)/np.sqrt(np.sum(vectors**2,1))
  • mostSimilar = []
  • [mostSimilar.append(idx) for idx in ranks.argsort()[::-1]]






想要相识余弦间隔,可以看看这篇文章:http://masongallo.github.io/machine/learning,/python/2016/07/29/cosine-similarity.html



矢量化的余弦间隔比迭代法快得多,但速率大概太慢。是近似近来邻搜刮算法该出现时间了:它可以快速返回近似效果。许多时间你并不必要正确的最佳效果,比方:「Queen」这个单词的同义词是什么?在这种环境下,你只必要快速得到充足好的效果,你必要利用近似近来邻搜刮算法。


在本文中,我们将会先容一个简朴的 Python 脚原来快速找到近似近来邻。我们会利用的 Python 库是 Annoy 和 Imdb。对于我的语料库,我会利用词嵌入对,但该阐明现实上实用于任何范例的嵌入:如音乐保举引擎必要用到的歌曲嵌入,乃至以图搜图中的图片嵌入。


制作一个索引


让我们创建一个名为:「make_annoy_index」的 Python 脚本。起首我们必要参加用得到的依靠项:



  • '''
  • Usage: python2 make_annoy_index.py \
  •    --embeddings= \
  •    --num_trees= \
  •    --verbose
  • Generate an Annoy index and lmdb map given an embedding file
  • Embedding file can be
  •  1. A .bin file that is compatible with word2vec binary formats.
  •     There are pre-trained vectors to download at http://code.google.com/p/word2vec/
  •  2. A .gz file with the GloVe format (item then a list of floats in plaintext)
  •  3. A plain text file with the same format as above
  • '''

  • import annoy
  • import lmdb
  • import os
  • import sys
  • import argparse

  • from vector_utils import get_vectors




末了一行里非常紧张的是「vector_utils」。稍后我们会写「vector_utils」,以是不必担心。


接下来,让我们丰富这个脚本:参加「creat_index」函数。这里我们将天生 lmdb 图和 Annoy 索引。


1. 起首必要找到嵌入的长度,它会被用来做实例化 Annoy 的索引。
2. 接下来实例化一个 Imdb 图,利用:「env = lmdb.open(fn_lmdb, map_size=int(1e9))」。
3. 确保我们在当前路径中没有 Annoy 索引或 lmdb 图。
4. 将嵌入文件中的每一个 key 和向量添加至 lmdb 图和 Annoy 索引。
5. 构建和生存 Annoy 索引。



  • '''
  • function create_index(fn, num_trees=30, verbose=False)
  • -------------------------------
  • Creates an Annoy index and lmdb map given an embedding file fn
  • Input:
  •    fn              - filename of the embedding file
  •    num_trees       - number of trees to build Annoy index with
  •    verbose         - log status
  • Return:
  •    Void
  • '''
  • def create_index(fn, num_trees=30, verbose=False):
  •    fn_annoy = fn + '.annoy'
  •    fn_lmdb = fn + '.lmdb' # stores word  id mapping

  •    word, vec = get_vectors(fn).next()
  •    size = len(vec)
  •    if verbose:
  •        print("Vector size: {}".format(size))

  •    env = lmdb.open(fn_lmdb, map_size=int(1e9))
  •    if not os.path.exists(fn_annoy) or not os.path.exists(fn_lmdb):
  •        i = 0
  •        a = annoy.AnnoyIndex(size)
  •        with env.begin(write=True) as txn:
  •            for word, vec in get_vectors(fn):
  •                a.add_item(i, vec)
  •                id = 'i%d' % i
  •                word = 'w' + word
  •                txn.put(id, word)
  •                txn.put(word, id)
  •                i += 1
  •                if verbose:
  •                    if i % 1000 == 0:
  •                        print(i, '...')
  •        if verbose:
  •            print("Starting to build")
  •        a.build(num_trees)
  •        if verbose:
  •            print("Finished building")
  •        a.save(fn_annoy)
  •        if verbose:
  •            print("Annoy index saved to: {}".format(fn_annoy))
  •            print("lmdb map saved to: {}".format(fn_lmdb))
  •    else:
  •        print("Annoy index and lmdb map already in path")




我已经推断出 argparse,因此,我们可以使用下令行启用我们的脚本:



  • '''
  • private function _create_args()
  • -------------------------------
  • Creates an argeparse object for CLI for create_index() function
  • Input:
  •    Void
  • Return:
  •    args object with required arguments for threshold_image() function
  • '''
  • def _create_args():
  •    parser = argparse.ArgumentParser()
  •    parser.add_argument("--embeddings", help="filename of the embeddings", type=str)
  •    parser.add_argument("--num_trees", help="number of trees to build index with", type=int)
  •    parser.add_argument("--verbose", help="print logging", action="store_true")

  •    args = parser.parse_args()
  •    return args




添加主函数以启用脚本,得到 make_annoy_index.py:




  • if __name__ == '__main__':
  •    args = _create_args()
  •    create_index(args.embeddings, num_trees=args.num_trees, verbose=args.verbose)




如今我们可以仅使用下令行启用新脚本,以天生 Annoy 索引和对应的 lmdb 图!




  • python2 make_annoy_index.py \
  •    --embeddings= \
  •    --num_trees= \
  •    --verbose




写向 量Utils


我们在 make_annoy_index.py 中推导出 Python 脚本 vector_utils。如今要写该脚本,Vector_utils 用于资助读取.txt, .bin 和 .pkl 文件中的向量。


写该脚本与我们如今在做的不那么相干,因此我已经推导出整个脚本,如下:




  • '''
  • Vector Utils
  • Utils to read in vectors from txt, .bin, or .pkl.
  • Taken from Erik Bernhardsson
  • Source: http://github.com/erikbern/ann-presentation/blob/master/util.py
  • '''
  • import gzip
  • import struct
  • import cPickle

  • def _get_vectors(fn):
  •    if fn.endswith('.gz'):
  •        f = gzip.open(fn)
  •        fn = fn[:-3]

  •    else:
  •        f = open(fn)

  •    if fn.endswith('.bin'): # word2vec format
  •        words, size = (int(x) for x in f.readline().strip().split())

  •        t = 'f' * size

  •        while True:
  •            pos = f.tell()
  •            buf = f.read(1024)
  •            if buf == '' or buf == '\n': return
  •            i = buf.index(' ')
  •            word = buf[:i]
  •            f.seek(pos + i + 1)

  •            vec = struct.unpack(t, f.read(4 * size))

  •            yield word.lower(), vec

  •    elif fn.endswith('.txt'): # Assume simple text format
  •        for line in f:
  •            items = line.strip().split()
  •            yield items[0], [float(x) for x in items[1:]]

  •    elif fn.endswith('.pkl'): # Assume pickle (MNIST)
  •        i = 0
  •        for pics, labels in cPickle.load(f):
  •            for pic in pics:
  •                yield i, pic
  •                i += 1


  • def get_vectors(fn, n=float('inf')):
  •    i = 0
  •    for line in _get_vectors(fn):
  •        yield line
  •        i += 1
  •        if i >= n:
  •            break




测试 Annoy 索引和 lmdb 图


我们已经天生了 Annoy 索引和 lmdb 图,如今我们来写一个脚本利用它们举行推断。


将我们的文件定名为 annoy_inference.py,得到下列依靠项:




  • '''
  • Usage: python2 annoy_inference.py \
  •    --token='hello' \
  •    --num_results= \
  •    --verbose
  • Query an Annoy index to find approximate nearest neighbors
  • '''
  • import annoy
  • import lmdb
  • import argparse




如今我们必要在 Annoy 索引和 lmdb 图中加载依靠项,我们将举行全局加载,以方便访问。留意,这里设置的 VEC_LENGTH 为 50。确保你的 VEC_LENGTH 与嵌入长度匹配,否则 Annoy 会不开心的哦~




  • VEC_LENGTH = 50
  • FN_ANNOY = 'glove.6B.50d.txt.annoy'
  • FN_LMDB = 'glove.6B.50d.txt.lmdb'

  • a = annoy.AnnoyIndex(VEC_LENGTH)
  • a.load(FN_ANNOY)
  • env = lmdb.open(FN_LMDB, map_size=int(1e9))




风趣的部门在于「calculate」函数。


1. 从 lmdb 图中获取查询索引;
2. 用 get_item_vector(id) 获取 Annoy 对应的向量;
3. 用 a.get_nns_by_vector(v, num_results) 获取 Annoy 的近来邻。




  • '''
  • private function calculate(query, num_results)
  • -------------------------------
  • Queries a given Annoy index and lmdb map for num_results nearest neighbors
  • Input:
  •    query           - query to be searched
  •    num_results     - the number of results
  • Return:
  •    ret_keys        - list of num_results nearest neighbors keys
  • '''
  • def calculate(query, num_results, verbose=False):
  •    ret_keys = []
  •    with env.begin() as txn:
  •        id = int(txn.get('w' + query)[1:])
  •        if verbose:
  •            print("Query: {}, with id: {}".format(query, id))
  •        v = a.get_item_vector(id)
  •        for id in a.get_nns_by_vector(v, num_results):
  •            key = txn.get('i%d' % id)[1:]
  •            ret_keys.append(key)
  •    if verbose:
  •        print("Found: {} results".format(len(ret_keys)))
  •    return ret_keys




再次,这里利用 argparse 来使读取下令行参数更加简朴。



  • '''
  • private function _create_args()
  • -------------------------------
  • Creates an argeparse object for CLI for calculate() function
  • Input:
  •    Void
  • Return:
  •    args object with required arguments for threshold_image() function
  • '''
  • def _create_args():
  •    parser = argparse.ArgumentParser()
  •    parser.add_argument("--token", help="query word", type=str)
  •    parser.add_argument("--num_results", help="number of results to return", type=int)
  •    parser.add_argument("--verbose", help="print logging", action="store_true")

  •    args = parser.parse_args()
  •    return args




主函数从下令行中启用 annoy_inference.py。




  • if __name__ == '__main__':
  •    args = _create_args()
  •    print(calculate(args.token, args.num_results, args.verbose))




如今我们可以利用 Annoy 索引和 lmdb 图,获取查询的近来邻!




代码


本教程全部代码的 GitHub 地点:http://github.com/kyang6/annoy_tutorial


原文地点:http://medium.com/@kevin_yang/simple-approximate-nearest-neighbors-in-python-with-annoy-and-lmdb-e8a701baf905





本文为呆板之心编译,转载请接洽本公众号得到授权

✄------------------------------------------------
参加呆板之心(全职记者/练习生):[email protected]

投稿或寻求报道:[email protected]

广告&商务互助:[email protected]




上一篇:CentOS设置ip「快速设置」
下一篇:Python底子入门与进阶教程(早鸟可享特惠价)
您需要登录后才可以回帖 登录 | 加入社区

本版积分规则

 

QQ|申请友链|小黑屋|手机版|Hlshell Inc. ( 豫ICP备16002110号-5 )

GMT+8, 2024-6-29 12:15 , Processed in 0.064044 second(s), 44 queries .

HLShell有权修改版权声明内容,如有任何爭議,HLShell將保留最終決定權!

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表