UID567
现金0
在线时间0 小时
注册时间2018-2-3
黑狼菜鸟
![Rank: 1](static/image/common/star_level1.gif)
- 积分
- 0
|
选自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
本文为呆板之心编译,转载请接洽本公众号得到授权。
✄------------------------------------------------
|
上一篇:CentOS设置ip「快速设置」下一篇:Python底子入门与进阶教程(早鸟可享特惠价)
|