Поиск ближайших соседей с помощью иерархического маленького мира — различия между версиями
Marsermd (обсуждение | вклад) (→См. также) |
Marsermd (обсуждение | вклад) (→Операции над структурой) |
||
| Строка 41: | Строка 41: | ||
Жадно идём по уровню в сторону запроса. | Жадно идём по уровню в сторону запроса. | ||
'''searchLayer'''(q, ep, ef, layer)''':''' | '''searchLayer'''(q, ep, ef, layer)''':''' | ||
| − | <font color="green">// | + | <font color="green">// Входные данные: запрос q, входные точки ep, искомое количество ближайших соседей ef, номер слоя layer</font> |
| − | <font color="green">// | + | <font color="green">// Возвращает: ef ближайших соседей q в слое layer</font> |
candidates = new TreeSet(ep) <font color="green">// Вершины упорядочены по возрастанию расстояния до q. </font> | candidates = new TreeSet(ep) <font color="green">// Вершины упорядочены по возрастанию расстояния до q. </font> | ||
result = new TreeSet(ep) | result = new TreeSet(ep) | ||
| Строка 68: | Строка 68: | ||
На нулевом уровне жадно ищем '''K''' ближайших соседей. | На нулевом уровне жадно ищем '''K''' ближайших соседей. | ||
'''knn'''(hnsw, q, K, ef)''':''' | '''knn'''(hnsw, q, K, ef)''':''' | ||
| − | <font color="green">// | + | <font color="green">// Входные данные: граф hnsw, запрос q, искомое количество ближайших соседей K, количество кандидатов при поиске ef</font> |
| − | <font color="green">// | + | <font color="green">// Возвращает: K ближайших соседей q</font> |
result = new TreeSet() <font color="green">// Вершины упорядочены по возрастанию расстояния до request. </font> | result = new TreeSet() <font color="green">// Вершины упорядочены по возрастанию расстояния до request. </font> | ||
ep = случайная вершина из верхнего слоя hnsw | ep = случайная вершина из верхнего слоя hnsw | ||
| Строка 83: | Строка 83: | ||
Жадно ищем '''M''' ближайших вершин к '''q''' на каждом уровне, на котором она представлена; добавляем связи '''q''' с ними; удаляем лишние связи у новообразовавшихся соседей. | Жадно ищем '''M''' ближайших вершин к '''q''' на каждом уровне, на котором она представлена; добавляем связи '''q''' с ними; удаляем лишние связи у новообразовавшихся соседей. | ||
'''insert'''(hnsw, q, m, mMax, ef, mL)''':''' | '''insert'''(hnsw, q, m, mMax, ef, mL)''':''' | ||
| − | <font color="green">// | + | <font color="green">// Входные данные: граф hnsw, запрос на добавление q, желаемое количество связей m, максимальное количество связей вершины </font> |
<font color="green">// на одном слое mMax, количество кандидатов при поиске ef, коэффициент выбора высоты mL. </font> | <font color="green">// на одном слое mMax, количество кандидатов при поиске ef, коэффициент выбора высоты mL. </font> | ||
| − | <font color="green">// | + | <font color="green">// Возвращает: hnsw с вставленным элементом q. </font> |
result = new TreeSet() <font color="green">// Вершины упорядочены по возрастанию расстояния до q. </font> | result = new TreeSet() <font color="green">// Вершины упорядочены по возрастанию расстояния до q. </font> | ||
ep = случайная вершина из верхнего слоя hnsw | ep = случайная вершина из верхнего слоя hnsw | ||
Версия 22:48, 1 марта 2019
Иерархический маленький мир (англ. Hierarchical Navigable Small World) — структура данных, позволяющая эффективно находить K почти что ближайших соседей. По своей концепции напоминает список с пропусками.
Содержание
Маленький мир
Маленький мир (англ. Small World) — граф, в котором мат. ожидание кратчайшего пути между двумя случайно выбранными вершинами растёт пропорционально . Но при этом средняя степень вершины мала.
Для маленького мира на точках в Евклидовом пространстве, жадный поиск K ближайших соседей будет выглядеть так:
knn(request, m, k):
nearest = new TreeSet() // вершины упорядочены по возрастанию расстояния до request
candidates = new TreeSet()
visited = new HashSet()
for i = 1 to m
candidates.add(случайная вершина графа)
tempNearest = new TreeMap()
while true
current = candidates.popMin()
if current дальше чем k-й элемент nearest
break
for v : смежные с current вершины
if !visited.contains(v)
candidates.add(v)
visited.add(v)
tempNearest.add(v)
nearest.addAll(tempNearest)
return k первых вершин из nearest
Очевидный недостаток этого алгоритма — опасность свалиться в локальный минимум, остановившись в каком-то кластере. С увеличением числа m, вероятность такого застревания экспоненциально падает.
Описание структуры
Иерархический Маленький мир (англ. Hierarchical Navigable Small World) — слоистая структура графов. На нулевом слое представлены все N вершин из исходной выборки. Вершина, присутствующая на уровне L так же присутствует на уровне L + 1 с вероятностью P. Т.е. кол-во слоёв растет как . Количество соседей каждой вершины на каждом уровне ограниченно константой, что позволяет делать запросы на добавление и удаление вершины за .
Иерархический маленький мир. Источник |
Операции над структурой
Поиск ближайших соседей в слое
Жадно идём по уровню в сторону запроса.
searchLayer(q, ep, ef, layer):
// Входные данные: запрос q, входные точки ep, искомое количество ближайших соседей ef, номер слоя layer
// Возвращает: ef ближайших соседей q в слое layer
candidates = new TreeSet(ep) // Вершины упорядочены по возрастанию расстояния до q.
result = new TreeSet(ep)
visited = new HashSet(ep)
while candidates.isNotEmpty()
current = candidates.getMin()
furthest = result.getMax()
if distance(current, q) > distance(furthest, q)
break // Мы в локальном минимуме.
for v : смежные с current вершины в слое layer
if !visited.contains(r)
visited.add(v)
furthest = result.getMax()
if distance(v, q) < distance(furthest, q) or result.count() < ef
candidates.add(v)
result.add(v)
if result.count() > ef
result.removeLast()
return result
Поиск ближайших соседей во всей структуре
Жадно ищем ближайшего соседа на каждом уровне, кроме 0. Когда находим, спускаемся через него на уровень ниже. На нулевом уровне жадно ищем K ближайших соседей.
knn(hnsw, q, K, ef):
// Входные данные: граф hnsw, запрос q, искомое количество ближайших соседей K, количество кандидатов при поиске ef
// Возвращает: K ближайших соседей q
result = new TreeSet() // Вершины упорядочены по возрастанию расстояния до request.
ep = случайная вершина из верхнего слоя hnsw
maxLevel = индекс самого высокого слоя в hnsw
for level = maxLevel to 1
result = searchLayer(q, ep, ef=1, level) // На каждом уровне, кроме нижнего мы ищем всего одну ближайшую вершину.
ep = result.getMin()
result = searchLayer(q, ep, ef, lc=0)
return первые K элементов из result
Вставка элемента
Случайным образом выбираем максимальный слой, на котором представлена q. Жадно ищем M ближайших вершин к q на каждом уровне, на котором она представлена; добавляем связи q с ними; удаляем лишние связи у новообразовавшихся соседей.
insert(hnsw, q, m, mMax, ef, mL):
// Входные данные: граф hnsw, запрос на добавление q, желаемое количество связей m, максимальное количество связей вершины
// на одном слое mMax, количество кандидатов при поиске ef, коэффициент выбора высоты mL.
// Возвращает: hnsw с вставленным элементом q.
result = new TreeSet() // Вершины упорядочены по возрастанию расстояния до q.
ep = случайная вершина из верхнего слоя hnsw
maxLevel = индекс самого высокого слоя в hnsw
qLevel = -ln(rand(eps, 1.0)) * mL // Верхний слой для вершины q.
for level = maxLevel to qLevel + 1
result = searchLayer(q, ep, ef=1, level)
ep = result
for level = min(maxLevel, qLevel) to 0
result = searchLayer(q, ep, ef, level)
neighbors = searchLayer.getFirst(M)
Добавить связи между neighbours и q на уровне level
for v : neighbors
// Убираем лишние связи, если требуется.
vNeighbours = смежные с v на уровне level
if vNeighbours.Count() > mMax
оставить у v только связи с ближайшими mMax смежными вершинами на уровне level
ep = result
if qLevel > maxLevel
Добавить недостающие слои в hnsw, в каждый из них положить q
См. также
Метрический классификатор и метод ближайших соседей Иерархическая кластеризация
