Уровень алгоритма

Участник:Astandrik/Алгоритм кластеризации, основанный на минимальном остовном дереве: различия между версиями

Материал из Алговики
Перейти к навигации Перейти к поиску
(Создание шаблона страницы)
 
 
(не показано 105 промежуточных версий 3 участников)
Строка 1: Строка 1:
 +
{{Assignment|Zhum}}
 +
 
{{algorithm
 
{{algorithm
| name              = Разложение Холецкого
+
| name              = Алгоритм кластеризации, основанный на минимальном остовном дереве
| serial_complexity = <math>O(n^3)</math>
+
| serial_complexity = <math> O(N^2 log(N)) </math>
| pf_height        = <math>O(n)</math>
+
| pf_height        = <math> O(log(N)) </math>
| pf_width          = <math>O(n^2)</math>
+
| pf_width          = <math> O(N) </math>
| input_data        = <math>\frac{n (n + 1)}{2}</math>
+
| input_data        = <math> N </math>
| output_data      = <math>\frac{n (n + 1)}{2}</math>
+
| output_data      = <math> N </math>
 
}}
 
}}
  
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]], [[Участник:VadimVV|Вад.В.Воеводин]] ([[#Описание локальности данных и вычислений|раздел 2.2]]), [[Участник:Teplov|А.М.Теплов]] (разделы [[#Масштабируемость алгоритма и его реализации|2.4]], [[#Динамические характеристики и эффективность реализации алгоритма|2.5]])
+
 
 +
Основные авторы описания: [[Участник:Astandrik|Стандрик Антон Сергеевич]]
  
 
== Свойства и структура алгоритма ==
 
== Свойства и структура алгоритма ==
Строка 14: Строка 17:
 
=== Общее описание алгоритма ===
 
=== Общее описание алгоритма ===
  
'''Разложение Холецкого''' впервые предложено французским офицером и математиком Андре-Луи Холецким в конце Первой Мировой войны, незадолго до его гибели в бою в августе 1918 г. Идея этого разложения была опубликована в 1924 г. его сослуживцем<ref>Commandant Benoit, Note sur une méthode de résolution des équations normales provenant de l'application de la méthode des moindres carrés à un système d'équations linéaires en nombre inférieur à celui des inconnues (Procédé du Commandant Cholesky), Bulletin Géodésique 2 (1924), 67-77.</ref>. Потом оно было использовано поляком Т. Банашевичем<ref>Banachiewicz T. Principles d'une nouvelle technique de la méthode des moindres carrês. Bull. Intern. Acad. Polon. Sci. A., 1938,  134-135.</ref><ref>Banachiewicz T. Méthode de résoltution numérique des équations linéaires, du calcul des déterminants et des inverses et de réduction des formes quardatiques. Bull. Intern. Acad. Polon. Sci. A., 1938, 393-401.</ref> в 1938 г. В советской математической литературе называется также методом квадратного корня<ref>Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref><ref>Воеводин В.В., Кузнецов Ю.А. Матрицы и вычисления. М.: Наука, 1984.</ref><ref>Фаддеев Д.К., Фаддева В.Н. Вычислительные основы линейной алгебры. М.-Л.: Физматгиз, 1963.</ref>; название связано с характерными операциями, отсутствующими в родственном разложении Гаусса.  
+
Данный алгоритм предназначен для решения задач кластеризации. Говоря в общем, кластеризация — многомерная статистическая процедура, выполняющая сбор данных, содержащих информацию о выборке объектов, и затем упорядочивающая объекты в сравнительно однородные группы <ref>Классификация и кластер. Под ред. Дж. Вэн Райзина. М.: Мир, 1980. 390 с.</ref>.
 +
 
 +
Впервые идея использования минимального остовного дерева для кластеризации данных была преждожена C. T. Zah в 1971 году<ref name=zakh>C. T. Zahn, “graph-Theriotical methos for detecting and describing getalt clusters,” IEEE Trans. Computers, vol. 20, no. 1, pp. 68-86, 1971 </ref>. Этот метод широко используется исследователями при анализе изображений <ref>Y. Xu, V. Olman and E. C. Uberbacher, “A mentation using minimum spanning trees,” Image and Vission Computing, vol. 15, pp. 47-57,1997.</ref><ref>Zhi Min Wang, Yeng Chai Soh, Qing Song, Kang Sim, “Adaptive spatial information-theoretic clustering for image segmentation,” Pattern Recognition, vol. 42, no. 9, pp. 2029-2044, 2009.</ref>, а также при анализе биологических данных <ref>Y. Xu, V. Olman and D. Xu, “Clustering gene expression data using a graph-Theriotic approach: An application of minimum spanning trees,” Bioinformatics, vol. 18, no. 4, pp. 536-545, 2002.</ref>.
 +
 
 +
В общем описании работы алгоритма можно выделить следующие шаги:
  
Первоначально разложение Холецкого использовалось исключительно для плотных симметричных положительно определенных матриц. В настоящее время его использование гораздо шире. Оно может быть применено также, например, к эрмитовым матрицам. Для  повышения производительности вычислений часто применяется блочная версия разложения.
+
# Разбиение входных векторов на пары и вычисление расстояний в них. Для этого небходим выбор метрики пространства, в котором осуществляется кластерный анализ. В качестве примеров метрик можно привести:
 +
#* Метрика Евклида: <math>d_{ij} = \sqrt{\sum^n_{k=1}(x_{ik} - x_{jk})^2}</math>
 +
#* Метрика city-block (манхэттенская метрика): <math>d_{ij} = \sum^n_{k=1}|x_{ik} - x_{jk}| </math>
 +
#* Метрика Брея-Картиса: <math>d_{ij} = \sum^n_{k=1}\frac{|x_{ik} - x_{jk}|}{|x_{ik} + x_{jk}| }</math>
 +
# Построение минимального остовного дерева по заданному множеству. Существует три основных алгоритма [[Построение минимального остовного дерева (MST) | построения минимального остовного дерева]]:
 +
#* [[Алгоритм Прима]]
 +
#* [[Алгоритм Крускала]]
 +
#* [[Алгоритм Борувки]]
 +
# Разделение полученного дерева на несвязное множество поддеревьев в соответствии с некоторой стратегией. Можно выделить три популярные стратегии кластеризации:
 +
#* SEMST<ref>Asano, M.K.T., Bhattacharya, B., Yao, F.: Clustering algorithms based on minimum and maximum spanning trees. In: In Proceedings of the fourth annual symposium on Computational Geometry. (1998) 252–257</ref> (standard Euclidean minimum spanning tree clustering algorithm) - предполагается, что нам заранее известно число кластеров. В таком случае, если число кластеров равно <math>k</math>, то из полученного в первом пункте остовного дерева удаляются <math>k-1</math> связей с наибольшим весом.
 +
#* CEMST<ref>Ye, B., Chao, K.M.: Spanning Trees and Optimization Problems. Chapman and
 +
Hall (2004)</ref>(The maximum cost minimum spanning tree) - Работает так же как и SEMST, однако вместо веса вершины используется её стоимость, которая вычисляется следующим образом: Пусть ребро <math>e = (x_{i}, x_{j})</math>, тогда <math>cost(e) = w(e) * degree(x_{i}) * degree(x_{j})</math>. Далее, зная число кластеров <math>k</math> и стоимости рёбер, удаляются <math>k-1</math> рёбер с максимальной стоимостью.
 +
#* ZEMST<ref name=zakh /> (The Zahn’s minimum spanning tree) - от двух предыдущих эта стратегия отличается тем, что здесь число кластеров не известно заранее, а вычисляется в процессе кластеризации. Его суть в том, что удаляются те рёбра, веса которых значительно превосходят средний вес ребра в соседних поддеревьях.
  
Для разреженных матриц разложение Холецкого также широко применяется в качестве основного этапа прямого метода решения линейных систем. В этом случае используют специальные упорядочивания для уменьшения ширины профиля исключения, а следовательно и уменьшения количества арифметических операций. Другие упорядочивания используются для выделения независимых блоков вычислений при работе на системах с параллельной организацией.
+
==== Достоинства ====
  
Варианты разложения Холецкого нашли успешные применения и в итерационных методах для построения переобусловливателей разреженных симметричных положительно определенных матриц. В неполном треугольном разложении («по позициям») элементы переобусловливателя вычисляются только в заранее заданных позициях, например, в позициях ненулевых элементов исходной матрицы (так называемое разложение IC0). Для получения же более точного разложения применяется приближение, в котором фильтрация малых элементов производится «по значениям». В зависимости от используемого порога фильтрации можно получить более точное, но и более заполненное разложение. Существует и алгоритм разложения второго порядка точности<ref>Kaporin I.E. High quality preconditioning of a general symmetric positive definite matrix based on its UTU + UTR + RTU-decomposition. Numer. Lin. Algebra Appl. (1998) Vol. 5, No. 6, 483-509.</ref>. В нём при таком же заполнении множителей разложения удается улучшить точность. Для такого разложения в параллельном режиме используется специальный вариант аддитивного переобуславливания на основе разложения второго порядка<ref>Капорин И.Е., Коньшин И.Н. Параллельное решение симметричных положительно-определенных систем на основе перекрывающегося разбиения на блоки. Ж. вычисл. матем. и матем. физ.,  2001, Т, 41, N. 4, C. 515–528.</ref>.
+
Основным достоинством алгоритма является его способность выделять кластеры произвольной формы. На следующем примере можно видеть, как алгоритм даже в случае SEMST алгоритма корректно разбивает вложенные окружности на три кластера:
  
На этой странице представлено исходное разложение Холецкого с новых позиций нашего суперкомпьютерного века. Приведено описание конкретной версии разложения Холецкого — для плотных вещественных симметричных положительно определённых матриц, но структура для ряда других версий, например, для комплексного случая, почти такая же, различия состоят в замене большинства вещественных операций на комплексные. Список остальных основных вариантов разложения можно посмотреть на странице [[Метод Холецкого (нахождение симметричного треугольного разложения)]].
+
[[File:Selection_117_.png|frame|center|alt=Point-connected clusters| Слева направо: Рисунок 1: Начальные данные. Рисунок 2: построенное минимальное остовное дерево. Рисунок 3: разбиение на кластеры путём удаления самых длинных связей.]]
  
Используется для разложения положительно определённых эрмитовых (''в вещественном случае - симметрических'') матриц в виде <math>A = L L^*</math>,  <math>L</math> — нижняя треугольная матрица,
+
==== Недостатки ====
{{Шаблон:LCommon}}
 
или в виде <math>A = U^* U</math>, <math>U</math> — верхняя треугольная матрица,
 
{{Шаблон:UCommon}}
 
Эти разложения совершенно эквивалентны друг другу по вычислениям и различаются только способом представления данных). Он заключается в реализации формул для элементов матрицы <math>L</math>, получающихся из вышеприведённого равенства единственным образом. Получило широкое распространение благодаря следующим особенностям.
 
  
==== Симметричность и положительная определённость матрицы ====
+
Недостатком является трудность определения четких критериев, при которых осуществляется разбиение данных на разные кластеры.
  
Симметричность матрицы позволяет хранить и вычислять только чуть больше половины её элементов, что почти вдвое экономит как необходимые для вычислений объёмы памяти, так и количество операций в сравнении с, например, разложением по методу Гаусса. При этом альтернативное (без вычисления квадратных корней) LU-разложение, использующее симметрию матрицы, всё же несколько быстрее метода Холецкого (не использует извлечение квадратных корней), но требует хранения всей матрицы.  
+
Кроме того, существуют наборы данных, на которых популярные алгоритмы (SEMST, CEMST, ZEMST) не дают правильного результата, и для которых требуется применение более сложных алгоритмов, к числу которых можно отнести, например, алгоритм HEMST <ref>Clustering with Minimum Spanning Trees Yan Zhou, Oleksandr Grygorash, Thomas F. Hain, Meador Inge Zach Jorgensen School of Computer and Information Sciences University of South Alabama, Mobile, AL 36688 USA Urban Insight Inc. 5657 Wilshire Blvd Ste 290, Los Angeles, CA 90036</ref> с временем работы <math>O(n^2 * log(n))</math>.
Благодаря тому, что разлагаемая матрица не только симметрична, но и положительно определена, её LU-разложения, в том числе и разложение методом Холецкого, имеют наименьшее ''[[Глоссарий#Эквивалентное возмущение|эквивалентное возмущение]]'' из всех известных разложений матриц.
 
  
==== Режим накопления ====
+
В качестве иллюстрации можно привести два похожих визуально, но различных с точки зрения разбиения на кластеры с помощью описываемого алгоритма, примера. (В приведенных примерах первый рисунок обозначает начальный набор данных. Второй - построенное на основе данных минимальное остовное дерево. Третий рисунок - разбиение на кластеры.):
  
Алгоритм позволяет использовать так называемый ''режим накопления'', обусловленный тем, что значительную часть вычислений составляют ''вычисления скалярных произведений''.
+
[[File:Selection_116_.png|frame|center|alt=Point-connected clusters| Правильное разбиение на два кластера.]]
  
=== Математическое описание алгоритма ===
+
[[File:Selection_115_.png|frame|center|alt=Point-connected clusters| Неверное разбиение на один кластер.]]
  
Исходные данные: положительно определённая симметрическая матрица <math>A</math> (элементы <math>a_{ij}</math>).
+
Отличие второго примера - в наличии плотной цепочки связей между двумя кластерами, которая и приводит к ошибке наивного алгоритма удаления самых длинных связей, и таким образом здесь потребуется более сложный алгоритм с более значительным временем работы для правильного нахождения кластеров.
  
Вычисляемые данные: нижняя треугольная матрица <math>L</math> (элементы <math>l_{ij}</math>).
+
=== Математическое описание алгоритма ===
  
Формулы метода:
+
Исходные данные: набор векторов размерности <math>n</math> : <math>(a_1, a_2, ..., a_n)</math>, где  <math> a_j \in \mathbb {R} \quad \forall j \in [1,n] </math>.
:<math>
 
\begin{align}
 
l_{11} & = \sqrt{a_{11}}, \\
 
l_{j1} & = \frac{a_{j1}}{l_{11}}, \quad j \in [2, n], \\
 
l_{ii} & = \sqrt{a_{ii} - \sum_{p = 1}^{i - 1} l_{ip}^2}, \quad i \in [2, n], \\
 
l_{ji} & = \left (a_{ji} - \sum_{p = 1}^{i - 1} l_{ip} l_{jp} \right ) / l_{ii}, \quad i \in [2, n - 1], j \in [i + 1, n].
 
\end{align}
 
</math>
 
  
Существует также блочная версия метода, однако в данном описании разобран только точечный метод.
+
Вычисляемые данные: Сгруппированные по кластерам данные.
  
В ряде реализаций деление на диагональный элемент выполняется в два этапа: вычисление <math>\frac{1}{l_{ii}}</math> и затем умножение на него всех (видоизменённых) <math>a_{ji}</math> . Здесь мы этот вариант алгоритма не рассматриваем. Заметим только, что он имеет худшие параллельные характеристики, чем представленный.
+
==== Основные обозначения ====
  
=== Вычислительное ядро алгоритма ===
+
* <math>E</math> - множество рёбер.
 +
* <math>V</math> - множество вершин.
 +
* <math>G = (V,E) </math> - неориентированный граф, заданный множеством вершин <math>V</math> и множеством рёбер <math>E</math>
 +
* <math>MST(G)</math> (Minimum Spanning Tree) - минимальное остовное дерево графа <math></math>. Остовное дерево — ациклический связный подграф данного связного неориентированного графа, в который входят все его вершины. Минимальное остовное дерево в связанном взвешенном неориентированном графе — это остовное дерево этого графа, имеющее минимальный возможный вес, где под весом дерева понимается сумма весов входящих в него рёбер.
 +
* <math>MSF(G)</math> (Minimum Spanning Forest) - набор минимальных остовных деревьев для всех компонент связности графа <math> G </math>
  
Вычислительное ядро последовательной версии метода Холецкого можно составить из множественных (всего их <math>\frac{n (n - 1)}{2}</math>) вычислений скалярных произведений строк матрицы:
+
В описании алгоритмов этой статьи часто используется термин "построение MST", подразумевая, что на самом деле в результате построения может получиться как одна компонента связности (MST), так и множество компонент связности (MSF).
  
:<math>\sum_{p = 1}^{i - 1} l_{ip} l_{jp}</math>
+
==== Алгоритмы кластеризации ====
 +
'''SEMST'''(standard Euclidean minimum spanning tree) - простейший алгоритм, основанный на заранее известном числе кластеров <math> k </math>. Путём удаления <math> k - 1 </math> связей с наибольшим весом предполагается разбиение остовного дерева на нужное количество кластеров. Иными словами минимальное остовное дерево разбивается на <math> k </math>  поддеревьев: <math> G = \{G_1,G_2,...,G_k\} </math> путём минимизации суммы всех весов: <math> W = \sum ^k_{l=1} \sum _{x_i,x_j \in V_l} w_{x_i,x_j} -> min </math>, где <math> V_l </math> - множество вершин в поддереве <math> G_l </math>. В конце такой процедуры каждое множество <math> V_l </math> становится кластером <math> C_l </math>. Поскольку (в случае использования алгоритма Крускала для построения остовного дерева) рёбра перед началом кластеризации уже отсортированы, для удаления наибольших <math> k </math> рёбер требуется <math> O(k) </math> операций, и для распределения получившихся вершин по кластерам требуется время <math> O(n) </math>.'''
  
в режиме накопления или без него, в зависимости от требований задачи. Во многих последовательных реализациях упомянутый способ представления не используется. Дело в том, что в них вычисление сумм типа
+
'''CEMST'''(maximum cost Euclidean minimum spanning tree) - Работает в точности, как и SEMST, но здесь вместо весов рёбер используется т.н. "стоимость", которая высчитывается следующим образом: если ребро <math> (x_i, x_j) </math> соединяет вершины <math> x_i </math> и <math> x_j </math>, то его "стоимость" считается как: <math> Cost_{x_i, x_j} = w_{x_i,x_j} * Deg(x_i) * Deg(x_j) </math>, где <math> Deg(x_i) </math> - степень вершины <math> x_i </math> (т.е.  количество рёбер графа G, инцидентных вершине <math> x_i </math>). В остальном алгоритм действует точно так же, как и SEMST, удаляя <math> k-1 </math> рёбер с наибольшей "стоимостью". Сложность <math> O(k) </math> . Алгоритм направлен на удаление рёбер, соединяющих высоко нагруженные (с точки зрения количества инцидентных рёбер) вершины.
  
:<math>a_{ji} - \sum_{p = 1}^{i - 1} l_{ip} l_{jp}</math><nowiki/>
+
'''ZEMST''' (The Zahn’s maximum spanning tree (Zahn, 1971)) <ref> Attribute Clustering Based on Heuristic Tree Partition Jorge Cordero H. and Yifeng Zeng Department of Computer Science Aalborg University 9220 Aalborg, Denmark</ref> - отличается от двух предыдущих алгоритмов в первую очередь тем, что здесь количество кластеров заранее не известно, но определяется исходя из данных. Данный алгоритм не является жадным, в отличие от первых двух, так как в нём рассматривается не только само ребро и его вес, а также и поддеревья, которые соединены этим ребром. То есть для ребра <math> (x_i, x_j) </math> рассматриваются поддеревья <math> N_i = (V_{N_i}, E_{N_i}), N_j = (V_{N_j}, E_{N_j}) </math>, где <math> N_i </math> - поддерево  глубины(высоты) <math> d </math>(величина d определяется эмпирически), состоящее из всех достижимых из вершины <math> x_j </math> вершин за <math> d </math> шагов через все достижимые из неё рёбра (исключая само ребро <math> (x_i, x_j) </math>).
 +
Введём обозначения:
 +
*<math>\overline{w_{N_i}} = \frac{\sum_{(x_r, x_s) \in E_{N_j}}w_{x_r,x_s}}{|E_{N_j}|} </math> - средний вес ребра в поддереве <math> N_i </math>
 +
*<math>\sigma_{N_i} = \sqrt{\frac{\sum_{(x_r, x_s) \in E_{N_j}}(w_{x_r,x_s} - \overline{w_{N_i}})^2}{|E_{N_j}|}} </math> - стандартное отклонение веса ребра в поддереве <math> N_i </math>
 +
Ребро <math> (x_i, x_j) </math> удаляется из списка в случае, если выполняется хотя бы одно из двух условий:
 +
#<math> w_{x_i,x_j} > \overline{w_{N_i}} - \sigma_{N_i} </math>
 +
#<math> w_{x_i,x_j} > \overline{w_{N_j}} - \sigma_{N_j} </math>
 +
#<math> w_{x_i,x_j} > \frac{\sum_{(x_r, x_s) \in E_i \cup E_j}w_{x_r,x_s}}{|E_i \cup E_j|} </math>
  
в которых и встречаются скалярные произведения, ведутся не в порядке «вычислили скалярное произведение, а потом вычли его из элемента», а путём вычитания из элемента покомпонентных произведений, являющихся частями скалярных произведений. Поэтому следует считать вычислительным ядром метода не вычисления скалярных произведений, а вычисления выражений
+
Сложность этого алгоритма составляет в худшем случае <math> O(N^2) </math> (поскольку может оказаться нужным перебрать все рёбра), а в лучшем <math> O(Nd) </math>.
  
:<math>a_{ji} - \sum_{p = 1}^{i - 1} l_{ip} l_{jp}</math>
+
=== Вычислительное ядро алгоритма ===
  
в ''режиме накопления'' или без него.
+
Процесс решения задачи кластеризации при помощи минимального остовного дерева может быть разделен на три независимых этапа:
  
Тем не менее, в популярных зарубежных реализациях точечного метода Холецкого, в частности, в библиотеках LINPACK и LAPACK, основанных на BLAS, используются именно вычисления скалярных произведений в виде вызова соответствующих подпрограмм BLAS (конкретно — функции SDOT). На последовательном уровне это влечёт за собой дополнительную операцию суммирования на каждый из <math>\frac{n (n + 1)}{2}</math> вычисляемый элемент матрицы <math>L</math> и некоторое замедление работы программы (о других следствиях рассказано ниже в разделе «[[#Существующие реализации алгоритма|Существующие реализации алгоритма]]»). Поэтому в данных вариантах ядром метода Холецкого будут вычисления этих скалярных произведений.
+
# Приведение задачи наборе векторов данных к задаче о поиске минимального остовного дерева в графе (то есть нахождение расстояний между всеми парами данных векторов с использованием выбранной метрики, и запись этого расстояния как веса ребра, соединяющего эти вершины). Последовательная сложность: <math> O(n^2) </math>
 +
# Поиск минимального остовного дерева. Последовательная сложность: <math> O(E*log(E)) </math>
 +
# Разбиение остовного дерева на кластеры. Последовательная сложность зависит от реализации. (от <math> O(1) </math> в случае SEMST до <math> O(n^2) </math> в случае ZEMST).
  
 
=== Макроструктура алгоритма ===
 
=== Макроструктура алгоритма ===
  
Как записано и в [[#Вычислительное ядро алгоритма|описании ядра алгоритма]], основную часть метода составляют множественные (всего <math>\frac{n (n - 1)}{2}</math>) вычисления сумм
+
В реализации алгоритма используются следующие структуры данных:
 +
#'''Узел'''(Node)(Вершина) - Представляется набором координат в выбранном пространстве.
 +
#'''Ребро'''(Edge) - Представляется: двумя индексами вершин, которые соединяет; своим весом; флагом, который указывает, входит ли данное ребро в минимальное остовное дерево.
  
:<math>a_{ji}-\sum_{p=1}^{i-1}l_{ip} l_{jp}</math>
+
В дальнейшем описании алгоритма используются следующие макрооперации:
 +
#Вычисление расстояния между вершинами.
 +
#Добавление ребра в граф - Осуществляется путём выставление специального флага у соответствующего ребра в true;
 +
#Удаление ребра из графа - Операция, обратная к добавление: соответствующий флаг устанавливается в false. В начальный момент времени у всех рёбер флаг установлен в false, что означает отсутствие рёбер в графе.
  
в режиме накопления или без него.
+
Следующие три используемые операции относятся к реализации алгоритма Крускала при помощи леса непересекающихся множеств. Подробнее об этой структуре и связанных операциях можно почитать, например, здесь <ref> Томас Кормен и др. Алгоритмы: построение и анализ = INTRODUCTION TO ALGORITHMS. — 2-е изд. — М.: «Вильямс», 2006. — С. 1296. — ISBN 0-07-013151-1. (Глава 21. Раздел 21.3: "Леса непересекающихся множеств) </ref>, в данном пункте они будут только перечислены:
 +
#Создание дерева с одним узлом - Make-Set
 +
#Объединение компонент связности - Union
 +
#Поиск компонент связности - Find-Set
  
 
=== Схема реализации последовательного алгоритма ===
 
=== Схема реализации последовательного алгоритма ===
  
Последовательность исполнения метода следующая:
+
<source lang="cpp">
 +
int N;        // Размер входных данных
 +
int number_of_clusters;        // Число кластеров
 +
int size = N* (N-1) / 2;        // Количество связей в полном графе
 +
Node nodes[N];        // Вершины(точки), заданные начальными условиями
 +
Edge edges[size];        // Все расстояния между вершинами
 +
vector<Edge> MST;        // Рёбра минимального остовного дерева
 +
read_data(&nodes, file_name);        // Считывание начальных данных
  
1. <math>l_{11}= \sqrt{a_{11}}</math>
+
//----------------Часть 1--------------//
 +
for(int i = 0; i < N-1; i++) {
 +
  for(int j = i+1; j < N; j++) {
 +
double distance = getDistance(node[i], node[j]);        // Вычисление расстояния между вершинами
 +
edges[i*N + j] = Edge(i,j,distance);        // Запись значения расстояния в массив
 +
  }
 +
}
  
2. <math>l_{j1}= \frac{a_{j1}}{l_{11}}</math> (при <math>j</math> от <math>2</math> до <math>n</math>).
 
  
Далее для всех <math>i</math> от <math>2</math> до <math>n</math> по нарастанию выполняются
+
//----------------Часть 2--------------//
 +
sort(edges, size);        // Сортировка рёбер графа в порядке возрастания
 +
for(int i = 0; i < size; i++) {        // Обходим список рёбер по возрастанию
 +
if(find_set(edges[i].first) != find_set(edges[i].second)) {        // Если вершины принадлежат разным подграфам - связываем их
 +
          Union(edges[i].first, edges[i].second);        // Если вершины принадлежат разным подграфам - связываем их
 +
  MST.push_back(edges[i]);        // Запись использованного ребра в минимальное остовное дерево
 +
  edges[i]->is_in_MST = true;    // Запись использованного ребра в минимальное остовное дерево
 +
}
 +
}
  
3. <math>l_{ii} = \sqrt{a_{ii} - \sum_{p = 1}^{i - 1} l_{ip}^2}</math> и
 
  
4. (кроме <math>i = n</math>): <nowiki/><math>l_{ji} = \left (a_{ji} - \sum_{p = 1}^{i - 1} l_{ip} l_{jp} \right ) / l_{ii}</math> (для всех <math>j</math> от <math>i + 1</math> до <math>n</math>).
+
//----------------Часть 3--------------//
 
+
for(int i = MST.size() - 1; i > MST.size() - 1 - number_of_clusters; i--) {
После этого (если <math>i < n</math>) происходит переход к шагу 3 с бо́льшим <math>i</math>.
+
remove_edge(MST, i);        // Удаление самых длинных рёбер из MST - формирование кластеров
 +
}
 +
write_data(MST);        // Запись выходных данных
 +
</source>
  
Особо отметим, что вычисления сумм вида <math>a_{ji} - \sum_{p = 1}^{i - 1} l_{ip} l_{jp}</math> в обеих формулах производят в режиме накопления вычитанием из <math>a_{ji}</math> произведений <math>l_{ip} l_{jp}</math> для <math>p</math> от <math>1</math> до <math>i - 1</math>, c нарастанием <math>p</math>.
+
Как описано в пункте 1.3, весь описываемый алгоритм (начиная от момента получения данных и заканчивая моментом, когда данные отданы на вывод), может быть условно разделён на три части. В то время как первая часть может быть достаточно эффективно решена "в лоб", вторая и третьи части, как указано в пункте 1.1, могут иметь альтернативы. В данном фрагменте кода подробно описано использование [[Алгоритм Крускала | Алгоритма Крускала]] для построения MST, и алгоритма SEMST для разбиения на кластеры.
  
 
=== Последовательная сложность алгоритма ===
 
=== Последовательная сложность алгоритма ===
  
Для разложения матрицы порядка n методом Холецкого в последовательном (наиболее быстром) варианте требуется:
+
Как и в остальных пунктах, здесь сложность алгоритма состоит их суммы сложностей трёх его составных частей. Если оценивать сложность асимптотически в зависимости от входных данных, то:
 
* <math>n</math> вычислений квадратного корня,
 
* <math>\frac{n(n-1)}{2}</math> делений,
 
* <math>\frac{n^3-n}{6}</math> сложений (вычитаний),
 
* <math>\frac{n^3-n}{6}</math> умножений.
 
  
Умножения и сложения (вычитания) составляют ''основную часть алгоритма''.
+
<math> T(n) = O(n^2) + O(\frac{n (n-1)}{2})log(\frac{n (n-1)}{2})) + O(1) </math>
 
При этом использование режима накопления требует совершения умножений и вычитаний в режиме двойной точности (или использования функции вроде DPROD в Фортране), что ещё больше увеличивает долю умножений и сложений/вычитаний во времени, требуемом для выполнения метода Холецкого.
 
  
При классификации по последовательной сложности, таким образом, метод Холецкого относится к алгоритмам ''с кубической сложностью''.
+
На первый взгляд второе слагаемое может показаться слишком большим. Однако, верны и следующие равенства:
  
=== Информационный граф ===
+
<math> O(\frac{n (n-1)}{2} log(\frac{n (n-1)}{2}) = O(n^2 log(n^2)) = O(2 n^2 log(n)) = O(n^2 log(n)) </math>
  
Опишем [[глоссарий#Граф алгоритма|граф алгоритма]]<ref>Воеводин В.В.  Математические основы параллельных вычислений// М.: Изд. Моск. ун-та, 1991. 345 с.</ref><ref>Воеводин В.В., Воеводин Вл.В. Параллельные вычисления. – СПб.: БХВ - Петербург, 2002. – 608 с.</ref><ref>Фролов А.В.. Принципы построения и описание языка Сигма. Препринт ОВМ АН N 236. М.: ОВМ АН СССР, 1989.</ref> как аналитически, так и в виде рисунка.  
+
Отсюда видно, что для входных данных этого алгоритма асимптотические сложности алгоритмов Крускала, Прима и Борувки совпадают.
  
Граф алгоритма состоит из трёх групп вершин, расположенных в целочисленных узлах трёх областей разной размерности.
+
Таким образом, последовательная сложность алгоритма: <math> O(n^2 log(n)) </math>
  
'''Первая''' группа вершин расположена в одномерной области, соответствующая ей операция вычисляет функцию SQRT.
+
=== Информационный граф ===
Единственная координата каждой из вершин <math>i</math> меняется в диапазоне от <math>1</math> до <math>n</math>, принимая все целочисленные значения.
 
  
Аргумент этой функции
+
[[File:Graph_algo10.png|frame|center|alt=Алгоритм| Макро-описание параллельного алгоритма кластеризации]]
 
* при <math>i = 1</math> — элемент ''входных данных'', а именно  <math>a_{11}</math>;
 
* при <math>i > 1</math> — результат срабатывания операции, соответствующей вершине из третьей группы, с координатами <math>i - 1</math>, <math>i</math>, <math>i - 1</math>.
 
Результат срабатывания операции является ''выходным данным'' <math>l_{ii}</math>.
 
  
'''Вторая''' группа вершин расположена в двумерной области, соответствующая ей операция <math>a / b</math>.
 
Естественно введённые координаты области таковы:
 
* <math>i</math> — меняется в диапазоне от <math>1</math> до <math>n-1</math>, принимая все целочисленные значения;
 
* <math>j</math> — меняется в диапазоне от <math>i+1</math> до <math>n</math>, принимая все целочисленные значения.
 
  
Аргументы операции следующие:
 
*<math>a</math>:
 
** при <math>i = 1</math> — элементы ''входных данных'', а именно <math>a_{j1}</math>;
 
** при <math>i > 1</math> — результат срабатывания операции, соответствующей вершине из третьей группы, с координатами <math>i - 1, j, i - 1</math>;
 
* <math>b</math> — результат срабатывания операции, соответствующей вершине из первой группы, с координатой <math>i</math>.
 
 
Результат срабатывания операции является ''выходным данным'' <math>l_{ji}</math>.
 
 
'''Третья''' группа вершин расположена в трёхмерной области, соответствующая ей операция  <math>a - b * c</math>.
 
Естественно введённые координаты области таковы:
 
* <math>i</math> — меняется в диапазоне от <math>2</math> до <math>n</math>, принимая все целочисленные значения;
 
* <math>j</math> — меняется в диапазоне от <math>i</math> до <math>n</math>, принимая все целочисленные значения;
 
* <math>p</math> — меняется в диапазоне  от <math>1</math> до <math>i - 1</math>, принимая все целочисленные значения.
 
 
Аргументы операции следующие:
 
*<math>a</math>:
 
** при <math>p = 1</math> элемент входных данных <math>a_{ji}</math>;
 
** при <math>p > 1</math> — результат срабатывания операции, соответствующей вершине из третьей группы, с координатами <math>i, j, p - 1</math>;
 
*<math>b</math> — результат срабатывания операции, соответствующей вершине из второй группы, с координатами <math>p, i</math>;
 
*<math>c</math> — результат срабатывания операции, соответствующей вершине из второй группы, с координатами <math>p, j</math>;
 
 
Результат срабатывания операции является ''промежуточным данным'' алгоритма.
 
 
Описанный граф можно посмотреть на рис.1 и рис.2, выполненных для случая <math>n = 4</math>. Здесь вершины первой группы обозначены жёлтым цветом и буквосочетанием sq, вершины второй — зелёным цветом и знаком деления, третьей — красным цветом и буквой f. Вершины, соответствующие операциям, производящим выходные данные алгоритма, выполнены более крупно. Дублирующие друг друга дуги даны как одна. На рис.1 показан граф алгоритма согласно классическому определению , на рис.2 к графу алгоритма добавлены вершины , соответствующие входным (обозначены синим цветом) и выходным (обозначены розовым цветом) данным.
 
 
[[file:Cholesky full.png|thumb|center|1400px|Рисунок 1. Граф алгоритма без отображения входных и выходных данных. SQ - вычисление квадратного корня, F - операция a-bc, Div - деление.]]
 
[[file:Cholesky full_in_out.png|thumb|center|1400px|Рисунок 2. Граф алгоритма с отображением входных и выходных данных. SQ - вычисление квадратного корня, F - операция a-bc, Div - деление, In - входные данные, Out - результаты.]]
 
  
 
=== Ресурс параллелизма алгоритма ===
 
=== Ресурс параллелизма алгоритма ===
 +
Данная задача хорошо распараллеливается благодаря тому, что все "тяжелые" вычислительные задачи, начиная с момента считывания входных данных, можно распределить по процессам практически равномерно. Это достигается за счет следующей реализации:
 +
#Мастер-процесс считывает входные данные и отправляет их остальным процессам с помощью MPI_Broadcast.
 +
#Все пары точек разбиваются так, что каждому процессу достается одинаковое количество пар.
 +
#Каждый процесс-рабочий строит MST на своём наборе пар точек.
 +
#Процессы объединяют свои деревья по алгоритму, изображенному в информационном графе. То есть объединение осуществляется последовательно между группами размера <math>2^k</math>, где <math> k \in 1,2,..log_2p</math>, где p - число процессов.
  
Для разложения матрицы порядка <math>n</math> методом Холецкого в параллельном варианте требуется последовательно выполнить следующие ярусы:
+
Таким образом '''высота Ярусно-Параллельной формы''' (при наличии <math> p = 2^i </math> процессоров) будет <math> 1 + i + 1 + 1 </math>.
* <math>n</math> ярусов с вычислением квадратного корня (единичные вычисления в каждом из ярусов),
+
В то же время, как будет показано в пункте 1.10, оптимальное количетво процессоров может быть оценено по формуле  <math> p(N) \approx \sqrt {\frac{N-1}{2}} </math>. Тогда '''ширину Ярусно-Параллельной формы''' можно оценить как <math> O(\sqrt{N}) </math>. Учитывая приближенную формулу для числа процессоров, оценим высоту ЯП при помощи соотношения <math> 2^i \approx \sqrt {\frac{N-1}{2}} </math>. Тогда высоту ЯП можно оценить как <math> O(log(N)) </math>.
* <math>n - 1</math> ярус делений (в каждом из ярусов линейное количество делений, в зависимости от яруса — от <math>1</math> до <math>n - 1</math>),
 
* по <math>n - 1</math> ярусов умножений и сложений/вычитаний (в каждом из ярусов — квадратичное количество операций, от <math>1</math> до <math>\frac{n^2 - n}{2}</math>.
 
 
Таким образом, в параллельном варианте, в отличие от последовательного, вычисления квадратных корней и делений будут определять довольно значительную долю требуемого времени. При реализации на конкретных архитектурах наличие в отдельных ярусах [[глоссарий#Ярусно-параллельная форма графа алгоритма|ЯПФ]] отдельных вычислений квадратных корней может породить и другие проблемы. Например, при реализации на ПЛИСах остальные вычисления (деления и тем более умножения и сложения/вычитания) могут быть конвейеризованы, что даёт экономию и по ресурсам на программируемых платах; вычисления же квадратных корней из-за их изолированности приведут к занятию ресурсов на платах, которые будут простаивать большую часть времени. Таким образом, общая экономия в 2 раза, из-за которой метод Холецкого предпочитают в случае симметричных задач тому же методу Гаусса, в параллельном случае уже имеет место вовсе не по всем ресурсам, и главное - не по требуемому времени.
 
  
При этом использование режима накопления требует совершения умножений и вычитаний в режиме двойной точности, а в параллельном варианте это означает, что практически все промежуточные вычисления для выполнения метода Холецкого в режиме накопления должны быть двойной точности. В отличие от последовательного варианта это означает увеличение требуемой памяти почти в 2 раза.
+
Говоря о праллельной реализации вообще и, в частности, обращаясь к информационному графу, видно, что в основе параллельного алгоритма лежит массовый параллелизм, и единственным проблематичным местом может оказаться самый начальный этап считывания данных мастер-процессом с последующей рассылкой их всем процессам-рабочим. Проблема в том, что объем данных, пересылаемых каждому процессу, практически нельзя уменьшить из-за того, что в расчете MST на каждом наборе рёбер потенциально могут участвовать все вершины графа, и поэтому для их обработки требуется знание их пространственных координат.
  
При классификации по высоте ЯПФ, таким образом, метод Холецкого относится к алгоритмам со сложностью <math>O(n)</math>. При классификации по ширине ЯПФ его сложность будет <math>O(n^2)</math>.
+
Самой "тяжелой" операцией здесь является самый первый расчёт MST, поскольку он выполняется чуть больше чем за квадратичное время. Все последующие объединения деревьев занимают асимптотически одинаковое количество процессорного времени и не зависят друг от друга (вплоть до последнего объединения в MST).
  
 
=== Входные и выходные данные алгоритма ===
 
=== Входные и выходные данные алгоритма ===
  
'''Входные данные''': плотная матрица <math>A</math> (элементы <math>a_{ij}</math>).
+
==== Входные данные ====
Дополнительные ограничения:
+
В качестве входных данных программе дается множество векторов <math> [x_1,x_2,...,x_N]</math>, где <math> x_j = (a_{1j}, a_{2j}, ..., a_{nj})</math> размерности <math>n</math>. Для удобства изучения свойств алгоритма и отображения результатов можно брать массив векторов размерности 2. В этом случае наглядность будет обеспечена естественной возможностью отобразить вектора в плоскости.
* <math>A</math> – симметрическая матрица, т. е. <math>a_{ij}= a_{ji}, i, j = 1, \ldots, n</math>.
 
* <math>A</math> – положительно определённая матрица, т. е. для любых ненулевых векторов <math>\vec{x}</math> выполняется <math>\vec{x}^T A \vec{x} > 0</math>.
 
  
'''Объём входных данных''': <math>\frac{n (n + 1)}{2}</math> (в силу симметричности достаточно хранить только диагональ и над/поддиагональные элементы). В разных реализациях эта экономия хранения может быть выполнена разным образом. Например, в библиотеке, реализованной в НИВЦ МГУ, матрица A хранилась в одномерном массиве длины <math>\frac{n (n + 1)}{2}</math> по строкам своего нижнего треугольника.  
+
Кроме того может оказаться полезным особым образом подготовить данные перед анализом (удаление "шумов", выбросов и масштабирование данных по тем или иным координатам).
 +
==== Выходные данные ====
 +
Выходными данными программы будет множество <math> M = ((x_1,x_2,..,x_k),(x_{k+1},...),...) </math>, состоящее из сгруппированных по кластерам векторов из входных данных.
  
'''Выходные данные''': нижняя треугольная матрица <math>L</math> (элементы <math>l_{ij}</math>).
+
В конкретной программе это может быть реализовано путём записи <math> N </math> строк вида <math>(x_{i}, Cluster-Number)</math> в выходной файл.
 
 
'''Объём выходных данных''': <math>\frac{n (n + 1)}{2}</math>  (в силу треугольности достаточно хранить только ненулевые элементы). В разных реализациях эта экономия хранения может быть выполнена разным образом. Например, в той же  библиотеке, созданной в НИВЦ МГУ, матрица <math>L</math> хранилась в одномерном массиве длины <math>\frac{n (n + 1)}{2}</math> по строкам своей нижней части.
 
  
 
=== Свойства алгоритма ===
 
=== Свойства алгоритма ===
  
Соотношение последовательной и параллельной сложности в случае неограниченных ресурсов, как хорошо видно, является ''квадратичным'' (отношение кубической к линейной).
 
  
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных – всего лишь ''линейна''.
+
Одним из трудных мест параллельной реализации является правильный выбор количества процессов. Для удобства анализа рассматривается число процессоров <math> p = 2^i </math>, где <math> i \in \mathbb {N} </math>. Ясно, что такое допущение не повлияет на качество анализа, так как другое число процессоров лишь приведет к незначительным накладным расходам, сложность которых асимптотически стремится к нулю. В таком случае можно также ввести допущение о размере входных данных: <math> N = 2^j </math>, где <math> j \in \mathbb {N}, j > i </math>. В этом случае MST-часть дерева графа алгоритма является идеально сбалансированным бинарным деревом. Тогда сложность можно оценить следующим образом:
 +
#На первом этапе каждый процесс производит расчет на <math> 2^{j - i} * (2^j - 1)</math> рёбрах, получая локальное MST(или MSF) на не более чем <math> N </math> вершинах.
 +
#Далее следует <math> i = log_2(2^i) </math> шагов объединения не более чем по N рёбер в каждом.
  
При этом алгоритм почти полностью детерминирован, это гарантируется теоремой о единственности разложения. Использование другого порядка выполнения ассоциативных операций может привести к накоплению ошибок округления, однако это влияние в используемых вариантах алгоритма не так велико, как, скажем, отказ от использования режима накопления.
+
Таким образом, задача эффективного запуска алгоритма состоит в том, чтобы найти оптимальный баланс между количеством расчетов на каждом узле на первом шаге и общим количеством объединений поддеревьев. Пусть дано <math> p = 2^i </math> вычислительных узлов и <math> N = 2^j </math>. В таком случае каждый узел получает считает <math> 2^{j - i} * (2^j - 1)</math> рёбер на первом шаге. Высота части ЯП-формы, отвечающей за MST, равна <math> i </math>. Тогда всего будет <math> i </math> уровней объединений MST с числом рёбер не более чем <math> 2N </math> для каждого вычислительного узла.  
  
Дуги информационного графа, исходящие из вершин, соответствующих операциям квадратного корня и деления, образуют пучки т. н. рассылок линейной мощности (то есть степень исхода этих вершин и мощность работы с этими данными — линейная функция от порядка матрицы и координат этих вершин). При этом естественно наличие в этих пучках «длинных» дуг. Остальные дуги локальны.
+
==== Оценка скорости работы параллельного алгоритма ====
  
Наиболее известной является компактная укладка графа — его проекция на треугольник матрицы, который перевычисляется укладываемыми операциями. При этом «длинные» дуги можно убрать, заменив более дальнюю пересылку комбинацией нескольких ближних (к соседям).
+
Грубая оценка времени вычисления может помочь изучить качественные характеристики алгоритма. Учитывая рассуждения выше, приближенная качественная формула может быть записана таким образом:
  
[[Глоссарий#Эквивалентное возмущение|Эквивалентное возмущение]] <math>M</math> у метода Холецкого всего вдвое больше, чем возмущение <math>\delta A</math>, вносимое в матрицу при вводе чисел в компьютер:
+
*<math> T'(N,p) = \frac{N(N - 1)}{2 p} * logN  + 2N*log(N) * log_2p</math>.  
<math>
 
||M||_{E} \leq 2||\delta A||_{E}
 
</math>
 
 
 
Это явление обусловлено положительной определённостью матрицы. Среди всех используемых разложений матриц это наименьшее из эквивалентных возмущений.
 
 
 
== Программная реализация алгоритма ==
 
  
=== Особенности реализации последовательного алгоритма ===
+
Объяснение формулы: первое слагаемое, исходя из оценки для сложности алгоритма Прима: <math> O(ElogV) </math>, - время(число операций) расчета на каждом процессоре на первом шаге. После первого шага (поскольку построено минимальное остовное дерево) каждый процесс передает не более чем <math> N </math> рёбер дальше. Второе слагаемое: каждый процесс после первого шага получает не более чем <math> 2N </math> рёбер (от двух процессоров при объединении), и всего таких шагов объединения не более чем <math> log_2p </math>, при этом предполагается, что сложностью объединения <math> 2N </math> рёбер на каждом шаге можно пренебречь (или допустить его пренебрежительно малым), поскольку предполагается, что все процессы после первого шага получают свою часть уже отсортированных рёбер(то есть наибольшее ребро процессора <math> i </math> меньше наименьшего ребра процессора <math> i+1 </math>), и объединение будет просто конкатенацией блоков памяти.
  
В простейшем (без перестановок суммирования) варианте метод Холецкого на Фортране можно записать так:
+
С помощью этой формулы можно построить графики зависимости времени вычислений от количества процессоров и входных данных.Поскольку рост вычислительного времени при увеличении объёма входных данных достаточно очевиден, этот объём будет зафиксирован, и будет исследоваться поведение приближенного априорного вычислительного времени алгоритма в зависимости от числа процессоров. Проведём два вычислительных эксперимента:
<source lang="fortran">
 
DO  I = 1, N
 
S = A(I,I)
 
DO  IP=1, I-1
 
S = S - DPROD(A(I,IP), A(I,IP))
 
END DO
 
A(I,I) = SQRT (S)
 
DO  J = I+1, N
 
S = A(J,I)
 
DO  IP=1, I-1
 
S = S - DPROD(A(I,IP), A(J,IP))
 
END DO
 
A(J,I) = S/A(I,I)
 
END DO
 
END DO
 
</source>
 
При этом для реализации режима накопления переменная <math>S</math> должна быть двойной точности.  
 
  
Отдельно следует обратить внимание на используемую в такой реализации функцию DPROD. Её появление как раз связано с тем, как математики могли использовать режим накопления вычислений. Дело в том, что, как правило, при выполнении умножения двух чисел с плавающей запятой сначала результат получается с удвоенными длинами мантиссы и порядка, и только при выполнении присваивания переменной одинарной точности результат округляется. Эта возможность даёт выполнять умножение действительных чисел с двойной точностью без предварительного приведения их к типу двойной точности. На ранних этапах развития вычислительных библиотек снятие результата без округление реализовали вставками специального кода в фортран-программы, но уже в 70-х гг. XX века в ряде трансляторов Фортрана появилась функция DPROD, реализующая это без обращения программиста к машинным кодам. Она была закреплена среди стандартных функций в стандарте Фортран 77, и реализована во всех современных трансляторах Фортрана.  
+
1. <math> N = 10^4</math>
 +
[[File:График1_.JPG|frame|center|alt=Алгоритм| По оси абсцисс: количество вычислительных узлов. По оси ординат: время вычислений. При фиксированном <math> N </math>]]
  
Для метода Холецкого существует блочная версия, которая отличается от точечной не тем, что операции над числами заменены на аналоги этих операций над блоками; её построение основано на том, что практически все циклы точечной версии имеют тип SchedDo в терминах методологии, основанной на исследовании информационного графа и, следовательно, могут быть развёрнуты. Тем не менее, обычно блочную версию метода Холецкого записывают не в виде программы с развёрнутыми и переставленными циклами, а в виде программы, подобной реализации точечного метода, в которой вместо соответствующих скалярных операций присутствуют операции над блоками.
+
2. <math> N = 1.5*10^4 </math>
 +
[[File:График2_.JPG|frame|center|alt=Алгоритм| По оси абсцисс: количество вычислительных узлов. По оси ординат: время вычислений. При фиксированном <math> N </math>]]
  
Особенностью размещения массивов в Фортране является хранение их "по столбцам" (быстрее всего меняется первый индекс). Поэтому для обеспечения локальности работы с памятью представляется более эффективной такая схема метода Холецкого (полностью эквивалентная описанной), когда исходная матрица и её разложение хранятся не в нижнем, а в верхнем треугольнике. Тогда при вычислениях скалярных произведений программа будет "идти" по последовательным ячейкам памяти компьютера.
+
Из этих графиков становится видно качественное поведение времени вычислений при увеличении числа процессоров. Можно заметить, что для каждого набора входных данных есть число процессоров, минимизирующее время вычислений. Но в то же время видно, что при слишком большом количестве процессоров алгоритм начинает работать хуже, чем при меньшем. Это вполне понятно: Слишком большое число процессоров порождает большое число обменов между ними, объединений деревьев, сортировок, и т.д. Таким образом, именно эта часть <math>  2N*log(N) * log_2p </math> оценки времени вычислений отвечает за ухудшение эффективности алгоритма при числе процессоров, которое больше оптимального. Кроме того, видно, что при увеличении объёма входных данных оптимальное количество процессоров также увеличивается: количество обменов и слияний деревьев компенсируется уменьшением вычислительной нагрузки на каждый отдельный процессор.
  
Есть и другой вариант точечной схемы: использовать вычисляемые элементы матрицы <math>L</math> в качестве аргументов непосредственно «сразу после» их вычисления. Такая программа будет выглядеть так:
+
Благодаря такой оценке становится возможным оценить примерное оптимальное число процессоров <math> p </math> в зависимости от объёма входных данных <math> N </math>. Продифференцировав приближенную формулу <math> T'(N,p) = \frac{N(N - 1)}{2 p} * logN + 2N*log(N) * log_2p </math> по <math> p </math> и приравняв производную нулю, получим следующее выражение для оптимального числа процессоров:
<source lang="fortran">
 
DO  I = 1, N
 
A(I,I) = SQRT (A(I, I))
 
DO J = I+1, N
 
A(J,I) = A(J,I)/A(I,I)
 
END DO
 
DO  K=I+1,N
 
DO J = K, N
 
A(J,K) = A(J,K) - A(J,I)*A(K,I)
 
END DO
 
END DO
 
END DO
 
</source>
 
Как видно, в этом варианте для реализации режима накопления одинарной точности мы должны будем объявить двойную точность для массива, хранящего исходные данные и результат. Подчеркнём, что [[глоссарий#Граф алгоритма|граф алгоритма]] обеих схем - один и тот же (из п.1.7), если не считать изменением замену умножения на функцию DPROD!
 
  
=== Локальность данных и вычислений ===
+
<math> p(N) \approx \frac{N-1}{4} </math>
  
==== Локальность реализации алгоритма ====
+
==== Вычислительная мощность параллельного алгоритма ====
  
===== Структура обращений в память и качественная оценка локальности =====
+
Согласно определению, [[глоссарий#Вычислительная мощность|''вычислительная мощность'']] алгоритма равна отношению числа операций к суммарному объему входных и выходных данных. В то же время, учитывая вышесказанное, видно, что число операций в данном алгоритме зависит не только от объема входных данных, но также от числа процессоров. Таким образом, имеет смысл оценить вычислительную мощность как в случае оптимального числа процессоров, так и в общем случае числа процессоров, равного <math> p </math>.
  
[[file:Cholesky_locality1.jpg|thumb|center|700px|Рисунок 3. Реализация метода Холецкого. Общий профиль обращений в память]]
+
В общем случае, исходя из предыдущего пункта, число операций будет равно
  
На рис.3 представлен профиль обращений в память<ref>Воеводин Вад. В. Визуализация и анализ профиля обращений в память // Вестник Южно-Уральского государственного университета. Серия Математическое моделирование и про-граммирование. — 2011. — Т. 17, № 234. — С. 76–84.</ref><ref>Воеводин Вл. В., Воеводин Вад. В. Спасительная локальность суперкомпьютеров // Открытые системы. — 2013. — № 9. — С. 12–15.</ref> для реализации метода Холецкого. В программе задействован только 1 массив, поэтому в данном случае обращения в профиле происходят только к элементам этого массива. Программа состоит из одного основного этапа, который, в свою очередь, состоит из последовательности подобных итераций. Пример одной итерации выделен зеленым цветом.
+
<math> \frac{N(N - 1)}{2} * logN  + \sum_{i = 0}^{log_2p}(2N*log(N) * \frac{p}{2^i}) = N logN (\frac{N-1}{2} + 2 * p * \sum_{i = 0}^{log_2p}( \frac{1}{2^i})) = </math> {переходя к пределу в сумме} <math> \approx  N logN (\frac{N-1}{2} + 2 * p) </math>
  
Видно, что на каждой <math>i</math>-й итерации используются все адреса, начиная с некоторого, при этом адрес первого обрабатываемого элемента увеличивается. Также можно заметить, что число обращений в память на каждой итерации растет примерно до середины работы программы, после чего уменьшается вплоть до завершения работы. Это позволяет говорить о том, что данные в программе используются неравномерно, при этом многие итерации, особенно в начале выполнения программы, задействуют большой объем данных, что приводит к ухудшению локальности.
+
Таким образом, вычислительная мощность в общем случае: <math> \approx \frac{N logN (\frac{N-1}{2} + 2 * p)}{2N} = \frac{logN *(\frac{N-1}{2} + 2 * p)}{2} </math>
  
Однако в данном случае основным фактором, влияющим на локальность работы с памятью, является строение итерации. Рассмотрим фрагмент профиля, соответствующий нескольким первым итерациям.
+
В случае оптимального числа вычислительных узлов:  <math> \approx \frac{N logN (\frac{N-1}{2} + \frac{N-1}{2})}{2N} =  \frac{logN *  (N-1)}{2}</math>
  
[[file:Cholesky_locality2.jpg|thumb|center|700px|Рисунок 4. Реализация метода Холецкого. Фрагмент профиля (несколько первых итераций)]]
+
== Программная реализация алгоритма ==
  
Исходя из рис.4 видно, что каждая итерация состоит из двух различных фрагментов. Фрагмент 1 – последовательный перебор (с некоторым шагом) всех адресов, начиная с некоторого начального. При этом к каждому адресу происходит мало обращений. Такой фрагмент обладает достаточно неплохой пространственной локальностью, так как шаг по памяти между соседними обращениями невелик, но плохой временно́й локальностью, поскольку данные редко используются повторно.
 
  
Фрагмент 2 устроен гораздо лучше с точки зрения локальности. В рамках этого фрагмента выполняется большое число обращений подряд к одним и тем же данным, что обеспечивает гораздо более высокую степень как пространственной, так и временно́й локальности по сравнению с фрагментом 1.
+
=== Особенности реализации последовательного алгоритма ===
  
После рассмотрения фрагмента профиля на рис.4 можно оценить общую локальность двух фрагментов на каждой итерации. Однако стоит рассмотреть более подробно, как устроен каждый из фрагментов.
+
=== Локальность данных и вычислений ===
 
 
[[file:Cholesky_locality3.jpg|thumb|center|700px|Рисунок 5. Реализация метода Холецкого. Фрагмент профиля (часть одной итерации)]]
 
 
 
Рис.5, на котором представлена часть одной итерации общего профиля (см. рис.3), позволяет отметить достаточно интересный факт: строение каждого из фрагментов на самом деле заметно сложнее, чем это выглядит на рис.4. В частности, каждый шаг фрагмента 1 состоит из нескольких обращений к соседним адресам, причем выполняется не последовательный перебор. Также можно увидеть, что фрагмент 2 на самом деле в свою очередь состоит из повторяющихся итераций, при этом видно, что каждый шаг фрагмента 1 соответствует одной итерации фрагмента 2 (выделено зеленым на рис.5). Это лишний раз говорит о том, что для точного понимания локальной структуры профиля необходимо его рассмотреть на уровне отдельных обращений.
 
 
 
Стоит отметить, что выводы на основе рис.5 просто дополняют общее представлении о строении профиля обращений; сделанные на основе рис.4 выводы относительно общей локальности двух фрагментов остаются верны.
 
 
 
===== Количественная оценка локальности =====
 
 
 
Основной фрагмент реализации, на основе которого были получены количественные оценки, приведен [http://git.algowiki-project.org/Voevodin/locality/blob/master/benchmarks/holecky/holecky.h здесь] (функция Kernel). Условия запуска описаны [http://git.algowiki-project.org/Voevodin/locality/blob/master/README.md здесь].
 
 
 
Первая оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.
 
 
 
[[file:Cholesky_locality4.jpg|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]
 
 
 
На рис.6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что реализация метода Холецкого характеризуется достаточно высокой скоростью взаимодействия с памятью, однако ниже, чем, например, у теста Линпак или реализации метода Якоби.
 
 
 
Вторая характеристика – cvg – предназначена для получения более машинно-независимой оценки локальности. Она определяет, насколько часто в программе необходимо подтягивать данные в кэш-память. Соответственно, чем меньше значение cvg, тем реже это нужно делать, тем лучше локальность.
 
 
 
[[file:Cholesky_locality5.jpg|thumb|center|700px|Рисунок 7. Сравнение значений оценки cvg]]
 
 
 
На рис.7 приведены значения cvg для того же набора реализаций, отсортированные по убыванию (чем меньше cvg, тем в общем случае выше локальность). Можно увидеть, что, согласно данной оценке, реализация метода Холецкого оказалась ниже в списке по сравнению с оценкой daps.
 
  
 
=== Возможные способы и особенности параллельной реализации алгоритма ===
 
=== Возможные способы и особенности параллельной реализации алгоритма ===
 
Как нетрудно видеть по структуре графа алгоритма, вариантов распараллеливания алгоритма довольно много. Например, во втором варианте (см. раздел «[[#Особенности реализации последовательного алгоритма|Особенности реализации последовательного алгоритма]]») все внутренние циклы параллельны, в первом — параллелен цикл по <math>J</math>. Тем не менее, простое распараллеливание таким способом «в лоб» вызовет такое количество пересылок между процессорами с каждым шагом по внешнему циклу, которое почти сопоставимо с количеством арифметических операций. Поэтому перед размещением операций и данных между процессорами вычислительной системы предпочтительно разбиение всего пространства вычислений на блоки, с сопутствующим разбиением обрабатываемого массива.
 
 
Многое зависит от конкретного типа вычислительной системы. Присутствие конвейеров на узлах многопроцессорной системы делает рентабельным параллельное вычисление нескольких скалярных произведений сразу. Подобная возможность есть и на программировании ПЛИСов, но там быстродействие будет ограничено медленным последовательным выполнением операции извлечения квадратного корня.
 
 
В принципе, возможно и использование т. н. «скошенного» параллелизма. Однако его на практике никто не использует, из-за усложнения управляющей структуры программы.
 
  
 
=== Масштабируемость алгоритма и его реализации ===
 
=== Масштабируемость алгоритма и его реализации ===
  
==== Масштабируемость алгоритма ====
+
В настоящей статье приводится исследование самостоятельной реализации алгоритма: [http://pastebin.com/gH09k6id main.cpp]. Запуск производился на системе IBM Blue Gene/P.
 +
Для генерации данных использовался скрипт на питоне [http://pastebin.com/5YNxqqpA testGenerator]. Для отображения кластеров использовалась библиотека matplotlib и скрипт [http://pastebin.com/mTDZ6bqp graphDraw].
  
==== Масштабируемость реализации алгоритма ====
+
IBM Blue Gene/P — массивно-параллельная вычислительная система, которая состоит из двух стоек, включающих 2048 четырехъядерных вычислительных узлов, с пиковой производительностью 27,9 терафлопс.
Проведём исследование масштабируемости параллельной реализации разложения Холецкого согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].
 
  
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма:  
+
Характеристики системы:
 +
* две стойки с вычислительными узлами и узлами ввода-вывода
 +
* 1024 четырехъядерных вычислительных узла в каждой из стоек
 +
* 16 узлов ввода-вывода в стойке (в текущей конфигурации активны 8, т.е. одна I/O-карта на 128 вычислительных узлов)
 +
* программирование с использованием MPI, OpenMP/pthreads, POSIX I/O
 +
* высокая энергоэффективность: ∼ 372 MFlops/W
  
* число процессоров [4 : 256] с шагом 4;
+
Вычислительный узел включает в себя четырехъядерный процессор, 2 ГБ общей памяти и сетевые интерфейсы.
* размер матрицы [1024 : 5120].
 
  
В результате проведённых экспериментов был получен следующий диапазон [[Глоссарий#Эффективность реализации|эффективности реализации]] алгоритма:
+
Вычислительной узел:
 +
* четыре микропроцессорных ядра PowerPC 450 (4-way SMP)
 +
* пиковая производительность: 4 ядра x 3,4 GFlop/sec на ядро = 13,6 GFlop/sec
 +
* пропускная способность памяти: 13,6 GB/sec
 +
* 2 ГБ общей памяти
 +
* 2 x 4 МБ кэш-памяти 2-го уровня (в документации по BG/P носит название L3)
 +
* легковесное ядро (compute node kernel, CNK), представляющее собой Linux-подобную операционную систему, поддерживающую значительное подмножество Linux-совместимых системных вызовов
 +
* асинхронные операции межпроцессорных обменов (выполняются параллельно с вычислениями)
 +
* операции ввода-вывода перенаправляются I/O-картам через сеть коллективных операций
  
* минимальная эффективность реализации 0,11%;
 
* максимальная эффективность реализации 2,65%.
 
  
На следующих рисунках приведены графики [[Глоссарий#Производительность|производительности]] и эффективности выбранной реализации разложения Холецкого в зависимости от изменяемых параметров запуска.
+
Поскольку оптимальное число процессоров достаточно велико и линейно зависит от объема(размера) входных данных, задача хорошо масштабируется, и на графике ниже можно увидеть почти линейный рост производительности при фиксированном количестве входных данных и увеличении числа процессоров в том случае, когда число процессоров близко к оптимальному. В тоже время из графика видно, что когда входных данных слишком мало, а число процессоров возрастает, то происходит либо небольшой рост производительности, либо роста производительности нет.
  
[[file:Масштабируемость Параллельной реализации метода Холецкого Производительность3.png|thumb|center|700px|Рисунок 8. Параллельная реализация метода Холецкого. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]
+
[[File:Performance3.JPG|frame|center|alt=Алгоритм| Производительность алгоритма]]
[[file:Холецкий масштабируемость эффективность2.png|thumb|center|700px|Рисунок 9. Параллельная реализация метода Холецкого. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]
 
  
Построим оценки масштабируемости выбранной реализации разложения Холецкого:
+
Говоря о вычислительном времени и проводя тесты на реальных данных, можно заметить концептуальную правильность сделанных качественных выводов о поведении времени вычислений для различного набора входных данных и числа процессоров. На графиках ниже видно, что время вычисления задачи уменьшается до определенного момента, когда количество процессоров, нужное для задачи, становится слишком большим, после чего начинает увеличиваться, когда число процессоров растет.
* По числу процессов: -0,000593. При увеличении числа процессов эффективность на рассмотренной области изменений параметров запуска уменьшается, однако в целом уменьшение не очень быстрое. Малая интенсивность изменения объясняется крайне низкой общей эффективностью работы приложения с максимумом в 2,65%, и значение эффективности на рассмотренной области значений быстро доходит до десятых долей процента. Это свидетельствует о том, что на большей части области значений нет интенсивного снижения эффективности. Это объясняется также тем, что с ростом [[Глоссарий#Вычислительная сложность|вычислительной сложности]] падение эффективности становится не таким быстрым. Уменьшение эффективности на рассмотренной области работы параллельной программы объясняется быстрым ростом накладных расходов на организацию параллельного выполнения. С ростом вычислительной сложности задачи эффективность снижается так же быстро, но при больших значениях числа процессов. Это подтверждает предположение о том, что накладные расходы начинают сильно превалировать над вычислениями.
 
* По размеру задачи: 0,06017. При увеличении размера задачи эффективность возрастает. Эффективность возрастает тем быстрее, чем большее число процессов используется для выполнения. Это подтверждает предположение о том, что размер задачи сильно влияет на эффективность выполнения приложения. Оценка показывает, что с ростом размера задачи эффективность на рассмотренной области значений параметров запуска сильно увеличивается. Также, учитывая разницу максимальной и минимальной эффективности в 2,5%, можно сделать вывод, что рост эффективности при увеличении размера задачи наблюдается на большей части рассмотренной области значений.
 
* По двум направлениям: 0,000403. При рассмотрении увеличения как вычислительной сложности, так и числа процессов на всей рассмотренной области значений эффективность увеличивается, однако скорость увеличения эффективности небольшая. В совокупности с тем фактом, что разница между максимальной и минимальной эффективностью на рассмотренной области значений параметров небольшая, эффективность с увеличением масштабов возрастает, но медленно и с небольшими перепадами.
 
  
[http://git.algowiki-project.org/Teplov/Scalability/tree/master/cholesky-decomposition-master Исследованная параллельная реализация на языке C]
+
[[File:Timeline1.JPG|frame|center|alt=Алгоритм| Вычислительное время алгоритма для <math>N = 17000</math>]]
 +
[[File:Снимок2.JPG|frame|center|alt=Алгоритм| Вычислительное время алгоритма для <math>N = 3000</math>]]
  
 
=== Динамические характеристики и эффективность реализации алгоритма ===
 
=== Динамические характеристики и эффективность реализации алгоритма ===
 
Для проведения экспериментов использовалась реализация разложения Холецкого, представленная в пакете SCALAPACK библиотеки Intel MKL (метод pdpotrf).  Все результаты получены на суперкомпьютере «Ломоносов»<ref name="Lom" />. Использовались процессоры Intel Xeon X5570 с пиковой производительностью в 94 Гфлопс, а также компилятор Intel с опцией –O2.
 
На рисунках показана эффективность реализации разложения Холецкого (случай использования нижних треугольников матриц) для разного числа процессов и размерности матрицы 80000, запуск проводился на 256 процессах.
 
 
[[file:Cholesky CPU.png|thumb|center|700px|Рисунок 10. График загрузки CPU при выполнении разложения Холецкого]]
 
 
На графике загрузки процессора видно, что почти все время работы программы уровень загрузки составляет около 50%. Это хорошая картина для программ, запущенных без использования технологии Hyper Threading.
 
 
[[file:Cholesky FLOPS.png|thumb|center|700px|Рисунок 11. График операций с плавающей точкой в секунду при выполнении разложения Холецкого]]
 
 
На Рисунке 11 показан график количества операций с плавающей точкой в секунду. Видно, что к концу каждой итерации число операций возрастает.
 
[[file:Cholesky L1.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе разложения Холецкого]]
 
 
На графике кэш-промахов первого уровня видно, что число промахов достаточно большое и находится на уровне 25 млн/сек в среднем по всем узлам.
 
[[file:Cholesky L3.png|thumb|center|700px|Рисунок 13. График кэш-промахов L3 в секунду при работе разложения Холецкого]]
 
 
На графике кэш-промахов третьего уровня видно, что число промахов все еще достаточно большое и находится на уровне 1,5 млн/сек в среднем по всем узлам. Это указывает на то, что задача достаточно большая, и данные плохо укладываются в кэш-память.
 
[[file:Cholesky MemRead.png|thumb|center|700px|Рисунок 14. График количества чтений из оперативной памяти при работе разложения Холецкого]]
 
 
На графике чтений из памяти на протяжении всего времени работы программы наблюдается достаточно интенсивная и не сильно изменяющаяся работа с памятью.
 
[[file:Cholesky MemWrite.png|thumb|center|700px|Рисунок 15. График количества записей в оперативную память при работе разложения Холецкого]]
 
 
На графике записей в память видна периодичность: на каждой итерации к концу выполнения число записей в память достаточно сильно падает. Это коррелирует с возрастанием числа операций с плавающей точкой и может объясняться тем, что при меньшем числе записей в память программа уменьшает накладные расходы и увеличивает эффективность.
 
[[file:Cholesky Inf Bps.png|thumb|center|700px|Рисунок 16. График скорости передачи по сети Infiniband в байт/сек при работе разложения Холецкого]]
 
 
На графике скорости передачи данных по сети Infiniband наблюдается достаточно интенсивное использование коммуникационной сети на каждой итерации. Причем к концу каждой итерации интенсивность передачи данных сильно возрастает. Это указывает на большую необходимость в обмене данными между процессами к концу итерации.
 
[[file:Cholesky Inf Pps.png|thumb|center|700px|Рисунок 17. График скорости передачи по сети Infiniband в пакетах/сек при работе разложения Холецкого]]
 
 
На графике скорости передачи данных в пакетах в секунду наблюдается большая «кучность» показаний максимального минимального и среднего значений в сравнении с графиком скорости передачи в байт/сек. Это говорит о том, что, вероятно, процессы обмениваются сообщениями различной длины, что указывает на неравномерное распределение данных. Также наблюдается рост интенсивности использования сети к концу каждой итерации.
 
[[file:Cholesky LoadAVG.png|thumb|center|700px|Рисунок 18. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе разложения Холецкого]]
 
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно и приблизительно равняется 8. Это свидетельствует о стабильной работе программы с восьмью процессами на каждом узле. Это указывает на рациональную и статичную загрузку аппаратных ресурсов процессами.
 
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала достаточно эффективно и стабильно. Использование памяти и коммуникационной среды достаточно интенсивное, что может стать фактором снижения эффективности при существенном росте размера задачи или же числа процессоров.
 
Для существующих параллельных реализаций характерно отнесение всего ресурса параллелизма на блочный уровень. Относительно низкая эффективность работы связана с проблемами внутри одного узла, следующим фактором является неоптимальное соотношение между «арифметикой» и обменами. Видно, что при некотором (довольно большом) оптимальном размере блока обмены влияют не так уж сильно.
 
  
 
=== Выводы для классов архитектур ===
 
=== Выводы для классов архитектур ===
 
Как видно по показателям SCALAPACK на суперкомпьютерах, обмены при большом n хоть и уменьшают эффективность расчётов, но слабее, чем неоптимальность организации расчётов внутри одного узла. Поэтому, видимо, следует сначала оптимизировать не блочный алгоритм, а подпрограммы, используемые на отдельных процессорах: точечный метод Холецкого, перемножения матриц и др. подпрограммы. [[#Существующие реализации алгоритма|Ниже]] содержится информация о возможном направлении такой оптимизации.
 
 
Вообще эффективность метода Холецкого для параллельных архитектур не может быть высокой. Это связано со следующим свойством информационной структуры алгоритма: если операции деления или вычисления выражений <math>a - bc</math> являются не только массовыми, но и параллельными, и потому их вычисления сравнительно легко выстраивать в конвейеры или распределять по устройствам, то операции извлечения квадратных корней являются узким местом алгоритма. Поэтому для эффективной реализации  алгоритмов, столь же хороших по вычислительным характеристикам, как и метод квадратного корня, следует использовать не метод Холецкого, а его давно известную модификацию без извлечения квадратных корней — [[%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%BE%D0%BB%D0%B5%D1%86%D0%BA%D0%BE%D0%B3%D0%BE_(%D0%BD%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D0%BE%D0%B3%D0%BE_%D1%82%D1%80%D0%B5%D1%83%D0%B3%D0%BE%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE_%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F)#.5C.28LDL.5ET.5C.29_.D1.80.D0.B0.D0.B7.D0.BB.D0.BE.D0.B6.D0.B5.D0.BD.D0.B8.D0.B5|разложение матрицы в произведение <math>L D L^T</math>]]<ref>Krishnamoorthy A., Menon D. Matrix Inversion Using Cholesky Decomposition. 2013. eprint arXiv:1111.4144</ref>.
 
  
 
=== Существующие реализации алгоритма ===
 
=== Существующие реализации алгоритма ===
 +
Как таковые, алгоритмы самого процесса кластеризации в популярных библиотеках (таких, как Scipy, Numpy или Boost) не реализованы (в отличие от других алгоритмов кластеризации). Однако, можно найти пользовательские любительские реализации (некоторые из которых приведены в списке ниже). Кроме того, существует масса реализаций основного коспонента данного алгоритма - вычисления MST - на многих языках программирования в разных библиотеках. Ссылки на некоторые из этих реализаций(перечислить все было бы невозможно) также приведены в списке ниже.
  
Точечный метод Холецкого реализован как в основных библиотеках программ отечественных организаций, так и в западных пакетах LINPACK, LAPACK, SCALAPACK и др.  
+
*[https://github.com/jakevdp/mst_clustering  Minimum Spanning Tree Clustering in Python  Author: Jake VanderPlas]
+
*[https://github.com/amueller/information-theoretic-mst Information Theoretic Clustering using Minimum Spanning Trees]
При этом в отечественных реализациях, как правило, выполнены стандартные требования к методу с точки зрения ошибок округления, то есть, реализован режим накопления, и обычно нет лишних операций. Ряд старых отечественных реализаций использует для экономии памяти упаковку матриц <math>A</math> и <math>L</math> в одномерный массив.  
+
*[http://www.boost.org/doc/libs/1_51_0/libs/graph/doc/prim_minimum_spanning_tree.html C++ Boost library prim_minimum_spanning_tree]
+
*[http://www.boost.org/doc/libs/1_35_0/libs/graph/doc/kruskal_min_spanning_tree.html C++ Boost library kruskal_minimum_spanning_tree]
Реализация точечного метода Холецкого в современных западных пакетах обычно происходит из одной и той же реализации метода в LINPACK, а та использует пакет BLAS. В BLAS скалярное произведение реализовано без режима накопления, но это легко исправляется при желании.  
+
*[http://jgrapht.org/javadoc/org/jgrapht/alg/PrimMinimumSpanningTree.html Java library JGraphT PrimMinimumSpanningTree]
 
+
*[http://jgrapht.org/javadoc/org/jgrapht/alg/KruskalMinimumSpanningTree.html Java library JGraphT  KruskalMinimumSpanningTree]
Интересно, что в крупнейших библиотеках алгоритмов до сих пор предлагается именно разложение Холецкого, а более быстрый алгоритм LU-разложения без извлечения квадратных корней используется только в особых случаях (например, для трёхдиагональных матриц), в которых количество диагональных элементов уже сравнимо с количеством внедиагональных.
+
*[http://docs.scipy.org/doc/scipy-0.15.1/reference/generated/scipy.sparse.csgraph.minimum_spanning_tree.html SciPy library MST]
  
 
== Литература ==
 
== Литература ==
 
<references \>
 
 
[[en:Cholesky decomposition]]
 
 
[[Категория:Законченные статьи]]
 
[[Категория:Разложения матриц]]
 

Текущая версия на 14:43, 23 ноября 2016

Symbol wait.svgЭта работа прошла предварительную проверку
Дата последней правки страницы:
23.11.2016
Данная работа соответствует формальным критериям.
Проверено Zhum.



Алгоритм кластеризации, основанный на минимальном остовном дереве
Последовательный алгоритм
Последовательная сложность [math] O(N^2 log(N)) [/math]
Объём входных данных [math] N [/math]
Объём выходных данных [math] N [/math]
Параллельный алгоритм
Высота ярусно-параллельной формы [math] O(log(N)) [/math]
Ширина ярусно-параллельной формы [math] O(N) [/math]


Основные авторы описания: Стандрик Антон Сергеевич

Содержание

1 Свойства и структура алгоритма

1.1 Общее описание алгоритма

Данный алгоритм предназначен для решения задач кластеризации. Говоря в общем, кластеризация — многомерная статистическая процедура, выполняющая сбор данных, содержащих информацию о выборке объектов, и затем упорядочивающая объекты в сравнительно однородные группы [1].

Впервые идея использования минимального остовного дерева для кластеризации данных была преждожена C. T. Zah в 1971 году[2]. Этот метод широко используется исследователями при анализе изображений [3][4], а также при анализе биологических данных [5].

В общем описании работы алгоритма можно выделить следующие шаги:

  1. Разбиение входных векторов на пары и вычисление расстояний в них. Для этого небходим выбор метрики пространства, в котором осуществляется кластерный анализ. В качестве примеров метрик можно привести:
    • Метрика Евклида: [math]d_{ij} = \sqrt{\sum^n_{k=1}(x_{ik} - x_{jk})^2}[/math]
    • Метрика city-block (манхэттенская метрика): [math]d_{ij} = \sum^n_{k=1}|x_{ik} - x_{jk}| [/math]
    • Метрика Брея-Картиса: [math]d_{ij} = \sum^n_{k=1}\frac{|x_{ik} - x_{jk}|}{|x_{ik} + x_{jk}| }[/math]
  2. Построение минимального остовного дерева по заданному множеству. Существует три основных алгоритма построения минимального остовного дерева:
  3. Разделение полученного дерева на несвязное множество поддеревьев в соответствии с некоторой стратегией. Можно выделить три популярные стратегии кластеризации:
    • SEMST[6] (standard Euclidean minimum spanning tree clustering algorithm) - предполагается, что нам заранее известно число кластеров. В таком случае, если число кластеров равно [math]k[/math], то из полученного в первом пункте остовного дерева удаляются [math]k-1[/math] связей с наибольшим весом.
    • CEMST[7](The maximum cost minimum spanning tree) - Работает так же как и SEMST, однако вместо веса вершины используется её стоимость, которая вычисляется следующим образом: Пусть ребро [math]e = (x_{i}, x_{j})[/math], тогда [math]cost(e) = w(e) * degree(x_{i}) * degree(x_{j})[/math]. Далее, зная число кластеров [math]k[/math] и стоимости рёбер, удаляются [math]k-1[/math] рёбер с максимальной стоимостью.
    • ZEMST[2] (The Zahn’s minimum spanning tree) - от двух предыдущих эта стратегия отличается тем, что здесь число кластеров не известно заранее, а вычисляется в процессе кластеризации. Его суть в том, что удаляются те рёбра, веса которых значительно превосходят средний вес ребра в соседних поддеревьях.

1.1.1 Достоинства

Основным достоинством алгоритма является его способность выделять кластеры произвольной формы. На следующем примере можно видеть, как алгоритм даже в случае SEMST алгоритма корректно разбивает вложенные окружности на три кластера:

Point-connected clusters
Слева направо: Рисунок 1: Начальные данные. Рисунок 2: построенное минимальное остовное дерево. Рисунок 3: разбиение на кластеры путём удаления самых длинных связей.

1.1.2 Недостатки

Недостатком является трудность определения четких критериев, при которых осуществляется разбиение данных на разные кластеры.

Кроме того, существуют наборы данных, на которых популярные алгоритмы (SEMST, CEMST, ZEMST) не дают правильного результата, и для которых требуется применение более сложных алгоритмов, к числу которых можно отнести, например, алгоритм HEMST [8] с временем работы [math]O(n^2 * log(n))[/math].

В качестве иллюстрации можно привести два похожих визуально, но различных с точки зрения разбиения на кластеры с помощью описываемого алгоритма, примера. (В приведенных примерах первый рисунок обозначает начальный набор данных. Второй - построенное на основе данных минимальное остовное дерево. Третий рисунок - разбиение на кластеры.):

Point-connected clusters
Правильное разбиение на два кластера.
Point-connected clusters
Неверное разбиение на один кластер.

Отличие второго примера - в наличии плотной цепочки связей между двумя кластерами, которая и приводит к ошибке наивного алгоритма удаления самых длинных связей, и таким образом здесь потребуется более сложный алгоритм с более значительным временем работы для правильного нахождения кластеров.

1.2 Математическое описание алгоритма

Исходные данные: набор векторов размерности [math]n[/math] : [math](a_1, a_2, ..., a_n)[/math], где [math] a_j \in \mathbb {R} \quad \forall j \in [1,n] [/math].

Вычисляемые данные: Сгруппированные по кластерам данные.

1.2.1 Основные обозначения

  • [math]E[/math] - множество рёбер.
  • [math]V[/math] - множество вершин.
  • [math]G = (V,E) [/math] - неориентированный граф, заданный множеством вершин [math]V[/math] и множеством рёбер [math]E[/math]
  • [math]MST(G)[/math] (Minimum Spanning Tree) - минимальное остовное дерево графа [math][/math]. Остовное дерево — ациклический связный подграф данного связного неориентированного графа, в который входят все его вершины. Минимальное остовное дерево в связанном взвешенном неориентированном графе — это остовное дерево этого графа, имеющее минимальный возможный вес, где под весом дерева понимается сумма весов входящих в него рёбер.
  • [math]MSF(G)[/math] (Minimum Spanning Forest) - набор минимальных остовных деревьев для всех компонент связности графа [math] G [/math]

В описании алгоритмов этой статьи часто используется термин "построение MST", подразумевая, что на самом деле в результате построения может получиться как одна компонента связности (MST), так и множество компонент связности (MSF).

1.2.2 Алгоритмы кластеризации

SEMST(standard Euclidean minimum spanning tree) - простейший алгоритм, основанный на заранее известном числе кластеров [math] k [/math]. Путём удаления [math] k - 1 [/math] связей с наибольшим весом предполагается разбиение остовного дерева на нужное количество кластеров. Иными словами минимальное остовное дерево разбивается на [math] k [/math] поддеревьев: [math] G = \{G_1,G_2,...,G_k\} [/math] путём минимизации суммы всех весов: [math] W = \sum ^k_{l=1} \sum _{x_i,x_j \in V_l} w_{x_i,x_j} -\gt min [/math], где [math] V_l [/math] - множество вершин в поддереве [math] G_l [/math]. В конце такой процедуры каждое множество [math] V_l [/math] становится кластером [math] C_l [/math]. Поскольку (в случае использования алгоритма Крускала для построения остовного дерева) рёбра перед началом кластеризации уже отсортированы, для удаления наибольших [math] k [/math] рёбер требуется [math] O(k) [/math] операций, и для распределения получившихся вершин по кластерам требуется время [math] O(n) [/math].

CEMST(maximum cost Euclidean minimum spanning tree) - Работает в точности, как и SEMST, но здесь вместо весов рёбер используется т.н. "стоимость", которая высчитывается следующим образом: если ребро [math] (x_i, x_j) [/math] соединяет вершины [math] x_i [/math] и [math] x_j [/math], то его "стоимость" считается как: [math] Cost_{x_i, x_j} = w_{x_i,x_j} * Deg(x_i) * Deg(x_j) [/math], где [math] Deg(x_i) [/math] - степень вершины [math] x_i [/math] (т.е. количество рёбер графа G, инцидентных вершине [math] x_i [/math]). В остальном алгоритм действует точно так же, как и SEMST, удаляя [math] k-1 [/math] рёбер с наибольшей "стоимостью". Сложность [math] O(k) [/math] . Алгоритм направлен на удаление рёбер, соединяющих высоко нагруженные (с точки зрения количества инцидентных рёбер) вершины.

ZEMST (The Zahn’s maximum spanning tree (Zahn, 1971)) [9] - отличается от двух предыдущих алгоритмов в первую очередь тем, что здесь количество кластеров заранее не известно, но определяется исходя из данных. Данный алгоритм не является жадным, в отличие от первых двух, так как в нём рассматривается не только само ребро и его вес, а также и поддеревья, которые соединены этим ребром. То есть для ребра [math] (x_i, x_j) [/math] рассматриваются поддеревья [math] N_i = (V_{N_i}, E_{N_i}), N_j = (V_{N_j}, E_{N_j}) [/math], где [math] N_i [/math] - поддерево глубины(высоты) [math] d [/math](величина d определяется эмпирически), состоящее из всех достижимых из вершины [math] x_j [/math] вершин за [math] d [/math] шагов через все достижимые из неё рёбра (исключая само ребро [math] (x_i, x_j) [/math]). Введём обозначения:

  • [math]\overline{w_{N_i}} = \frac{\sum_{(x_r, x_s) \in E_{N_j}}w_{x_r,x_s}}{|E_{N_j}|} [/math] - средний вес ребра в поддереве [math] N_i [/math]
  • [math]\sigma_{N_i} = \sqrt{\frac{\sum_{(x_r, x_s) \in E_{N_j}}(w_{x_r,x_s} - \overline{w_{N_i}})^2}{|E_{N_j}|}} [/math] - стандартное отклонение веса ребра в поддереве [math] N_i [/math]

Ребро [math] (x_i, x_j) [/math] удаляется из списка в случае, если выполняется хотя бы одно из двух условий:

  1. [math] w_{x_i,x_j} \gt \overline{w_{N_i}} - \sigma_{N_i} [/math]
  2. [math] w_{x_i,x_j} \gt \overline{w_{N_j}} - \sigma_{N_j} [/math]
  3. [math] w_{x_i,x_j} \gt \frac{\sum_{(x_r, x_s) \in E_i \cup E_j}w_{x_r,x_s}}{|E_i \cup E_j|} [/math]

Сложность этого алгоритма составляет в худшем случае [math] O(N^2) [/math] (поскольку может оказаться нужным перебрать все рёбра), а в лучшем [math] O(Nd) [/math].

1.3 Вычислительное ядро алгоритма

Процесс решения задачи кластеризации при помощи минимального остовного дерева может быть разделен на три независимых этапа:

  1. Приведение задачи наборе векторов данных к задаче о поиске минимального остовного дерева в графе (то есть нахождение расстояний между всеми парами данных векторов с использованием выбранной метрики, и запись этого расстояния как веса ребра, соединяющего эти вершины). Последовательная сложность: [math] O(n^2) [/math]
  2. Поиск минимального остовного дерева. Последовательная сложность: [math] O(E*log(E)) [/math]
  3. Разбиение остовного дерева на кластеры. Последовательная сложность зависит от реализации. (от [math] O(1) [/math] в случае SEMST до [math] O(n^2) [/math] в случае ZEMST).

1.4 Макроструктура алгоритма

В реализации алгоритма используются следующие структуры данных:

  1. Узел(Node)(Вершина) - Представляется набором координат в выбранном пространстве.
  2. Ребро(Edge) - Представляется: двумя индексами вершин, которые соединяет; своим весом; флагом, который указывает, входит ли данное ребро в минимальное остовное дерево.

В дальнейшем описании алгоритма используются следующие макрооперации:

  1. Вычисление расстояния между вершинами.
  2. Добавление ребра в граф - Осуществляется путём выставление специального флага у соответствующего ребра в true;
  3. Удаление ребра из графа - Операция, обратная к добавление: соответствующий флаг устанавливается в false. В начальный момент времени у всех рёбер флаг установлен в false, что означает отсутствие рёбер в графе.

Следующие три используемые операции относятся к реализации алгоритма Крускала при помощи леса непересекающихся множеств. Подробнее об этой структуре и связанных операциях можно почитать, например, здесь [10], в данном пункте они будут только перечислены:

  1. Создание дерева с одним узлом - Make-Set
  2. Объединение компонент связности - Union
  3. Поиск компонент связности - Find-Set

1.5 Схема реализации последовательного алгоритма

int N;        // Размер входных данных
int number_of_clusters;        // Число кластеров
int size = N* (N-1) / 2;        // Количество связей в полном графе
Node nodes[N];        // Вершины(точки), заданные начальными условиями
Edge edges[size];        // Все расстояния между вершинами 
vector<Edge> MST;        // Рёбра минимального остовного дерева
read_data(&nodes, file_name);        // Считывание начальных данных

//----------------Часть 1--------------//
for(int i = 0; i < N-1; i++) {
  for(int j = i+1; j < N; j++) {
	double distance = getDistance(node[i], node[j]);        // Вычисление расстояния между вершинами
	edges[i*N + j] = Edge(i,j,distance);        // Запись значения расстояния в массив
  }
}


//----------------Часть 2--------------//
sort(edges, size);        // Сортировка рёбер графа в порядке возрастания
for(int i = 0; i < size; i++) {        // Обходим список рёбер по возрастанию 
	if(find_set(edges[i].first) != find_set(edges[i].second)) {        // Если вершины принадлежат разным подграфам - связываем их
          Union(edges[i].first, edges[i].second);        // Если вершины принадлежат разным подграфам - связываем их
	  MST.push_back(edges[i]);        // Запись использованного ребра в минимальное остовное дерево
	  edges[i]->is_in_MST = true;     // Запись использованного ребра в минимальное остовное дерево
	}
}


//----------------Часть 3--------------//
for(int i = MST.size() - 1; i > MST.size() - 1 - number_of_clusters; i--) {
	remove_edge(MST, i);        // Удаление самых длинных рёбер из MST - формирование кластеров
}
write_data(MST);        // Запись выходных данных

Как описано в пункте 1.3, весь описываемый алгоритм (начиная от момента получения данных и заканчивая моментом, когда данные отданы на вывод), может быть условно разделён на три части. В то время как первая часть может быть достаточно эффективно решена "в лоб", вторая и третьи части, как указано в пункте 1.1, могут иметь альтернативы. В данном фрагменте кода подробно описано использование Алгоритма Крускала для построения MST, и алгоритма SEMST для разбиения на кластеры.

1.6 Последовательная сложность алгоритма

Как и в остальных пунктах, здесь сложность алгоритма состоит их суммы сложностей трёх его составных частей. Если оценивать сложность асимптотически в зависимости от входных данных, то:

[math] T(n) = O(n^2) + O(\frac{n (n-1)}{2})log(\frac{n (n-1)}{2})) + O(1) [/math]

На первый взгляд второе слагаемое может показаться слишком большим. Однако, верны и следующие равенства:

[math] O(\frac{n (n-1)}{2} log(\frac{n (n-1)}{2}) = O(n^2 log(n^2)) = O(2 n^2 log(n)) = O(n^2 log(n)) [/math]

Отсюда видно, что для входных данных этого алгоритма асимптотические сложности алгоритмов Крускала, Прима и Борувки совпадают.

Таким образом, последовательная сложность алгоритма: [math] O(n^2 log(n)) [/math]

1.7 Информационный граф

Алгоритм
Макро-описание параллельного алгоритма кластеризации


1.8 Ресурс параллелизма алгоритма

Данная задача хорошо распараллеливается благодаря тому, что все "тяжелые" вычислительные задачи, начиная с момента считывания входных данных, можно распределить по процессам практически равномерно. Это достигается за счет следующей реализации:

  1. Мастер-процесс считывает входные данные и отправляет их остальным процессам с помощью MPI_Broadcast.
  2. Все пары точек разбиваются так, что каждому процессу достается одинаковое количество пар.
  3. Каждый процесс-рабочий строит MST на своём наборе пар точек.
  4. Процессы объединяют свои деревья по алгоритму, изображенному в информационном графе. То есть объединение осуществляется последовательно между группами размера [math]2^k[/math], где [math] k \in 1,2,..log_2p[/math], где p - число процессов.

Таким образом высота Ярусно-Параллельной формы (при наличии [math] p = 2^i [/math] процессоров) будет [math] 1 + i + 1 + 1 [/math]. В то же время, как будет показано в пункте 1.10, оптимальное количетво процессоров может быть оценено по формуле [math] p(N) \approx \sqrt {\frac{N-1}{2}} [/math]. Тогда ширину Ярусно-Параллельной формы можно оценить как [math] O(\sqrt{N}) [/math]. Учитывая приближенную формулу для числа процессоров, оценим высоту ЯП при помощи соотношения [math] 2^i \approx \sqrt {\frac{N-1}{2}} [/math]. Тогда высоту ЯП можно оценить как [math] O(log(N)) [/math].

Говоря о праллельной реализации вообще и, в частности, обращаясь к информационному графу, видно, что в основе параллельного алгоритма лежит массовый параллелизм, и единственным проблематичным местом может оказаться самый начальный этап считывания данных мастер-процессом с последующей рассылкой их всем процессам-рабочим. Проблема в том, что объем данных, пересылаемых каждому процессу, практически нельзя уменьшить из-за того, что в расчете MST на каждом наборе рёбер потенциально могут участвовать все вершины графа, и поэтому для их обработки требуется знание их пространственных координат.

Самой "тяжелой" операцией здесь является самый первый расчёт MST, поскольку он выполняется чуть больше чем за квадратичное время. Все последующие объединения деревьев занимают асимптотически одинаковое количество процессорного времени и не зависят друг от друга (вплоть до последнего объединения в MST).

1.9 Входные и выходные данные алгоритма

1.9.1 Входные данные

В качестве входных данных программе дается множество векторов [math] [x_1,x_2,...,x_N][/math], где [math] x_j = (a_{1j}, a_{2j}, ..., a_{nj})[/math] размерности [math]n[/math]. Для удобства изучения свойств алгоритма и отображения результатов можно брать массив векторов размерности 2. В этом случае наглядность будет обеспечена естественной возможностью отобразить вектора в плоскости.

Кроме того может оказаться полезным особым образом подготовить данные перед анализом (удаление "шумов", выбросов и масштабирование данных по тем или иным координатам).

1.9.2 Выходные данные

Выходными данными программы будет множество [math] M = ((x_1,x_2,..,x_k),(x_{k+1},...),...) [/math], состоящее из сгруппированных по кластерам векторов из входных данных.

В конкретной программе это может быть реализовано путём записи [math] N [/math] строк вида [math](x_{i}, Cluster-Number)[/math] в выходной файл.

1.10 Свойства алгоритма

Одним из трудных мест параллельной реализации является правильный выбор количества процессов. Для удобства анализа рассматривается число процессоров [math] p = 2^i [/math], где [math] i \in \mathbb {N} [/math]. Ясно, что такое допущение не повлияет на качество анализа, так как другое число процессоров лишь приведет к незначительным накладным расходам, сложность которых асимптотически стремится к нулю. В таком случае можно также ввести допущение о размере входных данных: [math] N = 2^j [/math], где [math] j \in \mathbb {N}, j \gt i [/math]. В этом случае MST-часть дерева графа алгоритма является идеально сбалансированным бинарным деревом. Тогда сложность можно оценить следующим образом:

  1. На первом этапе каждый процесс производит расчет на [math] 2^{j - i} * (2^j - 1)[/math] рёбрах, получая локальное MST(или MSF) на не более чем [math] N [/math] вершинах.
  2. Далее следует [math] i = log_2(2^i) [/math] шагов объединения не более чем по N рёбер в каждом.

Таким образом, задача эффективного запуска алгоритма состоит в том, чтобы найти оптимальный баланс между количеством расчетов на каждом узле на первом шаге и общим количеством объединений поддеревьев. Пусть дано [math] p = 2^i [/math] вычислительных узлов и [math] N = 2^j [/math]. В таком случае каждый узел получает считает [math] 2^{j - i} * (2^j - 1)[/math] рёбер на первом шаге. Высота части ЯП-формы, отвечающей за MST, равна [math] i [/math]. Тогда всего будет [math] i [/math] уровней объединений MST с числом рёбер не более чем [math] 2N [/math] для каждого вычислительного узла.

1.10.1 Оценка скорости работы параллельного алгоритма

Грубая оценка времени вычисления может помочь изучить качественные характеристики алгоритма. Учитывая рассуждения выше, приближенная качественная формула может быть записана таким образом:

  • [math] T'(N,p) = \frac{N(N - 1)}{2 p} * logN + 2N*log(N) * log_2p[/math].

Объяснение формулы: первое слагаемое, исходя из оценки для сложности алгоритма Прима: [math] O(ElogV) [/math], - время(число операций) расчета на каждом процессоре на первом шаге. После первого шага (поскольку построено минимальное остовное дерево) каждый процесс передает не более чем [math] N [/math] рёбер дальше. Второе слагаемое: каждый процесс после первого шага получает не более чем [math] 2N [/math] рёбер (от двух процессоров при объединении), и всего таких шагов объединения не более чем [math] log_2p [/math], при этом предполагается, что сложностью объединения [math] 2N [/math] рёбер на каждом шаге можно пренебречь (или допустить его пренебрежительно малым), поскольку предполагается, что все процессы после первого шага получают свою часть уже отсортированных рёбер(то есть наибольшее ребро процессора [math] i [/math] меньше наименьшего ребра процессора [math] i+1 [/math]), и объединение будет просто конкатенацией блоков памяти.

С помощью этой формулы можно построить графики зависимости времени вычислений от количества процессоров и входных данных.Поскольку рост вычислительного времени при увеличении объёма входных данных достаточно очевиден, этот объём будет зафиксирован, и будет исследоваться поведение приближенного априорного вычислительного времени алгоритма в зависимости от числа процессоров. Проведём два вычислительных эксперимента:

1. [math] N = 10^4[/math]

Алгоритм
По оси абсцисс: количество вычислительных узлов. По оси ординат: время вычислений. При фиксированном [math] N [/math]

2. [math] N = 1.5*10^4 [/math]

Алгоритм
По оси абсцисс: количество вычислительных узлов. По оси ординат: время вычислений. При фиксированном [math] N [/math]

Из этих графиков становится видно качественное поведение времени вычислений при увеличении числа процессоров. Можно заметить, что для каждого набора входных данных есть число процессоров, минимизирующее время вычислений. Но в то же время видно, что при слишком большом количестве процессоров алгоритм начинает работать хуже, чем при меньшем. Это вполне понятно: Слишком большое число процессоров порождает большое число обменов между ними, объединений деревьев, сортировок, и т.д. Таким образом, именно эта часть [math] 2N*log(N) * log_2p [/math] оценки времени вычислений отвечает за ухудшение эффективности алгоритма при числе процессоров, которое больше оптимального. Кроме того, видно, что при увеличении объёма входных данных оптимальное количество процессоров также увеличивается: количество обменов и слияний деревьев компенсируется уменьшением вычислительной нагрузки на каждый отдельный процессор.

Благодаря такой оценке становится возможным оценить примерное оптимальное число процессоров [math] p [/math] в зависимости от объёма входных данных [math] N [/math]. Продифференцировав приближенную формулу [math] T'(N,p) = \frac{N(N - 1)}{2 p} * logN + 2N*log(N) * log_2p [/math] по [math] p [/math] и приравняв производную нулю, получим следующее выражение для оптимального числа процессоров:

[math] p(N) \approx \frac{N-1}{4} [/math]

1.10.2 Вычислительная мощность параллельного алгоритма

Согласно определению, вычислительная мощность алгоритма равна отношению числа операций к суммарному объему входных и выходных данных. В то же время, учитывая вышесказанное, видно, что число операций в данном алгоритме зависит не только от объема входных данных, но также от числа процессоров. Таким образом, имеет смысл оценить вычислительную мощность как в случае оптимального числа процессоров, так и в общем случае числа процессоров, равного [math] p [/math].

В общем случае, исходя из предыдущего пункта, число операций будет равно

[math] \frac{N(N - 1)}{2} * logN + \sum_{i = 0}^{log_2p}(2N*log(N) * \frac{p}{2^i}) = N logN (\frac{N-1}{2} + 2 * p * \sum_{i = 0}^{log_2p}( \frac{1}{2^i})) = [/math] {переходя к пределу в сумме} [math] \approx N logN (\frac{N-1}{2} + 2 * p) [/math]

Таким образом, вычислительная мощность в общем случае: [math] \approx \frac{N logN (\frac{N-1}{2} + 2 * p)}{2N} = \frac{logN *(\frac{N-1}{2} + 2 * p)}{2} [/math]

В случае оптимального числа вычислительных узлов: [math] \approx \frac{N logN (\frac{N-1}{2} + \frac{N-1}{2})}{2N} = \frac{logN * (N-1)}{2}[/math]

2 Программная реализация алгоритма

2.1 Особенности реализации последовательного алгоритма

2.2 Локальность данных и вычислений

2.3 Возможные способы и особенности параллельной реализации алгоритма

2.4 Масштабируемость алгоритма и его реализации

В настоящей статье приводится исследование самостоятельной реализации алгоритма: main.cpp. Запуск производился на системе IBM Blue Gene/P. Для генерации данных использовался скрипт на питоне testGenerator. Для отображения кластеров использовалась библиотека matplotlib и скрипт graphDraw.

IBM Blue Gene/P — массивно-параллельная вычислительная система, которая состоит из двух стоек, включающих 2048 четырехъядерных вычислительных узлов, с пиковой производительностью 27,9 терафлопс.

Характеристики системы:

  • две стойки с вычислительными узлами и узлами ввода-вывода
  • 1024 четырехъядерных вычислительных узла в каждой из стоек
  • 16 узлов ввода-вывода в стойке (в текущей конфигурации активны 8, т.е. одна I/O-карта на 128 вычислительных узлов)
  • программирование с использованием MPI, OpenMP/pthreads, POSIX I/O
  • высокая энергоэффективность: ∼ 372 MFlops/W

Вычислительный узел включает в себя четырехъядерный процессор, 2 ГБ общей памяти и сетевые интерфейсы.

Вычислительной узел:

  • четыре микропроцессорных ядра PowerPC 450 (4-way SMP)
  • пиковая производительность: 4 ядра x 3,4 GFlop/sec на ядро = 13,6 GFlop/sec
  • пропускная способность памяти: 13,6 GB/sec
  • 2 ГБ общей памяти
  • 2 x 4 МБ кэш-памяти 2-го уровня (в документации по BG/P носит название L3)
  • легковесное ядро (compute node kernel, CNK), представляющее собой Linux-подобную операционную систему, поддерживающую значительное подмножество Linux-совместимых системных вызовов
  • асинхронные операции межпроцессорных обменов (выполняются параллельно с вычислениями)
  • операции ввода-вывода перенаправляются I/O-картам через сеть коллективных операций


Поскольку оптимальное число процессоров достаточно велико и линейно зависит от объема(размера) входных данных, задача хорошо масштабируется, и на графике ниже можно увидеть почти линейный рост производительности при фиксированном количестве входных данных и увеличении числа процессоров в том случае, когда число процессоров близко к оптимальному. В тоже время из графика видно, что когда входных данных слишком мало, а число процессоров возрастает, то происходит либо небольшой рост производительности, либо роста производительности нет.

Алгоритм
Производительность алгоритма

Говоря о вычислительном времени и проводя тесты на реальных данных, можно заметить концептуальную правильность сделанных качественных выводов о поведении времени вычислений для различного набора входных данных и числа процессоров. На графиках ниже видно, что время вычисления задачи уменьшается до определенного момента, когда количество процессоров, нужное для задачи, становится слишком большим, после чего начинает увеличиваться, когда число процессоров растет.

Алгоритм
Вычислительное время алгоритма для [math]N = 17000[/math]
Алгоритм
Вычислительное время алгоритма для [math]N = 3000[/math]

2.5 Динамические характеристики и эффективность реализации алгоритма

2.6 Выводы для классов архитектур

2.7 Существующие реализации алгоритма

Как таковые, алгоритмы самого процесса кластеризации в популярных библиотеках (таких, как Scipy, Numpy или Boost) не реализованы (в отличие от других алгоритмов кластеризации). Однако, можно найти пользовательские любительские реализации (некоторые из которых приведены в списке ниже). Кроме того, существует масса реализаций основного коспонента данного алгоритма - вычисления MST - на многих языках программирования в разных библиотеках. Ссылки на некоторые из этих реализаций(перечислить все было бы невозможно) также приведены в списке ниже.

3 Литература

  1. Классификация и кластер. Под ред. Дж. Вэн Райзина. М.: Мир, 1980. 390 с.
  2. 2,0 2,1 C. T. Zahn, “graph-Theriotical methos for detecting and describing getalt clusters,” IEEE Trans. Computers, vol. 20, no. 1, pp. 68-86, 1971
  3. Y. Xu, V. Olman and E. C. Uberbacher, “A mentation using minimum spanning trees,” Image and Vission Computing, vol. 15, pp. 47-57,1997.
  4. Zhi Min Wang, Yeng Chai Soh, Qing Song, Kang Sim, “Adaptive spatial information-theoretic clustering for image segmentation,” Pattern Recognition, vol. 42, no. 9, pp. 2029-2044, 2009.
  5. Y. Xu, V. Olman and D. Xu, “Clustering gene expression data using a graph-Theriotic approach: An application of minimum spanning trees,” Bioinformatics, vol. 18, no. 4, pp. 536-545, 2002.
  6. Asano, M.K.T., Bhattacharya, B., Yao, F.: Clustering algorithms based on minimum and maximum spanning trees. In: In Proceedings of the fourth annual symposium on Computational Geometry. (1998) 252–257
  7. Ye, B., Chao, K.M.: Spanning Trees and Optimization Problems. Chapman and Hall (2004)
  8. Clustering with Minimum Spanning Trees Yan Zhou, Oleksandr Grygorash, Thomas F. Hain, Meador Inge Zach Jorgensen School of Computer and Information Sciences University of South Alabama, Mobile, AL 36688 USA Urban Insight Inc. 5657 Wilshire Blvd Ste 290, Los Angeles, CA 90036
  9. Attribute Clustering Based on Heuristic Tree Partition Jorge Cordero H. and Yifeng Zeng Department of Computer Science Aalborg University 9220 Aalborg, Denmark
  10. Томас Кормен и др. Алгоритмы: построение и анализ = INTRODUCTION TO ALGORITHMS. — 2-е изд. — М.: «Вильямс», 2006. — С. 1296. — ISBN 0-07-013151-1. (Глава 21. Раздел 21.3: "Леса непересекающихся множеств)