1.6. Ближайшие соседи
sklearn.neighbors предоставляет функциональность для методов обучения на основе соседей с учителем и без учителя. Обучение без учителя на основе ближайших соседей лежит в основе многих других методов обучения, в частности обучения многообразий и спектральной кластеризации. Обучение с учителем на основе соседей бывает двух видов: классификация для данных с дискретными метками и регрессия для данных с непрерывными метками.
Принцип методов ближайших соседей заключается в том, чтобы найти заданное число обучающих образцов, наиболее близких к новой точке, и предсказать метку на основе этих образцов. Число образцов может быть задано пользователем (обучение по k ближайшим соседям) или меняться в зависимости от локальной плотности точек (обучение на основе соседей в радиусе). В общем случае в качестве расстояния можно использовать любую метрику: чаще всего используется стандартное евклидово расстояние. Методы на основе соседей известны как методы машинного обучения, не обобщающие данные, поскольку они просто «запоминают» все обучающие данные (возможно, преобразуя их в быструю структуру индексирования, например Ball Tree или KD Tree).
Несмотря на простоту, метод ближайших соседей успешно применяется во многих задачах классификации и регрессии, включая распознавание рукописных цифр и сцен на спутниковых снимках. Будучи непараметрическим методом, он часто хорошо работает в задачах классификации, где граница принятия решений имеет очень сложную форму.
Классы в sklearn.neighbors могут принимать на вход массивы NumPy или матрицы scipy.sparse. Для плотных матриц поддерживается большое количество метрик расстояния. Для разреженных матриц при поиске поддерживаются произвольные метрики Минковского.
Многие алгоритмы обучения используют ближайших соседей в своей основе. Один из примеров — оценка плотности ядра, рассматриваемая в разделе оценка плотности.
1.6.1. Ближайшие соседи без учителя
NearestNeighbors реализует обучение без учителя на основе ближайших соседей. Этот класс предоставляет единый интерфейс к трем различным алгоритмам поиска ближайших соседей: BallTree, KDTree и алгоритму полного перебора на основе процедур из sklearn.metrics.pairwise. Выбор алгоритма поиска соседей задается с помощью ключевого аргумента 'algorithm', который должен принимать одно из значений ['auto', 'ball_tree', 'kd_tree', 'brute']. Если передано значение по умолчанию 'auto', алгоритм пытается определить наилучший подход на основе обучающих данных. Обсуждение сильных и слабых сторон каждого варианта см. в разделе Алгоритмы поиска ближайших соседей.
Предупреждение
Для алгоритмов поиска ближайших соседей, если два соседа \(k+1\) и \(k\) находятся на одинаковом расстоянии, но имеют разные метки, результат будет зависеть от порядка обучающих данных.
1.6.1.1. Поиск ближайших соседей
Для простой задачи поиска ближайших соседей между двумя наборами данных можно использовать алгоритмы обучения без учителя из sklearn.neighbors:
>>> from sklearn.neighbors import NearestNeighbors
>>> import numpy as np
>>> X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]])
>>> nbrs = NearestNeighbors(n_neighbors=2, algorithm='ball_tree').fit(X)
>>> distances, indices = nbrs.kneighbors(X)
>>> indices
array([[0, 1],
[1, 0],
[2, 1],
[3, 4],
[4, 3],
[5, 4]]...)
>>> distances
array([[0. , 1. ],
[0. , 1. ],
[0. , 1.41421356],
[0. , 1. ],
[0. , 1. ],
[0. , 1.41421356]])
Поскольку набор запросов совпадает с обучающим набором, ближайшим соседом каждой точки является она сама, а расстояние до нее равно нулю.
Также можно эффективно построить разреженный граф, отображающий связи между соседними точками:
>>> nbrs.kneighbors_graph(X).toarray()
array([[1., 1., 0., 0., 0., 0.],
[1., 1., 0., 0., 0., 0.],
[0., 1., 1., 0., 0., 0.],
[0., 0., 0., 1., 1., 0.],
[0., 0., 0., 1., 1., 0.],
[0., 0., 0., 0., 1., 1.]])
Набор данных устроен так, что точки, близкие друг к другу по индексам, близки и в пространстве параметров, поэтому матрица K ближайших соседей имеет приблизительно блочно-диагональную структуру. Такой разреженный граф полезен в различных случаях, когда для обучения без учителя используются пространственные взаимосвязи между точками; в частности, см. Isomap, LocallyLinearEmbedding и SpectralClustering.
1.6.1.2. Классы KDTree и BallTree
Кроме того, для поиска ближайших соседей можно напрямую использовать классы KDTree или BallTree. Именно эту функциональность предоставляет используемый выше класс NearestNeighbors. Дерево Ball Tree и KD-дерево имеют одинаковый интерфейс; ниже приведен пример использования KD-дерева:
>>> from sklearn.neighbors import KDTree
>>> import numpy as np
>>> X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]])
>>> kdt = KDTree(X, leaf_size=30, metric='euclidean')
>>> kdt.query(X, k=2, return_distance=False)
array([[0, 1],
[1, 0],
[2, 1],
[3, 4],
[4, 3],
[5, 4]]...)
Дополнительную информацию о доступных параметрах поиска ближайших соседей, в том числе о настройке стратегий запросов, метрик расстояния и т. д., см. в документации классов KDTree и BallTree. Список допустимых метрик можно получить с помощью KDTree.valid_metrics и BallTree.valid_metrics:
>>> from sklearn.neighbors import KDTree, BallTree >>> KDTree.valid_metrics ['euclidean', 'l2', 'minkowski', 'p', 'manhattan', 'cityblock', 'l1', 'chebyshev', 'infinity'] >>> BallTree.valid_metrics ['euclidean', 'l2', 'minkowski', 'p', 'manhattan', 'cityblock', 'l1', 'chebyshev', 'infinity', 'seuclidean', 'mahalanobis', 'hamming', 'canberra', 'braycurtis', 'jaccard', 'dice', 'rogerstanimoto', 'russellrao', 'sokalmichener', 'sokalsneath', 'haversine', 'pyfunc']
1.6.2. Классификация методом ближайших соседей
Классификация на основе соседей — это разновидность обучения на экземплярах или обучения без обобщения: она не пытается построить общую внутреннюю модель, а просто сохраняет экземпляры обучающих данных. Классификация выполняется простым голосованием большинства ближайших соседей каждой точки: точке запроса присваивается класс данных, представители которого встречаются среди ближайших соседей этой точки чаще всего.
В scikit-learn реализованы два классификатора на основе ближайших соседей: KNeighborsClassifier реализует обучение на основе \(k\) ближайших соседей каждой точки запроса, где \(k\) — целое число, заданное пользователем. RadiusNeighborsClassifier реализует обучение на основе числа соседей в пределах фиксированного радиуса \(r\) от каждой обучающей точки, где \(r\) — вещественное число, заданное пользователем.
Классификация по \(k\) соседям в KNeighborsClassifier — наиболее распространенный метод. Оптимальный выбор значения \(k\) во многом зависит от данных: как правило, большее значение \(k\) уменьшает влияние шума, но делает границы классов менее четкими.
Если данные распределены неравномерно, классификация по соседям в радиусе в RadiusNeighborsClassifier может быть более удачным выбором. Пользователь задает фиксированный радиус \(r\), поэтому для точек в более разреженных окрестностях при классификации используется меньше ближайших соседей. В пространствах параметров высокой размерности этот метод становится менее эффективным из-за так называемого «проклятия размерности».
В базовом методе классификации по ближайшим соседям используются равные веса: значение для точки запроса вычисляется простым голосованием большинства ближайших соседей. В некоторых случаях лучше назначать соседям веса так, чтобы более близкие соседи сильнее влияли на результат. Это можно сделать с помощью ключевого аргумента weights. Значение по умолчанию, weights = 'uniform', назначает всем соседям одинаковые веса. weights = 'distance' назначает веса, обратно пропорциональные расстоянию до точки запроса. В качестве альтернативы для вычисления весов можно задать пользовательскую функцию расстояния.
Примеры
- Классификация методом ближайших соседей: пример классификации с использованием ближайших соседей.
1.6.3. Регрессия методом ближайших соседей
Регрессию на основе соседей можно использовать, если метки данных являются непрерывными, а не дискретными переменными. Метка, присваиваемая точке запроса, вычисляется как среднее значение меток ее ближайших соседей.
В scikit-learn реализованы два регрессора на основе соседей: KNeighborsRegressor реализует обучение на основе \(k\) ближайших соседей каждой точки запроса, где \(k\) — целое число, заданное пользователем. RadiusNeighborsRegressor реализует обучение на основе соседей в пределах фиксированного радиуса \(r\) от точки запроса, где \(r\) — вещественное число, заданное пользователем.
В базовом методе регрессии по ближайшим соседям используются равные веса: каждая точка локальной окрестности в равной степени участвует в прогнозе для точки запроса. В некоторых случаях полезно назначать точкам веса так, чтобы близкие точки сильнее влияли на регрессию, чем удаленные. Это можно сделать с помощью ключевого аргумента weights. Значение по умолчанию, weights = 'uniform', назначает всем точкам одинаковые веса. weights = 'distance' назначает веса, обратно пропорциональные расстоянию до точки запроса. В качестве альтернативы можно задать пользовательскую функцию расстояния, которая будет использоваться для вычисления весов.
Применение регрессии по ближайшим соседям с несколькими выходами показано в примере Восстановление лица с помощью оценивателей с несколькими выходами. В этом примере входные данные X — это пиксели верхней половины лиц, а выходные данные Y — пиксели нижней половины этих лиц.
Примеры
- Регрессия методом ближайших соседей: пример регрессии с использованием ближайших соседей.
- Восстановление лица с помощью оценивателей с несколькими выходами: пример регрессии с несколькими выходами с использованием ближайших соседей.
1.6.4. Алгоритмы поиска ближайших соседей
1.6.4.1. Полный перебор
Быстрое вычисление ближайших соседей — активная область исследований в машинном обучении. Самая простая реализация поиска соседей предполагает полный перебор расстояний между всеми парами точек в наборе данных: для \(N\) образцов в \(D\) измерениях сложность этого подхода составляет \(O[D N^2]\). Эффективный поиск соседей полным перебором может быть весьма конкурентоспособным на небольших выборках данных. Однако по мере роста числа образцов \(N\) подход полного перебора быстро становится неприемлемым. В классах из sklearn.neighbors поиск соседей полным перебором задаётся с помощью ключевого слова algorithm = 'brute' и выполняется с использованием процедур, доступных в sklearn.metrics.pairwise.
1.6.4.2. K-D-дерево
Для устранения вычислительной неэффективности подхода полного перебора были разработаны различные древовидные структуры данных. В целом эти структуры стремятся уменьшить необходимое количество вычислений расстояний, эффективно кодируя агрегированную информацию о расстояниях для образца. Основная идея состоит в том, что если точка \(A\) находится очень далеко от точки \(B\), а точка \(B\) находится очень близко к точке \(C\), то мы знаем, что точки \(A\) и \(C\) находятся очень далеко друг от друга, и для этого не нужно вычислять расстояние между ними напрямую. Таким образом, вычислительную стоимость поиска ближайших соседей можно снизить до \(O[D N \log(N)]\) или меньше. Это значительное улучшение по сравнению с полным перебором при больших значениях \(N\).
Одним из первых подходов к использованию этой агрегированной информации стала структура данных KD-дерево (сокращение от K-dimensional tree — K-мерное дерево), которая обобщает двумерные квадродеревья и трёхмерные октодеревья для произвольного числа измерений. KD-дерево — это двоичная древовидная структура, которая рекурсивно разбивает пространство параметров вдоль осей данных, деля его на вложенные ортотропные области, в которые распределяются точки данных. Построение KD-дерева выполняется очень быстро: поскольку разбиение проводится только вдоль осей данных, вычислять расстояния в \(D\)-мерном пространстве не требуется. После построения ближайшего соседа для точки-запроса можно определить, выполнив всего \(O[\log(N)]\) вычислений расстояния. Хотя подход с KD-деревом очень эффективен для поиска соседей в пространствах малой размерности (\(D < 20\)), он становится неэффективным при значительном росте \(D\): это одно из проявлений так называемого «проклятия размерности». В scikit-learn поиск соседей с помощью KD-дерева задаётся ключевым словом algorithm = 'kd_tree' и выполняется с помощью класса KDTree.
Ссылки
- «Многомерные двоичные деревья поиска, используемые для ассоциативного поиска», Bentley, J.L., Communications of the ACM (1975)
1.6.4.3. Шаровое дерево
Для устранения неэффективности KD-деревьев в пространствах высокой размерности была разработана структура данных шаровое дерево. В отличие от KD-деревьев, которые разбивают данные вдоль декартовых осей, шаровые деревья делят данные на ряд вложенных гиперсфер. Это делает построение дерева более затратным, чем построение KD-дерева, но в результате получается структура данных, которая может быть очень эффективна для хорошо структурированных данных, даже в пространствах очень высокой размерности.
Шаровое дерево рекурсивно делит данные на узлы, задаваемые центроидом \(C\) и радиусом \(r\), так что каждая точка узла находится внутри гиперсферы, определяемой \(r\) и \(C\). Число точек-кандидатов для поиска соседей сокращается благодаря использованию неравенства треугольника:
При такой структуре достаточно одного вычисления расстояния между тестовой точкой и центроидом, чтобы определить нижнюю и верхнюю границы расстояний до всех точек узла. Благодаря сферической геометрии узлов шарового дерева оно может превосходить KD-дерево в пространствах высокой размерности, хотя фактическая производительность сильно зависит от структуры обучающих данных. В scikit-learn поиск соседей на основе шарового дерева задаётся ключевым словом algorithm = 'ball_tree' и выполняется с помощью класса BallTree. Также пользователь может напрямую работать с классом BallTree.
Ссылки
- «Пять алгоритмов построения шарового дерева», Omohundro, S.M., технический отчёт International Computer Science Institute (1989)
Выбор алгоритма поиска ближайших соседей
Выбор оптимального алгоритма для конкретного набора данных — сложная задача, зависящая от ряда факторов:
-
число образцов \(N\) (т. е.
n_samples) и размерность \(D\) (т. е.n_features).- Время запроса при полном переборе растёт как \(O[D N]\)
- Время запроса для шарового дерева растёт приблизительно как \(O[D \log(N)]\)
- Время запроса для KD-дерева меняется с ростом \(D\) так, что это трудно точно описать. При небольшом \(D\) (примерно меньше 20) стоимость составляет приблизительно \(O[D\log(N)]\), и запросы к KD-дереву могут быть очень эффективными. При большем \(D\) стоимость возрастает почти до \(O[DN]\), а накладные расходы на древовидную структуру могут привести к тому, что запросы будут выполняться медленнее, чем при полном переборе.
Для небольших наборов данных (\(N\) примерно меньше 30) величина \(\log(N)\) сопоставима с \(N\), поэтому алгоритмы полного перебора могут быть эффективнее древовидного подхода. В классах
KDTreeиBallTreeэто учитывается за счёт параметра размера листа: он задаёт число образцов, при котором запрос переключается на полный перебор. Благодаря этому оба алгоритма могут приближаться по эффективности к вычислению полным перебором при небольшом \(N\). -
структура данных: внутренняя размерность данных и/или их разреженность. Внутренняя размерность — это размерность \(d \le D\) многообразия, на котором расположены данные и которое может быть линейно или нелинейно вложено в пространство параметров. Разреженность характеризует степень заполнения данными пространства параметров (это понятие следует отличать от его использования применительно к «разреженным» матрицам. Матрица данных может не содержать нулевых элементов, но её структура всё равно может быть «разреженной» в этом смысле).
- Время запроса при полном переборе не зависит от структуры данных.
- На время запроса для шарового дерева и KD-дерева структура данных может сильно влиять. В целом более разреженные данные с меньшей внутренней размерностью обеспечивают более быстрое выполнение запросов. Поскольку внутреннее представление KD-дерева выровнено по осям параметров, для данных с произвольной структурой оно обычно не даёт такого же значительного ускорения, как шаровое дерево.
Наборы данных, используемые в машинном обучении, как правило, хорошо структурированы и отлично подходят для запросов на основе деревьев.
-
число соседей \(k\), запрашиваемых для точки.
- На время запроса при полном переборе значение \(k\) практически не влияет.
- При увеличении \(k\) время запроса для шарового дерева и KD-дерева увеличивается. Это связано с двумя причинами: во-первых, большее значение \(k\) требует поиска в большей части пространства параметров. Во-вторых, использование \(k > 1\) требует внутренней очереди результатов при обходе дерева.
По мере того как \(k\) становится большим по сравнению с \(N\), возможность отсекать ветви при древовидном поиске уменьшается. В такой ситуации запросы полным перебором могут быть эффективнее.
- число точек-запросов. Для шарового дерева и KD-дерева требуется этап построения. Затраты на это построение становятся незначительными при распределении на большое число запросов. Однако если запросов будет немного, построение может составлять значительную часть общих затрат. Если требуется обработать очень мало точек-запросов, полный перебор предпочтительнее древовидного метода.
В настоящее время algorithm = 'auto' выбирает 'brute', если выполняется хотя бы одно из следующих условий:
- входные данные разреженные
metric = 'precomputed'- \(D > 15\)
- \(k >= N/2\)
-
effective_metric_отсутствует в спискеVALID_METRICSдля'kd_tree'или'ball_tree'
В противном случае выбирается первый из 'kd_tree' и 'ball_tree', в списке VALID_METRICS которого есть effective_metric_. Эта эвристика основана на следующих предположениях:
- число точек-запросов имеет порядок величины не меньше числа обучающих точек
-
значение
leaf_sizeблизко к значению по умолчанию30 - при \(D > 15\) внутренняя размерность данных обычно слишком велика для древовидных методов
Влияние параметра leaf_size
Как отмечалось выше, для небольших выборок поиск полным перебором может быть эффективнее древовидного запроса. В шаровом дереве и KD-дереве это учитывается внутренним переключением на поиск полным перебором внутри листовых узлов. Уровень переключения можно задать параметром leaf_size. Выбор этого параметра имеет несколько последствий:
- время построения
-
Большее значение
leaf_sizeускоряет построение дерева, поскольку требуется создать меньше узлов. - время запроса
-
Как слишком большое, так и слишком малое значение
leaf_sizeможет привести к неоптимальной стоимости запроса. При значенииleaf_size, близком к 1, накладные расходы на обход узлов могут значительно замедлить запросы. При значенииleaf_size, близком к размеру обучающей выборки, запросы фактически сводятся к полному перебору. Хороший компромисс между этими вариантами —leaf_size = 30, значение параметра по умолчанию. - память
-
При увеличении
leaf_sizeуменьшается объём памяти, необходимый для хранения древовидной структуры. Это особенно важно для шарового дерева, в котором для каждого узла хранится \(D\)-мерный центроид. Необходимый объём памяти дляBallTreeсоставляет приблизительно1 / leaf_sizeот размера обучающей выборки.
Параметр leaf_size не используется при запросах полным перебором.
Допустимые метрики для алгоритмов поиска ближайших соседей
Список доступных метрик см. в документации класса DistanceMetric и среди метрик, перечисленных в sklearn.metrics.pairwise.PAIRWISE_DISTANCE_FUNCTIONS. Обратите внимание, что метрика «cosine» использует cosine_distances.
Список допустимых метрик для любого из перечисленных выше алгоритмов можно получить с помощью атрибута valid_metric. Например, допустимые метрики для KDTree можно получить следующим образом:
>>> from sklearn.neighbors import KDTree >>> print(sorted(KDTree.valid_metrics)) ['chebyshev', 'cityblock', 'euclidean', 'infinity', 'l1', 'l2', 'manhattan', 'minkowski', 'p']
1.6.5. Классификатор ближайшего центроида
Классификатор NearestCentroid — это простой алгоритм, представляющий каждый класс центроидом его элементов. По сути, это делает его похожим на этап обновления меток алгоритма KMeans. У него также нет параметров, которые нужно выбирать, поэтому он служит хорошей базовой моделью классификации. Однако он плохо справляется с невыпуклыми классами, а также с классами, дисперсии которых сильно различаются, поскольку предполагается одинаковая дисперсия во всех измерениях. Более сложные методы, не требующие такого предположения, см. в разделах о линейном дискриминантном анализе (LinearDiscriminantAnalysis) и квадратичном дискриминантном анализе (QuadraticDiscriminantAnalysis). Использовать NearestCentroid с параметрами по умолчанию просто:
>>> from sklearn.neighbors import NearestCentroid >>> import numpy as np >>> X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]]) >>> y = np.array([1, 1, 1, 2, 2, 2]) >>> clf = NearestCentroid() >>> clf.fit(X, y) NearestCentroid() >>> print(clf.predict([[-0.8, -1]])) [1]
1.6.5.1. Ближайший сжатый центроид
Классификатор NearestCentroid имеет параметр shrink_threshold, реализующий классификатор ближайшего сжатого центроида. По сути, значение каждого признака для каждого центроида делится на внутриклассовую дисперсию этого признака. Затем значения признаков уменьшаются на shrink_threshold. В частности, если значение какого-либо признака пересекает ноль, оно устанавливается равным нулю. Фактически это исключает влияние признака на классификацию. Например, такой подход полезен для удаления шумовых признаков.
В примере ниже небольшой порог сжатия повышает точность модели с 0.81 до 0.82.
Примеры
- Классификация по ближайшему центроиду: пример классификации с использованием ближайшего центроида и различных порогов сжатия.
1.6.6. Преобразователь ближайших соседей
Многие оценщики scikit-learn используют ближайших соседей: несколько классификаторов и регрессоров, например KNeighborsClassifier и KNeighborsRegressor, а также некоторые методы кластеризации, такие как DBSCAN и SpectralClustering, и некоторые методы вложения многообразий, например TSNE и Isomap.
Все эти оценщики могут самостоятельно вычислять ближайших соседей, однако большинство из них также принимают предварительно вычисленный разреженный граф ближайших соседей, например полученный с помощью kneighbors_graph и radius_neighbors_graph. В режиме mode='connectivity' эти функции возвращают бинарный разреженный граф смежности, необходимый, например, для SpectralClustering. В режиме mode='distance' они возвращают разреженный граф расстояний, необходимый, например, для DBSCAN. Чтобы включить эти функции в конвейер scikit-learn, можно также использовать соответствующие классы KNeighborsTransformer и RadiusNeighborsTransformer. API разреженных графов имеет несколько преимуществ.
Во-первых, предварительно вычисленный граф можно использовать повторно, например при изменении параметра оценщика. Это можно сделать вручную или с помощью возможностей кэширования конвейера scikit-learn:
>>> import tempfile >>> from sklearn.manifold import Isomap >>> from sklearn.neighbors import KNeighborsTransformer >>> from sklearn.pipeline import make_pipeline >>> from sklearn.datasets import make_regression >>> cache_path = tempfile.gettempdir() # we use a temporary folder here >>> X, _ = make_regression(n_samples=50, n_features=25, random_state=0) >>> estimator = make_pipeline( ... KNeighborsTransformer(mode='distance'), ... Isomap(n_components=3, metric='precomputed'), ... memory=cache_path) >>> X_embedded = estimator.fit_transform(X) >>> X_embedded.shape (50, 3)
Во-вторых, предварительное вычисление графа позволяет точнее управлять поиском ближайших соседей, например включить многопроцессорную обработку с помощью параметра n_jobs, который может быть доступен не во всех оценщиках.
Наконец, предварительное вычисление могут выполнять пользовательские оценщики, чтобы использовать другие реализации, например методы приближённого поиска ближайших соседей или реализации со специальными типами данных. Предварительно вычисленный разреженный граф соседей должен иметь тот же формат, что и результат radius_neighbors_graph:
- матрица CSR (при этом также принимаются COO, CSC или LIL).
- для каждого образца следует явно хранить только окрестности, ближайшие относительно обучающих данных. Они должны включать точки на расстоянии 0 от точки-запроса, в том числе диагональ матрицы при вычислении ближайших окрестностей между обучающими данными и ими же.
dataкаждой строки должны хранить расстояния в порядке возрастания (необязательно. Несортированные данные будут отсортированы стабильной сортировкой, что добавит вычислительные затраты).- все значения данных должны быть неотрицательными.
- в каждой строке не должно быть повторяющихся
indices(см. scipy/scipy#5807). - если алгоритм, которому передаётся предварительно вычисленная матрица, использует k ближайших соседей (а не окрестность заданного радиуса), в каждой строке должно храниться не менее k соседей (или k+1, как поясняется в следующем примечании).
Примечание
При запросе заданного числа соседей (с помощью KNeighborsTransformer) определение n_neighbors неоднозначно: можно учитывать каждую обучающую точку как своего собственного соседа или исключать её. Ни один из вариантов не идеален: включение точки приводит к разному числу соседей, не совпадающих с самой точкой, при обучении и тестировании, а её исключение приводит к различию между fit(X).transform(X) и fit_transform(X), что противоречит API scikit-learn. В KNeighborsTransformer используется определение, при котором каждая обучающая точка учитывается как собственный сосед в значении n_neighbors. Однако для совместимости с другими оценщиками, использующими альтернативное определение, при mode == 'distance' будет вычислен ещё один сосед. Для максимальной совместимости со всеми оценщиками безопаснее всегда включать одного дополнительного соседа в пользовательский оценщик ближайших соседей, поскольку последующие оценщики отфильтруют лишних соседей.
Примеры
-
Приближённый поиск ближайших соседей в TSNE: пример объединения в конвейер
KNeighborsTransformerиTSNE. Также представлены два пользовательских оценщика ближайших соседей на основе внешних пакетов. -
Кэширование ближайших соседей: пример объединения в конвейер
KNeighborsTransformerиKNeighborsClassifierдля кэширования графа соседей при поиске по сетке гиперпараметров.
1.6.7. Анализ компонент окрестности
Анализ компонент окрестности (NCA, NeighborhoodComponentsAnalysis) — это алгоритм обучения метрики расстояния, предназначенный для повышения точности классификации методом ближайших соседей по сравнению со стандартным евклидовым расстоянием. Алгоритм напрямую максимизирует стохастический вариант оценки k ближайших соседей (KNN) с исключением классифицируемого объекта, вычисляемой на обучающей выборке. Он также может обучать линейную проекцию данных в пространство меньшей размерности, которую можно использовать для визуализации данных и быстрой классификации.
На приведённой выше иллюстрации мы рассматриваем несколько точек из случайно сгенерированного набора данных. Сосредоточимся на стохастической классификации методом KNN для точки № 3. Толщина связи между образцом 3 и другой точкой пропорциональна расстоянию между ними и может рассматриваться как относительный вес (или вероятность), который стохастическое правило предсказания по ближайшим соседям присвоило бы этой точке. В исходном пространстве у образца 3 много стохастических соседей из разных классов, поэтому вероятность правильного класса невелика. Однако в пространстве проекции, обученном с помощью NCA, все стохастические соседи с ненулевым весом принадлежат тому же классу, что и образец 3, что гарантирует правильную классификацию последнего. Подробнее см. в разделе математическая формулировка.
1.6.7.1. Классификация
В сочетании с классификатором ближайших соседей (KNeighborsClassifier) NCA привлекателен для задач классификации, поскольку позволяет естественным образом обрабатывать многоклассовые задачи без увеличения размера модели и не вводит дополнительных параметров, требующих тонкой настройки пользователем.
На практике классификация с помощью NCA хорошо работает на наборах данных разного размера и сложности. В отличие от родственных методов, таких как линейный дискриминантный анализ, NCA не делает никаких предположений о распределениях классов. Классификация методом ближайших соседей позволяет естественным образом строить весьма нерегулярные границы принятия решений.
Чтобы использовать эту модель для классификации, нужно объединить экземпляр NeighborhoodComponentsAnalysis, который обучает оптимальное преобразование, с экземпляром KNeighborsClassifier, выполняющим классификацию в пространстве проекции. Ниже приведён пример с использованием этих двух классов:
>>> from sklearn.neighbors import (NeighborhoodComponentsAnalysis,
... KNeighborsClassifier)
>>> from sklearn.datasets import load_iris
>>> from sklearn.model_selection import train_test_split
>>> from sklearn.pipeline import Pipeline
>>> X, y = load_iris(return_X_y=True)
>>> X_train, X_test, y_train, y_test = train_test_split(X, y,
... stratify=y, test_size=0.7, random_state=42)
>>> nca = NeighborhoodComponentsAnalysis(random_state=42)
>>> knn = KNeighborsClassifier(n_neighbors=3)
>>> nca_pipe = Pipeline([('nca', nca), ('knn', knn)])
>>> nca_pipe.fit(X_train, y_train)
Pipeline(...)
>>> print(nca_pipe.score(X_test, y_test))
0.96190476...
На графике показаны границы принятия решений для классификации методом ближайших соседей и классификации с помощью анализа компонент окрестности на наборе данных iris. Для наглядности при обучении и оценке использованы только два признака.
1.6.7.2. Понижение размерности
NCA можно использовать для контролируемого понижения размерности. Входные данные проецируются на линейное подпространство, состоящее из направлений, минимизирующих целевую функцию NCA. Требуемую размерность можно задать с помощью параметра n_components. Например, на следующем рисунке сравнивается понижение размерности с помощью метода главных компонент (PCA), линейного дискриминантного анализа (LinearDiscriminantAnalysis) и анализа компонент окрестности (NeighborhoodComponentsAnalysis) на наборе данных Digits размером \(n_{samples} = 1797\) и с \(n_{features} = 64\) признаками. Набор данных делится на обучающую и тестовую выборки одинакового размера, а затем стандартизируется. Для оценки вычисляется точность классификации методом 3 ближайших соседей на точках двумерной проекции, полученной каждым методом. Каждый образец данных принадлежит одному из 10 классов.
Примеры
1.6.7.3. Математическая формулировка
Цель NCA — обучить оптимальную матрицу линейного преобразования размером (n_components, n_features), которая максимизирует сумму вероятностей \(p_i\) по всем образцам \(i\), где \(p_i\) — вероятность правильной классификации образца \(i\), то есть:
где \(N\) = n_samples, а \(p_i\) — вероятность правильной классификации образца \(i\) согласно стохастическому правилу ближайших соседей в обученном пространстве вложения:
где \(C_i\) — множество точек того же класса, что и образец \(i\), а \(p_{i j}\) — функция softmax от евклидовых расстояний в пространстве вложения:
Расстояние Махаланобиса
NCA можно рассматривать как обучение метрики (квадрата) расстояния Махаланобиса:
где \(M = L^T L\) — симметричная положительно полуопределённая матрица размером (n_features, n_features).
1.6.7.4. Реализация
Эта реализация соответствует описанию в исходной статье [1]. В качестве метода оптимизации сейчас используется L-BFGS-B из scipy с полным вычислением градиента на каждой итерации, чтобы избежать настройки скорости обучения и обеспечить стабильное обучение.
Дополнительную информацию см. в примерах ниже и в строке документации метода NeighborhoodComponentsAnalysis.fit.
1.6.7.5. Сложность
1.6.7.5.1. Обучение
NCA хранит матрицу попарных расстояний, для чего требуется n_samples ** 2 памяти. Временная сложность зависит от количества итераций, выполненных алгоритмом оптимизации. Однако максимальное количество итераций можно задать аргументом max_iter. Для каждой итерации временная сложность составляет O(n_components x n_samples x min(n_samples, n_features)).
1.6.7.5.2. Преобразование
Операция transform возвращает \(LX^T\), поэтому её временная сложность равна n_components * n_features * n_samples_test. Эта операция не увеличивает пространственную сложность.
Литература
© 2007–2026 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.9/modules/neighbors.html