В продолжение цикла заметок о кластерном анализе сегодня поговорим о алгоритме минимального покрывающего дерева (MST) и работе с ним на языке python.
В первой заметке уже был описан набор данных, который мы исследуем, а также приведён список литературы на тему, поэтому перейдём сразу к делу.
Обширный класс алгоритмов кластеризации основан на представлении выборки в виде графа. Вершинам графа соответствуют объекты выборки, а рёбрам — попарные расстояния между объектами.
Достоинством графовых алгоритмов кластеризации является наглядность, относительная простота реализации и возможность вносить различные усовершенствования, опираясь на простые геометрические соображения.
Алгоритм минимального покрывающего дерева (MST) строит граф из N-1 рёбер так, чтобы они соединяли все N точек и обладали минимальной суммарной длиной. Такой граф называется кратчайшим незамкнутым путём, минимальным покрывающим деревом или каркасом графа.
Описание работы алгоритма:
Общеизвестны два недостатка этого алгоритма:
Для начала необходимо получить исходные данные и расстояния между всеми элементами набора.
Следом попробуем построить граф, представляющий наш исходный набор данных. Каждая вершина этого графа будет обозначать отдельный объект исходной выборки и будет связана со всеми остальными вершинами посредством взвешенных рёбер. Вес каждого ребра будет равен расстоянию близости между объектами.
Теперь создадим ещё один граф, включающий все вершины нашего исходного набора, но добавим в него только те рёбра, которые являлись частью остовного дерева и весили меньше порогового уровня в 0.05 (выбран чисто эмпирически по гистограмме).
Что же, осталось лишь отобразить результат. Для этого в NetworkX также есть встроенные средства, а именно функция draw_graphviz. Интерес для нас представляет само остовное дерево и полученный граф кластеров.
В первой заметке уже был описан набор данных, который мы исследуем, а также приведён список литературы на тему, поэтому перейдём сразу к делу.
Теория
Достоинством графовых алгоритмов кластеризации является наглядность, относительная простота реализации и возможность вносить различные усовершенствования, опираясь на простые геометрические соображения.
Алгоритм минимального покрывающего дерева (MST) строит граф из N-1 рёбер так, чтобы они соединяли все N точек и обладали минимальной суммарной длиной. Такой граф называется кратчайшим незамкнутым путём, минимальным покрывающим деревом или каркасом графа.
Описание работы алгоритма:
- Найти пару точек с наименьшим расстоянием (весом ребра) и соединить их ребром;
- пока в выборке остаются изолированные точки:
- найти изолированную точку, ближайшую к некоторой неизолированной;
- соединить эти две точки ребром;
- удалить K-1 самых длинных рѐбер;
Общеизвестны два недостатка этого алгоритма:
- ограниченная применимость. Алгоритм наиболее подходит для выделения кластеров типа сгущений или лент. Наличие разреженного фона или «узких перемычек» между кластерами приводит к неадекватным результатам;
- высокая трудоёмкость — для построения кратчайшего незамкнутого пути требуется O(N^3) операций.
Практика
Для работы с графами будет использоваться NetworkX — библиотека для создания и манипуляции графами. Кроме этого, она предоставляет готовую реализацию алгоритма нахождения минимального остовного дерева и удобное API для визуализации результатов.Для начала необходимо получить исходные данные и расстояния между всеми элементами набора.
from scipy.spatial.distance import pdist
names, data = get_data()
dist = pdist(data, 'euclidean')
Функции get_data() и pdist() были описаны в первой заметке, поэтому не будем на них останавливаться.Следом попробуем построить граф, представляющий наш исходный набор данных. Каждая вершина этого графа будет обозначать отдельный объект исходной выборки и будет связана со всеми остальными вершинами посредством взвешенных рёбер. Вес каждого ребра будет равен расстоянию близости между объектами.
from collections import deque import networkx as nx s = nx.Graph() s.add_nodes_from(names) dq = deque(dist) len_x = len(names) for x in xrange(len_x - 1): for y in xrange(x + 1, len_x): s.add_edge(names[x], names[y], weight=dq.popleft())В переменной s содержится исходный граф нашего набора данных. Следующим шагом построим минимальное покрывающее дерево полученного графа и отобразим гистограмму весов рёбер в нём (дабы выбрать пороговый уровень веса).
import matplotlib.pyplot as plt mst = nx.minimum_spanning_tree(s) plt.hist([edge[2]['weight'] for edge in mst.edges_iter(data=True)], 100, color='red', alpha=0.3)
Теперь создадим ещё один граф, включающий все вершины нашего исходного набора, но добавим в него только те рёбра, которые являлись частью остовного дерева и весили меньше порогового уровня в 0.05 (выбран чисто эмпирически по гистограмме).
r = nx.Graph() r.add_nodes_from(names) edges = [edge for edge in mst.edges_iter(data=True) if edge[2]['weight'] <= 0.05] r.add_edges_from(edges)Как вы могли заметить, в отличии от классического алгоритма MST, я предпочёл отрезать рёбра не по количеству кластеров, а по пороговому уровню, как в иерархическом алгоритме. Это позволило снизить влияние на результат кластеризации качество предварительной подготовки данных.
Что же, осталось лишь отобразить результат. Для этого в NetworkX также есть встроенные средства, а именно функция draw_graphviz. Интерес для нас представляет само остовное дерево и полученный граф кластеров.
def graph_draw(g): """Draw graph""" plt.figure() nx.draw_graphviz(g, with_labels=False, node_size=3, prog='neato') graph_draw(mst) graph_draw(r) plt.show()
Минимальное остовное дерево графа |
Результат кластеризации |
Комментариев нет:
Отправить комментарий