https://algowiki-project.org/w/ru/api.php?action=feedcontributions&user=Konshin&feedformat=atomАлговики - Вклад участника [ru]2024-03-29T15:27:56ZВклад участникаMediaWiki 1.34.0https://algowiki-project.org/w/ru/index.php?title=%D0%9A%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D1%8F_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%BE%D0%B2&diff=25298Классификация алгоритмов2018-01-18T08:51:35Z<p>Konshin: </p>
<hr />
<div>__TOC__<br />
=Векторные операции=<br />
# {{level|Суммирование сдваиванием}}<br />
## {{level|Нахождение суммы элементов массива сдваиванием}}<br />
## {{level|Нахождение частных сумм элементов массива сдваиванием}}<br />
# {{level|Равномерная норма вектора, вещественная версия, последовательно-параллельный вариант}}<br />
# {{level|Скалярное произведение векторов, вещественная версия, последовательно-параллельный вариант}}<br />
# {{level|Последовательно-параллельный метод суммирования}}<br />
=Матрично-векторные операции<br />
# {{level|Умножение плотной матрицы на вектор}}<br />
## {{level|Умножение плотной неособенной матрицы на вектор (последовательный вещественный вариант)}}<br />
=Матричные операции=<br />
# {{level|Умножение плотных матриц}}<br />
## {{level|Перемножение плотных неособенных матриц (последовательный вещественный вариант)}}<br />
## {{level|Метод Штрассена}}<br />
=Разложения матриц=<br />
{{level|Задача разложения матриц}}<br />
# {{level|Треугольные разложения}}<br />
## {{level|Метод Гаусса (нахождение LU-разложения)}}<br />
### {{level|LU-разложение методом Гаусса без перестановок}}<br />
#### {{level|LU-разложение методом Гаусса}}<br />
#### {{level|Компактная схема метода Гаусса и её модификации}}<br />
##### {{level|Компактная схема метода Гаусса для плотной матрицы}}<br />
##### {{level|Компактная схема метода Гаусса для трёхдиагональной матрицы и её модификации}}<br />
###### {{level|Компактная схема метода Гаусса для трёхдиагональной матрицы, последовательный вариант}}<br />
###### {{level|Алгоритм сдваивания Стоуна для LU-разложения трёхдиагональной матрицы}}<br />
###### {{level|Последовательно-параллельный алгоритм для LU-разложения трёхдиагональной матрицы}}<br />
### {{level|LU-разложение методом Гаусса с перестановками}}<br />
#### {{level|LU-разложение методом Гаусса с выбором ведущего элемента по столбцу}}<br />
#### {{level|LU-разложение методом Гаусса с выбором ведущего элемента по строке}}<br />
#### {{level|LU-разложение методом Гаусса с выбором ведущего элемента по главной диагонали}}<br />
#### {{level|LU-разложение методом Гаусса с выбором ведущего элемента по всей матрице}}<br />
## {{level|Метод Холецкого (нахождение симметричного треугольного разложения)}}<br />
### {{level|Разложение Холецкого (метод квадратного корня)}} базовый точечный вещественный вариант для плотной симметричной положительно-определённой матрицы<br />
# {{level|Известные треугольные разложения для матриц специального вида}}<br />
# {{level|Унитарно-треугольные разложения}}<br />
## {{level|QR-разложения плотных неособенных матриц}}<br />
### {{level|Метод Гивенса (вращений) QR-разложения матрицы}}<br />
#### {{level|Метод Гивенса (вращений) QR-разложения квадратной матрицы (вещественный точечный вариант)}}<br />
### {{level|Метод Хаусхолдера (отражений) QR-разложения матрицы}}<br />
#### {{level|Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный точечный вариант}}<br />
### {{level|Метод ортогонализации}}<br />
#### {{level|Классический метод ортогонализации}}<br />
#### {{level|Метод ортогонализации с переортогонализацией}}<br />
### {{level|Метод треугольного разложения матрицы Грама}}<br />
## {{level|Методы QR-разложения плотных хессенберговых матриц}}<br />
### {{level|Метод Гивенса (вращений) QR-разложения хессенберговой матрицы (вещественный вариант)}}<br />
### {{level|Метод Хаусхолдера (отражений) QR-разложения хессенберговой матрицы (вещественный вариант)}}<br />
# {{level|Подобные разложения}}<br />
## {{level|Подобные разложения на унитарные и хессенберговы матрицы}}<br />
### {{level|Метод Хаусхолдера (отражений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
#### {{level|Классический точечный метод Хаусхолдера (отражений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
### {{level|Метод Гивенса (вращений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
#### {{level|Классический точечный метод Гивенса (вращений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
## {{level|Симметричные разложения на унитарные и трёхдиагональные матрицы}}<br />
### {{level|Метод Хаусхолдера (отражений) приведения к трёхдиагональному виду}}<br />
#### {{level|Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду}}<br />
#### {{level|Метод Хаусхолдера (отражений) для приведения комплексных эрмитовых матриц к трёхдиагональному симметричному виду}}<br />
### {{level|Метод Гивенса (вращений) приведения матрицы к трёхдиагональной форме}}<br />
## {{level|Спектральное разложение (нахождение собственных значений и векторов)}}<br />
# {{level|Неподобные унитарные разложения}}<br />
## {{level|Неподобные разложения на унитарные и двухдиагональные матрицы}}<br />
### {{level|Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме}}<br />
### {{level|Метод Гивенса (вращений) приведения матрицы к двухдиагональной форме}}<br />
## {{level|Разложения на унитарные и диагональные матрицы}}<br />
### {{level|Сингулярное разложение (нахождение сингулярных значений и векторов)}}<br />
#### {{level|Методы нахождения сингулярных чисел двухдиагональных матриц}}<br />
##### {{level|Алгоритм dqds нахождения сингулярных чисел двухдиагональной матрицы}}<br />
###### {{level|Итерация алгоритма dqds}}<br />
=Решение систем линейных уравнений=<br />
# {{level|Прямые методы решения СЛАУ}}<br />
## {{level|Linpack benchmark}}<br />
## {{level|Методы решения СЛАУ с матрицами специального вида}}<br />
### {{level|Методы решения СЛАУ с треугольными матрицами}}<br />
#### {{level|Прямая подстановка (вещественный вариант)|Прямая подстановка}}<br />
#### {{level|Обратная подстановка (вещественный вариант)|Обратная подстановка}}<br />
#### {{level|Методы решения СЛАУ с двудиагональными матрицами}}<br />
##### {{level|Прямая и обратная подстановка в СЛАУ с двухдиагональной матрицей}}<br />
##### {{level|Метод сдваивания Стоуна для решения двудиагональных СЛАУ}}<br />
##### {{level|Последовательно-параллельный вариант обратной подстановки}}<br />
### {{level|Методы решения СЛАУ с трёхдиагональными матрицами}}<br />
#### {{level|Методы, основанные на стандартном LU-разложении матрицы}}<br />
##### {{level|Прогонка}}<br />
###### {{level|Прогонка, точечный вариант}}<br />
###### {{level|Классическая монотонная прогонка, повторный вариант}}<br />
##### {{level|Метод сдваивания Стоуна}}<br />
###### {{level|Алгоритм сдваивания Стоуна для LU-разложения трёхдиагональной матрицы}}<br />
###### {{level|Метод сдваивания Стоуна для решения двудиагональных СЛАУ}}<br />
##### {{level|Последовательно-параллельный вариант решения трёхдиагональной СЛАУ с LU-разложением и обратными подстановками}}<br />
#### Другие методы<br />
##### {{level|Метод редукции}}<br />
###### {{level|Полный метод редукции}}<br />
###### {{level|Повторный метод редукции для новой правой части}}<br />
##### {{level|Встречная прогонка}}<br />
###### {{level|Встречная прогонка, точечный вариант}}<br />
###### {{level|Повторная встречная прогонка, точечный вариант}}<br />
##### {{level|Метод циклической редукции}}<br />
###### {{level|Полный метод циклической редукции}}<br />
###### {{level|Повторный метод циклической редукции для новой правой части}}<br />
##### {{level|Метод окаймления}}<br />
### Методы решения СЛАУ с блочно-треугольными матрицами<br />
#### {{level|Блочная прямая подстановка (вещественный вариант)|Блочная прямая подстановка}}<br />
#### {{level|Блочная обратная подстановка (вещественный вариант)|Блочная обратная подстановка}}<br />
#### Методы решения СЛАУ с блочно-двухдиагональными матрицами<br />
##### {{level|Прямая и обратная подстановка в СЛАУ с блочно-двухдиагональной матрицей}}<br />
##### {{level|Метод сдваивания Стоуна для решения блочно-двухдиагональных СЛАУ}}<br />
##### {{level|Блочный последовательно-параллельный вариант обратной подстановки для решения блочно-двухдиагональных СЛАУ}}<br />
### {{level|Методы решения СЛАУ с блочно-трёхдиагональными матрицами}}<br />
#### Методы, основанные на стандартном LU-разложении матрицы<br />
##### {{level|Блочная прогонка}}<br />
##### {{level|Блочный последовательно-параллельный вариант решения с LU-разложением и обратными подстановками}}<br />
#### Другие методы<br />
##### {{level|Встречная прогонка, блочный вариант}}<br />
##### {{level|Блочный метод циклической редукции}}<br />
##### {{level|Блочный метод окаймления}}<br />
## {{level|Решения СЛАУ с матрицами специального вида, имеющими известные обратные матрицы}}<br />
# Итерационные методы решения СЛАУ<br />
## {{level|High Performance Conjugate Gradient (HPCG) benchmark}}<br />
## {{level|Стабилизированный метод бисопряженных градиентов (BiCGStab)}}<br />
## {{level|Алгоритм_Качмажа}}<br />
=Решение систем нелинейных уравнений=<br />
# {{level|Метод Ньютона для систем нелинейных уравнений}}<br />
=Решения спектральных задач=<br />
# {{level|Спектральное разложение (нахождение собственных значений и векторов)}}<br />
## {{level|QR-алгоритм}}<br />
### {{level|QR-алгоритм, используемый в SCALAPACK}}<br />
#### {{level|Классический точечный метод Хаусхолдера (отражений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
#### {{level|QR-алгоритм для хессенберговой матрицы, используемый в SCALAPACK}}<br />
### {{level|QR-алгоритм для симметричных матриц, используемый в SCALAPACK}}<br />
#### {{level|Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду}}<br />
#### {{level|QR-алгоритм для симметричных трёхдиагональных матриц, используемый в SCALAPACK}}<br />
### {{level|QR-алгоритм для комплексных эрмитовых матриц, используемый в SCALAPACK}}<br />
#### {{level|Метод Хаусхолдера (отражений) для приведения комплексных эрмитовых матриц к трёхдиагональному симметричному виду}}<br />
#### {{level|QR-алгоритм для симметричных трёхдиагональных матриц, используемый в SCALAPACK}}<br />
## {{level|Метод Якоби (вращений) для решения спектральной задачи у симметричных матриц}}<br />
### {{level|Классический метод Якоби (вращений) для симметричных матриц с выбором по всей матрице}}<br />
### {{level|Метод Якоби (вращений) для симметричных матриц с циклическим исключением}}<br />
### {{level|Метод Якоби (вращений) для симметричных матриц с циклическим исключением и барьерами}}<br />
## {{level|Метод Ланцоша}}<br />
### {{level|Алгоритм Ланцоша для точной арифметики (без переортогонализации)}}<br />
# {{level|Частичная спектральная задача}}<br />
## {{level|Метод бисекций}}<br />
# {{level|Сингулярное разложение (нахождение сингулярных значений и векторов)}}<br />
## {{level|Метод Якоби (вращений) для нахождения сингулярных значений неособенных матриц}}<br />
### {{level|Метод Якоби (вращений) для нахождения сингулярных значений с циклическим перебором}}<br />
### {{level|Метод Якоби для нахождения сингулярных значений со специальным подбором вращений}}<br />
## {{level|QR-алгоритм в приложении к сингулярному разложению}}<br />
=Тесты производительности компьютеров=<br />
# {{level|High Performance Conjugate Gradient (HPCG) benchmark}}<br />
# {{level|Linpack benchmark}}<br />
=Преобразование Фурье=<br />
# {{level|Быстрое преобразование Фурье}}<br />
## {{level|Быстрое преобразование Фурье для степеней двойки}}<br />
=Алгебра многочленов=<br />
# {{level|Схема Горнера, вещественная версия, последовательный вариант}}<br />
=Численные методы интегрирования=<br />
# {{level|Квадратурные формулы}}<br />
# {{level|Квадратурные (кубатурные) методы численного интегрирования по отрезку (многомерному кубу)}}<br />
## [[Квадратурные_(кубатурные)_методы_численного_интегрирования_по_отрезку_(многомерному_кубу)#Метод прямоугольников|Метод прямоугольников]]<br />
## [[Квадратурные_(кубатурные)_методы_численного_интегрирования_по_отрезку_(многомерному_кубу)#Метод трапеций|Метод трапеций]]<br />
## [[Квадратурные_(кубатурные)_методы_численного_интегрирования_по_отрезку_(многомерному_кубу)#Метод парабол (метод Симпсона)|Метод парабол (метод Симпсона)]]<br />
## [[Квадратурные_(кубатурные)_методы_численного_интегрирования_по_отрезку_(многомерному_кубу)#Метод Гаусса|Метод Гаусса]]<br />
=Алгоритмы на графах=<br />
# Обход графа<br />
## {{level|Поиск в ширину (BFS)}}<br />
## {{level|Поиск в глубину (DFS)}}<br />
# {{level|Поиск кратчайшего пути от одной вершины (SSSP)}}<br />
## {{level|Поиск в ширину (BFS)}} (для невзвешенных графов)<br />
## {{level|Алгоритм Дейкстры}}<br />
## {{level|Алгоритм Беллмана-Форда}}<br />
## {{level|Алгоритм Δ-шагания}}<br />
# {{level|Поиск кратчайшего пути для всех пар вершин (APSP)}}<br />
## {{level|Алгоритм Джонсона}}<br />
## {{level|Алгоритм Флойда-Уоршелла}}<br />
# {{level|Поиск транзитивного замыкания орграфа}}<br />
## {{level|Алгоритм Пурдома}}<br />
# {{level|Определение диаметра графа}}<br />
# {{level|Построение минимального остовного дерева (MST)}}<br />
## {{level|Алгоритм Борувки}}<br />
## {{level|Алгоритм Крускала}}<br />
## {{level|Алгоритм Прима}}<br />
## {{level|Алгоритм GHS}}<br />
# {{level|Поиск изоморфных подграфов}}<br />
## {{level|Алгоритм Ульмана}}<br />
## {{level|Алгоритм VF2}}<br />
# {{level|Связность в графах}}<br />
## {{level|Алгоритм Шилоаха-Вишкина поиска компонент связности}}<br />
## {{level|Система непересекающихся множеств}}<br />
## {{level|Алгоритм Тарьяна поиска компонент сильной связности}}<br />
## {{level|Алгоритм DCSC поиска компонент сильной связности}}<br />
## {{level|Алгоритм Тарьяна поиска компонент двусвязности}}<br />
## {{level|Алгоритм Тарьяна-Вишкина поиска компонент двусвязности}}<br />
## {{level|Алгоритм Тарьяна поиска «мостов» в графе}}<br />
## {{level|Определение вершинной связности графа}}<br />
## {{level|Алгоритм Габова определения рёберной связности графа}}<br />
# {{level|Поиск максимального потока в транспортной сети}}<br />
## {{level|Алгоритм Форда-Фалкерсона}}<br />
## {{level|Алгоритм проталкивания предпотока}}<br />
# {{level|Поиск потока минимальной стоимости в транспортной сети}}<br />
# {{level|Задача о назначениях}}<br />
## {{level|Венгерский алгоритм}}<br />
## {{level|Алгоритм аукциона}}<br />
## {{level|Алгоритм Гопкрофта-Карпа}}<br />
# {{level|Вычисление betweenness centrality|Вычисление центральности вершин}}<br />
=Алгоритмы поиска=<br />
# {{level|Линейный поиск - находит элемент в любом списке|Линейный поиск}}, <math>O(n)</math><br />
# {{level|Двоичный поиск - находит элемент в отсортированном списке|Двоичный поиск}}, <math>O(\log(n))</math><br />
=Алгоритмы сортировки=<br />
# {{level|Сортировка с помощью двоичного дерева}}<br />
# {{level|Сортировка пузырьком}}<br />
# {{level|Сортировка слиянием (последовательный и параллельный варианты)}}<br />
=Вычислительная геометрия=<br />
# {{level|Поиск диаметра множества точек}}<br />
# {{level|Построение выпуклой оболочки набора точек}}<br />
# {{level|Триангуляция Делоне}}<br />
# {{level|Диаграмма Вороного}}<br />
# {{level|Принадлежность точки многоугольнику}}<br />
# {{level|Пересечения выпуклых многоугольников}} - трудоёмкость <math>O(n_1 + n_2)</math><br />
# {{level|Пересечение звёздных многоугольников}} - трудоёмкость <math>O(n_1 \cdot n_2)</math><br />
=Компьютерная графика=<br />
# {{level|Алгоритмы построения отрезка - алгоритмы для аппроксимации отрезка на дискретной графической поверхности}}<br />
# {{level|Алгоритм определения видимых частей трёхмерной сцены}}<br />
# {{level|Трассировка лучей - рендеринг реалистичных изображений}}<br />
# {{level|Глобальное освещение - рассматривает прямое освещение и отражение от других объектов}}<br />
=Криптографические алгоритмы=<br />
# {{level|Метод встречи посередине}}<br />
=Нейронные сети=<br />
# {{level|Распознование образов}}<br />
## {{level|Распознование текста}}<br />
## {{level|Распознование речи}}<br />
## {{level|Распознование лиц}}<br />
=Алгоритмы оптимизации=<br />
# {{level|Линейное программирование}}<br />
# {{level|Симплекс-метод}}<br />
# {{level|Метод ветвей и границ}}<br />
# {{level|Генетические алгоритмы}}<br />
# {{level|Муравьиные алгоритмы}}<br />
# {{level|Комбинированные алгоритмы}}<br />
# {{level|Стохастическое двойственное динамическое программирование (SDDP)}}<br />
=Алгоритмы машинного обучения=<br />
# {{level|Алгоритм k средних (k-means)}}<br />
=Алгоритмы теории игр=<br />
=Алгоритмы моделирования квантовых систем=<br />
# {{level|Алгоритмы моделирования квантовых вычислений}}<br />
## {{level|Однокубитное преобразование вектора-состояния}}<br />
## {{level|Двухкубитное преобразование вектора-состояния}}<br />
## {{level|Моделирование квантового преобразования Фурье}}<br />
=Алгоритмы решения уравнений математической физики=<br />
# {{level|Уравнение Пуассона, решение дискретным преобразованием Фурье}}<br />
=Другие алгоритмы=<br />
<br />
[[en:Algorithm classification]]<br />
<br />
[[Категория:Алгоритмы|*]]</div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86&diff=25297Задача разложения матриц2018-01-18T08:51:05Z<p>Konshin: Новая страница: «{{level-p}} '''Разложения матриц''' как задачи требуют разложения матриц в произведения различ…»</p>
<hr />
<div>{{level-p}}<br />
<br />
'''Разложения матриц''' как задачи требуют разложения матриц в произведения различных матриц специального вида (унитарных, треугольных и др.) в зависимости от того, что применяется в другой задаче более высокого уровня (решение СЛАУ, проблемы собственных значений и т.п.).</div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Konshin&diff=25292Обсуждение участника:Konshin2018-01-17T12:38:00Z<p>Konshin: /* Другие (5): */</p>
<hr />
<div>* явных списываний нет ни в одном из алгоритмов!<br />
* правда в алгоритме 19 (Алгоритм k средних) у 3-х из 4-х авторов использована одна и та же открытая реализация<br />
* все работы очень хорошие! только 4 оценки 4, остальные 5<br />
* в принципе для оценки выполнены все работы, хотя в 3-х было бы неплохо дождаться мелкой правки...<br />
<br />
Обозначения:<br />
<br />
* ... - ждем ответа на замечания<br />
* +++ - работа выполнена<br />
* +++!!! - выполнена и является претендентом на внесение в AlgoWiki<br />
* (X)(Y) - оценки по разделам 1 и 2, соответственно<br />
<br />
<br />
=== Алгоритм_4, Алгоритм Ланцоша (итерационный метод вычисления собственных значений симметричной матрицы) для точной арифметики (без переортогонализации) (6): ===<br />
<br />
https://algowiki-project.org/ru/Участник:Alexbashirov/Алгоритм_Ланцоша_для_точной_арифметики<br />
<br />
+++ (4)(4) по сравнению с другими замечательными описаниями и из-за задержки с п.2.4 оценка снижена, хотя формально все сделано, можно поставить и 5-<br />
<br />
https://algowiki-project.org/ru/Участник:A.Freeman/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (5)(5) собственная реализация на 256 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Danyanya/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации) <br />
<br />
+++ (5-)(5-) собственная реализация на 128 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:AleksLevin/Алгоритм_Ланцоша_вычисления_собственных_значений_симметричной_матрицы_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (5)(5) п.3! собственная реализация на 256 проц. Ломоносова; не очень понятно, открыт ли полный доступ к коду...<br />
<br />
https://algowiki-project.org/ru/Участник:VolkovNikita94/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++!!! (5-)(5+) Никита Волков; п.2.7 лучший!; реализация MPI+OpenMP тоже лучшая! должны еще поправить рис.4-6...<br />
<br />
https://algowiki-project.org/ru/Участник:Shostix/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (4)(4) нормально<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
=== Алгоритм_14, Решение системы нелинейных уравнений методом Ньютона (4): ===<br />
<br />
https://algowiki-project.org/ru/Участник:N_Zakharov/Метод_Ньютона_для_решения_систем_нелинейных_уравнений<br />
<br />
+++ (4+)(4+) пример из PETSc до 128 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Oleggium/Метод_Ньютона_для_решения_систем_нелинейных_уравнений(2)<br />
<br />
+++ (5)(5-) красивый граф, самостоятельная реализация на CUDA до 400 потока на персоналке, но бледно описано; все сделано, местами лучшие описания, но несколько лаконично :( <br />
<br />
https://algowiki-project.org/ru/Участник:SKirill/Метод_Ньютона_решения_систем_нелинейных_уравнений<br />
<br />
+++ (5-)(5) необычные и интересные блоксхемы п.1.5! и 1.7!; самостоятельная MPI реализация до 128 нитей; лучший п.2.7!<br />
<br />
[https://algowiki-project.org/ru/Участник:Арутюнов_А.В. https://algowiki-project.org/ru/Участник:Арутюнов_А.В.] (!!! осторожно с точкой в конце адреса !!!)<br />
<br />
+++ (4)(4) нормально<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Метод_Ньютона_для_систем_нелинейных_уравнений<br />
<br />
=== Алгоритм_19, Алгоритм k средних (4): ===<br />
<br />
https://algowiki-project.org/ru/Участник:IanaV/Алгоритм_k_means<br />
<br />
+++ (5)(5) хороший текст, особенно п.1.10, отличные графы, но витиеватый язык, видимо списано из учебника, чужая прогр. до 512 MPI проц. Ломоносова (там же имеются OpenMP и CUDA версии, но они не исследовались, собственно, как и 2-ми и 3-ми авторами)<br />
<br />
https://algowiki-project.org/ru/Участник:Parkhomenko/Алгоритм_k_средних<br />
<br />
+++ (5-)(5) специфически, но интересно представлены результаты в п.2.4!, дважды переделывали по моей просьбе, молодцы: и чужие и свои прогоны готовой реализации на 512 MPI проц. Blue Gene/P<br />
<br />
https://algowiki-project.org/ru/Участник:Бротиковская_Данута/Алгоритм_k-means<br />
<br />
+++!!! (5)(5) готовая реализация до 512 на Ломоносове, хороший 2.4, лучший 2.7 и 3, написан даже п.2.6!!<br />
<br />
https://algowiki-project.org/ru/Участник:Илья_Егоров/Алгоритм_k-средних<br />
<br />
+++ (5-)(5-) самостоятельная реализация OpenMP до 16 нитей Ломоносова (жаль что считали на головном узле?! поэтому-то и минус); очень красивые графы!<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Алгоритм_k_средних_(k-means)<br />
<br />
=== Другие (5): ===<br />
<br />
https://algowiki-project.org/ru/Участник:Bagnikita/Face_Recognition<br />
<br />
... (5+)(5+) заполнены все! пункты; лучшая работа, несмотря на некоторое смешивание рассмотрения "метода" и используемых в нем "алгоритмов", но по другому здесь сделать и не возможно (ждем мелкой правки...)(давно уже ждем...)(несмотря на ожидание все равно оценка 5+, но в расчете на исправления пока так и не поставил метку "принято")<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Распознование_лиц<br />
<br />
<br />
https://algowiki-project.org/ru/Участник:EasyBreezy/Стабилизированный_метод_биспоряженных_градиентов_(BiCGSTAB)<br />
<br />
... (4)(4) единственное описание на эту важную тему, вопросов много, хотелось бы довести до конца (тогда возможна и оценка 5); реализация HYPRE на 16 проц. Ломоносова (ждем уже давно... кое-что поправлено, но мало... без их помощи труднее будет довести статью до включения в основную вики, пока не ставлю метку "принято")<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Стабилизированный_метод_бисопряженных_градиентов_(BiCGStab)<br />
<br />
<br />
https://algowiki-project.org/ru/Участница:Александра/Метод_встречи_посередине<br />
<br />
+++!!! (5+)(5+) лучшее описание по этой теме; слегка изменила чужую реализацию и запустила на 8 ядрах Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Огнева_Мария/Метод_встречи_посередине <br />
<br />
... (4)(--) нет прогонов для п.2.4, хотя реализация ей и известна... (все-таки оценка 4, особенно по сравнению с другими работами... хотя выполняла она одна, а за первую часть можно было бы поставить и 5-)<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Метод_встречи_посередине<br />
<br />
<br />
https://algowiki-project.org/ru/VladimirDobrovolsky611/Алгоритм_SDDP<br />
<br />
+++ (4)(4) нормально описано, после небольшой правки можно выкладывать. Рисунки не совсем законченные. Собственная реализация автором не выложена.<br />
<br />
ГОТОВО: http://algowiki-project.org/ru/Стохастическое_двойственное_динамическое_программирование_(SDDP)</div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Konshin&diff=25291Обсуждение участника:Konshin2018-01-17T12:36:51Z<p>Konshin: /* Алгоритм_14, Решение системы нелинейных уравнений методом Ньютона (4): */</p>
<hr />
<div>* явных списываний нет ни в одном из алгоритмов!<br />
* правда в алгоритме 19 (Алгоритм k средних) у 3-х из 4-х авторов использована одна и та же открытая реализация<br />
* все работы очень хорошие! только 4 оценки 4, остальные 5<br />
* в принципе для оценки выполнены все работы, хотя в 3-х было бы неплохо дождаться мелкой правки...<br />
<br />
Обозначения:<br />
<br />
* ... - ждем ответа на замечания<br />
* +++ - работа выполнена<br />
* +++!!! - выполнена и является претендентом на внесение в AlgoWiki<br />
* (X)(Y) - оценки по разделам 1 и 2, соответственно<br />
<br />
<br />
=== Алгоритм_4, Алгоритм Ланцоша (итерационный метод вычисления собственных значений симметричной матрицы) для точной арифметики (без переортогонализации) (6): ===<br />
<br />
https://algowiki-project.org/ru/Участник:Alexbashirov/Алгоритм_Ланцоша_для_точной_арифметики<br />
<br />
+++ (4)(4) по сравнению с другими замечательными описаниями и из-за задержки с п.2.4 оценка снижена, хотя формально все сделано, можно поставить и 5-<br />
<br />
https://algowiki-project.org/ru/Участник:A.Freeman/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (5)(5) собственная реализация на 256 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Danyanya/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации) <br />
<br />
+++ (5-)(5-) собственная реализация на 128 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:AleksLevin/Алгоритм_Ланцоша_вычисления_собственных_значений_симметричной_матрицы_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (5)(5) п.3! собственная реализация на 256 проц. Ломоносова; не очень понятно, открыт ли полный доступ к коду...<br />
<br />
https://algowiki-project.org/ru/Участник:VolkovNikita94/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++!!! (5-)(5+) Никита Волков; п.2.7 лучший!; реализация MPI+OpenMP тоже лучшая! должны еще поправить рис.4-6...<br />
<br />
https://algowiki-project.org/ru/Участник:Shostix/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (4)(4) нормально<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
=== Алгоритм_14, Решение системы нелинейных уравнений методом Ньютона (4): ===<br />
<br />
https://algowiki-project.org/ru/Участник:N_Zakharov/Метод_Ньютона_для_решения_систем_нелинейных_уравнений<br />
<br />
+++ (4+)(4+) пример из PETSc до 128 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Oleggium/Метод_Ньютона_для_решения_систем_нелинейных_уравнений(2)<br />
<br />
+++ (5)(5-) красивый граф, самостоятельная реализация на CUDA до 400 потока на персоналке, но бледно описано; все сделано, местами лучшие описания, но несколько лаконично :( <br />
<br />
https://algowiki-project.org/ru/Участник:SKirill/Метод_Ньютона_решения_систем_нелинейных_уравнений<br />
<br />
+++ (5-)(5) необычные и интересные блоксхемы п.1.5! и 1.7!; самостоятельная MPI реализация до 128 нитей; лучший п.2.7!<br />
<br />
[https://algowiki-project.org/ru/Участник:Арутюнов_А.В. https://algowiki-project.org/ru/Участник:Арутюнов_А.В.] (!!! осторожно с точкой в конце адреса !!!)<br />
<br />
+++ (4)(4) нормально<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Метод_Ньютона_для_систем_нелинейных_уравнений<br />
<br />
=== Алгоритм_19, Алгоритм k средних (4): ===<br />
<br />
https://algowiki-project.org/ru/Участник:IanaV/Алгоритм_k_means<br />
<br />
+++ (5)(5) хороший текст, особенно п.1.10, отличные графы, но витиеватый язык, видимо списано из учебника, чужая прогр. до 512 MPI проц. Ломоносова (там же имеются OpenMP и CUDA версии, но они не исследовались, собственно, как и 2-ми и 3-ми авторами)<br />
<br />
https://algowiki-project.org/ru/Участник:Parkhomenko/Алгоритм_k_средних<br />
<br />
+++ (5-)(5) специфически, но интересно представлены результаты в п.2.4!, дважды переделывали по моей просьбе, молодцы: и чужие и свои прогоны готовой реализации на 512 MPI проц. Blue Gene/P<br />
<br />
https://algowiki-project.org/ru/Участник:Бротиковская_Данута/Алгоритм_k-means<br />
<br />
+++!!! (5)(5) готовая реализация до 512 на Ломоносове, хороший 2.4, лучший 2.7 и 3, написан даже п.2.6!!<br />
<br />
https://algowiki-project.org/ru/Участник:Илья_Егоров/Алгоритм_k-средних<br />
<br />
+++ (5-)(5-) самостоятельная реализация OpenMP до 16 нитей Ломоносова (жаль что считали на головном узле?! поэтому-то и минус); очень красивые графы!<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Алгоритм_k_средних_(k-means)<br />
<br />
=== Другие (5): ===<br />
<br />
https://algowiki-project.org/ru/Участник:Bagnikita/Face_Recognition<br />
<br />
... (5+)(5+) заполнены все! пункты; лучшая работа, несмотря на некоторое смешивание рассмотрения "метода" и используемых в нем "алгоритмов", но по другому здесь сделать и не возможно (ждем мелкой правки...)(давно уже ждем...)(несмотря на ожидание все равно оценка 5+, но в расчете на исправления пока так и не поставил метку "принято")<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Распознование_лиц<br />
<br />
<br />
https://algowiki-project.org/ru/Участник:EasyBreezy/Стабилизированный_метод_биспоряженных_градиентов_(BiCGSTAB)<br />
<br />
... (4)(4) единственное описание на эту важную тему, вопросов много, хотелось бы довести до конца (тогда возможна и оценка 5); реализация HYPRE на 16 проц. Ломоносова (ждем уже давно... кое-что поправлено, но мало... без их помощи труднее будет довести статью до включения в основную вики, пока не ставлю метку "принято")<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Стабилизированный_метод_бисопряженных_градиентов_(BiCGStab)<br />
<br />
<br />
https://algowiki-project.org/ru/Участница:Александра/Метод_встречи_посередине<br />
<br />
+++!!! (5+)(5+) лучшее описание по этой теме; слегка изменила чужую реализацию и запустила на 8 ядрах Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Огнева_Мария/Метод_встречи_посередине <br />
<br />
... (4)(--) нет прогонов для п.2.4, хотя реализация ей и известна... (все-таки оценка 4, особенно по сравнению с другими работами... хотя выполняла она одна, а за первую часть можно было бы поставить и 5-)<br />
<br />
ГОТОВО (Батарина): https://algowiki-project.org/ru/Метод_встречи_посередине<br />
<br />
<br />
https://algowiki-project.org/ru/VladimirDobrovolsky611/Алгоритм_SDDP<br />
<br />
+++ (4)(4) нормально описано, после небольшой правки можно выкладывать. Рисунки не совсем законченные. Собственная реализация автором не выложена.<br />
<br />
ГОТОВО: http://algowiki-project.org/ru/Стохастическое_двойственное_динамическое_программирование_(SDDP)</div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9B%D0%B0%D0%BD%D1%86%D0%BE%D1%88%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%82%D0%BE%D1%87%D0%BD%D0%BE%D0%B9_%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B5%D1%82%D0%B8%D0%BA%D0%B8_(%D0%B1%D0%B5%D0%B7_%D0%BF%D0%B5%D1%80%D0%B5%D0%BE%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8)&diff=25288Алгоритм Ланцоша для точной арифметики (без переортогонализации)2018-01-17T09:48:52Z<p>Konshin: </p>
<hr />
<div>Основные авторы статьи (разделы 1, 2.4, 2.7):<br />
[https://algowiki-project.org/ru/Участник:Bashleon<b>Башлыков Л.А.</b>] и<br />
[https://algowiki-project.org/ru/Участник:VolkovNikita94<b>Волков Н.И.</b>]<br/><br />
Авторы отдельных разделов (2.4, 2.7):<br />
[https://algowiki-project.org/ru/Участник:M.grigoriev<b>Григорьев М.А.</b>],<br />
[https://algowiki-project.org/ru/Участник:KAKTUZ<b>Заспа А.Ю.</b>],<br />
[https://algowiki-project.org/ru/Участник:AleksLevin<b>Левин А.Д.</b>],<br />
[https://algowiki-project.org/ru/Участник:Danyanya<b>Слюсарь Д.Р.</b>],<br />
[https://algowiki-project.org/ru/Участник:A.Freeman<b>Фролов А.А.</b>] и<br />
[https://algowiki-project.org/ru/Участник:Shostix<b>Шостик А.В.</b>]<br/><br />
<!-- В работе также принимали участие:<br />
[https://algowiki-project.org/ru/Участник:ASA<b>Антонов А.С.</b>] и<br />
[https://algowiki-project.org/ru/Участник:Konshin<b>Коньшин И.Н.</b>]<br/> --><br />
<br />
{{algorithm<br />
| name = Алгоритм Ланцоша для точной арифметики (без переортогонализации)<br />
| serial_complexity = <math>O(n^2) + O(k^3)</math> - на одну итерацию и <math>O(kn^2)</math> - суммарно для <math>k</math> итераций<br />
| pf_height = <math>O(n)</math><br />
| pf_width = <math>O(n^2)</math><br />
| input_data = <math>n^2 + n + 1</math>, при экономии памяти затраты сокращаются до <math>\frac{n(n - 1)}{2} + n + 1</math><br />
| output_data = <math>k</math>, а если нужны собственные векторы, то <math>k + kn</math><br />
}}<br />
<br />
==Свойства и структура алгоритма==<br />
<br />
===Общее описание алгоритма===<br />
<br />
'''Алгоритм Ланцоша''' был опубликован венгерским математиком и физиком Корнелием Ланцошем<ref>https://ru.wikipedia.org/wiki/Ланцош,_Корнелий</ref> в 1950 году<ref>Lanczos, C. "An iteration method for the solution of the eigenvalue problem of linear differential and integral operators", J. Res. Nat’l Bur. Std. 45, 255-282 (1950).</ref>. Этот метод является частным случаем алгоритма Арнольда в случае, если исходная матрица <math>A</math> - симметрична, и был представлен как итерационный метод вычисления собственных значений симметричной матрицы. Этот метод позволяет за <math>k</math> итераций вычислять <math>k</math> приближений собственных значений и собственных векторов исходной матрицы. Хотя алгоритм и был эффективным в вычислительном смысле, но он на некоторое время был предан забвению из-за численной неустойчивости. Только в 1970 Ojalvo и Newman модифицировали алгоритм для использования в арифметике с плавающей точкой<ref>Ojalvo, I.U. and Newman, M., "Vibration modes of large structures by an automatic matrix-reduction method", AIAA J., 8 (7), 1234–1239 (1970).</ref>. Новый метод получил название [[Алгоритм Ланцоша с полной переортогонализацией | алгоритма Ланцоша с полной переортогонализацией]]. <br />
<br />
В данной статье рассматривается исходная версия алгоритма<ref>Деммель Дж. Вычислительная линейная алгебра. Теория и приложения - М.: Мир, 2001. ([http://www.bookvoed.ru/book?id=5252274 см.])</ref>.<br />
<br />
===Математическое описание алгоритма===<br />
<br />
Математическое описание алгоритма Ланцоша для вычисления собственных значений симметричной матрицы А<br />
требует 1) математического описания метода Ланцоща для построения крыловского подпространства и 2)<br />
математического описания т.н. процедуры Рэлея-Ритца.<br />
<br />
==== Метод Ланцоша для построения крыловского подпространства ====<br />
<br />
Пусть дано уравнение <math>Ax = b</math>, причём вектор <math>b</math> известен и имеется<br />
способ вычисления <math>Ax</math>.<br/><br />
<br />
Вычислим <math>y_{1} = b</math>, <math>y_{2} = Ab</math>, <math>y_{3} = A^{2}b</math>,...,<math>y_{n} = A^{(n - 1)}b</math>, где <math>n</math> - <br />
порядок матрицы <math>A</math>. Определим матрицу <math>K = [y_{1},...,y_{n}]</math>.<br/><br />
<br />
Тогда <math>AK = [Ay_{1},...,Ay_{n - 1}, Ay_{n}] = [y_{2},...,y_{n},A^n{y_{1}}]</math>. <br />
В предположении, что матрица <math>K</math> - невырождена, можно вычислить вектор <math>c = -K^{-1}A^{ny_{1}}</math>.<br />
Поскольку первые <math>n - 1</math> столбцов в <math>AK</math> совпадают с последними <math>n - 1</math> столбцами в<br />
<math>K</math>, то справедливо: <math>AK = K[e_{2},e_{3},...,e_{n},-c] = KC</math>, то есть <math>K^{-1}AK = C</math>, где <math>C</math> - верхняя хессенбергова матрица.<br/><br />
<br />
Заменим <math>K</math> ортогональной матрицей <math>Q</math> так, что при любом <math>k</math><br />
линейные оболочки первых <math>k</math> столбцов в <math>K</math> и <math>Q</math> - одно и то же<br />
подпространство. Это подпространство называется крыловским и точно определяется как<br />
<math>\kappa_{k}(A, b)</math> - линейная оболочка векторов <math>b</math>,<math>Ab</math>,...,<math>A^{(k - 1)}b</math>.<br />
Суть алгоритма Ланцоша заключается в вычислении стольких первых столбцов в <math>Q</math>,<br />
сколько необходимо для получения требуемого приближения к решению <math>Ax = b </math> (<math>Ax = {\lambda}x</math>).<br />
<br />
Используем QR-разложение матрицы <math>K = QQ</math><br/><br />
Тогда <math>K^{-1}AK = (R^{-1}Q^{T})A(QR) = C</math>, откуда <math>Q^{T}AQ = RCR^{-1} = H</math><br/><br />
<br />
Матрица <math>H</math> является верхней хессенберговой в силу того, что верхней хессенберговой<br />
является матрица <math>C</math>, а матрицы <math>R</math> и <math>R^{-1}</math> - верхние треугольные.<br />
<br />
Однако алгоритм Ланцоша подразумевает симметричность матрицы <math>A</math>.<br />
Положим <math>Q = [Q_{k}, Q_{u}]</math>, где <math>Q_{k} = [q_{1},...,q_{k}]</math> и <br />
<math>Q_{u} = [q_{k+1},...,q_{n}]</math>. В итоге получается, что <math>H = Q^{T}AQ = [Q_{k},Q_{u}]^{T}A[Q_{k},Q_{u}]</math><br />
Из симметричности <math>A</math>, таким образом, следует симметричность и трехдиагональность матрицы <math>H = T</math>.<br />
Теперь можно вывести две основные формулы, используемые в методе Ланцоша:<br />
<br />
*<math>AQ_{j} = \beta_{j - 1}q_{j - 1} + \alpha_{j}q_{j} + \beta_{j}q_{j + 1}</math> - получается приравниванием столбцов <math>j</math> в обеих частях равенства <math>AQ = QT</math>.<br />
*<math>q_{j}^{T}Aq_{j} = \alpha_{j}</math> - получается домножением обеих частей предыдущего соотношения на <math>q_{j}^{T}</math> и учетом ортогональности <math>Q</math>.<br />
<br />
==== Процедура Рэлея-Ритца ====<br />
<br />
Пусть <math>Q = [Q_{k}, Q_{u}]</math> - произвольная ортогональная матрица порядка <math>n</math>, причём<br />
<math>Q_{k}</math> и <math>Q_{u}</math> имеют размеры <math>nk</math> и <math>n(n - k)</math>.<br />
<br />
Пусть <math>T = Q^{T}AQ = [Q_{k},Q_{u}]^{T}A[Q_{k},Q_{u}]</math>. Обратим внимание, что в случае <math>k = 1</math> выражение <math>T_{k} = Q_{k}^{T}AQ_{k}</math> -<br />
отношение Рэлея <math>\rho(Q_{1},A)</math>, то есть число <math>(Q_{1}^{T}AQ_{1}) / (Q_{1}^{T}Q_{1})</math>. Определим теперь суть<br />
процедуры Рэлея-Ритца:<br/><br />
<br />
*Процедура Рэлея-Ритца - интерпретация собственных значений матрицы <math>T = Q^{T}AQ</math> как приближений к собственным значениям матрицы <math>A</math>. Эти приближения называются числами Ритца. Пусть <math>T_{k} = V{\lambda}V^{T}</math> - спектральное разложение матрицы <math>T_{k}</math>. Столбцы матрицы <math>Q_{k}V</math> рассматриваются как приближения к соответствующим собственным векторам и называются векторами Ритца.<br />
<br />
Существует несколько важных фактов о процедуре Рэлея-Ритца, характеризующих получаемые приближения собственных значений матрицы <math>A</math>.<br />
<br />
* Минимум величины <math>||AQ_{k} - Q_{k}R||_{2}</math> по всем симметричным <math>k^2</math> матрицам <math>R</math> достигается при <math>R = T_{k}</math>. При этом <math>||AQ_{k} - Q_{k}R||_{2} = ||T_{ku}||_{2}</math>. Пусть <math>T_{k} = V{\lambda}V^{T}</math> - спектральное разложение матрицы <math>T_{k}</math>. Минимум величины <math>||AP_{k} - P_{k}D||_{2}</math>, когда <math>P_{k}</math> пробегает множество <math>nk </math> матрицы с ортонормированными столбцами, таких, что <math>span(P_{k}) = span(Q_{k})</math>, а <math>D</math> пробегает множество диагональных <math>k^2</math> матриц также равен <math>||T_{ku}||_{2}</math> и достигается для <math>P_{k} = Q_{k}V</math> и <math>D = \lambda</math>.<br />
<br />
Пусть <math>T_{k}</math>, <math>T_{ku}</math>, <math>Q_{k}</math> - те же самые матрицы. Пусть <math>T_{k} = V{\lambda}V^{T}</math> - всё то же спектральное разложение,<br />
причём <math>V = [v_{1},...,v_{k}]</math> - ортогональная матрица, а <math>\lambda = diag(\theta_{1},...,\theta_{k})</math>. Тогда:<br />
<br />
* Найдутся <math>k</math> необязательно наибольших собственных значений <math>\alpha_{1},...,\alpha_{k}</math> матрицы <math>A</math>, такие, что <math>|\theta_{i} - \alpha_{i}| <= ||T_{ku}||_{2}</math> для <math>i = 1,...,k</math>. Если <math>Q_{k}</math> вычислена методом Ланцоша,то правая часть этого выражения равняется <math>\beta_{k}</math>, то есть единственному элементу блока <math>T_{ku}</math>, который может быть отличен от нуля. Указанный эдемент находится в правом верхнем углу блока.<br />
* <math>||A(Q_{k}v_{i}) - (Q_{k}v_{i})\theta_{i}||_{2} = ||T_{ku}v_{i}||_{2}</math>. Таким образом, разность между числом Ритца <math>\theta_{j}</math> и некоторым собственным значением <math>\alpha</math> матрицы <math>A</math> не превышает величины <math>||T_{ku}v_{i}||_{2}</math>, которая может быть много меньше, чем <math>||T_{ku}||_{2}</math>. Если матрица <math>Q_{k}</math> вычислена алгоритмом Ланцоша, то эта величина равна <math>\beta_{k}|v_{i}(k)|</math>, где <math>v_{i}(k)</math> - k-ая компонента вектора <math>v_{i}</math>. Таким образом, можно дещево вычислить норму невязки - левой части выражения, не выполняя ни одного умножения вектора на матрицы <math>Q_{k}</math> или <math>A</math>.<br />
* Не имея информации о спектре матрицы <math>T_{u}</math>, нельзя дать какую-либо содержательную оценку для погрешности в векторе Ритца <math>Q_{k}v_{i}</math>. Если известно, что <math>\theta_{j}</math> отделено расстоянием, не меньшим <math>g</math>, от прочих собственных значений матриц <math>T_{k}</math>, <math>T_{u}</math> и матрица <math>Q_{k}</math> вычислена алгоритмом Ланцоша, то угол <math>\theta</math> между <math>Q_{k}v_{i}</math> и точным собственным вектором <math>A</math> можно оценить формулой <math>\frac{1}{2}\sin{2\theta} <= \frac{b_{k}}{g}</math><br />
<br />
В алгоритме Ланцоша из ортонормированных векторов строится матрица <math>Q_{k} = [q_{1},...,q_{k}]</math> и в качестве приближенных собственных значений <math>A</math> принимаются числа Ритца - собственные значения симметричной трёхдиагональной матрицы <math>T_{k} = Q_{k}^{T}AQ_{k}</math>.<br />
<br />
===Вычислительное ядро алгоритма===<br />
<br />
Вычислительное ядро алгоритма Ланцоща состоит в получении на каждой итерации очередного промежуточного вектора <math>z</math>, получаемого путём умножения исходной матрицы <math>A</math> на вектор Ланцоша <math>q_{j}</math>, полученный на предыдущей итерации.<br />
<br />
Также при значениях <math>k</math>, сопоставимых с <math>n</math>, процедура вычисления собственных значений и собственных векторов симметричной трёхдиагональной матрицы по некоторому выбранному алгоритму подразумевает значительный объём расчётов и может считаться вычислительным ядром. Однако на практике метод используется прежде всего при малых <math>k</math>.<br />
<br />
===Макроструктура алгоритма===<br />
<br />
Метод Ланцоша соединяет метод Ланцоша для построения крыловского подпространства в процедурой Рэлея-Ритца, подразумевающей вычисление собственных значений симметричной трёхдиагональной матрицы. Первая часть алгоритма представляет собой строго последовательные итерации, на каждой из которых вначале строится очередной столбец матрицы <math>Q_{j}</math> из первых <math>j</math> ортонормированных векторов Ланцоша и матрица <math>T_{j} = Q^T_{j}AQ_{j}</math> порядка <math>j</math>. Итерационный процесс завершается, когда <math>j = k</math>. Вторая часть алгоритма представляет собой поиск собственных значений и собственных векторов симметричной трёхдиагональной матрицы <math>T_{j}</math>, что реализуется отдельным алгоритмом, на практике используется QR-алгоритм.<br/><br />
<br />
Особенность методов крыловского подпространства состоит в предположении, что матрица <math>A</math> доступна в виде "черного ящика", а именно подпрограммы, вычисляющей по заданному вектору <math>z</math> произведение <math>y = Az</math> (а также, возможно, произведение <math>y = A^{T}z</math>, если матрица <math>A</math> несимметрична). Другими словами, прямой доступ к элементам матрицы и их изменение не используются, т.к. <br />
* Умножение матрицы на вектор - наиболее дешевая нетривиальная операция, которую можно проделать с разреженной матрицей: в случае разреженной <math>A</math>, содержащей <math>m</math> ненулевых элементов, для матрично-векторного умножения нужны <math>m</math> скалярных умножений и не более <math>m</math> сложений.<br />
* <math>A</math> может быть не представлена в виде матрицы явно, а доступна именно через подпрограмму для вычисления произведений <math>Ax</math>.<br />
Таким образом, как QR-алгоритм поиска собственных значений, собственных векторов матрицы <math>T_{j}</math> и оценки погрешности в них результирующей трехдиагональной матрицы, так и умножение матрицы на вектор внутри каждой итерации могут быть рассмотрены в качестве отдельных макроопераций. Проблемой является то, что именно эти манипуляции порождают основную вычислительную сложность алгоритма. Поскольку матрица, к которой применяется QR-алгоритм, имеет порядок <math>k</math> и на практике бывает мала, сделаем допущение о плотности матрицы <math>A</math>. Тогда умножение этой матрицы на вектор внутри каждой итерации нужно будет реализовывать в рамках самого алгоритма. Еще одним важным замечанием является момент вычисления собственных значений матрицы <math>T_{j}</math>. Её собственные значения, полученные на итерации <math>p</math>, никак не используются на итерации <math>p + 1</math>, поэтому целесообразно вынести эту процедуру за основной цикл. Однако на практике в силу малых размеров матрицы <math>T_{j}</math> вычисление соответствующих величин производится непосредственно в теле основного цикла, что позволяет сразу оценить достигнутую точность вычислений.<br />
<br />
===Схема последовательной реализации алгоритма===<br />
<br />
Алгоритм итерационный, на каждой итерации выполняется вычисление очередного столбца матрицы <math>Q</math>, а также вычисление элементов очередной пары (диагонального и околодиагонального) элементов матрицы <math>T</math> с использованием значений столбца матрицы <math>Q</math>, полученного на предыдущей итерации и значения околодиагонального элемента матрицы <math>T</math> с предыдущей итерации. Вектор <math>z</math> носит вспомогательный характер. Коэффициенты <math>\alpha_{j}</math> соответствуют диагональным элементам матрицы <math>T_{j}</math>, коэффициенты <math>\beta_{j}</math> – наддиагональным и поддиагональным элементам той же матрицы. Далее приводится пример псевдокода. По окончании итераций вычислеяются собственные значения и собственные векторы матрицы <math>T_{j}</math>, что в псевдокоде не отражается (т.к. это отдельный алгоритм).<br />
<br />
Вычислить столбец q1 матрицы Qk q1 = b / ||b||<br />
q0 - нулевой вектор<br />
beta_0 = 0<br />
foreach ( j = 1 .. k ) <br />
{<br />
z = A * qj<br />
alpha_j = qj * z <br />
z = z – alpha_j * qj – beta_(j-1) * q(j – 1)<br />
beta_j = ||z||<br />
if ( beta_j == 0 )<br />
{<br />
break // Все ненулевые собственные значения найдены. Выход может быть досрочным.<br />
}<br />
else <br />
{<br />
q(j + 1) = z/beta_j<br />
}<br />
}<br />
<br />
Вычислить собственные значения и собственные векторы симметричной трехдиагональной матрицы Tj наиболее подходящим методом.<br />
Полученный вектор Lj есть искомые собственные значения.<br />
<br />
===Последовательная сложность алгоритма===<br />
<br />
Все дальнейшие выкладки верны для наиболее быстрого последовательного варианта выполнения указываемых операций. <br />
* Умножение квадратной матрицы порядка <math>n</math> на вектор длины <math>n</math> требует <math>n^2</math> умножений и сложений.<br />
* Перемножение векторов длины <math>n</math> требует по <math>n</math> умножений и сложений.<br/><br />
* Поэлементное сложение векторов длины <math>n</math> требует <math>n</math> сложений.<br />
* Умножение вектора длины <math>n</math> на число требует <math>n</math> умножений.<br />
* Нахождение квадратичной нормы вектора длины <math>n</math> требует по <math>n</math> умножений и сложений, а также одну операцию извлечения квадратного корня.<br />
* Вычисление собственных значений матрицы порядка <math>k</math> QR-алгоритмом требует <math>O(k^2)</math> операций, вычисление также и собственных векторов требует примерно <math>6k^3</math>, то есть <math>O(k^3)</math> операций; при использовании метода «Разделяй-и-властвуй» для вычисления собственных значений и векторов аналогичной матрицы в среднем затрачивается <math>O(k^{2})</math> операций.<br />
Таким образом, для выполнения одной итерации метода Ланцоша требуется 1 операция вычисления квадратного корня, <math>n</math> умножений, а также по <math>n ^ 2 + n + 2n + n</math> сложений и умножений, а также суммарно <math>O(k ^ 3)</math> операций, требуемых для поиска собственных значений и собственных векторов матрицы <math>T_{j}</math>. Таким образом последовательная сложность алгоритма Ланцоша составляет <math>O(n ^ 2) + O(k ^ 3)</math>. Это, очевидно, меньше чем <math>O(n^3)</math> операций, требуемых в алгоритмах вычисления всех собственных значений произвольных симметричных матриц, в чём и заключается эффективность применения метода Ланцоша.<br />
<br />
===Информационный граф===<br />
<br />
Информационный граф алгоритма, определяемый в <ref>Параллельные вычисления (Воеводин В.В., Воеводин Вл.В.) - Спб, изд-во "БХВ-Петербург", 2002 ([https://parallel.ru/news/bhv_parallelcomputing.html см.])</ref> требует следующего замечания: подразумевается, что, например, умножение квадратной матрицы на вектор распараллеливается на <math>n</math> потоков, где <math>n</math> - порядок матрицы, а не на <math>n\log{n}</math> потоков, чего можно было бы достичь, используя суммирование сдваиванием. Это допущение аналогично допущению из статьи [https://algowiki-project.org/ru/%D0%A3%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D0%BB%D0%BE%D1%82%D0%BD%D0%BE%D0%B9_%D0%BD%D0%B5%D0%BE%D1%81%D0%BE%D0%B1%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BD%D0%B0_%D0%B2%D0%B5%D0%BA%D1%82%D0%BE%D1%80_(%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82) Умножение матрицы на вектор] и Это же допущение будет использовано и при оценке ресурса параллелизма алгоритма. Приводится граф без отображения входных и выходных данных.<br />
<br />
[[File:Volkov Lanzcos 1.png|600px|thumb|center|Рис. 1: Граф алгоритма для матрицы А порядка 5 и k = 3. Op - одна итерация алгоритма, Eigen - процедура вычисления собственных значений]]<br />
[[File:Volkov Lanzcos 2.png|600px|thumb|center|Рис. 2: Внутренний граф итерации алгоритма для матрицы A порядка 5. Vector sum - нахождение суммы элементов вектора-<b>поэлементного произведения</b> векторов z и qj. Op - вычитания из z векторов qj и q(j - 1), домноженных на коэффициенты. Norm - вычисление нормы вектора z.]]<br />
<br />
===Ресурс параллелизма алгоритма===<br />
<br />
Каждая итерация алгоритма Ланцоша выполняется строго последовательно. Однако внутри итерации возможно распараллеливание алгоритма.<br />
<br />
* Умножение квадратной матрицы порядка <math>n</math> на вектор длины <math>n</math> требует последовательного выполнения <math>n</math> ярусов умножений и сложений.<br />
* Все остальные операции в рамках одной итерации, ведущие к вычислению матрицы <math>T_{j}</math> выполняются строго последовательно. При этом вычисление значений векторов может быть выполнено за 1 ярус умножений / сложений, а вычисление чисел - за <math>\log{n}</math> таких операций.<br />
* Ресурс параллелизма алгоритма вычисления собственных значений зависит от используемого алгоритма и рассматривается в соответствующей статье.<br />
<br />
Таким образом, при классификации по ширине ЯПФ алгоритм обладает <i>линейной</i> сложностью. В случае классификации по высоте ЯПФ алгоритм также имеет <i>линейную</i> сложность.<br />
<br />
===Входные и выходные данные алгоритма===<br />
<br />
<b>Входные данные: </b> квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math>, число <math>k</math><br/><br />
<b>Объём входных данных:</b> <math>n^2 + n + 1</math><br/><br />
<b>Выходные данные:</b> вектор <math>L</math>, матрица <math>E</math> (элементы <math>e_{pq}</math>)<br/><br />
<b>Объём выходных данных:</b> <math>k + kn</math><br/><br />
<br />
===Свойства алгоритма===<br />
<br />
В случае неограниченности ресурсов соотношение последовательной и параллельной сложности алгоритма Ланцоща по сумме всех итераций представляет собой <math>k ^ 3 + kn ^ 2</math> к <math>X + kn</math>, где <math>X</math> - параллельная сложность алгоритма, используемого для вычисления собственных значений. В случае использования метода вычисления собственных значений с линейной сложностью при классисифкации по высоте ЯПФ это соотношение будет равно <math>(k ^ 2 + n ^ 2) / (k + n)</math>. <br />
<br />
Алгоритм Ланцоша не является полностью детерминированным в том смысле, что возможно выполнение числа итераций алгоритма, меньшего, чем заданное (из-за того, что вычислены все ненулевые собственные значения матрицы <math>A</math>). <br />
<br />
Важное свойство метода Ланцоша состоит в том, что первыми в матрице <math>T_{j}</math> появляются собственные значения с максимальной величиной по модулю. Таким образом, метод особенно хорошо подходит для вычисления собственных значений матрицы <math>A</math>, находящихся на краях её спектра.<br />
<br />
==Программная реализация алгоритма==<br />
===Особенности реализации последователього алгоритма===<br />
===Локальность данных и вычислений===<br />
===Возможные способы и особенности параллельной реализации алгоритма===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций алгоритма Ланцоша без переортогонализации согласно [[Scalability methodology|методике]] AlgoWiki. Исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
Для проверки масштабируемости алгоритма из реализации была исключена непосредственно задача поиска собственных значений матрицы <math>T_{j}</math> на каждой итерации, так как это является предметом отдельных статей ([https://algowiki-project.org/ru/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:F-morozov/%D0%9D%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D1%87%D0%B8%D1%81%D0%B5%D0%BB_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_QR_%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_(4) QR-алгоритм], метод [https://algowiki-project.org/ru/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%C2%AB%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9%C2%BB_%D0%B2%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B9_%D0%B8_%D0%B2%D0%B5%D0%BA%D1%82%D0%BE%D1%80%D0%BE%D0%B2_%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D0%BE%D0%B9_%D1%82%D1%80%D0%B5%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B «Разделяй-и-властвуй»], [https://en.wikipedia.org/wiki/Arnoldi_iteration итерации Арнольди] и.т.д.). Таким образом, проверяемая на масштабируемость задача сводится к итерационному вычислению коэффициентов матрицы <math>T_{j}</math>. Матрица <math>A</math> в рассматриваемой реализации хранится построчно и построчно же разделяется между процессорами. Также в целях исключения влияния недетерминированности алгоритма этот процесс изменен так, что при вычислении всех ненулевых собственных значений матрицы <math>A</math> работа алгоритма продолжается над фиктивными данными, пока не будет завершено заранее заданное число итераций. При этом в реализации используется OpenMP для распараллеливания в пределах одного узла. <br />
<br />
[[File:Ланцош_масштабы.png|1000px|thumb|center|Рис. 3: Производительность алгоритма для указанных значений размера задачи и числа MPI процессов (по 4 OpenMP нити каждый). Потенциальная максимальная производительность, которая может быть достигнута - 13.6 GFLOPS на 1 MPI процесс. Тогда, например, эффективность вычислений на 100 процессах при размере задачи 4000<math>{\cdot}</math>4000 составляет приблизительно 14.33%]]<br />
<br />
Набор изменяемых параметров запуска реализации алгоритма и границы значений параметров алгоритма:<br />
<br />
*Число MPI процессов [1 : 200]<br />
*Число OpenMP нитей [1 : 4] (Запуск с 1 OpenMP нитью производился только для 1 MPI процесса, ускорение замерялось относительно этого запуска).<br />
*Размерность матрицы [400 : 4000]<br />
<br />
Эффективность выполнения реализации алгоритма:<br />
<br />
*Минимальная эффективность - <1% (неактуально, так как в исследовании достигнут предел масштабируемости алгоритма).<br />
*Максимальная эффективность - ~30% (получается на 2-8 процессов при ~640000 ячеек матрицы на процесс; в случае 8 процессов это входная матрица 1600<math>{\cdot}</math>1600). <br />
<br />
Оценка масштабируемости:<br />
<br />
*По числу процессов - при увеличении числа процессов эффективность уменьшается на всей области рассмотренных значений, но не слишком интенсивно. Это связано с небольшим объёмом пересылок данных, реализованных через достаточно эффективные операции редукции.<br />
*По размеру задачи - при увеличении размера задачи эффективность вычислений вначале кратковременно возрастает, но затем начинает относительно равномерно убывать на всей рассматриваемой области.<br />
*По двум направлениям - при рассмотрении увеличения, как вычислительной сложности, так и числа процессов по всей рассмотренной области значений наибольшая эффективность наблюдается при соответствии числа процессов и размера задачи. По этой "диагонали" эффективность вначале кратковременно возрастает, затем начинает убывать.<br />
<br />
Интересно, что для разного числа процессоров положительные скачки производительности (зачастую - довольно заметные) имеют место на разных объёмах входных данных.<br />
<br />
Далее приводятся графики ускорения программы для разных объёмов входных данных. Ускорение считалось относительно варианта с 1 MPI процессом без дальнейшнего распараллеливания посредством OpenMP. На оси абсцисс отмечается число MPI процессов, число OpenMP нитей во всех случаях равно четырём.<br />
<br />
[[File:Speedup_2000.png|600px|thumb|center|Рис. 4: Ускорение для размера задачи 2000<math>{\cdot}</math>2000]]<br />
[[File:Speedup_3000.png|600px|thumb|center|Рис. 5: Ускорение для размера задачи 3000<math>{\cdot}</math>3000]]<br />
[[File:Speedup_4000.png|600px|thumb|center|Рис. 6: Ускорение для размера задачи 4000<math>{\cdot}</math>4000]]<br />
[[File:Speedup total.png|600px|thumb|center|Рис. 6: Сравнение ускорения для различных размеров задачи]]<br />
<br />
[https://github.com/VolkovNikita94/Lanczos Используемая реализация алгоритма]<br />
Реализация алгоритма гибридная, использует как MPI версии 2.2 (использование MPI_IN_PLACE), так и OpenMP. Указанный код компилировался с помощью компилятора IBM (mpixlcxx_r) с опцией -qsmp=omp (она включает в себя оптимизацию -O2). Реализация алгоритма полностью собственная и не использует встроенные библиотеки. Каждая "quadcore node" - это 1 узел системы BG/P, содержащий 4 микропроцессорных ядра IBM PowerPC 450 c суммарной пиковой производительностью 13.6 GFLOPS на узел и 2 GB общей памяти с пропускной способностью 13.6 GB/sec.<br />
<br />
==== Реализация 2 ====<br />
<br />
Для исследования масштабируемости рассматриваемого алгоритма Ланцоша без переортогонализации автором была реализована программа на языке С++ с использованием технологии MPI. <ref>https://goo.gl/pNOFmW или альтернативная ссылка с прикреплённым кодом: https://goo.gl/9AapM6 , по запросу автор реализации готов предоставить желающим доступ к проекту с этим кодом на ресурсе Bitbucket</ref> Дальнейшее исследование было проведено на суперкомпьютере "Ломоносов" Суперкомпьютерного комплекса МГУ.<ref>https://parallel.ru/cluster/lomonosov.html</ref> <br> Важно отметить, что реализованная программа выполняет только первую часть алгоритма - формирование трёхдиагональной матрицы <math >T_j</math>. Вторая часть алгоритма (задача поиска собственных значений матрицы <math >T_j</math>) может быть решена несколькими методами, каждый из которых обладает своей эффективностью распараллеливания и характерными свойствами. Подробно этот вопрос уже обсуждался в статье ранее, поэтому не будем ещё раз заострять на этом внимание и останавливаться.<br />
<br><br><br />
Сборка осуществлялась со следующими параметрами:<br />
* Компилятор intel/15.0.090 <br />
* Openmpi/1.8.4-icc<br />
В серии проведённых расчётов рассматривались следующие числовые значения параметров:<br />
* Размер <math style="vertical-align:0%;>n</math> задачи и, соответственно, матрицы <math style="vertical-align:0%;>A</math>, задействованной в операции умножения на вектор: [2000 : 30000] с шагом 1000<br />
* Число <math style="vertical-align:-20%;>p</math> процессоров: 1, 2, 4, 16, 32, 48, 64, 96, 128, 192, 256<br />
* Число итераций в алгоритме: <math style="vertical-align:0%;>k=350</math><br />
Число <math style="vertical-align:0%;>k</math> взято таким вопреки информации в начале статьи, где говорится, что значение <math style="vertical-align:0%;>k</math> редко превышает <math style="vertical-align:-10%;>10^2</math> (хоть мы и имеем право брать его любым по нашему усмотрению). Это сделано исключительно для лучшей визуализации пиков на графиках в данном разделе и наглядности получаемых результатов, так как при меньших на порядок значениях параметра <math style="vertical-align:0%;>k</math> времена выполнения программ были слишком малы даже при минимальных числах процессоров и максимальных размерах <math style="vertical-align:0%;>n</math> задачи.<br />
<br><br />
На рис. 2 изображён график зависимости времени выполнения программы от числа процессоров и размера <math style="vertical-align:0%;>n</math> задачи. <br><br />
{|align="center"<br />
|-valign="top"<br />
|[[file:Levin_task1_execTIME.png|620px|thumb|center|Рис. 2 График зависимости времени выполнения программы.]]<br />
|[[file:Levin604 task1 result EFFICIENCY.png|630px|thumb|center|Рис. 3 График зависимости эффективности распараллеливания программы.]]<br />
|}<br />
Многочисленные тесты показали при малых количествах ядер уменьшение времени выполнения программы почти в 2 раза при увеличении числа ядер в 2 раза на всём диапазоне размеров матриц, что и отражает сильный излом на первом графике. Изменение значений времени можно легко оценить по предоставленной цветовой шкале.<br />
Был построен график эффективности распараллеливания (parallel efficiency)<ref>https://goo.gl/WCNEjR</ref> в зависимости от числа процессоров и размера задачи, изображённый на рис. 3.<br />
Получены следующие результаты численных экспериментов:<br />
* Средняя эффективность распараллеливания программы составила: 42.5576% ;<br />
* Минимальная эффективность реализации: 0.674% (число процессоров 256 и минимальный размер матрицы 2000);<br />
* Максимальная эффективность реализации: 77.773% (число процессоров 2, размер матрицы 20000).<br />
Нужно понимать, что это лишь экстремумы, соответствующие двум экспериментам, которые сами по себе не дают полной картины и не позволяют однозначно судить о параллельных качествах алгоритма и реализующей его программы при столь большом числе параметров и проводимых расчётов. <br>На основании рис. 3 можно отметить следующие значимые и важные факты:<br />
* Имеется ярко выраженный спад эффективности при минимальном рассматриваемом размере матрицы <math style="vertical-align:0%;>n=2000</math> вдоль всей оси OY, отвечающей за число процессоров;<br />
* Присутствуют два пика эффективности (жёлтого цвета на рис. 3): при числе <math style="vertical-align:-20%;>p</math> процессоров 2 и 128. Затем при дальнейшем увеличении числа процессоров с 128 до 256 наблюдается постепенное уменьшение эффективности реализации. Это связано с увеличением накладных расходов и затрат времени на обмены сообщениями между процессами.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масшабируемости алгоритма была написана реализация[https://github.com/danyanya/ss16-task1] на языке C++ с использованием MPI. Реализация была протестирована на суперкомпьютере Ломоносов[http://parallel.ru/cluster/].<br />
<br />
Были исследованы:<br />
* время выполнения программы в зависимости от размера входных данных (матрицы);<br />
* время выполнения в зависимости от размера параллельных нод.<br />
<br />
Дополнительно было измерено время работы исходя из оптимизационных флагов компилятора GCC [https://gcc.gnu.org]. <br />
<br />
Параметры запуска алгоритма:<br />
* размер матрицы от 20000 до 175000 с шагом 2500;<br />
* количество процессоров от 8 до 128 с шагом 8.<br />
<br />
Программа запускалась на суперкомьютере "Ломоносов" со следующими характеристиками:<br />
* Компилятор GCC 5.2.1;<br />
* Версия MPI 1.8.4;<br />
* Сборка проводилась командой: <pre>mpic++ -std=c++0x -O2 <CPP_FILE> -lm -static-libstdc++</pre><br />
<br />
Полученная эффективность реализации варьируется в пределах от 2% (на маленьких входных данных) до 45% (на больших входных данных и максимальном числе нодов).<br />
<br />
На рисунке 4 представлена эффективность программы при отсутствии флага оптимизации O2. <br />
<br />
[[Файл:Grigorev_lanczos_eff_no_02.png|thumb|center|600px|Рисунок 4. Эффективность параллельной реализации алгоритма Ланцоща на MPI.]]<br />
<br />
На рисунке 5 представлена эффективность алгоритма с оптимизацией компилятора GCC (-O2).<br />
<br />
[[Файл:Grigorev_lanczos_eff_o2.png|thumb|center|600px|Рисунок 5. Эффективность параллельной реализации алгоритма Ланцоща на MPI с использованием оптимизации компилятора -O2.]]<br />
<br />
Как видно из графиков выше, средняя эффективность минимальна при малых входных данных (на размере матрицы в 10000). <br />
В среднем данный показатель держится между 9% и 14%.<br />
<br />
При этом, оптимизация компилятора MPIC++ позволяет получить выигрыш в эффективности более чем в 3 раза (45,5% против 14,7%). Однако, как можно заметить из графиков, при оптимизации алгоритм ведет себя более нестабильно, скорее всего из-за значительной траты времени на работу с памятью (более плавные участки свидетельствует о том, что необходимые данные нашлись в кеше).<br />
<br />
==== Реализация 4 ====<br />
<br />
Для исследования масшабируемости алгоритма была написана реализация[https://github.com/xrstalker/lanczos] на языке C с использованием MPI. Реализация была протестирована на суперкомпьютере Ломоносов[http://parallel.ru/cluster/].<br />
<br />
Сборка осуществлялась со следующими параметрами:<br />
<br />
* gcc-5.2.0<br />
* openmpi-1.8.4<br />
* аргументы компилятора: -std=c11 -Ofast<br />
<br />
Набор и границы значений изменяемых параметров реализации алгоритма: <br />
<br />
* число процессоров [32 : 256] с шагом 16;<br />
* размер матрицы [5000 : 200000] с шагом 5000.<br />
<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации 1.92%;<br />
* максимальная эффективность реализации 59.47%.<br />
<br />
График полученного распределения эффективности:<br />
<br />
[[file:Lanczos Without Orthogonalization Eff2Dist.png|thumb|center|600px|Рисунок 4. Параллельная реализация алгоритма Ланцоша. Распределение производительности.]]<br />
<br />
На следующих рисунках приведены графики производительности и эффективности данной реализации в зависимости от изменяемых параметров запуска.<br />
{|align="center"<br />
|-valign="top"<br />
|[[file:Lanczos Without Orthogonalization Perf2.png|thumb|center|600px|Рисунок 5. Параллельная реализация алгоритма Ланцоша. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
|[[file:Lanczos Without Orthogonalization Eff2.png|thumb|center|600px|Рисунок 6. Параллельная реализация алгоритма Ланцоша. Изменение эффективности в зависимости от числа процессоров и размера матрицы.]]<br />
|}<br />
<br />
Средняя эффективность для матриц с размером больше 25000 составила 52.7%.<br />
<br />
==== Реализация 5 ====<br />
<br />
Для изучения свойств масштабируемости алгоритма Ланцоша без переортогонализации былы исследованы результаты выполненных на с/к Ломоносов вычислений.<br />
<br />
В рамках алгоритма не решается проблема поиска собственных значений, т.к. эта задача является самостоятельной задачей.<br />
<br />
Для компиляции программы использовались компиляторы intel/15.0.090 и Openmpi/1.8.4-icc.<br />
<br />
Для проведения расчетов и получения полноценной картины поведения алгоритма в зависимости от входных данных и числа процессоров, программа была запущена на следующих параметрах:<br />
<br />
* В качестве размера входной матрицы подавались значения в диапазоне [2000:30000] c шагом 2000.<br />
* Число процессоров варьировалось от 1 до 256. <br />
* В качестве числа, отвечающего за количество выполняемых методом итераций бралось значение 100.<br />
<br />
Ниже на рисунке приведен трехмерный график, показывающий зависимость времени выполнения программы от входных данных.<br />
<br />
[[Файл:Lanczos_time_matrix.png|1000px|thumb|center| График зависимости времени выполнения программы от входных данных]]<br />
<br />
Также была исследована эффективность распараллеливания:<br />
* Средняя эффективность выполнения алгоритма составила порядка 12%<br />
* Наивысший показатель равен 43%<br />
* Минимальное значение составило менее 1%<br />
<br />
==== Реализация 6 ====<br />
<br />
Исследование проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster/].<br />
<br />
Исследовалась реализация<ref>http://pastebin.com/3W4uUVGj</ref> на языке C++ с использованием MPI. Компилятор GCC 5.2.1; версия MPI 1.8.4.<br />
<br />
Для компиляции и запуска использовались следующие команды:<br />
<pre>$ mpic++ -std=c++0x -O2 <CPP_FILE> -lm -static-libstdc++ -o <EXE_FILE><br />
$ mpirun -np <N_PROCESSORS> <EXE_FILE><br />
</pre><br />
<br />
* Число процессоров: 2, 4, 8, 16, 32, 64, 128.<br />
* Размерность матрицы <math>N</math> от 5 000 до 50 000, шаг 5 000.<br />
* Количество итераций алгоритма Ланцоша для всех экспериментов 200.<br />
<br />
Результаты:<br />
<br />
{| class="wikitable"<br />
|+ Время выполнения алгоритма<br />
|-<br />
!<br />
! 2<br />
! 4<br />
! 8<br />
! 16 <br />
! 32<br />
! 48<br />
! 64 <br />
! 96<br />
! 128<br />
|-<br />
! 5000<br />
| 1,5243<br />
| 1,0951<br />
| 0,9334<br />
| 0,6631<br />
| 0,7456<br />
| 0,7038<br />
| 0,7989<br />
| 0,9985<br />
| 0,985<br />
|-<br />
! 10 000<br />
| 5,667<br />
| 4,2625<br />
| 2,7502<br />
| 3,6762<br />
| 3,4435<br />
| 3,6192<br />
| 3,5194<br />
| 3,8486<br />
| 3,2956<br />
|-<br />
! 15 000<br />
| 12,5207<br />
| 6,5995<br />
| 7,2897<br />
| 8,5086<br />
| 8,539<br />
| 9,0018<br />
| 8,9427<br />
| 15,0132<br />
| 7,7088<br />
|-<br />
! 20 000<br />
| 23,8143<br />
| 15,851<br />
| 10,6313<br />
| 13,7241<br />
| 12,8051<br />
| 14,3183<br />
| 13,2946<br />
| 14,0864<br />
| 13,7499<br />
|-<br />
! 25 000<br />
| 37,5419<br />
| 17,9436<br />
| 15,5451<br />
| 18,3944<br />
| 19,6076<br />
| 18,5444<br />
| 19,1347<br />
| 21,8366<br />
| 21,4454<br />
|-<br />
! 30 000<br />
| 55,014<br />
| 26,2702<br />
| 22,3943<br />
| 26,4935<br />
| 25,5618<br />
| 29,6784<br />
| 28,0853<br />
| 27,8872<br />
| 31,2415<br />
|-<br />
! 35 000<br />
| 71,3969<br />
| 37,4811<br />
| 30,2274<br />
| 34,0041<br />
| 38,1028<br />
| 38,4912<br />
| 38,0174<br />
| 37,1407<br />
| 40,4463<br />
|-<br />
! 40 000<br />
| 100,326<br />
| 47,4248<br />
| 40,2295<br />
| 44,0332<br />
| 50,0015<br />
| 52,9428<br />
| 46,2961<br />
| 51,4372<br />
| 49,7504<br />
|-<br />
! 45 000<br />
| 142,93<br />
| 66,4829<br />
| 52,1382<br />
| 55,4683<br />
| 62,3042<br />
| 65,0885<br />
| 60,0512<br />
| 57,8891<br />
| 64,3764<br />
|}<br />
<br />
Ниже приведен график зависимости времени выполнения программы от числа процессоров и размера исходной матрицы.<br />
<br />
[[File:Lanzcos.png|600px|thumb|center|Рисунок 2. Время работы программы в зависимости от количества процессоров и размера матрицы.<br/><br />
<br />
<math>Procs</math> — количество процессоров, <br/><br />
<math>Size</math> — размерность матрицы <math>N</math>, <br/><br />
<math>Time</math> — время работы алгоритма в сек. <br/>]]<br />
<br />
===Динамические характеристики и эффективность реализации алгоритма===<br />
===Выводы для классов архитектур===<br />
===Существующие реализации алгоритма===<br />
<br />
Подробный анализ алгоритмов нахождения собственных значений, включая алгоритм Ланцоша, был проведен в <ref>V. Hernandez, J. E. Roman, A. Tomas, V. Vidal. A Survey of Software for Sparse Eigenvalue Problems ([http://slepc.upv.es/documentation/reports/str6.pdf см.])</ref>. Существует несколько как последовательных, так и параллельных реализаций алгоритма Ланцоша, включенных в программные библиотеки для поиска собственных значений матриц. Свойства этих реализаций можно увидеть в таблицах ниже. Первая таблица — относительно недавние реализации, вторая - «музейные экспонаты». Следует уделить особенное внимание столбцам «тип метода» и «параллелизация». В первом из них значение только N соответствует описанному варианту без реортогонализации, F – полной реортогонализации, P – частичной реортогонализации, S – выборочной реортогонализации. Во втором значние «none» соответствует отсутствию параллельной реализации, M – параллельной реализации посредством MPI, O – параллельной реализации посредством OpenMP. Их приведение целесообразно, так как на практике алгоритм Ланцоша без переортогонализации неустойчив. Также указываются библиотеки, в которых реализованы более глубокие модификации метода Ланцоша, с указанием изменений в графе «тип метода».<br />
<br />
{| class="wikitable"<br />
|+<b>Таблица 1 - «современные» реализации.</b><br />
!Название библиотеки<br />
!Язык программирования<br />
!Дата появления, версия библиотки<br />
!Тип метода<br />
!Параллелизация<br />
|-<br />
|BLKLAN<br />
|C/Matlab<br />
|2003<br />
|P<br />
|none<br />
|-<br />
|BLZPACK<br />
|F77<br />
|2000, 04/00<br />
|P + S<br />
|M<br />
|-<br />
|IETL<br />
|C++<br />
|2006, 2.2<br />
|N<br />
|none<br />
|-<br />
|SLEPc<br />
|C/F77<br />
|2009, 3.0.0<br />
|All<br />
|M<br />
|-<br />
|TRLAN<br />
|F90<br />
|2006<br />
|Dynamic thick-restart<br />
|M<br />
|-<br />
|PROPACK<br />
|F77/Matlab<br />
|2005, 2.1 / 1.1<br />
|P, finds SVD<br />
|O<br />
|-<br />
|IRBLEIGS<br />
|Matlab<br />
|2000, 1.0<br />
|Indefinitie symmetric<br />
|none<br />
|}<br />
<br />
{| class="wikitable"<br />
|+<b>Таблица 2 - «исторические» реализации.</b><br />
!Название библиотеки<br />
!Язык программирования<br />
!Дата появления, версия библиотки<br />
!Тип метода<br />
!Параллелизация<br />
|-<br />
|ARPACK<br />
|F77<br />
|1995, 2.1<br />
|Arnoldi iterations, impliicit restart<br />
|M<br />
|-<br />
|ARPACK++<br />
|C++<br />
|1998, 1.1<br />
|Arnoldi iterations, impliicit restart<br />
|none<br />
|-<br />
|LANCZOS<br />
|F77<br />
|1992<br />
|N<br />
|none<br />
|-<br />
|LANZ<br />
|F77<br />
|1991, 1.0<br />
|P<br />
|none<br />
|-<br />
|LASO<br />
|F77<br />
|1983, 2<br />
|S<br />
|none<br />
|-<br />
|NAPACK<br />
|F77<br />
|1987<br />
|N<br />
|none<br />
|-<br />
|QMRPACK<br />
|F77<br />
|1996<br />
|Designed for nonsymmetrical matrices (lookahead)<br />
|none<br />
|-<br />
|SVDPACK<br />
|C/F77<br />
|1992<br />
|P, finds SVD<br />
|none<br />
|-<br />
|Underwood<br />
|F77<br />
|1975<br />
|F, block version<br />
|none<br />
|}<br />
<br />
В настоящее время алгоритм Ланцоша поиска собственных значений квадратной симметричной матрицы включён в несколько библиотек и реализован на различных языках, среди которых такие наиболее распространенные как '''C''', '''C++''', '''FORTRAN77/90''', '''MathLab'''. Ссылки на наиболее проверенные и часто используемые реализации алгоритма:<br />
<br />
* Так, например, в языке '''MathLab''' во встроенном пакете '''ARPACK''' найти собственные значения методом Ланцоша можно при помощи вызова функции ''eigs()''. <br />
<br />
* На языке '''С''' существует библиотека '''[http://www.cs.wm.edu/~andreas/software/ PRIMME]''', название которой расшифровывается как PReconditioned Iterative MultiMethod Eigensolver (итерационные методы поиска собственных значений с предусловием).<br />
<br />
* Библиотека '''[http://www.nag.co.uk NAG] Numerical Library''' также содержит обширный набор функций, позволяющих проводить математический анализ, среди которых есть и реализация алгоритма Ланцоша. Библиотека доступна в трех пакетах: NAG C Library, NAG Fortran Library и NAG Library for .NET, совместна со многими языками и с основными операционными системами (Windows, Linux и OS X, а также Solaris, AIX и HP-UX).<br />
<br />
Заслуживают внимания также реализации в проектах IETL<ref>http://www.comp-phys.org/software/ietl/lanczos.html</ref>, ARPACK <ref>http://www.caam.rice.edu/software/ARPACK/</ref> и SLEPc<ref>Hernandez V., Roman J. E., Vidal V. SLEPc: A scalable and flexible toolkit for the solution of eigenvalue problems //ACM Transactions on Mathematical Software (TOMS).– Т. 31. – №. 3. – С. 351-362.</ref>.<br />
<br />
==Литература==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25287Метод Ньютона для систем нелинейных уравнений2018-01-17T09:39:28Z<p>Konshin: </p>
<hr />
<div><!--------------------------------------------------------------------<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
---------------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев_Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр_Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N_Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.В.Арутюнов</b>] и<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.С.Жилкин</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# <math>n</math> - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math> (в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое (<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хотя сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания отдельных этапов.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сходится за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset {\mathbb R}^n</math>. Предположим, что существуют <math>\overline{x^*} \in {\mathbb R}^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра (см. [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
Характеристики реализации алгоритма сильно зависят от выбранного способа нахождения матрицы Якоби и решения СЛАУ. <br />
Для примера рассматривается реализация алгоритма с использованием метода Гаусса на функциях вида:<br />
<br />
<math>f_i(x) = cos(x_i) - 1</math>.<br />
<br />
Для этих функций можно задать точное значение производной в любой точке:<br />
<br />
<math>f_i^\prime (x) = -sin(x_i)</math>.<br />
<br />
Для тестирования программы было решено использовать исключительно технологию MPI в реализации Intel (IntelMPI<ref name="LINK_IMPI">https://software.intel.com/en-us/intel-mpi-library</ref>) без дополнительных. <br />
Тесты проводились на суперкомпьютере Ломоносов<ref name="LOM_PARALLEL_LINK">https://parallel.ru/cluster/lomonosov.html</ref> <ref name="LOM_WIKI_LINK">https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер)</ref> в разделе test. <br />
Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
* Количество ядер: 8 ядер архитектуры x86<br />
* Количество памяти: 12Гб<br />
<br />
Строка компиляции: mpicxx _scratch/Source.cpp -o _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Строка запуска: sbatch -nN -p test impi _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Результаты тестирования представлены на рисунках 10 и 11, где рис. 10 отображает время работы данной реализации, а рис. 11 - ускорение:<br />
<br />
[[Файл:Nwt 1024 2048 4096.png|thumb|center|800px|Рис.2 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
[[Файл:Nwt spdup.png|thumb|center|800px|Рис.3 Ускорение решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Из рис. 10 и 11 видно, что увеличение числа задействованных в вычислениях процессоров дает выигрыш во времени, однако, из-за необходимости обмениваться данными при решении СЛАУ методом Гаусса, при вовлечении слишком большого числа процессоров, время, требуемое на обмен данными может превысить время непосредственного вычисления. Этот эффект ограничивает масштабируемость программы. Для подтверждения этого тезиса приведён рис. 12, отдельно показывающий время работы задачи с размерностью матрицы 1024:<br />
<br />
[[Файл:ChartGo 1024.png|thumb|center|600px|Рис. 12. Время решения системы из 1024 уравнений в зависимости от количества задействованных процессоров]]<br />
<br />
Как видно из рис. 12 время выполнения программы на 128 процессорах возрастает по сравнению с временем работы на 64 процессорах. <br />
Это связано с тем, что на каждый процессор приходится недостаточно индивидуальной загрузки и основное время работы программы тратится на передачу данных между процессорами.<br />
Если же увеличение количества задействованных процессоров не приводит к возникновению этого эффекта, то увеличение количества процессоров в 2 раза ведёт к увеличению скорости работы программы также приблизительно в 2 раза, что хорошо видно на рис. 12.<br />
<br />
{|class="wikitable mw-collapsible mw-collapsed"<br />
!Исходный код программы<br />
|-<br />
|<source hide="yes" lang="cpp">#include <iostream><br />
#include <cmath><br />
#include <fstream><br />
#include <vector><br />
#include <cstdlib><br />
#include <limits><br />
#include <mpi.h><br />
#include <stdio.h><br />
#include <assert.h><br />
<br />
typedef std::vector<double> Vec;<br />
typedef double(*Function)(const size_t&, const Vec&);<br />
typedef double(*Deriv)(const size_t&, const size_t&, const Vec&);<br />
typedef std::vector<Function> Sys_;<br />
typedef std::vector<std::vector<Deriv> > Derivatives;<br />
typedef std::vector<std::vector<double> > Matrix;<br />
typedef Vec(*LSSolver)(const Matrix&, const Vec&);<br />
typedef Vec(*VecSum)(const Vec&, const Vec&);<br />
typedef double(*VecDiff)(const Vec&, const Vec&);<br />
<br />
struct LS {<br />
Matrix A;<br />
Vec b;<br />
};<br />
<br />
size_t system_size = 4096;<br />
<br />
double func(const size_t& i, const Vec& v) {<br />
return cos(v[i]) - 1;<br />
}<br />
<br />
double derivative(const size_t& i, const size_t& j, const Vec& v) {<br />
if (i == j) {<br />
return -sin(v[i]);<br />
}<br />
return 0.0;<br />
}<br />
<br />
static void printVec(const Vec& v) {<br />
std::cout << "size: " << v.size() << std::endl;<br />
for (size_t i = 0; i < v.size(); ++i) {<br />
std::cout << v[i] << " ";<br />
}<br />
std::cout << std::endl;<br />
}<br />
<br />
Vec gauss(const Matrix& A, const Vec& b) {<br />
MPI_Status status;<br />
int nSize, nRowsBloc, nRows, nCols;<br />
int Numprocs, MyRank, Root = 0;<br />
int irow, jrow, icol, index, ColofPivot, neigh_proc;<br />
double *Input_A, *Input_B, *ARecv, *BRecv;<br />
double *Output, Pivot;<br />
double *X_buffer, *Y_buffer;<br />
double *Buffer_Pivot, *Buffer_bksub;<br />
double tmp;<br />
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &Numprocs);<br />
if (MyRank == 0) {<br />
nRows = A.size();<br />
nCols = A[0].size();<br />
nSize = nRows;<br />
Input_B = (double *)malloc(nSize * sizeof(double));<br />
for (irow = 0; irow < nSize; irow++) {<br />
Input_B[irow] = b[irow];<br />
}<br />
Input_A = (double *)malloc(nSize*nSize * sizeof(double));<br />
index = 0;<br />
for (irow = 0; irow < nSize; irow++)<br />
for (icol = 0; icol < nSize; icol++)<br />
Input_A[index++] = A[irow][icol];<br />
}<br />
MPI_Bcast(&nRows, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
MPI_Bcast(&nCols, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
/* .... Broad cast the size of the matrix to all ....*/<br />
MPI_Bcast(&nSize, 1, MPI_INT, 0, MPI_COMM_WORLD);<br />
nRowsBloc = nSize / Numprocs;<br />
/*......Memory of input matrix and vector on each process .....*/<br />
ARecv = (double *)malloc(nRowsBloc * nSize * sizeof(double));<br />
BRecv = (double *)malloc(nRowsBloc * sizeof(double));<br />
/*......Scatter the Input Data to all process ......*/<br />
MPI_Scatter(Input_A, nRowsBloc * nSize, MPI_DOUBLE, ARecv, nRowsBloc * nSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Scatter(Input_B, nRowsBloc, MPI_DOUBLE, BRecv, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* ....Memory allocation of ray Buffer_Pivot .....*/<br />
Buffer_Pivot = (double *)malloc((nRowsBloc + 1 + nSize * nRowsBloc) * sizeof(double));<br />
/* Receive data from all processors (i=0 to k-1) above my processor (k).... */<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Recv(Buffer_Pivot, nRowsBloc * nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc, MPI_COMM_WORLD, &status);<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
/* .... Buffer_Pivot[0] : locate the rank of the processor received */<br />
/* .... Index is used to reduce the matrix to traingular matrix */<br />
/* .... Buffer_Pivot[0] is used to determine the starting value of<br />
pivot in each row of the matrix, on each processor */<br />
ColofPivot = ((int)Buffer_Pivot[0]) * nRowsBloc + irow;<br />
for (jrow = 0; jrow < nRowsBloc; jrow++) {<br />
index = jrow*nSize;<br />
tmp = ARecv[index + ColofPivot];<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] -= tmp * Buffer_Pivot[irow*nSize + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Buffer_Pivot[1 + irow];<br />
ARecv[index + ColofPivot] = 0.0;<br />
}<br />
}<br />
}<br />
Y_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
/* ....Modification of row entries on each processor ...*/<br />
/* ....Division by pivot value and modification ...*/<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
ColofPivot = MyRank * nRowsBloc + irow;<br />
index = irow*nSize;<br />
Pivot = ARecv[index + ColofPivot];<br />
assert(Pivot != 0);<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] = ARecv[index + icol] / Pivot;<br />
Buffer_Pivot[index + icol + 1 + nRowsBloc] = ARecv[index + icol];<br />
}<br />
Y_buffer[irow] = BRecv[irow] / Pivot;<br />
Buffer_Pivot[irow + 1] = Y_buffer[irow];<br />
for (jrow = irow + 1; jrow < nRowsBloc; jrow++) {<br />
tmp = ARecv[jrow*nSize + ColofPivot];<br />
for (icol = ColofPivot + 1; icol < nSize; icol++) {<br />
ARecv[jrow*nSize + icol] -= tmp * Buffer_Pivot[index + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Y_buffer[irow];<br />
ARecv[jrow*nSize + irow] = 0;<br />
}<br />
}<br />
/*....Send data to all processors below the current processors */<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; neigh_proc++) {<br />
/* ...... Rank is stored in first location of Buffer_Pivot and<br />
this is used in reduction to triangular form ....*/<br />
Buffer_Pivot[0] = (double)MyRank;<br />
MPI_Send(Buffer_Pivot, nRowsBloc*nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Back Substitution starts from here ........*/<br />
/*.... Receive from all higher processors ......*/<br />
Buffer_bksub = (double *)malloc(nRowsBloc * 2 * sizeof(double));<br />
X_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; ++neigh_proc) {<br />
MPI_Recv(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc,<br />
MPI_COMM_WORLD, &status);<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
for (icol = nRowsBloc - 1; icol >= 0; icol--) {<br />
/* ... Pick up starting Index .....*/<br />
index = (int)Buffer_bksub[icol];<br />
Y_buffer[irow] -= Buffer_bksub[nRowsBloc + icol] * ARecv[irow*nSize + index];<br />
}<br />
}<br />
}<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
index = MyRank*nRowsBloc + irow;<br />
Buffer_bksub[irow] = (double)index;<br />
Buffer_bksub[nRowsBloc + irow] = X_buffer[irow] = Y_buffer[irow];<br />
for (jrow = irow - 1; jrow >= 0; jrow--)<br />
Y_buffer[jrow] -= X_buffer[irow] * ARecv[jrow*nSize + index];<br />
}<br />
/*.... Send to all lower processes...*/<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Send(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Gather the result on the processor 0 ....*/<br />
Output = (double *)malloc(nSize * sizeof(double));<br />
MPI_Gather(X_buffer, nRowsBloc, MPI_DOUBLE, Output, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* .......Output vector .....*/<br />
Vec res(nSize);<br />
if (MyRank == 0) {<br />
for (irow = 0; irow < nSize; irow++)<br />
res[irow] = Output[irow];<br />
}<br />
return res;<br />
}<br />
<br />
Vec sum(const Vec& lhs, const Vec& rhs) {<br />
Vec res(lhs.size());<br />
if (lhs.size() != rhs.size()) {<br />
return res;<br />
}<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res[i] = lhs[i] + rhs[i];<br />
}<br />
return res;<br />
}<br />
<br />
double diff(const Vec& lhs, const Vec& rhs) {<br />
double res = 0.;<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res += lhs[i] - rhs[i];<br />
}<br />
return fabs(res);<br />
}<br />
<br />
class Newtone {<br />
public:<br />
Newtone(int rank) : m_rank(rank) { <br />
m_h = std::numeric_limits<double>::epsilon(); <br />
}<br />
<br />
Vec find_solution(const Sys_& sys, const Vec& start, const Derivatives& d, LSSolver solver, <br />
VecSum vec_summator, VecDiff vec_differ, const double& eps, const size_t& max_iter) {<br />
size_t iter_count = 1;<br />
double diff = 0.;<br />
Vec sys_val(sys.size(), 0);<br />
if (m_rank == 0) {<br />
m_jac.reserve(sys.size());<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
Vec v(sys.size());<br />
m_jac.push_back(v);<br />
}<br />
compute_jacobian(sys, d, start);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, start);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
Vec delta = solver(m_jac, sys_val);<br />
Vec new_sol(sys.size()), old_sol(sys.size());<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(start, delta);<br />
old_sol = start;<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Bcast(old_sol.data(), old_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
diff = vec_differ(new_sol, old_sol);<br />
while (diff > eps && iter_count <= max_iter) {<br />
old_sol = new_sol;<br />
if (m_rank == 0) {<br />
compute_jacobian(sys, d, old_sol);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, old_sol);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
delta = solver(m_jac, sys_val);<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(old_sol, delta);<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
iter_count++;<br />
diff = vec_differ(new_sol, old_sol);<br />
}<br />
return new_sol;<br />
}<br />
<br />
double compute_derivative(const size_t& pos, Function func, const size_t& var_num, const Vec& point) {<br />
Vec left_point(point), right_point(point);<br />
left_point[var_num] -= m_h;<br />
right_point[var_num] += m_h;<br />
double left = func(pos, left_point), right = func(pos, right_point);<br />
return (right - left) / (2 * m_h);<br />
}<br />
<br />
private:<br />
void compute_jacobian(const Sys_& sys, const Derivatives& d, const Vec& point) {<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
for (size_t j = 0; j < sys.size(); ++j) {<br />
double res_val;<br />
res_val = d[i][j](i, j, point);<br />
m_jac[i][j] = res_val;<br />
}<br />
}<br />
}<br />
double m_h;<br />
int m_rank;<br />
Matrix m_jac;<br />
Vec m_right_part;<br />
};<br />
<br />
int main(int argc, char** argv) {<br />
int rank, size;<br />
Sys_ s;<br />
Derivatives d(system_size);<br />
Vec start(system_size, 0.87);<br />
for (size_t i = 0; i < system_size; ++i) {<br />
s.push_back(&func);<br />
}<br />
for (size_t i = 0; i < system_size; ++i) {<br />
for (size_t j = 0; j < system_size; ++j)<br />
d[i].push_back(&derivative);<br />
}<br />
MPI_Init(&argc, &argv);<br />
MPI_Comm_rank(MPI_COMM_WORLD, &rank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &size);<br />
Newtone n(rank);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
double time = MPI_Wtime();<br />
Vec sol = n.find_solution(s, start, d, &gauss, &sum, &diff, 0.0001, 100);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
time = MPI_Wtime() - time;<br />
double max_time;<br />
MPI_Reduce(&time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);<br />
if (rank == 0) {<br />
std::ofstream myfile;<br />
char filename[32];<br />
snprintf(filename, 32, "out_%ld_%d.txt", system_size, size);<br />
myfile.open(filename);<br />
for (size_t i = 0; i < sol.size(); ++i) {<br />
myfile << sol[i] << " ";<br />
}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25286Метод Ньютона для систем нелинейных уравнений2018-01-17T09:36:12Z<p>Konshin: /* Реализация 4 */</p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев_Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр_Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N_Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.В.Арутюнов</b>] и<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.С.Жилкин</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# <math>n</math> - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math> (в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое (<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хотя сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания отдельных этапов.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сходится за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset {\mathbb R}^n</math>. Предположим, что существуют <math>\overline{x^*} \in {\mathbb R}^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра (см. [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
Характеристики реализации алгоритма сильно зависят от выбранного способа нахождения матрицы Якоби и решения СЛАУ. <br />
Для примера рассматривается реализация алгоритма с использованием метода Гаусса на функциях вида:<br />
<br />
<math>f_i(x) = cos(x_i) - 1</math>.<br />
<br />
Для этих функций можно задать точное значение производной в любой точке:<br />
<br />
<math>f_i^\prime (x) = -sin(x_i)</math>.<br />
<br />
Для тестирования программы было решено использовать исключительно технологию MPI в реализации Intel (IntelMPI<ref name="LINK_IMPI">https://software.intel.com/en-us/intel-mpi-library</ref>) без дополнительных. <br />
Тесты проводились на суперкомпьютере Ломоносов<ref name="LOM_PARALLEL_LINK">https://parallel.ru/cluster/lomonosov.html</ref> <ref name="LOM_WIKI_LINK">https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер)</ref> в разделе test. <br />
Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
* Количество ядер: 8 ядер архитектуры x86<br />
* Количество памяти: 12Гб<br />
<br />
Строка компиляции: mpicxx _scratch/Source.cpp -o _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Строка запуска: sbatch -nN -p test impi _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Результаты тестирования представлены на рисунках 10 и 11, где рис. 10 отображает время работы данной реализации, а рис. 11 - ускорение:<br />
<br />
[[Файл:Nwt 1024 2048 4096.png|thumb|center|800px|Рис.2 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
[[Файл:Nwt spdup.png|thumb|center|800px|Рис.3 Ускорение решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Из рис. 10 и 11 видно, что увеличение числа задействованных в вычислениях процессоров дает выигрыш во времени, однако, из-за необходимости обмениваться данными при решении СЛАУ методом Гаусса, при вовлечении слишком большого числа процессоров, время, требуемое на обмен данными может превысить время непосредственного вычисления. Этот эффект ограничивает масштабируемость программы. Для подтверждения этого тезиса приведён рис. 12, отдельно показывающий время работы задачи с размерностью матрицы 1024:<br />
<br />
[[Файл:ChartGo 1024.png|thumb|center|600px|Рис. 12. Время решения системы из 1024 уравнений в зависимости от количества задействованных процессоров]]<br />
<br />
Как видно из рис. 12 время выполнения программы на 128 процессорах возрастает по сравнению с временем работы на 64 процессорах. <br />
Это связано с тем, что на каждый процессор приходится недостаточно индивидуальной загрузки и основное время работы программы тратится на передачу данных между процессорами.<br />
Если же увеличение количества задействованных процессоров не приводит к возникновению этого эффекта, то увеличение количества процессоров в 2 раза ведёт к увеличению скорости работы программы также приблизительно в 2 раза, что хорошо видно на рис. 12.<br />
<br />
{|class="wikitable mw-collapsible mw-collapsed"<br />
!Исходный код программы<br />
|-<br />
|<source hide="yes" lang="cpp">#include <iostream><br />
#include <cmath><br />
#include <fstream><br />
#include <vector><br />
#include <cstdlib><br />
#include <limits><br />
#include <mpi.h><br />
#include <stdio.h><br />
#include <assert.h><br />
<br />
typedef std::vector<double> Vec;<br />
typedef double(*Function)(const size_t&, const Vec&);<br />
typedef double(*Deriv)(const size_t&, const size_t&, const Vec&);<br />
typedef std::vector<Function> Sys_;<br />
typedef std::vector<std::vector<Deriv> > Derivatives;<br />
typedef std::vector<std::vector<double> > Matrix;<br />
typedef Vec(*LSSolver)(const Matrix&, const Vec&);<br />
typedef Vec(*VecSum)(const Vec&, const Vec&);<br />
typedef double(*VecDiff)(const Vec&, const Vec&);<br />
<br />
struct LS {<br />
Matrix A;<br />
Vec b;<br />
};<br />
<br />
size_t system_size = 4096;<br />
<br />
double func(const size_t& i, const Vec& v) {<br />
return cos(v[i]) - 1;<br />
}<br />
<br />
double derivative(const size_t& i, const size_t& j, const Vec& v) {<br />
if (i == j) {<br />
return -sin(v[i]);<br />
}<br />
return 0.0;<br />
}<br />
<br />
static void printVec(const Vec& v) {<br />
std::cout << "size: " << v.size() << std::endl;<br />
for (size_t i = 0; i < v.size(); ++i) {<br />
std::cout << v[i] << " ";<br />
}<br />
std::cout << std::endl;<br />
}<br />
<br />
Vec gauss(const Matrix& A, const Vec& b) {<br />
MPI_Status status;<br />
int nSize, nRowsBloc, nRows, nCols;<br />
int Numprocs, MyRank, Root = 0;<br />
int irow, jrow, icol, index, ColofPivot, neigh_proc;<br />
double *Input_A, *Input_B, *ARecv, *BRecv;<br />
double *Output, Pivot;<br />
double *X_buffer, *Y_buffer;<br />
double *Buffer_Pivot, *Buffer_bksub;<br />
double tmp;<br />
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &Numprocs);<br />
if (MyRank == 0) {<br />
nRows = A.size();<br />
nCols = A[0].size();<br />
nSize = nRows;<br />
Input_B = (double *)malloc(nSize * sizeof(double));<br />
for (irow = 0; irow < nSize; irow++) {<br />
Input_B[irow] = b[irow];<br />
}<br />
Input_A = (double *)malloc(nSize*nSize * sizeof(double));<br />
index = 0;<br />
for (irow = 0; irow < nSize; irow++)<br />
for (icol = 0; icol < nSize; icol++)<br />
Input_A[index++] = A[irow][icol];<br />
}<br />
MPI_Bcast(&nRows, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
MPI_Bcast(&nCols, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
/* .... Broad cast the size of the matrix to all ....*/<br />
MPI_Bcast(&nSize, 1, MPI_INT, 0, MPI_COMM_WORLD);<br />
nRowsBloc = nSize / Numprocs;<br />
/*......Memory of input matrix and vector on each process .....*/<br />
ARecv = (double *)malloc(nRowsBloc * nSize * sizeof(double));<br />
BRecv = (double *)malloc(nRowsBloc * sizeof(double));<br />
/*......Scatter the Input Data to all process ......*/<br />
MPI_Scatter(Input_A, nRowsBloc * nSize, MPI_DOUBLE, ARecv, nRowsBloc * nSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Scatter(Input_B, nRowsBloc, MPI_DOUBLE, BRecv, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* ....Memory allocation of ray Buffer_Pivot .....*/<br />
Buffer_Pivot = (double *)malloc((nRowsBloc + 1 + nSize * nRowsBloc) * sizeof(double));<br />
/* Receive data from all processors (i=0 to k-1) above my processor (k).... */<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Recv(Buffer_Pivot, nRowsBloc * nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc, MPI_COMM_WORLD, &status);<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
/* .... Buffer_Pivot[0] : locate the rank of the processor received */<br />
/* .... Index is used to reduce the matrix to traingular matrix */<br />
/* .... Buffer_Pivot[0] is used to determine the starting value of<br />
pivot in each row of the matrix, on each processor */<br />
ColofPivot = ((int)Buffer_Pivot[0]) * nRowsBloc + irow;<br />
for (jrow = 0; jrow < nRowsBloc; jrow++) {<br />
index = jrow*nSize;<br />
tmp = ARecv[index + ColofPivot];<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] -= tmp * Buffer_Pivot[irow*nSize + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Buffer_Pivot[1 + irow];<br />
ARecv[index + ColofPivot] = 0.0;<br />
}<br />
}<br />
}<br />
Y_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
/* ....Modification of row entries on each processor ...*/<br />
/* ....Division by pivot value and modification ...*/<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
ColofPivot = MyRank * nRowsBloc + irow;<br />
index = irow*nSize;<br />
Pivot = ARecv[index + ColofPivot];<br />
assert(Pivot != 0);<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] = ARecv[index + icol] / Pivot;<br />
Buffer_Pivot[index + icol + 1 + nRowsBloc] = ARecv[index + icol];<br />
}<br />
Y_buffer[irow] = BRecv[irow] / Pivot;<br />
Buffer_Pivot[irow + 1] = Y_buffer[irow];<br />
for (jrow = irow + 1; jrow < nRowsBloc; jrow++) {<br />
tmp = ARecv[jrow*nSize + ColofPivot];<br />
for (icol = ColofPivot + 1; icol < nSize; icol++) {<br />
ARecv[jrow*nSize + icol] -= tmp * Buffer_Pivot[index + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Y_buffer[irow];<br />
ARecv[jrow*nSize + irow] = 0;<br />
}<br />
}<br />
/*....Send data to all processors below the current processors */<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; neigh_proc++) {<br />
/* ...... Rank is stored in first location of Buffer_Pivot and<br />
this is used in reduction to triangular form ....*/<br />
Buffer_Pivot[0] = (double)MyRank;<br />
MPI_Send(Buffer_Pivot, nRowsBloc*nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Back Substitution starts from here ........*/<br />
/*.... Receive from all higher processors ......*/<br />
Buffer_bksub = (double *)malloc(nRowsBloc * 2 * sizeof(double));<br />
X_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; ++neigh_proc) {<br />
MPI_Recv(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc,<br />
MPI_COMM_WORLD, &status);<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
for (icol = nRowsBloc - 1; icol >= 0; icol--) {<br />
/* ... Pick up starting Index .....*/<br />
index = (int)Buffer_bksub[icol];<br />
Y_buffer[irow] -= Buffer_bksub[nRowsBloc + icol] * ARecv[irow*nSize + index];<br />
}<br />
}<br />
}<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
index = MyRank*nRowsBloc + irow;<br />
Buffer_bksub[irow] = (double)index;<br />
Buffer_bksub[nRowsBloc + irow] = X_buffer[irow] = Y_buffer[irow];<br />
for (jrow = irow - 1; jrow >= 0; jrow--)<br />
Y_buffer[jrow] -= X_buffer[irow] * ARecv[jrow*nSize + index];<br />
}<br />
/*.... Send to all lower processes...*/<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Send(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Gather the result on the processor 0 ....*/<br />
Output = (double *)malloc(nSize * sizeof(double));<br />
MPI_Gather(X_buffer, nRowsBloc, MPI_DOUBLE, Output, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* .......Output vector .....*/<br />
Vec res(nSize);<br />
if (MyRank == 0) {<br />
for (irow = 0; irow < nSize; irow++)<br />
res[irow] = Output[irow];<br />
}<br />
return res;<br />
}<br />
<br />
Vec sum(const Vec& lhs, const Vec& rhs) {<br />
Vec res(lhs.size());<br />
if (lhs.size() != rhs.size()) {<br />
return res;<br />
}<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res[i] = lhs[i] + rhs[i];<br />
}<br />
return res;<br />
}<br />
<br />
double diff(const Vec& lhs, const Vec& rhs) {<br />
double res = 0.;<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res += lhs[i] - rhs[i];<br />
}<br />
return fabs(res);<br />
}<br />
<br />
class Newtone {<br />
public:<br />
Newtone(int rank) : m_rank(rank) { <br />
m_h = std::numeric_limits<double>::epsilon(); <br />
}<br />
<br />
Vec find_solution(const Sys_& sys, const Vec& start, const Derivatives& d, LSSolver solver, <br />
VecSum vec_summator, VecDiff vec_differ, const double& eps, const size_t& max_iter) {<br />
size_t iter_count = 1;<br />
double diff = 0.;<br />
Vec sys_val(sys.size(), 0);<br />
if (m_rank == 0) {<br />
m_jac.reserve(sys.size());<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
Vec v(sys.size());<br />
m_jac.push_back(v);<br />
}<br />
compute_jacobian(sys, d, start);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, start);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
Vec delta = solver(m_jac, sys_val);<br />
Vec new_sol(sys.size()), old_sol(sys.size());<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(start, delta);<br />
old_sol = start;<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Bcast(old_sol.data(), old_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
diff = vec_differ(new_sol, old_sol);<br />
while (diff > eps && iter_count <= max_iter) {<br />
old_sol = new_sol;<br />
if (m_rank == 0) {<br />
compute_jacobian(sys, d, old_sol);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, old_sol);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
delta = solver(m_jac, sys_val);<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(old_sol, delta);<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
iter_count++;<br />
diff = vec_differ(new_sol, old_sol);<br />
}<br />
return new_sol;<br />
}<br />
<br />
double compute_derivative(const size_t& pos, Function func, const size_t& var_num, const Vec& point) {<br />
Vec left_point(point), right_point(point);<br />
left_point[var_num] -= m_h;<br />
right_point[var_num] += m_h;<br />
double left = func(pos, left_point), right = func(pos, right_point);<br />
return (right - left) / (2 * m_h);<br />
}<br />
<br />
private:<br />
void compute_jacobian(const Sys_& sys, const Derivatives& d, const Vec& point) {<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
for (size_t j = 0; j < sys.size(); ++j) {<br />
double res_val;<br />
res_val = d[i][j](i, j, point);<br />
m_jac[i][j] = res_val;<br />
}<br />
}<br />
}<br />
double m_h;<br />
int m_rank;<br />
Matrix m_jac;<br />
Vec m_right_part;<br />
};<br />
<br />
int main(int argc, char** argv) {<br />
int rank, size;<br />
Sys_ s;<br />
Derivatives d(system_size);<br />
Vec start(system_size, 0.87);<br />
for (size_t i = 0; i < system_size; ++i) {<br />
s.push_back(&func);<br />
}<br />
for (size_t i = 0; i < system_size; ++i) {<br />
for (size_t j = 0; j < system_size; ++j)<br />
d[i].push_back(&derivative);<br />
}<br />
MPI_Init(&argc, &argv);<br />
MPI_Comm_rank(MPI_COMM_WORLD, &rank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &size);<br />
Newtone n(rank);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
double time = MPI_Wtime();<br />
Vec sol = n.find_solution(s, start, d, &gauss, &sum, &diff, 0.0001, 100);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
time = MPI_Wtime() - time;<br />
double max_time;<br />
MPI_Reduce(&time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);<br />
if (rank == 0) {<br />
std::ofstream myfile;<br />
char filename[32];<br />
snprintf(filename, 32, "out_%ld_%d.txt", system_size, size);<br />
myfile.open(filename);<br />
for (size_t i = 0; i < sol.size(); ++i) {<br />
myfile << sol[i] << " ";<br />
}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25285Метод Ньютона для систем нелинейных уравнений2018-01-17T09:32:53Z<p>Konshin: /* Реализация 4 */</p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев_Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр_Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N_Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.В.Арутюнов</b>] и<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.С.Жилкин</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# <math>n</math> - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math> (в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое (<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хотя сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания отдельных этапов.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сходится за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset {\mathbb R}^n</math>. Предположим, что существуют <math>\overline{x^*} \in {\mathbb R}^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра (см. [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
Характеристики реализации алгоритма сильно зависят от выбранного способа нахождения матрицы Якоби и решения СЛАУ. <br />
Для примера рассматривается реализация алгоритма с использованием метода Гаусса на функциях вида:<br />
<br />
<math>f_i(x) = cos(x_i) - 1</math>.<br />
<br />
Для этих функций можно задать точное значение производной в любой точке:<br />
<br />
<math>f_i^\prime (x) = -sin(x_i)</math>.<br />
<br />
Для тестирования программы было решено использовать исключительно технологию MPI в реализации Intel (IntelMPI<ref name="LINK_IMPI">https://software.intel.com/en-us/intel-mpi-library</ref>) без дополнительных. <br />
Тесты проводились на суперкомпьютере Ломоносов<ref name="LOM_PARALLEL_LINK">https://parallel.ru/cluster/lomonosov.html</ref> <ref name="LOM_WIKI_LINK">https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер)</ref> в разделе test. <br />
Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
* Количество ядер: 8 ядер архитектуры x86<br />
* Количество памяти: 12Гб<br />
<br />
Строка компиляции: mpicxx _scratch/Source.cpp -o _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Строка запуска: sbatch -nN -p test impi _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Результаты тестирования представлены на рисунках 10 и 11, где рис. 10 отображает время работы данной реализации, а рис. 11 - ускорение:<br />
<br />
[[Файл:Nwt 1024 2048 4096.png|thumb|center|800px|Рис.2 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
[[Файл:Nwt spdup.png|thumb|center|800px|Рис.3 Ускорение решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Из рис. 10 и 11 видно, что увеличение числа задействованных в вычислениях процессоров дает выигрыш во времени, однако, из-за необходимости обмениваться данными при решении СЛАУ методом Гаусса, при вовлечении слишком большого числа процессоров, время, требуемое на обмен данными может превысить время непосредственного вычисления. Этот эффект ограничивает масштабируемость программы. Для подтверждения этого тезиса приведён рис. 12, отдельно показывающий время работы задачи с размерностью матрицы 1024:<br />
<br />
[[Файл:ChartGo 1024.png|thumb|center|600px|Рис. 12. Время решения системы из 1024 уравнений в зависимости от количества задействованных процессоров]]<br />
<br />
Как видно из рис. 12 время выполнения программы на 128 процессорах возрастает по сравнению с временем работы на 64 процессорах. <br />
Это связано с тем, что на каждый процессор приходится недостаточно индивидуальной загрузки и основное время работы программы тратится на передачу данных между процессорами.<br />
Если же увеличение количества задействованных процессоров не приводит к возникновению этого эффекта, то увеличение количества процессоров в 2 раза ведёт к увеличению скорости работы программы также приблизительно в 2 раза, что хорошо видно на рис. 12.<br />
<br />
{|class="wikitable mw-collapsible mw-collapsed"<br />
!Исходный код программы<br />
|-<br />
|<source hide="yes" lang="cpp">#include <iostream><br />
#include <cmath><br />
#include <fstream><br />
#include <vector><br />
#include <cstdlib><br />
#include <limits><br />
#include <mpi.h><br />
#include <stdio.h><br />
#include <assert.h><br />
<br />
typedef std::vector<double> Vec;<br />
typedef double(*Function)(const size_t&, const Vec&);<br />
typedef double(*Deriv)(const size_t&, const size_t&, const Vec&);<br />
typedef std::vector<Function> Sys_;<br />
typedef std::vector<std::vector<Deriv> > Derivatives;<br />
typedef std::vector<std::vector<double> > Matrix;<br />
typedef Vec(*LSSolver)(const Matrix&, const Vec&);<br />
typedef Vec(*VecSum)(const Vec&, const Vec&);<br />
typedef double(*VecDiff)(const Vec&, const Vec&);<br />
<br />
struct LS {<br />
Matrix A;<br />
Vec b;<br />
};<br />
<br />
size_t system_size = 4096;<br />
<br />
double func(const size_t& i, const Vec& v) {<br />
return cos(v[i]) - 1;<br />
}<br />
<br />
double derivative(const size_t& i, const size_t& j, const Vec& v) {<br />
if (i == j) {<br />
return -sin(v[i]);<br />
}<br />
return 0.0;<br />
}<br />
<br />
static void printVec(const Vec& v) {<br />
std::cout << "size: " << v.size() << std::endl;<br />
for (size_t i = 0; i < v.size(); ++i) {<br />
std::cout << v[i] << " ";<br />
}<br />
std::cout << std::endl;<br />
}<br />
<br />
Vec gauss(const Matrix& A, const Vec& b) {<br />
MPI_Status status;<br />
int nSize, nRowsBloc, nRows, nCols;<br />
int Numprocs, MyRank, Root = 0;<br />
int irow, jrow, icol, index, ColofPivot, neigh_proc;<br />
double *Input_A, *Input_B, *ARecv, *BRecv;<br />
double *Output, Pivot;<br />
double *X_buffer, *Y_buffer;<br />
double *Buffer_Pivot, *Buffer_bksub;<br />
double tmp;<br />
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &Numprocs);<br />
if (MyRank == 0) {<br />
nRows = A.size();<br />
nCols = A[0].size();<br />
nSize = nRows;<br />
Input_B = (double *)malloc(nSize * sizeof(double));<br />
for (irow = 0; irow < nSize; irow++) {<br />
Input_B[irow] = b[irow];<br />
}<br />
Input_A = (double *)malloc(nSize*nSize * sizeof(double));<br />
index = 0;<br />
for (irow = 0; irow < nSize; irow++)<br />
for (icol = 0; icol < nSize; icol++)<br />
Input_A[index++] = A[irow][icol];<br />
}<br />
MPI_Bcast(&nRows, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
MPI_Bcast(&nCols, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
/* .... Broad cast the size of the matrix to all ....*/<br />
MPI_Bcast(&nSize, 1, MPI_INT, 0, MPI_COMM_WORLD);<br />
nRowsBloc = nSize / Numprocs;<br />
/*......Memory of input matrix and vector on each process .....*/<br />
ARecv = (double *)malloc(nRowsBloc * nSize * sizeof(double));<br />
BRecv = (double *)malloc(nRowsBloc * sizeof(double));<br />
/*......Scatter the Input Data to all process ......*/<br />
MPI_Scatter(Input_A, nRowsBloc * nSize, MPI_DOUBLE, ARecv, nRowsBloc * nSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Scatter(Input_B, nRowsBloc, MPI_DOUBLE, BRecv, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* ....Memory allocation of ray Buffer_Pivot .....*/<br />
Buffer_Pivot = (double *)malloc((nRowsBloc + 1 + nSize * nRowsBloc) * sizeof(double));<br />
/* Receive data from all processors (i=0 to k-1) above my processor (k).... */<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Recv(Buffer_Pivot, nRowsBloc * nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc, MPI_COMM_WORLD, &status);<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
/* .... Buffer_Pivot[0] : locate the rank of the processor received */<br />
/* .... Index is used to reduce the matrix to traingular matrix */<br />
/* .... Buffer_Pivot[0] is used to determine the starting value of<br />
pivot in each row of the matrix, on each processor */<br />
ColofPivot = ((int)Buffer_Pivot[0]) * nRowsBloc + irow;<br />
for (jrow = 0; jrow < nRowsBloc; jrow++) {<br />
index = jrow*nSize;<br />
tmp = ARecv[index + ColofPivot];<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] -= tmp * Buffer_Pivot[irow*nSize + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Buffer_Pivot[1 + irow];<br />
ARecv[index + ColofPivot] = 0.0;<br />
}<br />
}<br />
}<br />
Y_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
/* ....Modification of row entries on each processor ...*/<br />
/* ....Division by pivot value and modification ...*/<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
ColofPivot = MyRank * nRowsBloc + irow;<br />
index = irow*nSize;<br />
Pivot = ARecv[index + ColofPivot];<br />
assert(Pivot != 0);<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] = ARecv[index + icol] / Pivot;<br />
Buffer_Pivot[index + icol + 1 + nRowsBloc] = ARecv[index + icol];<br />
}<br />
Y_buffer[irow] = BRecv[irow] / Pivot;<br />
Buffer_Pivot[irow + 1] = Y_buffer[irow];<br />
for (jrow = irow + 1; jrow < nRowsBloc; jrow++) {<br />
tmp = ARecv[jrow*nSize + ColofPivot];<br />
for (icol = ColofPivot + 1; icol < nSize; icol++) {<br />
ARecv[jrow*nSize + icol] -= tmp * Buffer_Pivot[index + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Y_buffer[irow];<br />
ARecv[jrow*nSize + irow] = 0;<br />
}<br />
}<br />
/*....Send data to all processors below the current processors */<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; neigh_proc++) {<br />
/* ...... Rank is stored in first location of Buffer_Pivot and<br />
this is used in reduction to triangular form ....*/<br />
Buffer_Pivot[0] = (double)MyRank;<br />
MPI_Send(Buffer_Pivot, nRowsBloc*nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Back Substitution starts from here ........*/<br />
/*.... Receive from all higher processors ......*/<br />
Buffer_bksub = (double *)malloc(nRowsBloc * 2 * sizeof(double));<br />
X_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; ++neigh_proc) {<br />
MPI_Recv(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc,<br />
MPI_COMM_WORLD, &status);<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
for (icol = nRowsBloc - 1; icol >= 0; icol--) {<br />
/* ... Pick up starting Index .....*/<br />
index = (int)Buffer_bksub[icol];<br />
Y_buffer[irow] -= Buffer_bksub[nRowsBloc + icol] * ARecv[irow*nSize + index];<br />
}<br />
}<br />
}<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
index = MyRank*nRowsBloc + irow;<br />
Buffer_bksub[irow] = (double)index;<br />
Buffer_bksub[nRowsBloc + irow] = X_buffer[irow] = Y_buffer[irow];<br />
for (jrow = irow - 1; jrow >= 0; jrow--)<br />
Y_buffer[jrow] -= X_buffer[irow] * ARecv[jrow*nSize + index];<br />
}<br />
/*.... Send to all lower processes...*/<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Send(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Gather the result on the processor 0 ....*/<br />
Output = (double *)malloc(nSize * sizeof(double));<br />
MPI_Gather(X_buffer, nRowsBloc, MPI_DOUBLE, Output, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* .......Output vector .....*/<br />
Vec res(nSize);<br />
if (MyRank == 0) {<br />
for (irow = 0; irow < nSize; irow++)<br />
res[irow] = Output[irow];<br />
}<br />
return res;<br />
}<br />
<br />
Vec sum(const Vec& lhs, const Vec& rhs) {<br />
Vec res(lhs.size());<br />
if (lhs.size() != rhs.size()) {<br />
return res;<br />
}<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res[i] = lhs[i] + rhs[i];<br />
}<br />
return res;<br />
}<br />
<br />
double diff(const Vec& lhs, const Vec& rhs) {<br />
double res = 0.;<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res += lhs[i] - rhs[i];<br />
}<br />
return fabs(res);<br />
}<br />
<br />
class Newtone {<br />
public:<br />
Newtone(int rank) : m_rank(rank) { <br />
m_h = std::numeric_limits<double>::epsilon(); <br />
}<br />
<br />
Vec find_solution(const Sys_& sys, const Vec& start, const Derivatives& d, LSSolver solver, <br />
VecSum vec_summator, VecDiff vec_differ, const double& eps, const size_t& max_iter) {<br />
size_t iter_count = 1;<br />
double diff = 0.;<br />
Vec sys_val(sys.size(), 0);<br />
if (m_rank == 0) {<br />
m_jac.reserve(sys.size());<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
Vec v(sys.size());<br />
m_jac.push_back(v);<br />
}<br />
compute_jacobian(sys, d, start);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, start);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
Vec delta = solver(m_jac, sys_val);<br />
Vec new_sol(sys.size()), old_sol(sys.size());<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(start, delta);<br />
old_sol = start;<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Bcast(old_sol.data(), old_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
diff = vec_differ(new_sol, old_sol);<br />
while (diff > eps && iter_count <= max_iter) {<br />
old_sol = new_sol;<br />
if (m_rank == 0) {<br />
compute_jacobian(sys, d, old_sol);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, old_sol);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
delta = solver(m_jac, sys_val);<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(old_sol, delta);<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
iter_count++;<br />
diff = vec_differ(new_sol, old_sol);<br />
}<br />
return new_sol;<br />
}<br />
<br />
double compute_derivative(const size_t& pos, Function func, const size_t& var_num, const Vec& point) {<br />
Vec left_point(point), right_point(point);<br />
left_point[var_num] -= m_h;<br />
right_point[var_num] += m_h;<br />
double left = func(pos, left_point), right = func(pos, right_point);<br />
return (right - left) / (2 * m_h);<br />
}<br />
<br />
private:<br />
void compute_jacobian(const Sys_& sys, const Derivatives& d, const Vec& point) {<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
for (size_t j = 0; j < sys.size(); ++j) {<br />
double res_val;<br />
res_val = d[i][j](i, j, point);<br />
m_jac[i][j] = res_val;<br />
}<br />
}<br />
}<br />
double m_h;<br />
int m_rank;<br />
Matrix m_jac;<br />
Vec m_right_part;<br />
};<br />
<br />
int main(int argc, char** argv) {<br />
int rank, size;<br />
Sys_ s;<br />
Derivatives d(system_size);<br />
Vec start(system_size, 0.87);<br />
for (size_t i = 0; i < system_size; ++i) {<br />
s.push_back(&func);<br />
}<br />
for (size_t i = 0; i < system_size; ++i) {<br />
for (size_t j = 0; j < system_size; ++j)<br />
d[i].push_back(&derivative);<br />
}<br />
MPI_Init(&argc, &argv);<br />
MPI_Comm_rank(MPI_COMM_WORLD, &rank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &size);<br />
Newtone n(rank);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
double time = MPI_Wtime();<br />
Vec sol = n.find_solution(s, start, d, &gauss, &sum, &diff, 0.0001, 100);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
time = MPI_Wtime() - time;<br />
double max_time;<br />
MPI_Reduce(&time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);<br />
if (rank == 0) {<br />
std::ofstream myfile;<br />
char filename[32];<br />
snprintf(filename, 32, "out_%ld_%d.txt", system_size, size);<br />
myfile.open(filename);<br />
for (size_t i = 0; i < sol.size(); ++i) {<br />
myfile << sol[i] << " ";<br />
}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25284Метод Ньютона для систем нелинейных уравнений2018-01-17T09:32:06Z<p>Konshin: /* Реализация 3 */</p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев_Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр_Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N_Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.В.Арутюнов</b>] и<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.С.Жилкин</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# <math>n</math> - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math> (в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое (<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хотя сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания отдельных этапов.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сходится за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset {\mathbb R}^n</math>. Предположим, что существуют <math>\overline{x^*} \in {\mathbb R}^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра (см. [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
Характеристики реализации алгоритма сильно зависят от выбранного способа нахождения матрицы Якоби и решения СЛАУ. <br />
Для примера рассматривается реализация алгоритма с использованием метода Гаусса на функциях вида:<br />
<br />
<math>f_i(x) = cos(x_i) - 1</math>.<br />
<br />
Для этих функций можно задать точное значение производной в любой точке:<br />
<br />
<math>f_i^' (x) = -sin(x_i)</math>.<br />
<br />
Для тестирования программы было решено использовать исключительно технологию MPI в реализации Intel (IntelMPI<ref name="LINK_IMPI">https://software.intel.com/en-us/intel-mpi-library</ref>) без дополнительных. <br />
Тесты проводились на суперкомпьютере Ломоносов<ref name="LOM_PARALLEL_LINK">https://parallel.ru/cluster/lomonosov.html</ref> <ref name="LOM_WIKI_LINK">https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер)</ref> в разделе test. <br />
Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
* Количество ядер: 8 ядер архитектуры x86<br />
* Количество памяти: 12Гб<br />
<br />
Строка компиляции: mpicxx _scratch/Source.cpp -o _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Строка запуска: sbatch -nN -p test impi _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Результаты тестирования представлены на рисунках 10 и 11, где рис. 10 отображает время работы данной реализации, а рис. 11 - ускорение:<br />
<br />
[[Файл:Nwt 1024 2048 4096.png|thumb|center|800px|Рис.2 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
[[Файл:Nwt spdup.png|thumb|center|800px|Рис.3 Ускорение решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Из рис. 10 и 11 видно, что увеличение числа задействованных в вычислениях процессоров дает выигрыш во времени, однако, из-за необходимости обмениваться данными при решении СЛАУ методом Гаусса, при вовлечении слишком большого числа процессоров, время, требуемое на обмен данными может превысить время непосредственного вычисления. Этот эффект ограничивает масштабируемость программы. Для подтверждения этого тезиса приведён рис. 12, отдельно показывающий время работы задачи с размерностью матрицы 1024:<br />
<br />
[[Файл:ChartGo 1024.png|thumb|center|600px|Рис. 12. Время решения системы из 1024 уравнений в зависимости от количества задействованных процессоров]]<br />
<br />
Как видно из рис. 12 время выполнения программы на 128 процессорах возрастает по сравнению с временем работы на 64 процессорах. <br />
Это связано с тем, что на каждый процессор приходится недостаточно индивидуальной загрузки и основное время работы программы тратится на передачу данных между процессорами.<br />
Если же увеличение количества задействованных процессоров не приводит к возникновению этого эффекта, то увеличение количества процессоров в 2 раза ведёт к увеличению скорости работы программы также приблизительно в 2 раза, что хорошо видно на рис. 12.<br />
<br />
{|class="wikitable mw-collapsible mw-collapsed"<br />
!Исходный код программы<br />
|-<br />
|<source hide="yes" lang="cpp">#include <iostream><br />
#include <cmath><br />
#include <fstream><br />
#include <vector><br />
#include <cstdlib><br />
#include <limits><br />
#include <mpi.h><br />
#include <stdio.h><br />
#include <assert.h><br />
<br />
typedef std::vector<double> Vec;<br />
typedef double(*Function)(const size_t&, const Vec&);<br />
typedef double(*Deriv)(const size_t&, const size_t&, const Vec&);<br />
typedef std::vector<Function> Sys_;<br />
typedef std::vector<std::vector<Deriv> > Derivatives;<br />
typedef std::vector<std::vector<double> > Matrix;<br />
typedef Vec(*LSSolver)(const Matrix&, const Vec&);<br />
typedef Vec(*VecSum)(const Vec&, const Vec&);<br />
typedef double(*VecDiff)(const Vec&, const Vec&);<br />
<br />
struct LS {<br />
Matrix A;<br />
Vec b;<br />
};<br />
<br />
size_t system_size = 4096;<br />
<br />
double func(const size_t& i, const Vec& v) {<br />
return cos(v[i]) - 1;<br />
}<br />
<br />
double derivative(const size_t& i, const size_t& j, const Vec& v) {<br />
if (i == j) {<br />
return -sin(v[i]);<br />
}<br />
return 0.0;<br />
}<br />
<br />
static void printVec(const Vec& v) {<br />
std::cout << "size: " << v.size() << std::endl;<br />
for (size_t i = 0; i < v.size(); ++i) {<br />
std::cout << v[i] << " ";<br />
}<br />
std::cout << std::endl;<br />
}<br />
<br />
Vec gauss(const Matrix& A, const Vec& b) {<br />
MPI_Status status;<br />
int nSize, nRowsBloc, nRows, nCols;<br />
int Numprocs, MyRank, Root = 0;<br />
int irow, jrow, icol, index, ColofPivot, neigh_proc;<br />
double *Input_A, *Input_B, *ARecv, *BRecv;<br />
double *Output, Pivot;<br />
double *X_buffer, *Y_buffer;<br />
double *Buffer_Pivot, *Buffer_bksub;<br />
double tmp;<br />
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &Numprocs);<br />
if (MyRank == 0) {<br />
nRows = A.size();<br />
nCols = A[0].size();<br />
nSize = nRows;<br />
Input_B = (double *)malloc(nSize * sizeof(double));<br />
for (irow = 0; irow < nSize; irow++) {<br />
Input_B[irow] = b[irow];<br />
}<br />
Input_A = (double *)malloc(nSize*nSize * sizeof(double));<br />
index = 0;<br />
for (irow = 0; irow < nSize; irow++)<br />
for (icol = 0; icol < nSize; icol++)<br />
Input_A[index++] = A[irow][icol];<br />
}<br />
MPI_Bcast(&nRows, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
MPI_Bcast(&nCols, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
/* .... Broad cast the size of the matrix to all ....*/<br />
MPI_Bcast(&nSize, 1, MPI_INT, 0, MPI_COMM_WORLD);<br />
nRowsBloc = nSize / Numprocs;<br />
/*......Memory of input matrix and vector on each process .....*/<br />
ARecv = (double *)malloc(nRowsBloc * nSize * sizeof(double));<br />
BRecv = (double *)malloc(nRowsBloc * sizeof(double));<br />
/*......Scatter the Input Data to all process ......*/<br />
MPI_Scatter(Input_A, nRowsBloc * nSize, MPI_DOUBLE, ARecv, nRowsBloc * nSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Scatter(Input_B, nRowsBloc, MPI_DOUBLE, BRecv, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* ....Memory allocation of ray Buffer_Pivot .....*/<br />
Buffer_Pivot = (double *)malloc((nRowsBloc + 1 + nSize * nRowsBloc) * sizeof(double));<br />
/* Receive data from all processors (i=0 to k-1) above my processor (k).... */<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Recv(Buffer_Pivot, nRowsBloc * nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc, MPI_COMM_WORLD, &status);<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
/* .... Buffer_Pivot[0] : locate the rank of the processor received */<br />
/* .... Index is used to reduce the matrix to traingular matrix */<br />
/* .... Buffer_Pivot[0] is used to determine the starting value of<br />
pivot in each row of the matrix, on each processor */<br />
ColofPivot = ((int)Buffer_Pivot[0]) * nRowsBloc + irow;<br />
for (jrow = 0; jrow < nRowsBloc; jrow++) {<br />
index = jrow*nSize;<br />
tmp = ARecv[index + ColofPivot];<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] -= tmp * Buffer_Pivot[irow*nSize + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Buffer_Pivot[1 + irow];<br />
ARecv[index + ColofPivot] = 0.0;<br />
}<br />
}<br />
}<br />
Y_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
/* ....Modification of row entries on each processor ...*/<br />
/* ....Division by pivot value and modification ...*/<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
ColofPivot = MyRank * nRowsBloc + irow;<br />
index = irow*nSize;<br />
Pivot = ARecv[index + ColofPivot];<br />
assert(Pivot != 0);<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] = ARecv[index + icol] / Pivot;<br />
Buffer_Pivot[index + icol + 1 + nRowsBloc] = ARecv[index + icol];<br />
}<br />
Y_buffer[irow] = BRecv[irow] / Pivot;<br />
Buffer_Pivot[irow + 1] = Y_buffer[irow];<br />
for (jrow = irow + 1; jrow < nRowsBloc; jrow++) {<br />
tmp = ARecv[jrow*nSize + ColofPivot];<br />
for (icol = ColofPivot + 1; icol < nSize; icol++) {<br />
ARecv[jrow*nSize + icol] -= tmp * Buffer_Pivot[index + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Y_buffer[irow];<br />
ARecv[jrow*nSize + irow] = 0;<br />
}<br />
}<br />
/*....Send data to all processors below the current processors */<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; neigh_proc++) {<br />
/* ...... Rank is stored in first location of Buffer_Pivot and<br />
this is used in reduction to triangular form ....*/<br />
Buffer_Pivot[0] = (double)MyRank;<br />
MPI_Send(Buffer_Pivot, nRowsBloc*nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Back Substitution starts from here ........*/<br />
/*.... Receive from all higher processors ......*/<br />
Buffer_bksub = (double *)malloc(nRowsBloc * 2 * sizeof(double));<br />
X_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; ++neigh_proc) {<br />
MPI_Recv(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc,<br />
MPI_COMM_WORLD, &status);<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
for (icol = nRowsBloc - 1; icol >= 0; icol--) {<br />
/* ... Pick up starting Index .....*/<br />
index = (int)Buffer_bksub[icol];<br />
Y_buffer[irow] -= Buffer_bksub[nRowsBloc + icol] * ARecv[irow*nSize + index];<br />
}<br />
}<br />
}<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
index = MyRank*nRowsBloc + irow;<br />
Buffer_bksub[irow] = (double)index;<br />
Buffer_bksub[nRowsBloc + irow] = X_buffer[irow] = Y_buffer[irow];<br />
for (jrow = irow - 1; jrow >= 0; jrow--)<br />
Y_buffer[jrow] -= X_buffer[irow] * ARecv[jrow*nSize + index];<br />
}<br />
/*.... Send to all lower processes...*/<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Send(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Gather the result on the processor 0 ....*/<br />
Output = (double *)malloc(nSize * sizeof(double));<br />
MPI_Gather(X_buffer, nRowsBloc, MPI_DOUBLE, Output, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* .......Output vector .....*/<br />
Vec res(nSize);<br />
if (MyRank == 0) {<br />
for (irow = 0; irow < nSize; irow++)<br />
res[irow] = Output[irow];<br />
}<br />
return res;<br />
}<br />
<br />
Vec sum(const Vec& lhs, const Vec& rhs) {<br />
Vec res(lhs.size());<br />
if (lhs.size() != rhs.size()) {<br />
return res;<br />
}<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res[i] = lhs[i] + rhs[i];<br />
}<br />
return res;<br />
}<br />
<br />
double diff(const Vec& lhs, const Vec& rhs) {<br />
double res = 0.;<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res += lhs[i] - rhs[i];<br />
}<br />
return fabs(res);<br />
}<br />
<br />
class Newtone {<br />
public:<br />
Newtone(int rank) : m_rank(rank) { <br />
m_h = std::numeric_limits<double>::epsilon(); <br />
}<br />
<br />
Vec find_solution(const Sys_& sys, const Vec& start, const Derivatives& d, LSSolver solver, <br />
VecSum vec_summator, VecDiff vec_differ, const double& eps, const size_t& max_iter) {<br />
size_t iter_count = 1;<br />
double diff = 0.;<br />
Vec sys_val(sys.size(), 0);<br />
if (m_rank == 0) {<br />
m_jac.reserve(sys.size());<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
Vec v(sys.size());<br />
m_jac.push_back(v);<br />
}<br />
compute_jacobian(sys, d, start);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, start);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
Vec delta = solver(m_jac, sys_val);<br />
Vec new_sol(sys.size()), old_sol(sys.size());<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(start, delta);<br />
old_sol = start;<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Bcast(old_sol.data(), old_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
diff = vec_differ(new_sol, old_sol);<br />
while (diff > eps && iter_count <= max_iter) {<br />
old_sol = new_sol;<br />
if (m_rank == 0) {<br />
compute_jacobian(sys, d, old_sol);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, old_sol);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
delta = solver(m_jac, sys_val);<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(old_sol, delta);<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
iter_count++;<br />
diff = vec_differ(new_sol, old_sol);<br />
}<br />
return new_sol;<br />
}<br />
<br />
double compute_derivative(const size_t& pos, Function func, const size_t& var_num, const Vec& point) {<br />
Vec left_point(point), right_point(point);<br />
left_point[var_num] -= m_h;<br />
right_point[var_num] += m_h;<br />
double left = func(pos, left_point), right = func(pos, right_point);<br />
return (right - left) / (2 * m_h);<br />
}<br />
<br />
private:<br />
void compute_jacobian(const Sys_& sys, const Derivatives& d, const Vec& point) {<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
for (size_t j = 0; j < sys.size(); ++j) {<br />
double res_val;<br />
res_val = d[i][j](i, j, point);<br />
m_jac[i][j] = res_val;<br />
}<br />
}<br />
}<br />
double m_h;<br />
int m_rank;<br />
Matrix m_jac;<br />
Vec m_right_part;<br />
};<br />
<br />
int main(int argc, char** argv) {<br />
int rank, size;<br />
Sys_ s;<br />
Derivatives d(system_size);<br />
Vec start(system_size, 0.87);<br />
for (size_t i = 0; i < system_size; ++i) {<br />
s.push_back(&func);<br />
}<br />
for (size_t i = 0; i < system_size; ++i) {<br />
for (size_t j = 0; j < system_size; ++j)<br />
d[i].push_back(&derivative);<br />
}<br />
MPI_Init(&argc, &argv);<br />
MPI_Comm_rank(MPI_COMM_WORLD, &rank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &size);<br />
Newtone n(rank);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
double time = MPI_Wtime();<br />
Vec sol = n.find_solution(s, start, d, &gauss, &sum, &diff, 0.0001, 100);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
time = MPI_Wtime() - time;<br />
double max_time;<br />
MPI_Reduce(&time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);<br />
if (rank == 0) {<br />
std::ofstream myfile;<br />
char filename[32];<br />
snprintf(filename, 32, "out_%ld_%d.txt", system_size, size);<br />
myfile.open(filename);<br />
for (size_t i = 0; i < sol.size(); ++i) {<br />
myfile << sol[i] << " ";<br />
}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25283Метод Ньютона для систем нелинейных уравнений2018-01-17T09:30:29Z<p>Konshin: /* Свойства алгоритма */</p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев_Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр_Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N_Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.В.Арутюнов</b>] и<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.С.Жилкин</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# <math>n</math> - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math> (в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое (<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хотя сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания отдельных этапов.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сходится за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset {\mathbb R}^n</math>. Предположим, что существуют <math>\overline{x^*} \in {\mathbb R}^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра(см [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
Характеристики реализации алгоритма сильно зависят от выбранного способа нахождения матрицы Якоби и решения СЛАУ. <br />
Для примера рассматривается реализация алгоритма с использованием метода Гаусса на функциях вида:<br />
<br />
<math>f_i(x) = cos(x_i) - 1</math>.<br />
<br />
Для этих функций можно задать точное значение производной в любой точке:<br />
<br />
<math>f_i^' (x) = -sin(x_i)</math>.<br />
<br />
Для тестирования программы было решено использовать исключительно технологию MPI в реализации Intel (IntelMPI<ref name="LINK_IMPI">https://software.intel.com/en-us/intel-mpi-library</ref>) без дополнительных. <br />
Тесты проводились на суперкомпьютере Ломоносов<ref name="LOM_PARALLEL_LINK">https://parallel.ru/cluster/lomonosov.html</ref> <ref name="LOM_WIKI_LINK">https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер)</ref> в разделе test. <br />
Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
* Количество ядер: 8 ядер архитектуры x86<br />
* Количество памяти: 12Гб<br />
<br />
Строка компиляции: mpicxx _scratch/Source.cpp -o _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Строка запуска: sbatch -nN -p test impi _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Результаты тестирования представлены на рисунках 10 и 11, где рис. 10 отображает время работы данной реализации, а рис. 11 - ускорение:<br />
<br />
[[Файл:Nwt 1024 2048 4096.png|thumb|center|800px|Рис.2 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
[[Файл:Nwt spdup.png|thumb|center|800px|Рис.3 Ускорение решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Из рис. 10 и 11 видно, что увеличение числа задействованных в вычислениях процессоров дает выигрыш во времени, однако, из-за необходимости обмениваться данными при решении СЛАУ методом Гаусса, при вовлечении слишком большого числа процессоров, время, требуемое на обмен данными может превысить время непосредственного вычисления. Этот эффект ограничивает масштабируемость программы. Для подтверждения этого тезиса приведён рис. 12, отдельно показывающий время работы задачи с размерностью матрицы 1024:<br />
<br />
[[Файл:ChartGo 1024.png|thumb|center|600px|Рис. 12. Время решения системы из 1024 уравнений в зависимости от количества задействованных процессоров]]<br />
<br />
Как видно из рис. 12 время выполнения программы на 128 процессорах возрастает по сравнению с временем работы на 64 процессорах. <br />
Это связано с тем, что на каждый процессор приходится недостаточно индивидуальной загрузки и основное время работы программы тратится на передачу данных между процессорами.<br />
Если же увеличение количества задействованных процессоров не приводит к возникновению этого эффекта, то увеличение количества процессоров в 2 раза ведёт к увеличению скорости работы программы также приблизительно в 2 раза, что хорошо видно на рис. 12.<br />
<br />
{|class="wikitable mw-collapsible mw-collapsed"<br />
!Исходный код программы<br />
|-<br />
|<source hide="yes" lang="cpp">#include <iostream><br />
#include <cmath><br />
#include <fstream><br />
#include <vector><br />
#include <cstdlib><br />
#include <limits><br />
#include <mpi.h><br />
#include <stdio.h><br />
#include <assert.h><br />
<br />
typedef std::vector<double> Vec;<br />
typedef double(*Function)(const size_t&, const Vec&);<br />
typedef double(*Deriv)(const size_t&, const size_t&, const Vec&);<br />
typedef std::vector<Function> Sys_;<br />
typedef std::vector<std::vector<Deriv> > Derivatives;<br />
typedef std::vector<std::vector<double> > Matrix;<br />
typedef Vec(*LSSolver)(const Matrix&, const Vec&);<br />
typedef Vec(*VecSum)(const Vec&, const Vec&);<br />
typedef double(*VecDiff)(const Vec&, const Vec&);<br />
<br />
struct LS {<br />
Matrix A;<br />
Vec b;<br />
};<br />
<br />
size_t system_size = 4096;<br />
<br />
double func(const size_t& i, const Vec& v) {<br />
return cos(v[i]) - 1;<br />
}<br />
<br />
double derivative(const size_t& i, const size_t& j, const Vec& v) {<br />
if (i == j) {<br />
return -sin(v[i]);<br />
}<br />
return 0.0;<br />
}<br />
<br />
static void printVec(const Vec& v) {<br />
std::cout << "size: " << v.size() << std::endl;<br />
for (size_t i = 0; i < v.size(); ++i) {<br />
std::cout << v[i] << " ";<br />
}<br />
std::cout << std::endl;<br />
}<br />
<br />
Vec gauss(const Matrix& A, const Vec& b) {<br />
MPI_Status status;<br />
int nSize, nRowsBloc, nRows, nCols;<br />
int Numprocs, MyRank, Root = 0;<br />
int irow, jrow, icol, index, ColofPivot, neigh_proc;<br />
double *Input_A, *Input_B, *ARecv, *BRecv;<br />
double *Output, Pivot;<br />
double *X_buffer, *Y_buffer;<br />
double *Buffer_Pivot, *Buffer_bksub;<br />
double tmp;<br />
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &Numprocs);<br />
if (MyRank == 0) {<br />
nRows = A.size();<br />
nCols = A[0].size();<br />
nSize = nRows;<br />
Input_B = (double *)malloc(nSize * sizeof(double));<br />
for (irow = 0; irow < nSize; irow++) {<br />
Input_B[irow] = b[irow];<br />
}<br />
Input_A = (double *)malloc(nSize*nSize * sizeof(double));<br />
index = 0;<br />
for (irow = 0; irow < nSize; irow++)<br />
for (icol = 0; icol < nSize; icol++)<br />
Input_A[index++] = A[irow][icol];<br />
}<br />
MPI_Bcast(&nRows, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
MPI_Bcast(&nCols, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
/* .... Broad cast the size of the matrix to all ....*/<br />
MPI_Bcast(&nSize, 1, MPI_INT, 0, MPI_COMM_WORLD);<br />
nRowsBloc = nSize / Numprocs;<br />
/*......Memory of input matrix and vector on each process .....*/<br />
ARecv = (double *)malloc(nRowsBloc * nSize * sizeof(double));<br />
BRecv = (double *)malloc(nRowsBloc * sizeof(double));<br />
/*......Scatter the Input Data to all process ......*/<br />
MPI_Scatter(Input_A, nRowsBloc * nSize, MPI_DOUBLE, ARecv, nRowsBloc * nSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Scatter(Input_B, nRowsBloc, MPI_DOUBLE, BRecv, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* ....Memory allocation of ray Buffer_Pivot .....*/<br />
Buffer_Pivot = (double *)malloc((nRowsBloc + 1 + nSize * nRowsBloc) * sizeof(double));<br />
/* Receive data from all processors (i=0 to k-1) above my processor (k).... */<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Recv(Buffer_Pivot, nRowsBloc * nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc, MPI_COMM_WORLD, &status);<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
/* .... Buffer_Pivot[0] : locate the rank of the processor received */<br />
/* .... Index is used to reduce the matrix to traingular matrix */<br />
/* .... Buffer_Pivot[0] is used to determine the starting value of<br />
pivot in each row of the matrix, on each processor */<br />
ColofPivot = ((int)Buffer_Pivot[0]) * nRowsBloc + irow;<br />
for (jrow = 0; jrow < nRowsBloc; jrow++) {<br />
index = jrow*nSize;<br />
tmp = ARecv[index + ColofPivot];<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] -= tmp * Buffer_Pivot[irow*nSize + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Buffer_Pivot[1 + irow];<br />
ARecv[index + ColofPivot] = 0.0;<br />
}<br />
}<br />
}<br />
Y_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
/* ....Modification of row entries on each processor ...*/<br />
/* ....Division by pivot value and modification ...*/<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
ColofPivot = MyRank * nRowsBloc + irow;<br />
index = irow*nSize;<br />
Pivot = ARecv[index + ColofPivot];<br />
assert(Pivot != 0);<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] = ARecv[index + icol] / Pivot;<br />
Buffer_Pivot[index + icol + 1 + nRowsBloc] = ARecv[index + icol];<br />
}<br />
Y_buffer[irow] = BRecv[irow] / Pivot;<br />
Buffer_Pivot[irow + 1] = Y_buffer[irow];<br />
for (jrow = irow + 1; jrow < nRowsBloc; jrow++) {<br />
tmp = ARecv[jrow*nSize + ColofPivot];<br />
for (icol = ColofPivot + 1; icol < nSize; icol++) {<br />
ARecv[jrow*nSize + icol] -= tmp * Buffer_Pivot[index + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Y_buffer[irow];<br />
ARecv[jrow*nSize + irow] = 0;<br />
}<br />
}<br />
/*....Send data to all processors below the current processors */<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; neigh_proc++) {<br />
/* ...... Rank is stored in first location of Buffer_Pivot and<br />
this is used in reduction to triangular form ....*/<br />
Buffer_Pivot[0] = (double)MyRank;<br />
MPI_Send(Buffer_Pivot, nRowsBloc*nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Back Substitution starts from here ........*/<br />
/*.... Receive from all higher processors ......*/<br />
Buffer_bksub = (double *)malloc(nRowsBloc * 2 * sizeof(double));<br />
X_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; ++neigh_proc) {<br />
MPI_Recv(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc,<br />
MPI_COMM_WORLD, &status);<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
for (icol = nRowsBloc - 1; icol >= 0; icol--) {<br />
/* ... Pick up starting Index .....*/<br />
index = (int)Buffer_bksub[icol];<br />
Y_buffer[irow] -= Buffer_bksub[nRowsBloc + icol] * ARecv[irow*nSize + index];<br />
}<br />
}<br />
}<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
index = MyRank*nRowsBloc + irow;<br />
Buffer_bksub[irow] = (double)index;<br />
Buffer_bksub[nRowsBloc + irow] = X_buffer[irow] = Y_buffer[irow];<br />
for (jrow = irow - 1; jrow >= 0; jrow--)<br />
Y_buffer[jrow] -= X_buffer[irow] * ARecv[jrow*nSize + index];<br />
}<br />
/*.... Send to all lower processes...*/<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Send(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Gather the result on the processor 0 ....*/<br />
Output = (double *)malloc(nSize * sizeof(double));<br />
MPI_Gather(X_buffer, nRowsBloc, MPI_DOUBLE, Output, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* .......Output vector .....*/<br />
Vec res(nSize);<br />
if (MyRank == 0) {<br />
for (irow = 0; irow < nSize; irow++)<br />
res[irow] = Output[irow];<br />
}<br />
return res;<br />
}<br />
<br />
Vec sum(const Vec& lhs, const Vec& rhs) {<br />
Vec res(lhs.size());<br />
if (lhs.size() != rhs.size()) {<br />
return res;<br />
}<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res[i] = lhs[i] + rhs[i];<br />
}<br />
return res;<br />
}<br />
<br />
double diff(const Vec& lhs, const Vec& rhs) {<br />
double res = 0.;<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res += lhs[i] - rhs[i];<br />
}<br />
return fabs(res);<br />
}<br />
<br />
class Newtone {<br />
public:<br />
Newtone(int rank) : m_rank(rank) { <br />
m_h = std::numeric_limits<double>::epsilon(); <br />
}<br />
<br />
Vec find_solution(const Sys_& sys, const Vec& start, const Derivatives& d, LSSolver solver, <br />
VecSum vec_summator, VecDiff vec_differ, const double& eps, const size_t& max_iter) {<br />
size_t iter_count = 1;<br />
double diff = 0.;<br />
Vec sys_val(sys.size(), 0);<br />
if (m_rank == 0) {<br />
m_jac.reserve(sys.size());<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
Vec v(sys.size());<br />
m_jac.push_back(v);<br />
}<br />
compute_jacobian(sys, d, start);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, start);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
Vec delta = solver(m_jac, sys_val);<br />
Vec new_sol(sys.size()), old_sol(sys.size());<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(start, delta);<br />
old_sol = start;<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Bcast(old_sol.data(), old_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
diff = vec_differ(new_sol, old_sol);<br />
while (diff > eps && iter_count <= max_iter) {<br />
old_sol = new_sol;<br />
if (m_rank == 0) {<br />
compute_jacobian(sys, d, old_sol);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, old_sol);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
delta = solver(m_jac, sys_val);<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(old_sol, delta);<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
iter_count++;<br />
diff = vec_differ(new_sol, old_sol);<br />
}<br />
return new_sol;<br />
}<br />
<br />
double compute_derivative(const size_t& pos, Function func, const size_t& var_num, const Vec& point) {<br />
Vec left_point(point), right_point(point);<br />
left_point[var_num] -= m_h;<br />
right_point[var_num] += m_h;<br />
double left = func(pos, left_point), right = func(pos, right_point);<br />
return (right - left) / (2 * m_h);<br />
}<br />
<br />
private:<br />
void compute_jacobian(const Sys_& sys, const Derivatives& d, const Vec& point) {<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
for (size_t j = 0; j < sys.size(); ++j) {<br />
double res_val;<br />
res_val = d[i][j](i, j, point);<br />
m_jac[i][j] = res_val;<br />
}<br />
}<br />
}<br />
double m_h;<br />
int m_rank;<br />
Matrix m_jac;<br />
Vec m_right_part;<br />
};<br />
<br />
int main(int argc, char** argv) {<br />
int rank, size;<br />
Sys_ s;<br />
Derivatives d(system_size);<br />
Vec start(system_size, 0.87);<br />
for (size_t i = 0; i < system_size; ++i) {<br />
s.push_back(&func);<br />
}<br />
for (size_t i = 0; i < system_size; ++i) {<br />
for (size_t j = 0; j < system_size; ++j)<br />
d[i].push_back(&derivative);<br />
}<br />
MPI_Init(&argc, &argv);<br />
MPI_Comm_rank(MPI_COMM_WORLD, &rank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &size);<br />
Newtone n(rank);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
double time = MPI_Wtime();<br />
Vec sol = n.find_solution(s, start, d, &gauss, &sum, &diff, 0.0001, 100);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
time = MPI_Wtime() - time;<br />
double max_time;<br />
MPI_Reduce(&time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);<br />
if (rank == 0) {<br />
std::ofstream myfile;<br />
char filename[32];<br />
snprintf(filename, 32, "out_%ld_%d.txt", system_size, size);<br />
myfile.open(filename);<br />
for (size_t i = 0; i < sol.size(); ++i) {<br />
myfile << sol[i] << " ";<br />
}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25282Метод Ньютона для систем нелинейных уравнений2018-01-17T09:29:14Z<p>Konshin: /* Ресурс параллелизма алгоритма */</p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев_Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр_Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N_Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.В.Арутюнов</b>] и<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.С.Жилкин</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# <math>n</math> - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math> (в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое (<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хотя сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания отдельных этапов.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сходится за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра(см [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
Характеристики реализации алгоритма сильно зависят от выбранного способа нахождения матрицы Якоби и решения СЛАУ. <br />
Для примера рассматривается реализация алгоритма с использованием метода Гаусса на функциях вида:<br />
<br />
<math>f_i(x) = cos(x_i) - 1</math>.<br />
<br />
Для этих функций можно задать точное значение производной в любой точке:<br />
<br />
<math>f_i^' (x) = -sin(x_i)</math>.<br />
<br />
Для тестирования программы было решено использовать исключительно технологию MPI в реализации Intel (IntelMPI<ref name="LINK_IMPI">https://software.intel.com/en-us/intel-mpi-library</ref>) без дополнительных. <br />
Тесты проводились на суперкомпьютере Ломоносов<ref name="LOM_PARALLEL_LINK">https://parallel.ru/cluster/lomonosov.html</ref> <ref name="LOM_WIKI_LINK">https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер)</ref> в разделе test. <br />
Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
* Количество ядер: 8 ядер архитектуры x86<br />
* Количество памяти: 12Гб<br />
<br />
Строка компиляции: mpicxx _scratch/Source.cpp -o _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Строка запуска: sbatch -nN -p test impi _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Результаты тестирования представлены на рисунках 10 и 11, где рис. 10 отображает время работы данной реализации, а рис. 11 - ускорение:<br />
<br />
[[Файл:Nwt 1024 2048 4096.png|thumb|center|800px|Рис.2 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
[[Файл:Nwt spdup.png|thumb|center|800px|Рис.3 Ускорение решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Из рис. 10 и 11 видно, что увеличение числа задействованных в вычислениях процессоров дает выигрыш во времени, однако, из-за необходимости обмениваться данными при решении СЛАУ методом Гаусса, при вовлечении слишком большого числа процессоров, время, требуемое на обмен данными может превысить время непосредственного вычисления. Этот эффект ограничивает масштабируемость программы. Для подтверждения этого тезиса приведён рис. 12, отдельно показывающий время работы задачи с размерностью матрицы 1024:<br />
<br />
[[Файл:ChartGo 1024.png|thumb|center|600px|Рис. 12. Время решения системы из 1024 уравнений в зависимости от количества задействованных процессоров]]<br />
<br />
Как видно из рис. 12 время выполнения программы на 128 процессорах возрастает по сравнению с временем работы на 64 процессорах. <br />
Это связано с тем, что на каждый процессор приходится недостаточно индивидуальной загрузки и основное время работы программы тратится на передачу данных между процессорами.<br />
Если же увеличение количества задействованных процессоров не приводит к возникновению этого эффекта, то увеличение количества процессоров в 2 раза ведёт к увеличению скорости работы программы также приблизительно в 2 раза, что хорошо видно на рис. 12.<br />
<br />
{|class="wikitable mw-collapsible mw-collapsed"<br />
!Исходный код программы<br />
|-<br />
|<source hide="yes" lang="cpp">#include <iostream><br />
#include <cmath><br />
#include <fstream><br />
#include <vector><br />
#include <cstdlib><br />
#include <limits><br />
#include <mpi.h><br />
#include <stdio.h><br />
#include <assert.h><br />
<br />
typedef std::vector<double> Vec;<br />
typedef double(*Function)(const size_t&, const Vec&);<br />
typedef double(*Deriv)(const size_t&, const size_t&, const Vec&);<br />
typedef std::vector<Function> Sys_;<br />
typedef std::vector<std::vector<Deriv> > Derivatives;<br />
typedef std::vector<std::vector<double> > Matrix;<br />
typedef Vec(*LSSolver)(const Matrix&, const Vec&);<br />
typedef Vec(*VecSum)(const Vec&, const Vec&);<br />
typedef double(*VecDiff)(const Vec&, const Vec&);<br />
<br />
struct LS {<br />
Matrix A;<br />
Vec b;<br />
};<br />
<br />
size_t system_size = 4096;<br />
<br />
double func(const size_t& i, const Vec& v) {<br />
return cos(v[i]) - 1;<br />
}<br />
<br />
double derivative(const size_t& i, const size_t& j, const Vec& v) {<br />
if (i == j) {<br />
return -sin(v[i]);<br />
}<br />
return 0.0;<br />
}<br />
<br />
static void printVec(const Vec& v) {<br />
std::cout << "size: " << v.size() << std::endl;<br />
for (size_t i = 0; i < v.size(); ++i) {<br />
std::cout << v[i] << " ";<br />
}<br />
std::cout << std::endl;<br />
}<br />
<br />
Vec gauss(const Matrix& A, const Vec& b) {<br />
MPI_Status status;<br />
int nSize, nRowsBloc, nRows, nCols;<br />
int Numprocs, MyRank, Root = 0;<br />
int irow, jrow, icol, index, ColofPivot, neigh_proc;<br />
double *Input_A, *Input_B, *ARecv, *BRecv;<br />
double *Output, Pivot;<br />
double *X_buffer, *Y_buffer;<br />
double *Buffer_Pivot, *Buffer_bksub;<br />
double tmp;<br />
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &Numprocs);<br />
if (MyRank == 0) {<br />
nRows = A.size();<br />
nCols = A[0].size();<br />
nSize = nRows;<br />
Input_B = (double *)malloc(nSize * sizeof(double));<br />
for (irow = 0; irow < nSize; irow++) {<br />
Input_B[irow] = b[irow];<br />
}<br />
Input_A = (double *)malloc(nSize*nSize * sizeof(double));<br />
index = 0;<br />
for (irow = 0; irow < nSize; irow++)<br />
for (icol = 0; icol < nSize; icol++)<br />
Input_A[index++] = A[irow][icol];<br />
}<br />
MPI_Bcast(&nRows, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
MPI_Bcast(&nCols, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
/* .... Broad cast the size of the matrix to all ....*/<br />
MPI_Bcast(&nSize, 1, MPI_INT, 0, MPI_COMM_WORLD);<br />
nRowsBloc = nSize / Numprocs;<br />
/*......Memory of input matrix and vector on each process .....*/<br />
ARecv = (double *)malloc(nRowsBloc * nSize * sizeof(double));<br />
BRecv = (double *)malloc(nRowsBloc * sizeof(double));<br />
/*......Scatter the Input Data to all process ......*/<br />
MPI_Scatter(Input_A, nRowsBloc * nSize, MPI_DOUBLE, ARecv, nRowsBloc * nSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Scatter(Input_B, nRowsBloc, MPI_DOUBLE, BRecv, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* ....Memory allocation of ray Buffer_Pivot .....*/<br />
Buffer_Pivot = (double *)malloc((nRowsBloc + 1 + nSize * nRowsBloc) * sizeof(double));<br />
/* Receive data from all processors (i=0 to k-1) above my processor (k).... */<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Recv(Buffer_Pivot, nRowsBloc * nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc, MPI_COMM_WORLD, &status);<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
/* .... Buffer_Pivot[0] : locate the rank of the processor received */<br />
/* .... Index is used to reduce the matrix to traingular matrix */<br />
/* .... Buffer_Pivot[0] is used to determine the starting value of<br />
pivot in each row of the matrix, on each processor */<br />
ColofPivot = ((int)Buffer_Pivot[0]) * nRowsBloc + irow;<br />
for (jrow = 0; jrow < nRowsBloc; jrow++) {<br />
index = jrow*nSize;<br />
tmp = ARecv[index + ColofPivot];<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] -= tmp * Buffer_Pivot[irow*nSize + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Buffer_Pivot[1 + irow];<br />
ARecv[index + ColofPivot] = 0.0;<br />
}<br />
}<br />
}<br />
Y_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
/* ....Modification of row entries on each processor ...*/<br />
/* ....Division by pivot value and modification ...*/<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
ColofPivot = MyRank * nRowsBloc + irow;<br />
index = irow*nSize;<br />
Pivot = ARecv[index + ColofPivot];<br />
assert(Pivot != 0);<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] = ARecv[index + icol] / Pivot;<br />
Buffer_Pivot[index + icol + 1 + nRowsBloc] = ARecv[index + icol];<br />
}<br />
Y_buffer[irow] = BRecv[irow] / Pivot;<br />
Buffer_Pivot[irow + 1] = Y_buffer[irow];<br />
for (jrow = irow + 1; jrow < nRowsBloc; jrow++) {<br />
tmp = ARecv[jrow*nSize + ColofPivot];<br />
for (icol = ColofPivot + 1; icol < nSize; icol++) {<br />
ARecv[jrow*nSize + icol] -= tmp * Buffer_Pivot[index + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Y_buffer[irow];<br />
ARecv[jrow*nSize + irow] = 0;<br />
}<br />
}<br />
/*....Send data to all processors below the current processors */<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; neigh_proc++) {<br />
/* ...... Rank is stored in first location of Buffer_Pivot and<br />
this is used in reduction to triangular form ....*/<br />
Buffer_Pivot[0] = (double)MyRank;<br />
MPI_Send(Buffer_Pivot, nRowsBloc*nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Back Substitution starts from here ........*/<br />
/*.... Receive from all higher processors ......*/<br />
Buffer_bksub = (double *)malloc(nRowsBloc * 2 * sizeof(double));<br />
X_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; ++neigh_proc) {<br />
MPI_Recv(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc,<br />
MPI_COMM_WORLD, &status);<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
for (icol = nRowsBloc - 1; icol >= 0; icol--) {<br />
/* ... Pick up starting Index .....*/<br />
index = (int)Buffer_bksub[icol];<br />
Y_buffer[irow] -= Buffer_bksub[nRowsBloc + icol] * ARecv[irow*nSize + index];<br />
}<br />
}<br />
}<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
index = MyRank*nRowsBloc + irow;<br />
Buffer_bksub[irow] = (double)index;<br />
Buffer_bksub[nRowsBloc + irow] = X_buffer[irow] = Y_buffer[irow];<br />
for (jrow = irow - 1; jrow >= 0; jrow--)<br />
Y_buffer[jrow] -= X_buffer[irow] * ARecv[jrow*nSize + index];<br />
}<br />
/*.... Send to all lower processes...*/<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Send(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Gather the result on the processor 0 ....*/<br />
Output = (double *)malloc(nSize * sizeof(double));<br />
MPI_Gather(X_buffer, nRowsBloc, MPI_DOUBLE, Output, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* .......Output vector .....*/<br />
Vec res(nSize);<br />
if (MyRank == 0) {<br />
for (irow = 0; irow < nSize; irow++)<br />
res[irow] = Output[irow];<br />
}<br />
return res;<br />
}<br />
<br />
Vec sum(const Vec& lhs, const Vec& rhs) {<br />
Vec res(lhs.size());<br />
if (lhs.size() != rhs.size()) {<br />
return res;<br />
}<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res[i] = lhs[i] + rhs[i];<br />
}<br />
return res;<br />
}<br />
<br />
double diff(const Vec& lhs, const Vec& rhs) {<br />
double res = 0.;<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res += lhs[i] - rhs[i];<br />
}<br />
return fabs(res);<br />
}<br />
<br />
class Newtone {<br />
public:<br />
Newtone(int rank) : m_rank(rank) { <br />
m_h = std::numeric_limits<double>::epsilon(); <br />
}<br />
<br />
Vec find_solution(const Sys_& sys, const Vec& start, const Derivatives& d, LSSolver solver, <br />
VecSum vec_summator, VecDiff vec_differ, const double& eps, const size_t& max_iter) {<br />
size_t iter_count = 1;<br />
double diff = 0.;<br />
Vec sys_val(sys.size(), 0);<br />
if (m_rank == 0) {<br />
m_jac.reserve(sys.size());<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
Vec v(sys.size());<br />
m_jac.push_back(v);<br />
}<br />
compute_jacobian(sys, d, start);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, start);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
Vec delta = solver(m_jac, sys_val);<br />
Vec new_sol(sys.size()), old_sol(sys.size());<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(start, delta);<br />
old_sol = start;<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Bcast(old_sol.data(), old_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
diff = vec_differ(new_sol, old_sol);<br />
while (diff > eps && iter_count <= max_iter) {<br />
old_sol = new_sol;<br />
if (m_rank == 0) {<br />
compute_jacobian(sys, d, old_sol);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, old_sol);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
delta = solver(m_jac, sys_val);<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(old_sol, delta);<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
iter_count++;<br />
diff = vec_differ(new_sol, old_sol);<br />
}<br />
return new_sol;<br />
}<br />
<br />
double compute_derivative(const size_t& pos, Function func, const size_t& var_num, const Vec& point) {<br />
Vec left_point(point), right_point(point);<br />
left_point[var_num] -= m_h;<br />
right_point[var_num] += m_h;<br />
double left = func(pos, left_point), right = func(pos, right_point);<br />
return (right - left) / (2 * m_h);<br />
}<br />
<br />
private:<br />
void compute_jacobian(const Sys_& sys, const Derivatives& d, const Vec& point) {<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
for (size_t j = 0; j < sys.size(); ++j) {<br />
double res_val;<br />
res_val = d[i][j](i, j, point);<br />
m_jac[i][j] = res_val;<br />
}<br />
}<br />
}<br />
double m_h;<br />
int m_rank;<br />
Matrix m_jac;<br />
Vec m_right_part;<br />
};<br />
<br />
int main(int argc, char** argv) {<br />
int rank, size;<br />
Sys_ s;<br />
Derivatives d(system_size);<br />
Vec start(system_size, 0.87);<br />
for (size_t i = 0; i < system_size; ++i) {<br />
s.push_back(&func);<br />
}<br />
for (size_t i = 0; i < system_size; ++i) {<br />
for (size_t j = 0; j < system_size; ++j)<br />
d[i].push_back(&derivative);<br />
}<br />
MPI_Init(&argc, &argv);<br />
MPI_Comm_rank(MPI_COMM_WORLD, &rank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &size);<br />
Newtone n(rank);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
double time = MPI_Wtime();<br />
Vec sol = n.find_solution(s, start, d, &gauss, &sum, &diff, 0.0001, 100);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
time = MPI_Wtime() - time;<br />
double max_time;<br />
MPI_Reduce(&time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);<br />
if (rank == 0) {<br />
std::ofstream myfile;<br />
char filename[32];<br />
snprintf(filename, 32, "out_%ld_%d.txt", system_size, size);<br />
myfile.open(filename);<br />
for (size_t i = 0; i < sol.size(); ++i) {<br />
myfile << sol[i] << " ";<br />
}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25281Метод Ньютона для систем нелинейных уравнений2018-01-17T09:27:26Z<p>Konshin: /* Последовательная сложность алгоритма */</p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев_Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр_Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N_Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.В.Арутюнов</b>] и<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.С.Жилкин</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# <math>n</math> - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math> (в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое (<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра(см [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
Характеристики реализации алгоритма сильно зависят от выбранного способа нахождения матрицы Якоби и решения СЛАУ. <br />
Для примера рассматривается реализация алгоритма с использованием метода Гаусса на функциях вида:<br />
<br />
<math>f_i(x) = cos(x_i) - 1</math>.<br />
<br />
Для этих функций можно задать точное значение производной в любой точке:<br />
<br />
<math>f_i^' (x) = -sin(x_i)</math>.<br />
<br />
Для тестирования программы было решено использовать исключительно технологию MPI в реализации Intel (IntelMPI<ref name="LINK_IMPI">https://software.intel.com/en-us/intel-mpi-library</ref>) без дополнительных. <br />
Тесты проводились на суперкомпьютере Ломоносов<ref name="LOM_PARALLEL_LINK">https://parallel.ru/cluster/lomonosov.html</ref> <ref name="LOM_WIKI_LINK">https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер)</ref> в разделе test. <br />
Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
* Количество ядер: 8 ядер архитектуры x86<br />
* Количество памяти: 12Гб<br />
<br />
Строка компиляции: mpicxx _scratch/Source.cpp -o _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Строка запуска: sbatch -nN -p test impi _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Результаты тестирования представлены на рисунках 10 и 11, где рис. 10 отображает время работы данной реализации, а рис. 11 - ускорение:<br />
<br />
[[Файл:Nwt 1024 2048 4096.png|thumb|center|800px|Рис.2 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
[[Файл:Nwt spdup.png|thumb|center|800px|Рис.3 Ускорение решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Из рис. 10 и 11 видно, что увеличение числа задействованных в вычислениях процессоров дает выигрыш во времени, однако, из-за необходимости обмениваться данными при решении СЛАУ методом Гаусса, при вовлечении слишком большого числа процессоров, время, требуемое на обмен данными может превысить время непосредственного вычисления. Этот эффект ограничивает масштабируемость программы. Для подтверждения этого тезиса приведён рис. 12, отдельно показывающий время работы задачи с размерностью матрицы 1024:<br />
<br />
[[Файл:ChartGo 1024.png|thumb|center|600px|Рис. 12. Время решения системы из 1024 уравнений в зависимости от количества задействованных процессоров]]<br />
<br />
Как видно из рис. 12 время выполнения программы на 128 процессорах возрастает по сравнению с временем работы на 64 процессорах. <br />
Это связано с тем, что на каждый процессор приходится недостаточно индивидуальной загрузки и основное время работы программы тратится на передачу данных между процессорами.<br />
Если же увеличение количества задействованных процессоров не приводит к возникновению этого эффекта, то увеличение количества процессоров в 2 раза ведёт к увеличению скорости работы программы также приблизительно в 2 раза, что хорошо видно на рис. 12.<br />
<br />
{|class="wikitable mw-collapsible mw-collapsed"<br />
!Исходный код программы<br />
|-<br />
|<source hide="yes" lang="cpp">#include <iostream><br />
#include <cmath><br />
#include <fstream><br />
#include <vector><br />
#include <cstdlib><br />
#include <limits><br />
#include <mpi.h><br />
#include <stdio.h><br />
#include <assert.h><br />
<br />
typedef std::vector<double> Vec;<br />
typedef double(*Function)(const size_t&, const Vec&);<br />
typedef double(*Deriv)(const size_t&, const size_t&, const Vec&);<br />
typedef std::vector<Function> Sys_;<br />
typedef std::vector<std::vector<Deriv> > Derivatives;<br />
typedef std::vector<std::vector<double> > Matrix;<br />
typedef Vec(*LSSolver)(const Matrix&, const Vec&);<br />
typedef Vec(*VecSum)(const Vec&, const Vec&);<br />
typedef double(*VecDiff)(const Vec&, const Vec&);<br />
<br />
struct LS {<br />
Matrix A;<br />
Vec b;<br />
};<br />
<br />
size_t system_size = 4096;<br />
<br />
double func(const size_t& i, const Vec& v) {<br />
return cos(v[i]) - 1;<br />
}<br />
<br />
double derivative(const size_t& i, const size_t& j, const Vec& v) {<br />
if (i == j) {<br />
return -sin(v[i]);<br />
}<br />
return 0.0;<br />
}<br />
<br />
static void printVec(const Vec& v) {<br />
std::cout << "size: " << v.size() << std::endl;<br />
for (size_t i = 0; i < v.size(); ++i) {<br />
std::cout << v[i] << " ";<br />
}<br />
std::cout << std::endl;<br />
}<br />
<br />
Vec gauss(const Matrix& A, const Vec& b) {<br />
MPI_Status status;<br />
int nSize, nRowsBloc, nRows, nCols;<br />
int Numprocs, MyRank, Root = 0;<br />
int irow, jrow, icol, index, ColofPivot, neigh_proc;<br />
double *Input_A, *Input_B, *ARecv, *BRecv;<br />
double *Output, Pivot;<br />
double *X_buffer, *Y_buffer;<br />
double *Buffer_Pivot, *Buffer_bksub;<br />
double tmp;<br />
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &Numprocs);<br />
if (MyRank == 0) {<br />
nRows = A.size();<br />
nCols = A[0].size();<br />
nSize = nRows;<br />
Input_B = (double *)malloc(nSize * sizeof(double));<br />
for (irow = 0; irow < nSize; irow++) {<br />
Input_B[irow] = b[irow];<br />
}<br />
Input_A = (double *)malloc(nSize*nSize * sizeof(double));<br />
index = 0;<br />
for (irow = 0; irow < nSize; irow++)<br />
for (icol = 0; icol < nSize; icol++)<br />
Input_A[index++] = A[irow][icol];<br />
}<br />
MPI_Bcast(&nRows, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
MPI_Bcast(&nCols, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
/* .... Broad cast the size of the matrix to all ....*/<br />
MPI_Bcast(&nSize, 1, MPI_INT, 0, MPI_COMM_WORLD);<br />
nRowsBloc = nSize / Numprocs;<br />
/*......Memory of input matrix and vector on each process .....*/<br />
ARecv = (double *)malloc(nRowsBloc * nSize * sizeof(double));<br />
BRecv = (double *)malloc(nRowsBloc * sizeof(double));<br />
/*......Scatter the Input Data to all process ......*/<br />
MPI_Scatter(Input_A, nRowsBloc * nSize, MPI_DOUBLE, ARecv, nRowsBloc * nSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Scatter(Input_B, nRowsBloc, MPI_DOUBLE, BRecv, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* ....Memory allocation of ray Buffer_Pivot .....*/<br />
Buffer_Pivot = (double *)malloc((nRowsBloc + 1 + nSize * nRowsBloc) * sizeof(double));<br />
/* Receive data from all processors (i=0 to k-1) above my processor (k).... */<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Recv(Buffer_Pivot, nRowsBloc * nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc, MPI_COMM_WORLD, &status);<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
/* .... Buffer_Pivot[0] : locate the rank of the processor received */<br />
/* .... Index is used to reduce the matrix to traingular matrix */<br />
/* .... Buffer_Pivot[0] is used to determine the starting value of<br />
pivot in each row of the matrix, on each processor */<br />
ColofPivot = ((int)Buffer_Pivot[0]) * nRowsBloc + irow;<br />
for (jrow = 0; jrow < nRowsBloc; jrow++) {<br />
index = jrow*nSize;<br />
tmp = ARecv[index + ColofPivot];<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] -= tmp * Buffer_Pivot[irow*nSize + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Buffer_Pivot[1 + irow];<br />
ARecv[index + ColofPivot] = 0.0;<br />
}<br />
}<br />
}<br />
Y_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
/* ....Modification of row entries on each processor ...*/<br />
/* ....Division by pivot value and modification ...*/<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
ColofPivot = MyRank * nRowsBloc + irow;<br />
index = irow*nSize;<br />
Pivot = ARecv[index + ColofPivot];<br />
assert(Pivot != 0);<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] = ARecv[index + icol] / Pivot;<br />
Buffer_Pivot[index + icol + 1 + nRowsBloc] = ARecv[index + icol];<br />
}<br />
Y_buffer[irow] = BRecv[irow] / Pivot;<br />
Buffer_Pivot[irow + 1] = Y_buffer[irow];<br />
for (jrow = irow + 1; jrow < nRowsBloc; jrow++) {<br />
tmp = ARecv[jrow*nSize + ColofPivot];<br />
for (icol = ColofPivot + 1; icol < nSize; icol++) {<br />
ARecv[jrow*nSize + icol] -= tmp * Buffer_Pivot[index + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Y_buffer[irow];<br />
ARecv[jrow*nSize + irow] = 0;<br />
}<br />
}<br />
/*....Send data to all processors below the current processors */<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; neigh_proc++) {<br />
/* ...... Rank is stored in first location of Buffer_Pivot and<br />
this is used in reduction to triangular form ....*/<br />
Buffer_Pivot[0] = (double)MyRank;<br />
MPI_Send(Buffer_Pivot, nRowsBloc*nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Back Substitution starts from here ........*/<br />
/*.... Receive from all higher processors ......*/<br />
Buffer_bksub = (double *)malloc(nRowsBloc * 2 * sizeof(double));<br />
X_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; ++neigh_proc) {<br />
MPI_Recv(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc,<br />
MPI_COMM_WORLD, &status);<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
for (icol = nRowsBloc - 1; icol >= 0; icol--) {<br />
/* ... Pick up starting Index .....*/<br />
index = (int)Buffer_bksub[icol];<br />
Y_buffer[irow] -= Buffer_bksub[nRowsBloc + icol] * ARecv[irow*nSize + index];<br />
}<br />
}<br />
}<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
index = MyRank*nRowsBloc + irow;<br />
Buffer_bksub[irow] = (double)index;<br />
Buffer_bksub[nRowsBloc + irow] = X_buffer[irow] = Y_buffer[irow];<br />
for (jrow = irow - 1; jrow >= 0; jrow--)<br />
Y_buffer[jrow] -= X_buffer[irow] * ARecv[jrow*nSize + index];<br />
}<br />
/*.... Send to all lower processes...*/<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Send(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Gather the result on the processor 0 ....*/<br />
Output = (double *)malloc(nSize * sizeof(double));<br />
MPI_Gather(X_buffer, nRowsBloc, MPI_DOUBLE, Output, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* .......Output vector .....*/<br />
Vec res(nSize);<br />
if (MyRank == 0) {<br />
for (irow = 0; irow < nSize; irow++)<br />
res[irow] = Output[irow];<br />
}<br />
return res;<br />
}<br />
<br />
Vec sum(const Vec& lhs, const Vec& rhs) {<br />
Vec res(lhs.size());<br />
if (lhs.size() != rhs.size()) {<br />
return res;<br />
}<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res[i] = lhs[i] + rhs[i];<br />
}<br />
return res;<br />
}<br />
<br />
double diff(const Vec& lhs, const Vec& rhs) {<br />
double res = 0.;<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res += lhs[i] - rhs[i];<br />
}<br />
return fabs(res);<br />
}<br />
<br />
class Newtone {<br />
public:<br />
Newtone(int rank) : m_rank(rank) { <br />
m_h = std::numeric_limits<double>::epsilon(); <br />
}<br />
<br />
Vec find_solution(const Sys_& sys, const Vec& start, const Derivatives& d, LSSolver solver, <br />
VecSum vec_summator, VecDiff vec_differ, const double& eps, const size_t& max_iter) {<br />
size_t iter_count = 1;<br />
double diff = 0.;<br />
Vec sys_val(sys.size(), 0);<br />
if (m_rank == 0) {<br />
m_jac.reserve(sys.size());<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
Vec v(sys.size());<br />
m_jac.push_back(v);<br />
}<br />
compute_jacobian(sys, d, start);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, start);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
Vec delta = solver(m_jac, sys_val);<br />
Vec new_sol(sys.size()), old_sol(sys.size());<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(start, delta);<br />
old_sol = start;<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Bcast(old_sol.data(), old_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
diff = vec_differ(new_sol, old_sol);<br />
while (diff > eps && iter_count <= max_iter) {<br />
old_sol = new_sol;<br />
if (m_rank == 0) {<br />
compute_jacobian(sys, d, old_sol);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, old_sol);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
delta = solver(m_jac, sys_val);<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(old_sol, delta);<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
iter_count++;<br />
diff = vec_differ(new_sol, old_sol);<br />
}<br />
return new_sol;<br />
}<br />
<br />
double compute_derivative(const size_t& pos, Function func, const size_t& var_num, const Vec& point) {<br />
Vec left_point(point), right_point(point);<br />
left_point[var_num] -= m_h;<br />
right_point[var_num] += m_h;<br />
double left = func(pos, left_point), right = func(pos, right_point);<br />
return (right - left) / (2 * m_h);<br />
}<br />
<br />
private:<br />
void compute_jacobian(const Sys_& sys, const Derivatives& d, const Vec& point) {<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
for (size_t j = 0; j < sys.size(); ++j) {<br />
double res_val;<br />
res_val = d[i][j](i, j, point);<br />
m_jac[i][j] = res_val;<br />
}<br />
}<br />
}<br />
double m_h;<br />
int m_rank;<br />
Matrix m_jac;<br />
Vec m_right_part;<br />
};<br />
<br />
int main(int argc, char** argv) {<br />
int rank, size;<br />
Sys_ s;<br />
Derivatives d(system_size);<br />
Vec start(system_size, 0.87);<br />
for (size_t i = 0; i < system_size; ++i) {<br />
s.push_back(&func);<br />
}<br />
for (size_t i = 0; i < system_size; ++i) {<br />
for (size_t j = 0; j < system_size; ++j)<br />
d[i].push_back(&derivative);<br />
}<br />
MPI_Init(&argc, &argv);<br />
MPI_Comm_rank(MPI_COMM_WORLD, &rank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &size);<br />
Newtone n(rank);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
double time = MPI_Wtime();<br />
Vec sol = n.find_solution(s, start, d, &gauss, &sum, &diff, 0.0001, 100);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
time = MPI_Wtime() - time;<br />
double max_time;<br />
MPI_Reduce(&time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);<br />
if (rank == 0) {<br />
std::ofstream myfile;<br />
char filename[32];<br />
snprintf(filename, 32, "out_%ld_%d.txt", system_size, size);<br />
myfile.open(filename);<br />
for (size_t i = 0; i < sol.size(); ++i) {<br />
myfile << sol[i] << " ";<br />
}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25280Метод Ньютона для систем нелинейных уравнений2018-01-17T09:26:52Z<p>Konshin: /* Последовательная сложность алгоритма */</p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев_Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр_Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N_Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.В.Арутюнов</b>] и<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.С.Жилкин</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# <math>n</math> - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math> (в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра(см [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
Характеристики реализации алгоритма сильно зависят от выбранного способа нахождения матрицы Якоби и решения СЛАУ. <br />
Для примера рассматривается реализация алгоритма с использованием метода Гаусса на функциях вида:<br />
<br />
<math>f_i(x) = cos(x_i) - 1</math>.<br />
<br />
Для этих функций можно задать точное значение производной в любой точке:<br />
<br />
<math>f_i^' (x) = -sin(x_i)</math>.<br />
<br />
Для тестирования программы было решено использовать исключительно технологию MPI в реализации Intel (IntelMPI<ref name="LINK_IMPI">https://software.intel.com/en-us/intel-mpi-library</ref>) без дополнительных. <br />
Тесты проводились на суперкомпьютере Ломоносов<ref name="LOM_PARALLEL_LINK">https://parallel.ru/cluster/lomonosov.html</ref> <ref name="LOM_WIKI_LINK">https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер)</ref> в разделе test. <br />
Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
* Количество ядер: 8 ядер архитектуры x86<br />
* Количество памяти: 12Гб<br />
<br />
Строка компиляции: mpicxx _scratch/Source.cpp -o _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Строка запуска: sbatch -nN -p test impi _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Результаты тестирования представлены на рисунках 10 и 11, где рис. 10 отображает время работы данной реализации, а рис. 11 - ускорение:<br />
<br />
[[Файл:Nwt 1024 2048 4096.png|thumb|center|800px|Рис.2 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
[[Файл:Nwt spdup.png|thumb|center|800px|Рис.3 Ускорение решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Из рис. 10 и 11 видно, что увеличение числа задействованных в вычислениях процессоров дает выигрыш во времени, однако, из-за необходимости обмениваться данными при решении СЛАУ методом Гаусса, при вовлечении слишком большого числа процессоров, время, требуемое на обмен данными может превысить время непосредственного вычисления. Этот эффект ограничивает масштабируемость программы. Для подтверждения этого тезиса приведён рис. 12, отдельно показывающий время работы задачи с размерностью матрицы 1024:<br />
<br />
[[Файл:ChartGo 1024.png|thumb|center|600px|Рис. 12. Время решения системы из 1024 уравнений в зависимости от количества задействованных процессоров]]<br />
<br />
Как видно из рис. 12 время выполнения программы на 128 процессорах возрастает по сравнению с временем работы на 64 процессорах. <br />
Это связано с тем, что на каждый процессор приходится недостаточно индивидуальной загрузки и основное время работы программы тратится на передачу данных между процессорами.<br />
Если же увеличение количества задействованных процессоров не приводит к возникновению этого эффекта, то увеличение количества процессоров в 2 раза ведёт к увеличению скорости работы программы также приблизительно в 2 раза, что хорошо видно на рис. 12.<br />
<br />
{|class="wikitable mw-collapsible mw-collapsed"<br />
!Исходный код программы<br />
|-<br />
|<source hide="yes" lang="cpp">#include <iostream><br />
#include <cmath><br />
#include <fstream><br />
#include <vector><br />
#include <cstdlib><br />
#include <limits><br />
#include <mpi.h><br />
#include <stdio.h><br />
#include <assert.h><br />
<br />
typedef std::vector<double> Vec;<br />
typedef double(*Function)(const size_t&, const Vec&);<br />
typedef double(*Deriv)(const size_t&, const size_t&, const Vec&);<br />
typedef std::vector<Function> Sys_;<br />
typedef std::vector<std::vector<Deriv> > Derivatives;<br />
typedef std::vector<std::vector<double> > Matrix;<br />
typedef Vec(*LSSolver)(const Matrix&, const Vec&);<br />
typedef Vec(*VecSum)(const Vec&, const Vec&);<br />
typedef double(*VecDiff)(const Vec&, const Vec&);<br />
<br />
struct LS {<br />
Matrix A;<br />
Vec b;<br />
};<br />
<br />
size_t system_size = 4096;<br />
<br />
double func(const size_t& i, const Vec& v) {<br />
return cos(v[i]) - 1;<br />
}<br />
<br />
double derivative(const size_t& i, const size_t& j, const Vec& v) {<br />
if (i == j) {<br />
return -sin(v[i]);<br />
}<br />
return 0.0;<br />
}<br />
<br />
static void printVec(const Vec& v) {<br />
std::cout << "size: " << v.size() << std::endl;<br />
for (size_t i = 0; i < v.size(); ++i) {<br />
std::cout << v[i] << " ";<br />
}<br />
std::cout << std::endl;<br />
}<br />
<br />
Vec gauss(const Matrix& A, const Vec& b) {<br />
MPI_Status status;<br />
int nSize, nRowsBloc, nRows, nCols;<br />
int Numprocs, MyRank, Root = 0;<br />
int irow, jrow, icol, index, ColofPivot, neigh_proc;<br />
double *Input_A, *Input_B, *ARecv, *BRecv;<br />
double *Output, Pivot;<br />
double *X_buffer, *Y_buffer;<br />
double *Buffer_Pivot, *Buffer_bksub;<br />
double tmp;<br />
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &Numprocs);<br />
if (MyRank == 0) {<br />
nRows = A.size();<br />
nCols = A[0].size();<br />
nSize = nRows;<br />
Input_B = (double *)malloc(nSize * sizeof(double));<br />
for (irow = 0; irow < nSize; irow++) {<br />
Input_B[irow] = b[irow];<br />
}<br />
Input_A = (double *)malloc(nSize*nSize * sizeof(double));<br />
index = 0;<br />
for (irow = 0; irow < nSize; irow++)<br />
for (icol = 0; icol < nSize; icol++)<br />
Input_A[index++] = A[irow][icol];<br />
}<br />
MPI_Bcast(&nRows, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
MPI_Bcast(&nCols, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
/* .... Broad cast the size of the matrix to all ....*/<br />
MPI_Bcast(&nSize, 1, MPI_INT, 0, MPI_COMM_WORLD);<br />
nRowsBloc = nSize / Numprocs;<br />
/*......Memory of input matrix and vector on each process .....*/<br />
ARecv = (double *)malloc(nRowsBloc * nSize * sizeof(double));<br />
BRecv = (double *)malloc(nRowsBloc * sizeof(double));<br />
/*......Scatter the Input Data to all process ......*/<br />
MPI_Scatter(Input_A, nRowsBloc * nSize, MPI_DOUBLE, ARecv, nRowsBloc * nSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Scatter(Input_B, nRowsBloc, MPI_DOUBLE, BRecv, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* ....Memory allocation of ray Buffer_Pivot .....*/<br />
Buffer_Pivot = (double *)malloc((nRowsBloc + 1 + nSize * nRowsBloc) * sizeof(double));<br />
/* Receive data from all processors (i=0 to k-1) above my processor (k).... */<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Recv(Buffer_Pivot, nRowsBloc * nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc, MPI_COMM_WORLD, &status);<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
/* .... Buffer_Pivot[0] : locate the rank of the processor received */<br />
/* .... Index is used to reduce the matrix to traingular matrix */<br />
/* .... Buffer_Pivot[0] is used to determine the starting value of<br />
pivot in each row of the matrix, on each processor */<br />
ColofPivot = ((int)Buffer_Pivot[0]) * nRowsBloc + irow;<br />
for (jrow = 0; jrow < nRowsBloc; jrow++) {<br />
index = jrow*nSize;<br />
tmp = ARecv[index + ColofPivot];<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] -= tmp * Buffer_Pivot[irow*nSize + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Buffer_Pivot[1 + irow];<br />
ARecv[index + ColofPivot] = 0.0;<br />
}<br />
}<br />
}<br />
Y_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
/* ....Modification of row entries on each processor ...*/<br />
/* ....Division by pivot value and modification ...*/<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
ColofPivot = MyRank * nRowsBloc + irow;<br />
index = irow*nSize;<br />
Pivot = ARecv[index + ColofPivot];<br />
assert(Pivot != 0);<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] = ARecv[index + icol] / Pivot;<br />
Buffer_Pivot[index + icol + 1 + nRowsBloc] = ARecv[index + icol];<br />
}<br />
Y_buffer[irow] = BRecv[irow] / Pivot;<br />
Buffer_Pivot[irow + 1] = Y_buffer[irow];<br />
for (jrow = irow + 1; jrow < nRowsBloc; jrow++) {<br />
tmp = ARecv[jrow*nSize + ColofPivot];<br />
for (icol = ColofPivot + 1; icol < nSize; icol++) {<br />
ARecv[jrow*nSize + icol] -= tmp * Buffer_Pivot[index + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Y_buffer[irow];<br />
ARecv[jrow*nSize + irow] = 0;<br />
}<br />
}<br />
/*....Send data to all processors below the current processors */<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; neigh_proc++) {<br />
/* ...... Rank is stored in first location of Buffer_Pivot and<br />
this is used in reduction to triangular form ....*/<br />
Buffer_Pivot[0] = (double)MyRank;<br />
MPI_Send(Buffer_Pivot, nRowsBloc*nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Back Substitution starts from here ........*/<br />
/*.... Receive from all higher processors ......*/<br />
Buffer_bksub = (double *)malloc(nRowsBloc * 2 * sizeof(double));<br />
X_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; ++neigh_proc) {<br />
MPI_Recv(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc,<br />
MPI_COMM_WORLD, &status);<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
for (icol = nRowsBloc - 1; icol >= 0; icol--) {<br />
/* ... Pick up starting Index .....*/<br />
index = (int)Buffer_bksub[icol];<br />
Y_buffer[irow] -= Buffer_bksub[nRowsBloc + icol] * ARecv[irow*nSize + index];<br />
}<br />
}<br />
}<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
index = MyRank*nRowsBloc + irow;<br />
Buffer_bksub[irow] = (double)index;<br />
Buffer_bksub[nRowsBloc + irow] = X_buffer[irow] = Y_buffer[irow];<br />
for (jrow = irow - 1; jrow >= 0; jrow--)<br />
Y_buffer[jrow] -= X_buffer[irow] * ARecv[jrow*nSize + index];<br />
}<br />
/*.... Send to all lower processes...*/<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Send(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Gather the result on the processor 0 ....*/<br />
Output = (double *)malloc(nSize * sizeof(double));<br />
MPI_Gather(X_buffer, nRowsBloc, MPI_DOUBLE, Output, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* .......Output vector .....*/<br />
Vec res(nSize);<br />
if (MyRank == 0) {<br />
for (irow = 0; irow < nSize; irow++)<br />
res[irow] = Output[irow];<br />
}<br />
return res;<br />
}<br />
<br />
Vec sum(const Vec& lhs, const Vec& rhs) {<br />
Vec res(lhs.size());<br />
if (lhs.size() != rhs.size()) {<br />
return res;<br />
}<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res[i] = lhs[i] + rhs[i];<br />
}<br />
return res;<br />
}<br />
<br />
double diff(const Vec& lhs, const Vec& rhs) {<br />
double res = 0.;<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res += lhs[i] - rhs[i];<br />
}<br />
return fabs(res);<br />
}<br />
<br />
class Newtone {<br />
public:<br />
Newtone(int rank) : m_rank(rank) { <br />
m_h = std::numeric_limits<double>::epsilon(); <br />
}<br />
<br />
Vec find_solution(const Sys_& sys, const Vec& start, const Derivatives& d, LSSolver solver, <br />
VecSum vec_summator, VecDiff vec_differ, const double& eps, const size_t& max_iter) {<br />
size_t iter_count = 1;<br />
double diff = 0.;<br />
Vec sys_val(sys.size(), 0);<br />
if (m_rank == 0) {<br />
m_jac.reserve(sys.size());<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
Vec v(sys.size());<br />
m_jac.push_back(v);<br />
}<br />
compute_jacobian(sys, d, start);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, start);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
Vec delta = solver(m_jac, sys_val);<br />
Vec new_sol(sys.size()), old_sol(sys.size());<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(start, delta);<br />
old_sol = start;<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Bcast(old_sol.data(), old_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
diff = vec_differ(new_sol, old_sol);<br />
while (diff > eps && iter_count <= max_iter) {<br />
old_sol = new_sol;<br />
if (m_rank == 0) {<br />
compute_jacobian(sys, d, old_sol);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, old_sol);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
delta = solver(m_jac, sys_val);<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(old_sol, delta);<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
iter_count++;<br />
diff = vec_differ(new_sol, old_sol);<br />
}<br />
return new_sol;<br />
}<br />
<br />
double compute_derivative(const size_t& pos, Function func, const size_t& var_num, const Vec& point) {<br />
Vec left_point(point), right_point(point);<br />
left_point[var_num] -= m_h;<br />
right_point[var_num] += m_h;<br />
double left = func(pos, left_point), right = func(pos, right_point);<br />
return (right - left) / (2 * m_h);<br />
}<br />
<br />
private:<br />
void compute_jacobian(const Sys_& sys, const Derivatives& d, const Vec& point) {<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
for (size_t j = 0; j < sys.size(); ++j) {<br />
double res_val;<br />
res_val = d[i][j](i, j, point);<br />
m_jac[i][j] = res_val;<br />
}<br />
}<br />
}<br />
double m_h;<br />
int m_rank;<br />
Matrix m_jac;<br />
Vec m_right_part;<br />
};<br />
<br />
int main(int argc, char** argv) {<br />
int rank, size;<br />
Sys_ s;<br />
Derivatives d(system_size);<br />
Vec start(system_size, 0.87);<br />
for (size_t i = 0; i < system_size; ++i) {<br />
s.push_back(&func);<br />
}<br />
for (size_t i = 0; i < system_size; ++i) {<br />
for (size_t j = 0; j < system_size; ++j)<br />
d[i].push_back(&derivative);<br />
}<br />
MPI_Init(&argc, &argv);<br />
MPI_Comm_rank(MPI_COMM_WORLD, &rank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &size);<br />
Newtone n(rank);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
double time = MPI_Wtime();<br />
Vec sol = n.find_solution(s, start, d, &gauss, &sum, &diff, 0.0001, 100);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
time = MPI_Wtime() - time;<br />
double max_time;<br />
MPI_Reduce(&time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);<br />
if (rank == 0) {<br />
std::ofstream myfile;<br />
char filename[32];<br />
snprintf(filename, 32, "out_%ld_%d.txt", system_size, size);<br />
myfile.open(filename);<br />
for (size_t i = 0; i < sol.size(); ++i) {<br />
myfile << sol[i] << " ";<br />
}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25279Метод Ньютона для систем нелинейных уравнений2018-01-17T09:26:27Z<p>Konshin: /* Схема реализации последовательного алгоритма */</p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев_Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр_Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N_Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.В.Арутюнов</b>] и<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.С.Жилкин</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# <math>n</math> - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра(см [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
Характеристики реализации алгоритма сильно зависят от выбранного способа нахождения матрицы Якоби и решения СЛАУ. <br />
Для примера рассматривается реализация алгоритма с использованием метода Гаусса на функциях вида:<br />
<br />
<math>f_i(x) = cos(x_i) - 1</math>.<br />
<br />
Для этих функций можно задать точное значение производной в любой точке:<br />
<br />
<math>f_i^' (x) = -sin(x_i)</math>.<br />
<br />
Для тестирования программы было решено использовать исключительно технологию MPI в реализации Intel (IntelMPI<ref name="LINK_IMPI">https://software.intel.com/en-us/intel-mpi-library</ref>) без дополнительных. <br />
Тесты проводились на суперкомпьютере Ломоносов<ref name="LOM_PARALLEL_LINK">https://parallel.ru/cluster/lomonosov.html</ref> <ref name="LOM_WIKI_LINK">https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер)</ref> в разделе test. <br />
Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
* Количество ядер: 8 ядер архитектуры x86<br />
* Количество памяти: 12Гб<br />
<br />
Строка компиляции: mpicxx _scratch/Source.cpp -o _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Строка запуска: sbatch -nN -p test impi _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Результаты тестирования представлены на рисунках 10 и 11, где рис. 10 отображает время работы данной реализации, а рис. 11 - ускорение:<br />
<br />
[[Файл:Nwt 1024 2048 4096.png|thumb|center|800px|Рис.2 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
[[Файл:Nwt spdup.png|thumb|center|800px|Рис.3 Ускорение решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Из рис. 10 и 11 видно, что увеличение числа задействованных в вычислениях процессоров дает выигрыш во времени, однако, из-за необходимости обмениваться данными при решении СЛАУ методом Гаусса, при вовлечении слишком большого числа процессоров, время, требуемое на обмен данными может превысить время непосредственного вычисления. Этот эффект ограничивает масштабируемость программы. Для подтверждения этого тезиса приведён рис. 12, отдельно показывающий время работы задачи с размерностью матрицы 1024:<br />
<br />
[[Файл:ChartGo 1024.png|thumb|center|600px|Рис. 12. Время решения системы из 1024 уравнений в зависимости от количества задействованных процессоров]]<br />
<br />
Как видно из рис. 12 время выполнения программы на 128 процессорах возрастает по сравнению с временем работы на 64 процессорах. <br />
Это связано с тем, что на каждый процессор приходится недостаточно индивидуальной загрузки и основное время работы программы тратится на передачу данных между процессорами.<br />
Если же увеличение количества задействованных процессоров не приводит к возникновению этого эффекта, то увеличение количества процессоров в 2 раза ведёт к увеличению скорости работы программы также приблизительно в 2 раза, что хорошо видно на рис. 12.<br />
<br />
{|class="wikitable mw-collapsible mw-collapsed"<br />
!Исходный код программы<br />
|-<br />
|<source hide="yes" lang="cpp">#include <iostream><br />
#include <cmath><br />
#include <fstream><br />
#include <vector><br />
#include <cstdlib><br />
#include <limits><br />
#include <mpi.h><br />
#include <stdio.h><br />
#include <assert.h><br />
<br />
typedef std::vector<double> Vec;<br />
typedef double(*Function)(const size_t&, const Vec&);<br />
typedef double(*Deriv)(const size_t&, const size_t&, const Vec&);<br />
typedef std::vector<Function> Sys_;<br />
typedef std::vector<std::vector<Deriv> > Derivatives;<br />
typedef std::vector<std::vector<double> > Matrix;<br />
typedef Vec(*LSSolver)(const Matrix&, const Vec&);<br />
typedef Vec(*VecSum)(const Vec&, const Vec&);<br />
typedef double(*VecDiff)(const Vec&, const Vec&);<br />
<br />
struct LS {<br />
Matrix A;<br />
Vec b;<br />
};<br />
<br />
size_t system_size = 4096;<br />
<br />
double func(const size_t& i, const Vec& v) {<br />
return cos(v[i]) - 1;<br />
}<br />
<br />
double derivative(const size_t& i, const size_t& j, const Vec& v) {<br />
if (i == j) {<br />
return -sin(v[i]);<br />
}<br />
return 0.0;<br />
}<br />
<br />
static void printVec(const Vec& v) {<br />
std::cout << "size: " << v.size() << std::endl;<br />
for (size_t i = 0; i < v.size(); ++i) {<br />
std::cout << v[i] << " ";<br />
}<br />
std::cout << std::endl;<br />
}<br />
<br />
Vec gauss(const Matrix& A, const Vec& b) {<br />
MPI_Status status;<br />
int nSize, nRowsBloc, nRows, nCols;<br />
int Numprocs, MyRank, Root = 0;<br />
int irow, jrow, icol, index, ColofPivot, neigh_proc;<br />
double *Input_A, *Input_B, *ARecv, *BRecv;<br />
double *Output, Pivot;<br />
double *X_buffer, *Y_buffer;<br />
double *Buffer_Pivot, *Buffer_bksub;<br />
double tmp;<br />
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &Numprocs);<br />
if (MyRank == 0) {<br />
nRows = A.size();<br />
nCols = A[0].size();<br />
nSize = nRows;<br />
Input_B = (double *)malloc(nSize * sizeof(double));<br />
for (irow = 0; irow < nSize; irow++) {<br />
Input_B[irow] = b[irow];<br />
}<br />
Input_A = (double *)malloc(nSize*nSize * sizeof(double));<br />
index = 0;<br />
for (irow = 0; irow < nSize; irow++)<br />
for (icol = 0; icol < nSize; icol++)<br />
Input_A[index++] = A[irow][icol];<br />
}<br />
MPI_Bcast(&nRows, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
MPI_Bcast(&nCols, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
/* .... Broad cast the size of the matrix to all ....*/<br />
MPI_Bcast(&nSize, 1, MPI_INT, 0, MPI_COMM_WORLD);<br />
nRowsBloc = nSize / Numprocs;<br />
/*......Memory of input matrix and vector on each process .....*/<br />
ARecv = (double *)malloc(nRowsBloc * nSize * sizeof(double));<br />
BRecv = (double *)malloc(nRowsBloc * sizeof(double));<br />
/*......Scatter the Input Data to all process ......*/<br />
MPI_Scatter(Input_A, nRowsBloc * nSize, MPI_DOUBLE, ARecv, nRowsBloc * nSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Scatter(Input_B, nRowsBloc, MPI_DOUBLE, BRecv, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* ....Memory allocation of ray Buffer_Pivot .....*/<br />
Buffer_Pivot = (double *)malloc((nRowsBloc + 1 + nSize * nRowsBloc) * sizeof(double));<br />
/* Receive data from all processors (i=0 to k-1) above my processor (k).... */<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Recv(Buffer_Pivot, nRowsBloc * nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc, MPI_COMM_WORLD, &status);<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
/* .... Buffer_Pivot[0] : locate the rank of the processor received */<br />
/* .... Index is used to reduce the matrix to traingular matrix */<br />
/* .... Buffer_Pivot[0] is used to determine the starting value of<br />
pivot in each row of the matrix, on each processor */<br />
ColofPivot = ((int)Buffer_Pivot[0]) * nRowsBloc + irow;<br />
for (jrow = 0; jrow < nRowsBloc; jrow++) {<br />
index = jrow*nSize;<br />
tmp = ARecv[index + ColofPivot];<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] -= tmp * Buffer_Pivot[irow*nSize + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Buffer_Pivot[1 + irow];<br />
ARecv[index + ColofPivot] = 0.0;<br />
}<br />
}<br />
}<br />
Y_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
/* ....Modification of row entries on each processor ...*/<br />
/* ....Division by pivot value and modification ...*/<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
ColofPivot = MyRank * nRowsBloc + irow;<br />
index = irow*nSize;<br />
Pivot = ARecv[index + ColofPivot];<br />
assert(Pivot != 0);<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] = ARecv[index + icol] / Pivot;<br />
Buffer_Pivot[index + icol + 1 + nRowsBloc] = ARecv[index + icol];<br />
}<br />
Y_buffer[irow] = BRecv[irow] / Pivot;<br />
Buffer_Pivot[irow + 1] = Y_buffer[irow];<br />
for (jrow = irow + 1; jrow < nRowsBloc; jrow++) {<br />
tmp = ARecv[jrow*nSize + ColofPivot];<br />
for (icol = ColofPivot + 1; icol < nSize; icol++) {<br />
ARecv[jrow*nSize + icol] -= tmp * Buffer_Pivot[index + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Y_buffer[irow];<br />
ARecv[jrow*nSize + irow] = 0;<br />
}<br />
}<br />
/*....Send data to all processors below the current processors */<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; neigh_proc++) {<br />
/* ...... Rank is stored in first location of Buffer_Pivot and<br />
this is used in reduction to triangular form ....*/<br />
Buffer_Pivot[0] = (double)MyRank;<br />
MPI_Send(Buffer_Pivot, nRowsBloc*nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Back Substitution starts from here ........*/<br />
/*.... Receive from all higher processors ......*/<br />
Buffer_bksub = (double *)malloc(nRowsBloc * 2 * sizeof(double));<br />
X_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; ++neigh_proc) {<br />
MPI_Recv(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc,<br />
MPI_COMM_WORLD, &status);<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
for (icol = nRowsBloc - 1; icol >= 0; icol--) {<br />
/* ... Pick up starting Index .....*/<br />
index = (int)Buffer_bksub[icol];<br />
Y_buffer[irow] -= Buffer_bksub[nRowsBloc + icol] * ARecv[irow*nSize + index];<br />
}<br />
}<br />
}<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
index = MyRank*nRowsBloc + irow;<br />
Buffer_bksub[irow] = (double)index;<br />
Buffer_bksub[nRowsBloc + irow] = X_buffer[irow] = Y_buffer[irow];<br />
for (jrow = irow - 1; jrow >= 0; jrow--)<br />
Y_buffer[jrow] -= X_buffer[irow] * ARecv[jrow*nSize + index];<br />
}<br />
/*.... Send to all lower processes...*/<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Send(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Gather the result on the processor 0 ....*/<br />
Output = (double *)malloc(nSize * sizeof(double));<br />
MPI_Gather(X_buffer, nRowsBloc, MPI_DOUBLE, Output, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* .......Output vector .....*/<br />
Vec res(nSize);<br />
if (MyRank == 0) {<br />
for (irow = 0; irow < nSize; irow++)<br />
res[irow] = Output[irow];<br />
}<br />
return res;<br />
}<br />
<br />
Vec sum(const Vec& lhs, const Vec& rhs) {<br />
Vec res(lhs.size());<br />
if (lhs.size() != rhs.size()) {<br />
return res;<br />
}<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res[i] = lhs[i] + rhs[i];<br />
}<br />
return res;<br />
}<br />
<br />
double diff(const Vec& lhs, const Vec& rhs) {<br />
double res = 0.;<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res += lhs[i] - rhs[i];<br />
}<br />
return fabs(res);<br />
}<br />
<br />
class Newtone {<br />
public:<br />
Newtone(int rank) : m_rank(rank) { <br />
m_h = std::numeric_limits<double>::epsilon(); <br />
}<br />
<br />
Vec find_solution(const Sys_& sys, const Vec& start, const Derivatives& d, LSSolver solver, <br />
VecSum vec_summator, VecDiff vec_differ, const double& eps, const size_t& max_iter) {<br />
size_t iter_count = 1;<br />
double diff = 0.;<br />
Vec sys_val(sys.size(), 0);<br />
if (m_rank == 0) {<br />
m_jac.reserve(sys.size());<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
Vec v(sys.size());<br />
m_jac.push_back(v);<br />
}<br />
compute_jacobian(sys, d, start);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, start);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
Vec delta = solver(m_jac, sys_val);<br />
Vec new_sol(sys.size()), old_sol(sys.size());<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(start, delta);<br />
old_sol = start;<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Bcast(old_sol.data(), old_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
diff = vec_differ(new_sol, old_sol);<br />
while (diff > eps && iter_count <= max_iter) {<br />
old_sol = new_sol;<br />
if (m_rank == 0) {<br />
compute_jacobian(sys, d, old_sol);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, old_sol);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
delta = solver(m_jac, sys_val);<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(old_sol, delta);<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
iter_count++;<br />
diff = vec_differ(new_sol, old_sol);<br />
}<br />
return new_sol;<br />
}<br />
<br />
double compute_derivative(const size_t& pos, Function func, const size_t& var_num, const Vec& point) {<br />
Vec left_point(point), right_point(point);<br />
left_point[var_num] -= m_h;<br />
right_point[var_num] += m_h;<br />
double left = func(pos, left_point), right = func(pos, right_point);<br />
return (right - left) / (2 * m_h);<br />
}<br />
<br />
private:<br />
void compute_jacobian(const Sys_& sys, const Derivatives& d, const Vec& point) {<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
for (size_t j = 0; j < sys.size(); ++j) {<br />
double res_val;<br />
res_val = d[i][j](i, j, point);<br />
m_jac[i][j] = res_val;<br />
}<br />
}<br />
}<br />
double m_h;<br />
int m_rank;<br />
Matrix m_jac;<br />
Vec m_right_part;<br />
};<br />
<br />
int main(int argc, char** argv) {<br />
int rank, size;<br />
Sys_ s;<br />
Derivatives d(system_size);<br />
Vec start(system_size, 0.87);<br />
for (size_t i = 0; i < system_size; ++i) {<br />
s.push_back(&func);<br />
}<br />
for (size_t i = 0; i < system_size; ++i) {<br />
for (size_t j = 0; j < system_size; ++j)<br />
d[i].push_back(&derivative);<br />
}<br />
MPI_Init(&argc, &argv);<br />
MPI_Comm_rank(MPI_COMM_WORLD, &rank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &size);<br />
Newtone n(rank);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
double time = MPI_Wtime();<br />
Vec sol = n.find_solution(s, start, d, &gauss, &sum, &diff, 0.0001, 100);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
time = MPI_Wtime() - time;<br />
double max_time;<br />
MPI_Reduce(&time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);<br />
if (rank == 0) {<br />
std::ofstream myfile;<br />
char filename[32];<br />
snprintf(filename, 32, "out_%ld_%d.txt", system_size, size);<br />
myfile.open(filename);<br />
for (size_t i = 0; i < sol.size(); ++i) {<br />
myfile << sol[i] << " ";<br />
}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25278Метод Ньютона для систем нелинейных уравнений2018-01-17T09:25:08Z<p>Konshin: </p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев_Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр_Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N_Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.В.Арутюнов</b>] и<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.С.Жилкин</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра(см [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
Характеристики реализации алгоритма сильно зависят от выбранного способа нахождения матрицы Якоби и решения СЛАУ. <br />
Для примера рассматривается реализация алгоритма с использованием метода Гаусса на функциях вида:<br />
<br />
<math>f_i(x) = cos(x_i) - 1</math>.<br />
<br />
Для этих функций можно задать точное значение производной в любой точке:<br />
<br />
<math>f_i^' (x) = -sin(x_i)</math>.<br />
<br />
Для тестирования программы было решено использовать исключительно технологию MPI в реализации Intel (IntelMPI<ref name="LINK_IMPI">https://software.intel.com/en-us/intel-mpi-library</ref>) без дополнительных. <br />
Тесты проводились на суперкомпьютере Ломоносов<ref name="LOM_PARALLEL_LINK">https://parallel.ru/cluster/lomonosov.html</ref> <ref name="LOM_WIKI_LINK">https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер)</ref> в разделе test. <br />
Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
* Количество ядер: 8 ядер архитектуры x86<br />
* Количество памяти: 12Гб<br />
<br />
Строка компиляции: mpicxx _scratch/Source.cpp -o _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Строка запуска: sbatch -nN -p test impi _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Результаты тестирования представлены на рисунках 10 и 11, где рис. 10 отображает время работы данной реализации, а рис. 11 - ускорение:<br />
<br />
[[Файл:Nwt 1024 2048 4096.png|thumb|center|800px|Рис.2 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
[[Файл:Nwt spdup.png|thumb|center|800px|Рис.3 Ускорение решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Из рис. 10 и 11 видно, что увеличение числа задействованных в вычислениях процессоров дает выигрыш во времени, однако, из-за необходимости обмениваться данными при решении СЛАУ методом Гаусса, при вовлечении слишком большого числа процессоров, время, требуемое на обмен данными может превысить время непосредственного вычисления. Этот эффект ограничивает масштабируемость программы. Для подтверждения этого тезиса приведён рис. 12, отдельно показывающий время работы задачи с размерностью матрицы 1024:<br />
<br />
[[Файл:ChartGo 1024.png|thumb|center|600px|Рис. 12. Время решения системы из 1024 уравнений в зависимости от количества задействованных процессоров]]<br />
<br />
Как видно из рис. 12 время выполнения программы на 128 процессорах возрастает по сравнению с временем работы на 64 процессорах. <br />
Это связано с тем, что на каждый процессор приходится недостаточно индивидуальной загрузки и основное время работы программы тратится на передачу данных между процессорами.<br />
Если же увеличение количества задействованных процессоров не приводит к возникновению этого эффекта, то увеличение количества процессоров в 2 раза ведёт к увеличению скорости работы программы также приблизительно в 2 раза, что хорошо видно на рис. 12.<br />
<br />
{|class="wikitable mw-collapsible mw-collapsed"<br />
!Исходный код программы<br />
|-<br />
|<source hide="yes" lang="cpp">#include <iostream><br />
#include <cmath><br />
#include <fstream><br />
#include <vector><br />
#include <cstdlib><br />
#include <limits><br />
#include <mpi.h><br />
#include <stdio.h><br />
#include <assert.h><br />
<br />
typedef std::vector<double> Vec;<br />
typedef double(*Function)(const size_t&, const Vec&);<br />
typedef double(*Deriv)(const size_t&, const size_t&, const Vec&);<br />
typedef std::vector<Function> Sys_;<br />
typedef std::vector<std::vector<Deriv> > Derivatives;<br />
typedef std::vector<std::vector<double> > Matrix;<br />
typedef Vec(*LSSolver)(const Matrix&, const Vec&);<br />
typedef Vec(*VecSum)(const Vec&, const Vec&);<br />
typedef double(*VecDiff)(const Vec&, const Vec&);<br />
<br />
struct LS {<br />
Matrix A;<br />
Vec b;<br />
};<br />
<br />
size_t system_size = 4096;<br />
<br />
double func(const size_t& i, const Vec& v) {<br />
return cos(v[i]) - 1;<br />
}<br />
<br />
double derivative(const size_t& i, const size_t& j, const Vec& v) {<br />
if (i == j) {<br />
return -sin(v[i]);<br />
}<br />
return 0.0;<br />
}<br />
<br />
static void printVec(const Vec& v) {<br />
std::cout << "size: " << v.size() << std::endl;<br />
for (size_t i = 0; i < v.size(); ++i) {<br />
std::cout << v[i] << " ";<br />
}<br />
std::cout << std::endl;<br />
}<br />
<br />
Vec gauss(const Matrix& A, const Vec& b) {<br />
MPI_Status status;<br />
int nSize, nRowsBloc, nRows, nCols;<br />
int Numprocs, MyRank, Root = 0;<br />
int irow, jrow, icol, index, ColofPivot, neigh_proc;<br />
double *Input_A, *Input_B, *ARecv, *BRecv;<br />
double *Output, Pivot;<br />
double *X_buffer, *Y_buffer;<br />
double *Buffer_Pivot, *Buffer_bksub;<br />
double tmp;<br />
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &Numprocs);<br />
if (MyRank == 0) {<br />
nRows = A.size();<br />
nCols = A[0].size();<br />
nSize = nRows;<br />
Input_B = (double *)malloc(nSize * sizeof(double));<br />
for (irow = 0; irow < nSize; irow++) {<br />
Input_B[irow] = b[irow];<br />
}<br />
Input_A = (double *)malloc(nSize*nSize * sizeof(double));<br />
index = 0;<br />
for (irow = 0; irow < nSize; irow++)<br />
for (icol = 0; icol < nSize; icol++)<br />
Input_A[index++] = A[irow][icol];<br />
}<br />
MPI_Bcast(&nRows, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
MPI_Bcast(&nCols, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
/* .... Broad cast the size of the matrix to all ....*/<br />
MPI_Bcast(&nSize, 1, MPI_INT, 0, MPI_COMM_WORLD);<br />
nRowsBloc = nSize / Numprocs;<br />
/*......Memory of input matrix and vector on each process .....*/<br />
ARecv = (double *)malloc(nRowsBloc * nSize * sizeof(double));<br />
BRecv = (double *)malloc(nRowsBloc * sizeof(double));<br />
/*......Scatter the Input Data to all process ......*/<br />
MPI_Scatter(Input_A, nRowsBloc * nSize, MPI_DOUBLE, ARecv, nRowsBloc * nSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Scatter(Input_B, nRowsBloc, MPI_DOUBLE, BRecv, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* ....Memory allocation of ray Buffer_Pivot .....*/<br />
Buffer_Pivot = (double *)malloc((nRowsBloc + 1 + nSize * nRowsBloc) * sizeof(double));<br />
/* Receive data from all processors (i=0 to k-1) above my processor (k).... */<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Recv(Buffer_Pivot, nRowsBloc * nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc, MPI_COMM_WORLD, &status);<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
/* .... Buffer_Pivot[0] : locate the rank of the processor received */<br />
/* .... Index is used to reduce the matrix to traingular matrix */<br />
/* .... Buffer_Pivot[0] is used to determine the starting value of<br />
pivot in each row of the matrix, on each processor */<br />
ColofPivot = ((int)Buffer_Pivot[0]) * nRowsBloc + irow;<br />
for (jrow = 0; jrow < nRowsBloc; jrow++) {<br />
index = jrow*nSize;<br />
tmp = ARecv[index + ColofPivot];<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] -= tmp * Buffer_Pivot[irow*nSize + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Buffer_Pivot[1 + irow];<br />
ARecv[index + ColofPivot] = 0.0;<br />
}<br />
}<br />
}<br />
Y_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
/* ....Modification of row entries on each processor ...*/<br />
/* ....Division by pivot value and modification ...*/<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
ColofPivot = MyRank * nRowsBloc + irow;<br />
index = irow*nSize;<br />
Pivot = ARecv[index + ColofPivot];<br />
assert(Pivot != 0);<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] = ARecv[index + icol] / Pivot;<br />
Buffer_Pivot[index + icol + 1 + nRowsBloc] = ARecv[index + icol];<br />
}<br />
Y_buffer[irow] = BRecv[irow] / Pivot;<br />
Buffer_Pivot[irow + 1] = Y_buffer[irow];<br />
for (jrow = irow + 1; jrow < nRowsBloc; jrow++) {<br />
tmp = ARecv[jrow*nSize + ColofPivot];<br />
for (icol = ColofPivot + 1; icol < nSize; icol++) {<br />
ARecv[jrow*nSize + icol] -= tmp * Buffer_Pivot[index + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Y_buffer[irow];<br />
ARecv[jrow*nSize + irow] = 0;<br />
}<br />
}<br />
/*....Send data to all processors below the current processors */<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; neigh_proc++) {<br />
/* ...... Rank is stored in first location of Buffer_Pivot and<br />
this is used in reduction to triangular form ....*/<br />
Buffer_Pivot[0] = (double)MyRank;<br />
MPI_Send(Buffer_Pivot, nRowsBloc*nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Back Substitution starts from here ........*/<br />
/*.... Receive from all higher processors ......*/<br />
Buffer_bksub = (double *)malloc(nRowsBloc * 2 * sizeof(double));<br />
X_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; ++neigh_proc) {<br />
MPI_Recv(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc,<br />
MPI_COMM_WORLD, &status);<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
for (icol = nRowsBloc - 1; icol >= 0; icol--) {<br />
/* ... Pick up starting Index .....*/<br />
index = (int)Buffer_bksub[icol];<br />
Y_buffer[irow] -= Buffer_bksub[nRowsBloc + icol] * ARecv[irow*nSize + index];<br />
}<br />
}<br />
}<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
index = MyRank*nRowsBloc + irow;<br />
Buffer_bksub[irow] = (double)index;<br />
Buffer_bksub[nRowsBloc + irow] = X_buffer[irow] = Y_buffer[irow];<br />
for (jrow = irow - 1; jrow >= 0; jrow--)<br />
Y_buffer[jrow] -= X_buffer[irow] * ARecv[jrow*nSize + index];<br />
}<br />
/*.... Send to all lower processes...*/<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Send(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Gather the result on the processor 0 ....*/<br />
Output = (double *)malloc(nSize * sizeof(double));<br />
MPI_Gather(X_buffer, nRowsBloc, MPI_DOUBLE, Output, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* .......Output vector .....*/<br />
Vec res(nSize);<br />
if (MyRank == 0) {<br />
for (irow = 0; irow < nSize; irow++)<br />
res[irow] = Output[irow];<br />
}<br />
return res;<br />
}<br />
<br />
Vec sum(const Vec& lhs, const Vec& rhs) {<br />
Vec res(lhs.size());<br />
if (lhs.size() != rhs.size()) {<br />
return res;<br />
}<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res[i] = lhs[i] + rhs[i];<br />
}<br />
return res;<br />
}<br />
<br />
double diff(const Vec& lhs, const Vec& rhs) {<br />
double res = 0.;<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res += lhs[i] - rhs[i];<br />
}<br />
return fabs(res);<br />
}<br />
<br />
class Newtone {<br />
public:<br />
Newtone(int rank) : m_rank(rank) { <br />
m_h = std::numeric_limits<double>::epsilon(); <br />
}<br />
<br />
Vec find_solution(const Sys_& sys, const Vec& start, const Derivatives& d, LSSolver solver, <br />
VecSum vec_summator, VecDiff vec_differ, const double& eps, const size_t& max_iter) {<br />
size_t iter_count = 1;<br />
double diff = 0.;<br />
Vec sys_val(sys.size(), 0);<br />
if (m_rank == 0) {<br />
m_jac.reserve(sys.size());<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
Vec v(sys.size());<br />
m_jac.push_back(v);<br />
}<br />
compute_jacobian(sys, d, start);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, start);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
Vec delta = solver(m_jac, sys_val);<br />
Vec new_sol(sys.size()), old_sol(sys.size());<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(start, delta);<br />
old_sol = start;<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Bcast(old_sol.data(), old_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
diff = vec_differ(new_sol, old_sol);<br />
while (diff > eps && iter_count <= max_iter) {<br />
old_sol = new_sol;<br />
if (m_rank == 0) {<br />
compute_jacobian(sys, d, old_sol);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, old_sol);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
delta = solver(m_jac, sys_val);<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(old_sol, delta);<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
iter_count++;<br />
diff = vec_differ(new_sol, old_sol);<br />
}<br />
return new_sol;<br />
}<br />
<br />
double compute_derivative(const size_t& pos, Function func, const size_t& var_num, const Vec& point) {<br />
Vec left_point(point), right_point(point);<br />
left_point[var_num] -= m_h;<br />
right_point[var_num] += m_h;<br />
double left = func(pos, left_point), right = func(pos, right_point);<br />
return (right - left) / (2 * m_h);<br />
}<br />
<br />
private:<br />
void compute_jacobian(const Sys_& sys, const Derivatives& d, const Vec& point) {<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
for (size_t j = 0; j < sys.size(); ++j) {<br />
double res_val;<br />
res_val = d[i][j](i, j, point);<br />
m_jac[i][j] = res_val;<br />
}<br />
}<br />
}<br />
double m_h;<br />
int m_rank;<br />
Matrix m_jac;<br />
Vec m_right_part;<br />
};<br />
<br />
int main(int argc, char** argv) {<br />
int rank, size;<br />
Sys_ s;<br />
Derivatives d(system_size);<br />
Vec start(system_size, 0.87);<br />
for (size_t i = 0; i < system_size; ++i) {<br />
s.push_back(&func);<br />
}<br />
for (size_t i = 0; i < system_size; ++i) {<br />
for (size_t j = 0; j < system_size; ++j)<br />
d[i].push_back(&derivative);<br />
}<br />
MPI_Init(&argc, &argv);<br />
MPI_Comm_rank(MPI_COMM_WORLD, &rank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &size);<br />
Newtone n(rank);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
double time = MPI_Wtime();<br />
Vec sol = n.find_solution(s, start, d, &gauss, &sum, &diff, 0.0001, 100);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
time = MPI_Wtime() - time;<br />
double max_time;<br />
MPI_Reduce(&time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);<br />
if (rank == 0) {<br />
std::ofstream myfile;<br />
char filename[32];<br />
snprintf(filename, 32, "out_%ld_%d.txt", system_size, size);<br />
myfile.open(filename);<br />
for (size_t i = 0; i < sol.size(); ++i) {<br />
myfile << sol[i] << " ";<br />
}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25277Метод Ньютона для систем нелинейных уравнений2018-01-17T09:24:32Z<p>Konshin: </p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев_Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр_Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.В.Арутюнов</b>] и<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.С.Жилкин</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра(см [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
Характеристики реализации алгоритма сильно зависят от выбранного способа нахождения матрицы Якоби и решения СЛАУ. <br />
Для примера рассматривается реализация алгоритма с использованием метода Гаусса на функциях вида:<br />
<br />
<math>f_i(x) = cos(x_i) - 1</math>.<br />
<br />
Для этих функций можно задать точное значение производной в любой точке:<br />
<br />
<math>f_i^' (x) = -sin(x_i)</math>.<br />
<br />
Для тестирования программы было решено использовать исключительно технологию MPI в реализации Intel (IntelMPI<ref name="LINK_IMPI">https://software.intel.com/en-us/intel-mpi-library</ref>) без дополнительных. <br />
Тесты проводились на суперкомпьютере Ломоносов<ref name="LOM_PARALLEL_LINK">https://parallel.ru/cluster/lomonosov.html</ref> <ref name="LOM_WIKI_LINK">https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер)</ref> в разделе test. <br />
Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
* Количество ядер: 8 ядер архитектуры x86<br />
* Количество памяти: 12Гб<br />
<br />
Строка компиляции: mpicxx _scratch/Source.cpp -o _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Строка запуска: sbatch -nN -p test impi _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Результаты тестирования представлены на рисунках 10 и 11, где рис. 10 отображает время работы данной реализации, а рис. 11 - ускорение:<br />
<br />
[[Файл:Nwt 1024 2048 4096.png|thumb|center|800px|Рис.2 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
[[Файл:Nwt spdup.png|thumb|center|800px|Рис.3 Ускорение решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Из рис. 10 и 11 видно, что увеличение числа задействованных в вычислениях процессоров дает выигрыш во времени, однако, из-за необходимости обмениваться данными при решении СЛАУ методом Гаусса, при вовлечении слишком большого числа процессоров, время, требуемое на обмен данными может превысить время непосредственного вычисления. Этот эффект ограничивает масштабируемость программы. Для подтверждения этого тезиса приведён рис. 12, отдельно показывающий время работы задачи с размерностью матрицы 1024:<br />
<br />
[[Файл:ChartGo 1024.png|thumb|center|600px|Рис. 12. Время решения системы из 1024 уравнений в зависимости от количества задействованных процессоров]]<br />
<br />
Как видно из рис. 12 время выполнения программы на 128 процессорах возрастает по сравнению с временем работы на 64 процессорах. <br />
Это связано с тем, что на каждый процессор приходится недостаточно индивидуальной загрузки и основное время работы программы тратится на передачу данных между процессорами.<br />
Если же увеличение количества задействованных процессоров не приводит к возникновению этого эффекта, то увеличение количества процессоров в 2 раза ведёт к увеличению скорости работы программы также приблизительно в 2 раза, что хорошо видно на рис. 12.<br />
<br />
{|class="wikitable mw-collapsible mw-collapsed"<br />
!Исходный код программы<br />
|-<br />
|<source hide="yes" lang="cpp">#include <iostream><br />
#include <cmath><br />
#include <fstream><br />
#include <vector><br />
#include <cstdlib><br />
#include <limits><br />
#include <mpi.h><br />
#include <stdio.h><br />
#include <assert.h><br />
<br />
typedef std::vector<double> Vec;<br />
typedef double(*Function)(const size_t&, const Vec&);<br />
typedef double(*Deriv)(const size_t&, const size_t&, const Vec&);<br />
typedef std::vector<Function> Sys_;<br />
typedef std::vector<std::vector<Deriv> > Derivatives;<br />
typedef std::vector<std::vector<double> > Matrix;<br />
typedef Vec(*LSSolver)(const Matrix&, const Vec&);<br />
typedef Vec(*VecSum)(const Vec&, const Vec&);<br />
typedef double(*VecDiff)(const Vec&, const Vec&);<br />
<br />
struct LS {<br />
Matrix A;<br />
Vec b;<br />
};<br />
<br />
size_t system_size = 4096;<br />
<br />
double func(const size_t& i, const Vec& v) {<br />
return cos(v[i]) - 1;<br />
}<br />
<br />
double derivative(const size_t& i, const size_t& j, const Vec& v) {<br />
if (i == j) {<br />
return -sin(v[i]);<br />
}<br />
return 0.0;<br />
}<br />
<br />
static void printVec(const Vec& v) {<br />
std::cout << "size: " << v.size() << std::endl;<br />
for (size_t i = 0; i < v.size(); ++i) {<br />
std::cout << v[i] << " ";<br />
}<br />
std::cout << std::endl;<br />
}<br />
<br />
Vec gauss(const Matrix& A, const Vec& b) {<br />
MPI_Status status;<br />
int nSize, nRowsBloc, nRows, nCols;<br />
int Numprocs, MyRank, Root = 0;<br />
int irow, jrow, icol, index, ColofPivot, neigh_proc;<br />
double *Input_A, *Input_B, *ARecv, *BRecv;<br />
double *Output, Pivot;<br />
double *X_buffer, *Y_buffer;<br />
double *Buffer_Pivot, *Buffer_bksub;<br />
double tmp;<br />
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &Numprocs);<br />
if (MyRank == 0) {<br />
nRows = A.size();<br />
nCols = A[0].size();<br />
nSize = nRows;<br />
Input_B = (double *)malloc(nSize * sizeof(double));<br />
for (irow = 0; irow < nSize; irow++) {<br />
Input_B[irow] = b[irow];<br />
}<br />
Input_A = (double *)malloc(nSize*nSize * sizeof(double));<br />
index = 0;<br />
for (irow = 0; irow < nSize; irow++)<br />
for (icol = 0; icol < nSize; icol++)<br />
Input_A[index++] = A[irow][icol];<br />
}<br />
MPI_Bcast(&nRows, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
MPI_Bcast(&nCols, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
/* .... Broad cast the size of the matrix to all ....*/<br />
MPI_Bcast(&nSize, 1, MPI_INT, 0, MPI_COMM_WORLD);<br />
nRowsBloc = nSize / Numprocs;<br />
/*......Memory of input matrix and vector on each process .....*/<br />
ARecv = (double *)malloc(nRowsBloc * nSize * sizeof(double));<br />
BRecv = (double *)malloc(nRowsBloc * sizeof(double));<br />
/*......Scatter the Input Data to all process ......*/<br />
MPI_Scatter(Input_A, nRowsBloc * nSize, MPI_DOUBLE, ARecv, nRowsBloc * nSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Scatter(Input_B, nRowsBloc, MPI_DOUBLE, BRecv, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* ....Memory allocation of ray Buffer_Pivot .....*/<br />
Buffer_Pivot = (double *)malloc((nRowsBloc + 1 + nSize * nRowsBloc) * sizeof(double));<br />
/* Receive data from all processors (i=0 to k-1) above my processor (k).... */<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Recv(Buffer_Pivot, nRowsBloc * nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc, MPI_COMM_WORLD, &status);<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
/* .... Buffer_Pivot[0] : locate the rank of the processor received */<br />
/* .... Index is used to reduce the matrix to traingular matrix */<br />
/* .... Buffer_Pivot[0] is used to determine the starting value of<br />
pivot in each row of the matrix, on each processor */<br />
ColofPivot = ((int)Buffer_Pivot[0]) * nRowsBloc + irow;<br />
for (jrow = 0; jrow < nRowsBloc; jrow++) {<br />
index = jrow*nSize;<br />
tmp = ARecv[index + ColofPivot];<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] -= tmp * Buffer_Pivot[irow*nSize + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Buffer_Pivot[1 + irow];<br />
ARecv[index + ColofPivot] = 0.0;<br />
}<br />
}<br />
}<br />
Y_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
/* ....Modification of row entries on each processor ...*/<br />
/* ....Division by pivot value and modification ...*/<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
ColofPivot = MyRank * nRowsBloc + irow;<br />
index = irow*nSize;<br />
Pivot = ARecv[index + ColofPivot];<br />
assert(Pivot != 0);<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] = ARecv[index + icol] / Pivot;<br />
Buffer_Pivot[index + icol + 1 + nRowsBloc] = ARecv[index + icol];<br />
}<br />
Y_buffer[irow] = BRecv[irow] / Pivot;<br />
Buffer_Pivot[irow + 1] = Y_buffer[irow];<br />
for (jrow = irow + 1; jrow < nRowsBloc; jrow++) {<br />
tmp = ARecv[jrow*nSize + ColofPivot];<br />
for (icol = ColofPivot + 1; icol < nSize; icol++) {<br />
ARecv[jrow*nSize + icol] -= tmp * Buffer_Pivot[index + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Y_buffer[irow];<br />
ARecv[jrow*nSize + irow] = 0;<br />
}<br />
}<br />
/*....Send data to all processors below the current processors */<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; neigh_proc++) {<br />
/* ...... Rank is stored in first location of Buffer_Pivot and<br />
this is used in reduction to triangular form ....*/<br />
Buffer_Pivot[0] = (double)MyRank;<br />
MPI_Send(Buffer_Pivot, nRowsBloc*nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Back Substitution starts from here ........*/<br />
/*.... Receive from all higher processors ......*/<br />
Buffer_bksub = (double *)malloc(nRowsBloc * 2 * sizeof(double));<br />
X_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; ++neigh_proc) {<br />
MPI_Recv(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc,<br />
MPI_COMM_WORLD, &status);<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
for (icol = nRowsBloc - 1; icol >= 0; icol--) {<br />
/* ... Pick up starting Index .....*/<br />
index = (int)Buffer_bksub[icol];<br />
Y_buffer[irow] -= Buffer_bksub[nRowsBloc + icol] * ARecv[irow*nSize + index];<br />
}<br />
}<br />
}<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
index = MyRank*nRowsBloc + irow;<br />
Buffer_bksub[irow] = (double)index;<br />
Buffer_bksub[nRowsBloc + irow] = X_buffer[irow] = Y_buffer[irow];<br />
for (jrow = irow - 1; jrow >= 0; jrow--)<br />
Y_buffer[jrow] -= X_buffer[irow] * ARecv[jrow*nSize + index];<br />
}<br />
/*.... Send to all lower processes...*/<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Send(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Gather the result on the processor 0 ....*/<br />
Output = (double *)malloc(nSize * sizeof(double));<br />
MPI_Gather(X_buffer, nRowsBloc, MPI_DOUBLE, Output, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* .......Output vector .....*/<br />
Vec res(nSize);<br />
if (MyRank == 0) {<br />
for (irow = 0; irow < nSize; irow++)<br />
res[irow] = Output[irow];<br />
}<br />
return res;<br />
}<br />
<br />
Vec sum(const Vec& lhs, const Vec& rhs) {<br />
Vec res(lhs.size());<br />
if (lhs.size() != rhs.size()) {<br />
return res;<br />
}<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res[i] = lhs[i] + rhs[i];<br />
}<br />
return res;<br />
}<br />
<br />
double diff(const Vec& lhs, const Vec& rhs) {<br />
double res = 0.;<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res += lhs[i] - rhs[i];<br />
}<br />
return fabs(res);<br />
}<br />
<br />
class Newtone {<br />
public:<br />
Newtone(int rank) : m_rank(rank) { <br />
m_h = std::numeric_limits<double>::epsilon(); <br />
}<br />
<br />
Vec find_solution(const Sys_& sys, const Vec& start, const Derivatives& d, LSSolver solver, <br />
VecSum vec_summator, VecDiff vec_differ, const double& eps, const size_t& max_iter) {<br />
size_t iter_count = 1;<br />
double diff = 0.;<br />
Vec sys_val(sys.size(), 0);<br />
if (m_rank == 0) {<br />
m_jac.reserve(sys.size());<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
Vec v(sys.size());<br />
m_jac.push_back(v);<br />
}<br />
compute_jacobian(sys, d, start);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, start);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
Vec delta = solver(m_jac, sys_val);<br />
Vec new_sol(sys.size()), old_sol(sys.size());<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(start, delta);<br />
old_sol = start;<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Bcast(old_sol.data(), old_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
diff = vec_differ(new_sol, old_sol);<br />
while (diff > eps && iter_count <= max_iter) {<br />
old_sol = new_sol;<br />
if (m_rank == 0) {<br />
compute_jacobian(sys, d, old_sol);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, old_sol);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
delta = solver(m_jac, sys_val);<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(old_sol, delta);<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
iter_count++;<br />
diff = vec_differ(new_sol, old_sol);<br />
}<br />
return new_sol;<br />
}<br />
<br />
double compute_derivative(const size_t& pos, Function func, const size_t& var_num, const Vec& point) {<br />
Vec left_point(point), right_point(point);<br />
left_point[var_num] -= m_h;<br />
right_point[var_num] += m_h;<br />
double left = func(pos, left_point), right = func(pos, right_point);<br />
return (right - left) / (2 * m_h);<br />
}<br />
<br />
private:<br />
void compute_jacobian(const Sys_& sys, const Derivatives& d, const Vec& point) {<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
for (size_t j = 0; j < sys.size(); ++j) {<br />
double res_val;<br />
res_val = d[i][j](i, j, point);<br />
m_jac[i][j] = res_val;<br />
}<br />
}<br />
}<br />
double m_h;<br />
int m_rank;<br />
Matrix m_jac;<br />
Vec m_right_part;<br />
};<br />
<br />
int main(int argc, char** argv) {<br />
int rank, size;<br />
Sys_ s;<br />
Derivatives d(system_size);<br />
Vec start(system_size, 0.87);<br />
for (size_t i = 0; i < system_size; ++i) {<br />
s.push_back(&func);<br />
}<br />
for (size_t i = 0; i < system_size; ++i) {<br />
for (size_t j = 0; j < system_size; ++j)<br />
d[i].push_back(&derivative);<br />
}<br />
MPI_Init(&argc, &argv);<br />
MPI_Comm_rank(MPI_COMM_WORLD, &rank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &size);<br />
Newtone n(rank);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
double time = MPI_Wtime();<br />
Vec sol = n.find_solution(s, start, d, &gauss, &sum, &diff, 0.0001, 100);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
time = MPI_Wtime() - time;<br />
double max_time;<br />
MPI_Reduce(&time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);<br />
if (rank == 0) {<br />
std::ofstream myfile;<br />
char filename[32];<br />
snprintf(filename, 32, "out_%ld_%d.txt", system_size, size);<br />
myfile.open(filename);<br />
for (size_t i = 0; i < sol.size(); ++i) {<br />
myfile << sol[i] << " ";<br />
}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25276Метод Ньютона для систем нелинейных уравнений2018-01-17T09:23:24Z<p>Konshin: </p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.В.Арутюнов</b>] и<br />
[https://algowiki-project.org/ru/Участник:Noite<b>А.С.Жилкин</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра(см [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
Характеристики реализации алгоритма сильно зависят от выбранного способа нахождения матрицы Якоби и решения СЛАУ. <br />
Для примера рассматривается реализация алгоритма с использованием метода Гаусса на функциях вида:<br />
<br />
<math>f_i(x) = cos(x_i) - 1</math>.<br />
<br />
Для этих функций можно задать точное значение производной в любой точке:<br />
<br />
<math>f_i^' (x) = -sin(x_i)</math>.<br />
<br />
Для тестирования программы было решено использовать исключительно технологию MPI в реализации Intel (IntelMPI<ref name="LINK_IMPI">https://software.intel.com/en-us/intel-mpi-library</ref>) без дополнительных. <br />
Тесты проводились на суперкомпьютере Ломоносов<ref name="LOM_PARALLEL_LINK">https://parallel.ru/cluster/lomonosov.html</ref> <ref name="LOM_WIKI_LINK">https://ru.wikipedia.org/wiki/Ломоносов_(суперкомпьютер)</ref> в разделе test. <br />
Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
* Количество ядер: 8 ядер архитектуры x86<br />
* Количество памяти: 12Гб<br />
<br />
Строка компиляции: mpicxx _scratch/Source.cpp -o _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Строка запуска: sbatch -nN -p test impi _scratch/out_file <ref name="LOM_FAQ">http://users.parallel.ru/wiki/pages/17-quick_start</ref><br />
<br />
Результаты тестирования представлены на рисунках 10 и 11, где рис. 10 отображает время работы данной реализации, а рис. 11 - ускорение:<br />
<br />
[[Файл:Nwt 1024 2048 4096.png|thumb|center|800px|Рис.2 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
[[Файл:Nwt spdup.png|thumb|center|800px|Рис.3 Ускорение решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Из рис. 10 и 11 видно, что увеличение числа задействованных в вычислениях процессоров дает выигрыш во времени, однако, из-за необходимости обмениваться данными при решении СЛАУ методом Гаусса, при вовлечении слишком большого числа процессоров, время, требуемое на обмен данными может превысить время непосредственного вычисления. Этот эффект ограничивает масштабируемость программы. Для подтверждения этого тезиса приведён рис. 12, отдельно показывающий время работы задачи с размерностью матрицы 1024:<br />
<br />
[[Файл:ChartGo 1024.png|thumb|center|600px|Рис. 12. Время решения системы из 1024 уравнений в зависимости от количества задействованных процессоров]]<br />
<br />
Как видно из рис. 12 время выполнения программы на 128 процессорах возрастает по сравнению с временем работы на 64 процессорах. <br />
Это связано с тем, что на каждый процессор приходится недостаточно индивидуальной загрузки и основное время работы программы тратится на передачу данных между процессорами.<br />
Если же увеличение количества задействованных процессоров не приводит к возникновению этого эффекта, то увеличение количества процессоров в 2 раза ведёт к увеличению скорости работы программы также приблизительно в 2 раза, что хорошо видно на рис. 12.<br />
<br />
{|class="wikitable mw-collapsible mw-collapsed"<br />
!Исходный код программы<br />
|-<br />
|<source hide="yes" lang="cpp">#include <iostream><br />
#include <cmath><br />
#include <fstream><br />
#include <vector><br />
#include <cstdlib><br />
#include <limits><br />
#include <mpi.h><br />
#include <stdio.h><br />
#include <assert.h><br />
<br />
typedef std::vector<double> Vec;<br />
typedef double(*Function)(const size_t&, const Vec&);<br />
typedef double(*Deriv)(const size_t&, const size_t&, const Vec&);<br />
typedef std::vector<Function> Sys_;<br />
typedef std::vector<std::vector<Deriv> > Derivatives;<br />
typedef std::vector<std::vector<double> > Matrix;<br />
typedef Vec(*LSSolver)(const Matrix&, const Vec&);<br />
typedef Vec(*VecSum)(const Vec&, const Vec&);<br />
typedef double(*VecDiff)(const Vec&, const Vec&);<br />
<br />
struct LS {<br />
Matrix A;<br />
Vec b;<br />
};<br />
<br />
size_t system_size = 4096;<br />
<br />
double func(const size_t& i, const Vec& v) {<br />
return cos(v[i]) - 1;<br />
}<br />
<br />
double derivative(const size_t& i, const size_t& j, const Vec& v) {<br />
if (i == j) {<br />
return -sin(v[i]);<br />
}<br />
return 0.0;<br />
}<br />
<br />
static void printVec(const Vec& v) {<br />
std::cout << "size: " << v.size() << std::endl;<br />
for (size_t i = 0; i < v.size(); ++i) {<br />
std::cout << v[i] << " ";<br />
}<br />
std::cout << std::endl;<br />
}<br />
<br />
Vec gauss(const Matrix& A, const Vec& b) {<br />
MPI_Status status;<br />
int nSize, nRowsBloc, nRows, nCols;<br />
int Numprocs, MyRank, Root = 0;<br />
int irow, jrow, icol, index, ColofPivot, neigh_proc;<br />
double *Input_A, *Input_B, *ARecv, *BRecv;<br />
double *Output, Pivot;<br />
double *X_buffer, *Y_buffer;<br />
double *Buffer_Pivot, *Buffer_bksub;<br />
double tmp;<br />
MPI_Comm_rank(MPI_COMM_WORLD, &MyRank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &Numprocs);<br />
if (MyRank == 0) {<br />
nRows = A.size();<br />
nCols = A[0].size();<br />
nSize = nRows;<br />
Input_B = (double *)malloc(nSize * sizeof(double));<br />
for (irow = 0; irow < nSize; irow++) {<br />
Input_B[irow] = b[irow];<br />
}<br />
Input_A = (double *)malloc(nSize*nSize * sizeof(double));<br />
index = 0;<br />
for (irow = 0; irow < nSize; irow++)<br />
for (icol = 0; icol < nSize; icol++)<br />
Input_A[index++] = A[irow][icol];<br />
}<br />
MPI_Bcast(&nRows, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
MPI_Bcast(&nCols, 1, MPI_INT, Root, MPI_COMM_WORLD);<br />
/* .... Broad cast the size of the matrix to all ....*/<br />
MPI_Bcast(&nSize, 1, MPI_INT, 0, MPI_COMM_WORLD);<br />
nRowsBloc = nSize / Numprocs;<br />
/*......Memory of input matrix and vector on each process .....*/<br />
ARecv = (double *)malloc(nRowsBloc * nSize * sizeof(double));<br />
BRecv = (double *)malloc(nRowsBloc * sizeof(double));<br />
/*......Scatter the Input Data to all process ......*/<br />
MPI_Scatter(Input_A, nRowsBloc * nSize, MPI_DOUBLE, ARecv, nRowsBloc * nSize, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Scatter(Input_B, nRowsBloc, MPI_DOUBLE, BRecv, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* ....Memory allocation of ray Buffer_Pivot .....*/<br />
Buffer_Pivot = (double *)malloc((nRowsBloc + 1 + nSize * nRowsBloc) * sizeof(double));<br />
/* Receive data from all processors (i=0 to k-1) above my processor (k).... */<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Recv(Buffer_Pivot, nRowsBloc * nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc, MPI_COMM_WORLD, &status);<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
/* .... Buffer_Pivot[0] : locate the rank of the processor received */<br />
/* .... Index is used to reduce the matrix to traingular matrix */<br />
/* .... Buffer_Pivot[0] is used to determine the starting value of<br />
pivot in each row of the matrix, on each processor */<br />
ColofPivot = ((int)Buffer_Pivot[0]) * nRowsBloc + irow;<br />
for (jrow = 0; jrow < nRowsBloc; jrow++) {<br />
index = jrow*nSize;<br />
tmp = ARecv[index + ColofPivot];<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] -= tmp * Buffer_Pivot[irow*nSize + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Buffer_Pivot[1 + irow];<br />
ARecv[index + ColofPivot] = 0.0;<br />
}<br />
}<br />
}<br />
Y_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
/* ....Modification of row entries on each processor ...*/<br />
/* ....Division by pivot value and modification ...*/<br />
for (irow = 0; irow < nRowsBloc; irow++) {<br />
ColofPivot = MyRank * nRowsBloc + irow;<br />
index = irow*nSize;<br />
Pivot = ARecv[index + ColofPivot];<br />
assert(Pivot != 0);<br />
for (icol = ColofPivot; icol < nSize; icol++) {<br />
ARecv[index + icol] = ARecv[index + icol] / Pivot;<br />
Buffer_Pivot[index + icol + 1 + nRowsBloc] = ARecv[index + icol];<br />
}<br />
Y_buffer[irow] = BRecv[irow] / Pivot;<br />
Buffer_Pivot[irow + 1] = Y_buffer[irow];<br />
for (jrow = irow + 1; jrow < nRowsBloc; jrow++) {<br />
tmp = ARecv[jrow*nSize + ColofPivot];<br />
for (icol = ColofPivot + 1; icol < nSize; icol++) {<br />
ARecv[jrow*nSize + icol] -= tmp * Buffer_Pivot[index + icol + 1 + nRowsBloc];<br />
}<br />
BRecv[jrow] -= tmp * Y_buffer[irow];<br />
ARecv[jrow*nSize + irow] = 0;<br />
}<br />
}<br />
/*....Send data to all processors below the current processors */<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; neigh_proc++) {<br />
/* ...... Rank is stored in first location of Buffer_Pivot and<br />
this is used in reduction to triangular form ....*/<br />
Buffer_Pivot[0] = (double)MyRank;<br />
MPI_Send(Buffer_Pivot, nRowsBloc*nSize + 1 + nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Back Substitution starts from here ........*/<br />
/*.... Receive from all higher processors ......*/<br />
Buffer_bksub = (double *)malloc(nRowsBloc * 2 * sizeof(double));<br />
X_buffer = (double *)malloc(nRowsBloc * sizeof(double));<br />
for (neigh_proc = MyRank + 1; neigh_proc < Numprocs; ++neigh_proc) {<br />
MPI_Recv(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, neigh_proc,<br />
MPI_COMM_WORLD, &status);<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
for (icol = nRowsBloc - 1; icol >= 0; icol--) {<br />
/* ... Pick up starting Index .....*/<br />
index = (int)Buffer_bksub[icol];<br />
Y_buffer[irow] -= Buffer_bksub[nRowsBloc + icol] * ARecv[irow*nSize + index];<br />
}<br />
}<br />
}<br />
for (irow = nRowsBloc - 1; irow >= 0; irow--) {<br />
index = MyRank*nRowsBloc + irow;<br />
Buffer_bksub[irow] = (double)index;<br />
Buffer_bksub[nRowsBloc + irow] = X_buffer[irow] = Y_buffer[irow];<br />
for (jrow = irow - 1; jrow >= 0; jrow--)<br />
Y_buffer[jrow] -= X_buffer[irow] * ARecv[jrow*nSize + index];<br />
}<br />
/*.... Send to all lower processes...*/<br />
for (neigh_proc = 0; neigh_proc < MyRank; neigh_proc++) {<br />
MPI_Send(Buffer_bksub, 2 * nRowsBloc, MPI_DOUBLE, neigh_proc, MyRank, MPI_COMM_WORLD);<br />
}<br />
/*.... Gather the result on the processor 0 ....*/<br />
Output = (double *)malloc(nSize * sizeof(double));<br />
MPI_Gather(X_buffer, nRowsBloc, MPI_DOUBLE, Output, nRowsBloc, MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
/* .......Output vector .....*/<br />
Vec res(nSize);<br />
if (MyRank == 0) {<br />
for (irow = 0; irow < nSize; irow++)<br />
res[irow] = Output[irow];<br />
}<br />
return res;<br />
}<br />
<br />
Vec sum(const Vec& lhs, const Vec& rhs) {<br />
Vec res(lhs.size());<br />
if (lhs.size() != rhs.size()) {<br />
return res;<br />
}<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res[i] = lhs[i] + rhs[i];<br />
}<br />
return res;<br />
}<br />
<br />
double diff(const Vec& lhs, const Vec& rhs) {<br />
double res = 0.;<br />
for (size_t i = 0; i < lhs.size(); ++i) {<br />
res += lhs[i] - rhs[i];<br />
}<br />
return fabs(res);<br />
}<br />
<br />
class Newtone {<br />
public:<br />
Newtone(int rank) : m_rank(rank) { <br />
m_h = std::numeric_limits<double>::epsilon(); <br />
}<br />
<br />
Vec find_solution(const Sys_& sys, const Vec& start, const Derivatives& d, LSSolver solver, <br />
VecSum vec_summator, VecDiff vec_differ, const double& eps, const size_t& max_iter) {<br />
size_t iter_count = 1;<br />
double diff = 0.;<br />
Vec sys_val(sys.size(), 0);<br />
if (m_rank == 0) {<br />
m_jac.reserve(sys.size());<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
Vec v(sys.size());<br />
m_jac.push_back(v);<br />
}<br />
compute_jacobian(sys, d, start);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, start);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
Vec delta = solver(m_jac, sys_val);<br />
Vec new_sol(sys.size()), old_sol(sys.size());<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(start, delta);<br />
old_sol = start;<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
MPI_Bcast(old_sol.data(), old_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
diff = vec_differ(new_sol, old_sol);<br />
while (diff > eps && iter_count <= max_iter) {<br />
old_sol = new_sol;<br />
if (m_rank == 0) {<br />
compute_jacobian(sys, d, old_sol);<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
sys_val[i] = -sys[i](i, old_sol);<br />
}<br />
}<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
delta = solver(m_jac, sys_val);<br />
if (m_rank == 0) {<br />
new_sol = vec_summator(old_sol, delta);<br />
}<br />
MPI_Bcast(new_sol.data(), new_sol.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD);<br />
iter_count++;<br />
diff = vec_differ(new_sol, old_sol);<br />
}<br />
return new_sol;<br />
}<br />
<br />
double compute_derivative(const size_t& pos, Function func, const size_t& var_num, const Vec& point) {<br />
Vec left_point(point), right_point(point);<br />
left_point[var_num] -= m_h;<br />
right_point[var_num] += m_h;<br />
double left = func(pos, left_point), right = func(pos, right_point);<br />
return (right - left) / (2 * m_h);<br />
}<br />
<br />
private:<br />
void compute_jacobian(const Sys_& sys, const Derivatives& d, const Vec& point) {<br />
for (size_t i = 0; i < sys.size(); ++i) {<br />
for (size_t j = 0; j < sys.size(); ++j) {<br />
double res_val;<br />
res_val = d[i][j](i, j, point);<br />
m_jac[i][j] = res_val;<br />
}<br />
}<br />
}<br />
double m_h;<br />
int m_rank;<br />
Matrix m_jac;<br />
Vec m_right_part;<br />
};<br />
<br />
int main(int argc, char** argv) {<br />
int rank, size;<br />
Sys_ s;<br />
Derivatives d(system_size);<br />
Vec start(system_size, 0.87);<br />
for (size_t i = 0; i < system_size; ++i) {<br />
s.push_back(&func);<br />
}<br />
for (size_t i = 0; i < system_size; ++i) {<br />
for (size_t j = 0; j < system_size; ++j)<br />
d[i].push_back(&derivative);<br />
}<br />
MPI_Init(&argc, &argv);<br />
MPI_Comm_rank(MPI_COMM_WORLD, &rank);<br />
MPI_Comm_size(MPI_COMM_WORLD, &size);<br />
Newtone n(rank);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
double time = MPI_Wtime();<br />
Vec sol = n.find_solution(s, start, d, &gauss, &sum, &diff, 0.0001, 100);<br />
MPI_Barrier(MPI_COMM_WORLD);<br />
time = MPI_Wtime() - time;<br />
double max_time;<br />
MPI_Reduce(&time, &max_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);<br />
if (rank == 0) {<br />
std::ofstream myfile;<br />
char filename[32];<br />
snprintf(filename, 32, "out_%ld_%d.txt", system_size, size);<br />
myfile.open(filename);<br />
for (size_t i = 0; i < sol.size(); ++i) {<br />
myfile << sol[i] << " ";<br />
}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
myfile << std::endl;<br />
myfile << "Time: " << time << " " << max_time << std::endl;<br />
myfile.close();<br />
}<br />
MPI_Finalize();<br />
return 0;<br />
}</source><br />
|}<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25275Метод Ньютона для систем нелинейных уравнений2018-01-17T09:11:38Z<p>Konshin: </p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Oleggium<b>О.Р.Гирняк</b>] и<br />
[https://algowiki-project.org/ru/Участник:Dimx19<b>Д.А.Васильков</b>] (разделы 1.7, 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:<b></b>] и<br />
[https://algowiki-project.org/ru/Участник:<b></b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масштабируемости алгоритм был реализован нами самостоятельно на библиотеке CUDA 8.0 для языка C++(компилятор nvcc) для Windows на домашнем компьютере(OS Windows 10 x64, CPU Intel Core i7-2600 3.4 GHz, 8 GB RAM, GPU NVIDIA GTX 550 Ti). GPU поддерживает спецификацию CUDA 2.1 и максимальное число потоков в блоке [https://ru.wikipedia.org/wiki/CUDA 1024]. Однако физически у нас есть всего 192 CUDA ядра(см [http://www.geforce.com/hardware/desktop-gpus/geforce-gtx-550ti/specifications спецификации] видеокарты). Все потоки должны быть элементами двумерного квадратного блока. Поэтому исследования масштабируемости были проведены на 4, 16, 36, 64, 100, 144, 196, 256, 324, 400 потоках(на большем количестве потоков проводить исследования бессмысленно ввиду ухудшения производительности). Параметры компиляции: <br />
<source>"nvcc.exe" -gencode=arch=compute_20,code=\"sm_20,compute_20\" --use-local-env --cl-version 2015 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\include" -G --keep-dir Debug -maxrregcount=0 --machine 32 --compile -cudart static -g -DWIN32 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /FS /Zi /RTC1 /MDd " -o Debug\kernel.cu.obj "kernel.cu" </source><br />
Решение СЛАУ было реализовано в 2 этапа: <br />
<br />
1) Нахождение обратной матрицы с помощью метода Гаусса-Жордана. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void gaussjordan(float *A, float *I, int n, int i)</source><br />
<br />
2) Умножение обратной матрицы на вектор правых частей. Для этого было написано ядро для CUDA:<br />
<source lang="c++">__global__ void multiply_by_vec(float *mat, float *vec, float *out, const int N)</source><br />
<br />
Необходимо отметить, что использование своих ядер для CUDA непременно ведет к снижению производительности. Для улучшения производительности предпочтительно использовать матричные операции из библиотеки cuBLAS. Однако, при таком подходе невозможно явно задать количество потоков - для конкретной видеокарты сама библиотека выбирает, какое количество потоков ей использовать. В связи с этим и были написаны свои ядра, позволяющие задавать количество потоков.<br />
<br />
Считалось, что на вход алгоритму были заданы нелинейные функции и их производные. С данной реализацией можно ознакомиться [https://dl.dropboxusercontent.com/u/43317547/ParallelingCuda.zip здесь].<br />
<br />
Говоря о масштабируемости данного алгоритма, важно отметить следующее: <br />
<br />
1. Время вычисления набора функций и набора их производных очень сильно зависит от их сложности и не связано с мощностью алгоритма. <br />
<br />
2. Скорость отработки алгоритма от начала до конца может не быть связана с его мощностью: заранее неизвестно начальное приближение, а значит и количество итераций, за которое он сойдется. <br />
<br />
Вывод: для оценки масштабируемости необходимо сравнивать одну итерацию алгоритма, и притом, без учета времени вычисления значения функций и их производных. В итоге, приходим к выводу, что целесообразно измерить время выполнения одного решения СЛАУ. Также стоит отметить, что в нашей реализации при хорошем выборе начального приближения алгоритм сходился за 10-20 итераций.<br />
<br />
Можно рассмотреть зависимость масштабируемости от одного из двух критериев: размерности задачи или же количества потоков. На графике ниже представлена зависимость времени решения СЛАУ от количества потоков и размерности матрицы.<br />
<br />
[[Файл:Newton_plot.png|1100px|Рис. 9. Время решения СЛАУ в зависимости от размерности системы и количества потоков|мини|центр]]<br />
<br />
Как уже было сказано, из-за наличия только 192 физических процессоров, данный алгоритм показывает лучшее время работы при использовании 144 либо 256 потоков. 144 потока (размер блока 12х12) - наиболее близкое количество потоков к 192 физическим процессорам. Хорошее время работы на 256 потоках объясняется тем, что мы используем стандартный размер блока 16х16, а также многие операции деления или умножения на количество потоков или размер блока в этом случае компилятор может заменить на операции побитового сдвига, которые работают быстрее обычных.<br />
<br />
==== Реализация 4 ====<br />
<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25274Метод Ньютона для систем нелинейных уравнений2018-01-17T09:03:29Z<p>Konshin: </p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует также чья-то страница (октябрь 2017) без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<!----------------------------------------------------------------><br />
<br />
Основные авторы статьи: <br />
[https://algowiki-project.org/ru/Участник:SKirill<b>К.О.Шохин</b>] и<br />
[https://algowiki-project.org/ru/Участник:Лебедев Артём<b>А.А.Лебедев</b>] (разделы 1, 2.4.1, 2.7)<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:Александр Чернышев<b>А.Чернышев</b>] и<br />
[https://algowiki-project.org/ru/Участник:N Zakharov<b>Н.Захаров</b>] (раздел 2.4.2),<br />
<br />
Авторы: Гирняк О.Р., Васильков Д.А. (разделы 1.7, 2.4.Х)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>{\rm max\_iter}</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
==== Реализация 2 ====<br />
<br />
В качестве модельной задачи рассматривался один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref>, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
* Версия MPI - impi/4.1.0<br />
* Реализация BLAS - mkl/11.2.0<br />
* Запуски проводились в сегменте regulal4<br />
* Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
* Версия PETSc - 3.7.4<br />
* Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
# Pестарт алгоритма GMRES через 300 итераций<br />
# Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
# Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рис. 6. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рис. 7. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рис. 8. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи.<br />
<br />
==== Реализация 3 ====<br />
<br />
<br />
==== Реализация 4 ====<br />
<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25273Метод Ньютона для систем нелинейных уравнений2018-01-17T08:43:21Z<p>Konshin: /* Масштабируемость алгоритма и его реализации */</p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует чья-то среднего качества страница без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<br />
<br />
Основные авторы описания: [[Участник:SKirill|Шохин К.О.]](1.1,1.2,1.4,1.6,1.8,1.10,2.7), [[Участник:Лебедев Артём|Лебедев А.А.]](1.1,1.3,1.5,1.7,1.9,2.7) 2.4.X<br />
<br />
Авторы: Гирняк О.Р., Васильков Д.А. (1.7, 2.4.X)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>max\_iter</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
Масштабируемость алгоритма и его реализаций определяется главным образом масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций Метод Ньютона для систем нелинейных уравнений согласно [[Scalability methodology|методике]] AlgoWiki. В основном исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9B%D0%B0%D0%BD%D1%86%D0%BE%D1%88%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%82%D0%BE%D1%87%D0%BD%D0%BE%D0%B9_%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B5%D1%82%D0%B8%D0%BA%D0%B8_(%D0%B1%D0%B5%D0%B7_%D0%BF%D0%B5%D1%80%D0%B5%D0%BE%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8)&diff=25272Алгоритм Ланцоша для точной арифметики (без переортогонализации)2018-01-17T08:35:36Z<p>Konshin: </p>
<hr />
<div><br />
Основные авторы статьи (разделы 1, 2.4, 2.7):<br />
[https://algowiki-project.org/ru/Участник:Bashleon<b>Башлыков Л.А.</b>] и<br />
[https://algowiki-project.org/ru/Участник:VolkovNikita94<b>Волков Н.И.</b>]<br/><br />
Авторы отдельных разделов (2.4, 2.7):<br />
[https://algowiki-project.org/ru/Участник:M.grigoriev<b>Григорьев М.А.</b>],<br />
[https://algowiki-project.org/ru/Участник:KAKTUZ<b>Заспа А.Ю.</b>],<br />
[https://algowiki-project.org/ru/Участник:AleksLevin<b>Левин А.Д.</b>],<br />
[https://algowiki-project.org/ru/Участник:Danyanya<b>Слюсарь Д.Р.</b>],<br />
[https://algowiki-project.org/ru/Участник:A.Freeman<b>Фролов А.А.</b>] и<br />
[https://algowiki-project.org/ru/Участник:Shostix<b>Шостик А.В.</b>]<br/><br />
<!-- В работе также принимали участие:<br />
[https://algowiki-project.org/ru/Участник:ASA<b>Антонов А.С.</b>] и<br />
[https://algowiki-project.org/ru/Участник:Konshin<b>Коньшин И.Н.</b>]<br/> --><br />
<br />
{{algorithm<br />
| name = Алгоритм Ланцоша для точной арифметики (без переортогонализации)<br />
| serial_complexity = <math>O(n^2) + O(k^3)</math> - на одну итерацию и <math>O(kn^2)</math> - суммарно для <math>k</math> итераций<br />
| pf_height = <math>O(n)</math><br />
| pf_width = <math>O(n^2)</math><br />
| input_data = <math>n^2 + n + 1</math>, при экономии памяти затраты сокращаются до <math>\frac{n(n - 1)}{2} + n + 1</math><br />
| output_data = <math>k</math>, а если нужны собственные векторы, то <math>k + kn</math><br />
}}<br />
<br />
==Свойства и структура алгоритма==<br />
===Общее описание алгоритма===<br />
<br />
'''Алгоритм Ланцоша''' был опубликован венгерским математиком и физиком Корнелием Ланцошем<ref>https://ru.wikipedia.org/wiki/Ланцош,_Корнелий</ref> в 1950 году<ref>Lanczos, C. "An iteration method for the solution of the eigenvalue problem of linear differential and integral operators", J. Res. Nat’l Bur. Std. 45, 255-282 (1950).</ref>. Этот метод является частным случаем алгоритма Арнольда в случае, если исходная матрица <math>A</math> - симметрична, и был представлен как итерационный метод вычисления собственных значений симметричной матрицы. Этот метод позволяет за <math>k</math> итераций вычислять <math>k</math> приближений собственных значений и собственных векторов исходной матрицы. Хотя алгоритм и был эффективным в вычислительном смысле, но он на некоторое время был предан забвению из-за численной неустойчивости. Только в 1970 Ojalvo и Newman модифицировали алгоритм для использования в арифметике с плавающей точкой<ref>Ojalvo, I.U. and Newman, M., "Vibration modes of large structures by an automatic matrix-reduction method", AIAA J., 8 (7), 1234–1239 (1970).</ref>. Новый метод получил название [[Алгоритм Ланцоша с полной переортогонализацией | алгоритма Ланцоша с полной переортогонализацией]]. <br />
<br />
В данной статье рассматривается исходная версия алгоритма<ref>Деммель Дж. Вычислительная линейная алгебра. Теория и приложения - М.: Мир, 2001. ([http://www.bookvoed.ru/book?id=5252274 см.])</ref>.<br />
<br />
===Математическое описание алгоритма===<br />
<br />
Математическое описание алгоритма Ланцоша для вычисления собственных значений симметричной матрицы А<br />
требует 1) математического описания метода Ланцоща для построения крыловского подпространства и 2)<br />
математического описания т.н. процедуры Рэлея-Ритца.<br />
<br />
==== Метод Ланцоша для построения крыловского подпространства ====<br />
<br />
Пусть дано уравнение <math>Ax = b</math>, причём вектор <math>b</math> известен и имеется<br />
способ вычисления <math>Ax</math>.<br/><br />
<br />
Вычислим <math>y_{1} = b</math>, <math>y_{2} = Ab</math>, <math>y_{3} = A^{2}b</math>,...,<math>y_{n} = A^{(n - 1)}b</math>, где <math>n</math> - <br />
порядок матрицы <math>A</math>. Определим матрицу <math>K = [y_{1},...,y_{n}]</math>.<br/><br />
<br />
Тогда <math>AK = [Ay_{1},...,Ay_{n - 1}, Ay_{n}] = [y_{2},...,y_{n},A^n{y_{1}}]</math>. <br />
В предположении, что матрица <math>K</math> - невырождена, можно вычислить вектор <math>c = -K^{-1}A^{ny_{1}}</math>.<br />
Поскольку первые <math>n - 1</math> столбцов в <math>AK</math> совпадают с последними <math>n - 1</math> столбцами в<br />
<math>K</math>, то справедливо: <math>AK = K[e_{2},e_{3},...,e_{n},-c] = KC</math>, то есть <math>K^{-1}AK = C</math>, где <math>C</math> - верхняя хессенбергова матрица.<br/><br />
<br />
Заменим <math>K</math> ортогональной матрицей <math>Q</math> так, что при любом <math>k</math><br />
линейные оболочки первых <math>k</math> столбцов в <math>K</math> и <math>Q</math> - одно и то же<br />
подпространство. Это подпространство называется крыловским и точно определяется как<br />
<math>\kappa_{k}(A, b)</math> - линейная оболочка векторов <math>b</math>,<math>Ab</math>,...,<math>A^{(k - 1)}b</math>.<br />
Суть алгоритма Ланцоша заключается в вычислении стольких первых столбцов в <math>Q</math>,<br />
сколько необходимо для получения требуемого приближения к решению <math>Ax = b </math> (<math>Ax = {\lambda}x</math>).<br />
<br />
Используем QR-разложение матрицы <math>K = QQ</math><br/><br />
Тогда <math>K^{-1}AK = (R^{-1}Q^{T})A(QR) = C</math>, откуда <math>Q^{T}AQ = RCR^{-1} = H</math><br/><br />
<br />
Матрица <math>H</math> является верхней хессенберговой в силу того, что верхней хессенберговой<br />
является матрица <math>C</math>, а матрицы <math>R</math> и <math>R^{-1}</math> - верхние треугольные.<br />
<br />
Однако алгоритм Ланцоша подразумевает симметричность матрицы <math>A</math>.<br />
Положим <math>Q = [Q_{k}, Q_{u}]</math>, где <math>Q_{k} = [q_{1},...,q_{k}]</math> и <br />
<math>Q_{u} = [q_{k+1},...,q_{n}]</math>. В итоге получается, что <math>H = Q^{T}AQ = [Q_{k},Q_{u}]^{T}A[Q_{k},Q_{u}]</math><br />
Из симметричности <math>A</math>, таким образом, следует симметричность и трехдиагональность матрицы <math>H = T</math>.<br />
Теперь можно вывести две основные формулы, используемые в методе Ланцоша:<br />
<br />
*<math>AQ_{j} = \beta_{j - 1}q_{j - 1} + \alpha_{j}q_{j} + \beta_{j}q_{j + 1}</math> - получается приравниванием столбцов <math>j</math> в обеих частях равенства <math>AQ = QT</math>.<br />
*<math>q_{j}^{T}Aq_{j} = \alpha_{j}</math> - получается домножением обеих частей предыдущего соотношения на <math>q_{j}^{T}</math> и учетом ортогональности <math>Q</math>.<br />
<br />
==== Процедура Рэлея-Ритца ====<br />
<br />
Пусть <math>Q = [Q_{k}, Q_{u}]</math> - произвольная ортогональная матрица порядка <math>n</math>, причём<br />
<math>Q_{k}</math> и <math>Q_{u}</math> имеют размеры <math>nk</math> и <math>n(n - k)</math>.<br />
<br />
Пусть <math>T = Q^{T}AQ = [Q_{k},Q_{u}]^{T}A[Q_{k},Q_{u}]</math>. Обратим внимание, что в случае <math>k = 1</math> выражение <math>T_{k} = Q_{k}^{T}AQ_{k}</math> -<br />
отношение Рэлея <math>\rho(Q_{1},A)</math>, то есть число <math>(Q_{1}^{T}AQ_{1}) / (Q_{1}^{T}Q_{1})</math>. Определим теперь суть<br />
процедуры Рэлея-Ритца:<br/><br />
<br />
*Процедура Рэлея-Ритца - интерпретация собственных значений матрицы <math>T = Q^{T}AQ</math> как приближений к собственным значениям матрицы <math>A</math>. Эти приближения называются числами Ритца. Пусть <math>T_{k} = V{\lambda}V^{T}</math> - спектральное разложение матрицы <math>T_{k}</math>. Столбцы матрицы <math>Q_{k}V</math> рассматриваются как приближения к соответствующим собственным векторам и называются векторами Ритца.<br />
<br />
Существует несколько важных фактов о процедуре Рэлея-Ритца, характеризующих получаемые приближения собственных значений матрицы <math>A</math>.<br />
<br />
*Минимум величины <math>||AQ_{k} - Q_{k}R||_{2}</math> по всем симметричным <math>k^2</math> матрицам <math>R</math> достигается при <math>R = T_{k}</math>. При этом <math>||AQ_{k} - Q_{k}R||_{2} = ||T_{ku}||_{2}</math>. Пусть <math>T_{k} = V{\lambda}V^{T}</math> - спектральное разложение матрицы <math>T_{k}</math>. Минимум величины <math>||AP_{k} - P_{k}D||_{2}</math>, когда <math>P_{k}</math> пробегает множество <math>nk </math> матрицы с ортонормированными столбцами, таких, что <math>span(P_{k}) = span(Q_{k})</math>, а <math>D</math> пробегает множество диагональных <math>k^2</math> матриц также равен <math>||T_{ku}||_{2}</math> и достигается для <math>P_{k} = Q_{k}V</math> и <math>D = \lambda</math>.<br />
<br />
Пусть <math>T_{k}</math>, <math>T_{ku}</math>, <math>Q_{k}</math> - те же самые матрицы. Пусть <math>T_{k} = V{\lambda}V^{T}</math> - всё то же спектральное разложение,<br />
причём <math>V = [v_{1},...,v_{k}]</math> - ортогональная матрица, а <math>\lambda = diag(\theta_{1},...,\theta_{k})</math>. Тогда:<br />
<br />
* Найдутся <math>k</math> необязательно наибольших собственных значений <math>\alpha_{1},...,\alpha_{k}</math> матрицы <math>A</math>, такие, что <math>|\theta_{i} - \alpha_{i}| <= ||T_{ku}||_{2}</math> для <math>i = 1,...,k</math>. Если <math>Q_{k}</math> вычислена методом Ланцоша,то правая часть этого выражения равняется <math>\beta_{k}</math>, то есть единственному элементу блока <math>T_{ku}</math>, который может быть отличен от нуля. Указанный эдемент находится в правом верхнем углу блока.<br />
* <math>||A(Q_{k}v_{i}) - (Q_{k}v_{i})\theta_{i}||_{2} = ||T_{ku}v_{i}||_{2}</math>. Таким образом, разность между числом Ритца <math>\theta_{j}</math> и некоторым собственным значением <math>\alpha</math> матрицы <math>A</math> не превышает величины <math>||T_{ku}v_{i}||_{2}</math>, которая может быть много меньше, чем <math>||T_{ku}||_{2}</math>. Если матрица <math>Q_{k}</math> вычислена алгоритмом Ланцоша, то эта величина равна <math>\beta_{k}|v_{i}(k)|</math>, где <math>v_{i}(k)</math> - k-ая компонента вектора <math>v_{i}</math>. Таким образом, можно дещево вычислить норму невязки - левой части выражения, не выполняя ни одного умножения вектора на матрицы <math>Q_{k}</math> или <math>A</math>.<br />
* Не имея информации о спектре матрицы <math>T_{u}</math>, нельзя дать какую-либо содержательную оценку для погрешности в векторе Ритца <math>Q_{k}v_{i}</math>. Если известно, что <math>\theta_{j}</math> отделено расстоянием, не меньшим <math>g</math>, от прочих собственных значений матриц <math>T_{k}</math>, <math>T_{u}</math> и матрица <math>Q_{k}</math> вычислена алгоритмом Ланцоша, то угол <math>\theta</math> между <math>Q_{k}v_{i}</math> и точным собственным вектором <math>A</math> можно оценить формулой <math>\frac{1}{2}\sin{2\theta} <= \frac{b_{k}}{g}</math><br />
<br />
В алгоритме Ланцоша из ортонормированных векторов строится матрица <math>Q_{k} = [q_{1},...,q_{k}]</math> и в качестве приближенных собственных значений <math>A</math> принимаются числа Ритца - собственные значения симметричной трёхдиагональной матрицы <math>T_{k} = Q_{k}^{T}AQ_{k}</math>.<br />
<br />
===Вычислительное ядро алгоритма===<br />
Вычислительное ядро алгоритма Ланцоща состоит в получении на каждой итерации очередного промежуточного вектора <math>z</math>, получаемого путём умножения исходной матрицы <math>A</math> на вектор Ланцоша <math>q_{j}</math>, полученный на предыдущей итерации.<br />
<br />
Также при значениях <math>k</math>, сопоставимых с <math>n</math>, процедура вычисления собственных значений и собственных векторов симметричной трёхдиагональной матрицы по некоторому выбранному алгоритму подразумевает значительный объём расчётов и может считаться вычислительным ядром. Однако на практике метод используется прежде всего при малых <math>k</math>.<br />
<br />
===Макроструктура алгоритма===<br />
<br />
Метод Ланцоша соединяет метод Ланцоша для построения крыловского подпространства в процедурой Рэлея-Ритца, подразумевающей вычисление собственных значений симметричной трёхдиагональной матрицы. Первая часть алгоритма представляет собой строго последовательные итерации, на каждой из которых вначале строится очередной столбец матрицы <math>Q_{j}</math> из первых <math>j</math> ортонормированных векторов Ланцоша и матрица <math>T_{j} = Q^T_{j}AQ_{j}</math> порядка <math>j</math>. Итерационный процесс завершается, когда <math>j = k</math>. Вторая часть алгоритма представляет собой поиск собственных значений и собственных векторов симметричной трёхдиагональной матрицы <math>T_{j}</math>, что реализуется отдельным алгоритмом, на практике используется QR-алгоритм.<br/><br />
<br />
Особенность методов крыловского подпространства состоит в предположении, что матрица <math>A</math> доступна в виде "черного ящика", а именно подпрограммы, вычисляющей по заданному вектору <math>z</math> произведение <math>y = Az</math> (а также, возможно, произведение <math>y = A^{T}z</math>, если матрица <math>A</math> несимметрична). Другими словами, прямой доступ к элементам матрицы и их изменение не используются, т.к. <br />
*Умножение матрицы на вектор - наиболее дешевая нетривиальная операция, которую можно проделать с разреженной матрицей: в случае разреженной <math>A</math>, содержащей <math>m</math> ненулевых элементов, для матрично-векторного умножения нужны <math>m</math> скалярных умножений и не более <math>m</math> сложений.<br />
*<math>A</math> может быть не представлена в виде матрицы явно, а доступна именно через подпрограмму для вычисления произведений <math>Ax</math>.<br />
Таким образом, как QR-алгоритм поиска собственных значений, собственных векторов матрицы <math>T_{j}</math> и оценки погрешности в них результирующей трехдиагональной матрицы, так и умножение матрицы на вектор внутри каждой итерации могут быть рассмотрены в качестве отдельных макроопераций. Проблемой является то, что именно эти манипуляции порождают основную вычислительную сложность алгоритма. Поскольку матрица, к которой применяется QR-алгоритм, имеет порядок <math>k</math> и на практике бывает мала, сделаем допущение о плотности матрицы <math>A</math>. Тогда умножение этой матрицы на вектор внутри каждой итерации нужно будет реализовывать в рамках самого алгоритма. Еще одним важным замечанием является момент вычисления собственных значений матрицы <math>T_{j}</math>. Её собственные значения, полученные на итерации <math>p</math>, никак не используются на итерации <math>p + 1</math>, поэтому целесообразно вынести эту процедуру за основной цикл. Однако на практике в силу малых размеров матрицы <math>T_{j}</math> вычисление соответствующих величин производится непосредственно в теле основного цикла, что позволяет сразу оценить достигнутую точность вычислений.<br />
<br />
===Схема последовательной реализации алгоритма===<br />
<br />
Алгоритм итерационный, на каждой итерации выполняется вычисление очередного столбца матрицы <math>Q</math>, а также вычисление элементов очередной пары (диагонального и околодиагонального) элементов матрицы <math>T</math> с использованием значений столбца матрицы <math>Q</math>, полученного на предыдущей итерации и значения околодиагонального элемента матрицы <math>T</math> с предыдущей итерации. Вектор <math>z</math> носит вспомогательный характер. Коэффициенты <math>\alpha_{j}</math> соответствуют диагональным элементам матрицы <math>T_{j}</math>, коэффициенты <math>\beta_{j}</math> – наддиагональным и поддиагональным элементам той же матрицы. Далее приводится пример псевдокода. По окончании итераций вычислеяются собственные значения и собственные векторы матрицы <math>T_{j}</math>, что в псевдокоде не отражается (т.к. это отдельный алгоритм).<br />
<br />
Вычислить столбец q1 матрицы Qk q1 = b / ||b||<br />
q0 - нулевой вектор<br />
beta_0 = 0<br />
foreach ( j = 1 .. k ) <br />
{<br />
z = A * qj<br />
alpha_j = qj * z <br />
z = z – alpha_j * qj – beta_(j-1) * q(j – 1)<br />
beta_j = ||z||<br />
if ( beta_j == 0 )<br />
{<br />
break // Все ненулевые собственные значения найдены. Выход может быть досрочным.<br />
}<br />
else <br />
{<br />
q(j + 1) = z/beta_j<br />
}<br />
}<br />
<br />
Вычислить собственные значения и собственные векторы симметричной трехдиагональной матрицы Tj наиболее подходящим методом.<br />
Полученный вектор Lj есть искомые собственные значения.<br />
<br />
===Последовательная сложность алгоритма===<br />
Все дальнейшие выкладки верны для наиболее быстрого последовательного варианта выполнения указываемых операций. <br />
* Умножение квадратной матрицы порядка <math>n</math> на вектор длины <math>n</math> требует <math>n^2</math> умножений и сложений.<br />
* Перемножение векторов длины <math>n</math> требует по <math>n</math> умножений и сложений.<br/><br />
* Поэлементное сложение векторов длины <math>n</math> требует <math>n</math> сложений.<br />
* Умножение вектора длины <math>n</math> на число требует <math>n</math> умножений.<br />
* Нахождение квадратичной нормы вектора длины <math>n</math> требует по <math>n</math> умножений и сложений, а также одну операцию извлечения квадратного корня.<br />
* Вычисление собственных значений матрицы порядка <math>k</math> QR-алгоритмом требует <math>O(k^2)</math> операций, вычисление также и собственных векторов требует примерно <math>6k^3</math>, то есть <math>O(k^3)</math> операций; при использовании метода «Разделяй-и-властвуй» для вычисления собственных значений и векторов аналогичной матрицы в среднем затрачивается <math>O(k^{2})</math> операций.<br />
Таким образом, для выполнения одной итерации метода Ланцоша требуется 1 операция вычисления квадратного корня, <math>n</math> умножений, а также по <math>n ^ 2 + n + 2n + n</math> сложений и умножений, а также суммарно <math>O(k ^ 3)</math> операций, требуемых для поиска собственных значений и собственных векторов матрицы <math>T_{j}</math>. Таким образом последовательная сложность алгоритма Ланцоша составляет <math>O(n ^ 2) + O(k ^ 3)</math>. Это, очевидно, меньше чем <math>O(n^3)</math> операций, требуемых в алгоритмах вычисления всех собственных значений произвольных симметричных матриц, в чём и заключается эффективность применения метода Ланцоша.<br />
<br />
===Информационный граф===<br />
<br />
Информационный граф алгоритма, определяемый в <ref>Параллельные вычисления (Воеводин В.В., Воеводин Вл.В.) - Спб, изд-во "БХВ-Петербург", 2002 ([https://parallel.ru/news/bhv_parallelcomputing.html см.])</ref> требует следующего замечания: подразумевается, что, например, умножение квадратной матрицы на вектор распараллеливается на <math>n</math> потоков, где <math>n</math> - порядок матрицы, а не на <math>n\log{n}</math> потоков, чего можно было бы достичь, используя суммирование сдваиванием. Это допущение аналогично допущению из статьи [https://algowiki-project.org/ru/%D0%A3%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D0%BB%D0%BE%D1%82%D0%BD%D0%BE%D0%B9_%D0%BD%D0%B5%D0%BE%D1%81%D0%BE%D0%B1%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BD%D0%B0_%D0%B2%D0%B5%D0%BA%D1%82%D0%BE%D1%80_(%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82) Умножение матрицы на вектор] и Это же допущение будет использовано и при оценке ресурса параллелизма алгоритма. Приводится граф без отображения входных и выходных данных.<br />
<br />
[[File:Volkov Lanzcos 1.png|600px|thumb|center|Рис. 1: Граф алгоритма для матрицы А порядка 5 и k = 3. Op - одна итерация алгоритма, Eigen - процедура вычисления собственных значений]]<br />
[[File:Volkov Lanzcos 2.png|600px|thumb|center|Рис. 2: Внутренний граф итерации алгоритма для матрицы A порядка 5. Vector sum - нахождение суммы элементов вектора-<b>поэлементного произведения</b> векторов z и qj. Op - вычитания из z векторов qj и q(j - 1), домноженных на коэффициенты. Norm - вычисление нормы вектора z.]]<br />
<br />
===Ресурс параллелизма алгоритма===<br />
Каждая итерация алгоритма Ланцоша выполняется строго последовательно. Однако внутри итерации возможно распараллеливание алгоритма.<br />
<br />
*Умножение квадратной матрицы порядка <math>n</math> на вектор длины <math>n</math> требует последовательного выполнения <math>n</math> ярусов умножений и сложений.<br />
*Все остальные операции в рамках одной итерации, ведущие к вычислению матрицы <math>T_{j}</math> выполняются строго последовательно. При этом вычисление значений векторов может быть выполнено за 1 ярус умножений / сложений, а вычисление чисел - за <math>\log{n}</math> таких операций.<br />
*Ресурс параллелизма алгоритма вычисления собственных значений зависит от используемого алгоритма и рассматривается в соответствующей статье.<br />
<br />
Таким образом, при классификации по ширине ЯПФ алгоритм обладает <i>линейной</i> сложностью. В случае классификации по высоте ЯПФ алгоритм также имеет <i>линейную</i> сложность.<br />
<br />
===Входные и выходные данные алгоритма===<br />
<b>Входные данные: </b> квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math>, число <math>k</math><br/><br />
<b>Объём входных данных:</b> <math>n^2 + n + 1</math><br/><br />
<b>Выходные данные:</b> вектор <math>L</math>, матрица <math>E</math> (элементы <math>e_{pq}</math>)<br/><br />
<b>Объём выходных данных:</b> <math>k + kn</math><br/><br />
<br />
===Свойства алгоритма===<br />
В случае неограниченности ресурсов соотношение последовательной и параллельной сложности алгоритма Ланцоща по сумме всех итераций представляет собой <math>k ^ 3 + kn ^ 2</math> к <math>X + kn</math>, где <math>X</math> - параллельная сложность алгоритма, используемого для вычисления собственных значений. В случае использования метода вычисления собственных значений с линейной сложностью при классисифкации по высоте ЯПФ это соотношение будет равно <math>(k ^ 2 + n ^ 2) / (k + n)</math>. <br />
<br />
Алгоритм Ланцоша не является полностью детерминированным в том смысле, что возможно выполнение числа итераций алгоритма, меньшего, чем заданное (из-за того, что вычислены все ненулевые собственные значения матрицы <math>A</math>). <br />
<br />
Важное свойство метода Ланцоша состоит в том, что первыми в матрице <math>T_{j}</math> появляются собственные значения с максимальной величиной по модулю. Таким образом, метод особенно хорошо подходит для вычисления собственных значений матрицы <math>A</math>, находящихся на краях её спектра.<br />
<br />
==Программная реализация алгоритма==<br />
===Особенности реализации последователього алгоритма===<br />
===Локальность данных и вычислений===<br />
===Возможные способы и особенности параллельной реализации алгоритма===<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций алгоритма Ланцоша без переортогонализации согласно [[Scalability methodology|методике]] AlgoWiki. Исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
Для проверки масштабируемости алгоритма из реализации была исключена непосредственно задача поиска собственных значений матрицы <math>T_{j}</math> на каждой итерации, так как это является предметом отдельных статей ([https://algowiki-project.org/ru/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:F-morozov/%D0%9D%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D1%87%D0%B8%D1%81%D0%B5%D0%BB_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_QR_%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_(4) QR-алгоритм], метод [https://algowiki-project.org/ru/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%C2%AB%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9%C2%BB_%D0%B2%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B9_%D0%B8_%D0%B2%D0%B5%D0%BA%D1%82%D0%BE%D1%80%D0%BE%D0%B2_%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D0%BE%D0%B9_%D1%82%D1%80%D0%B5%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B «Разделяй-и-властвуй»], [https://en.wikipedia.org/wiki/Arnoldi_iteration итерации Арнольди] и.т.д.). Таким образом, проверяемая на масштабируемость задача сводится к итерационному вычислению коэффициентов матрицы <math>T_{j}</math>. Матрица <math>A</math> в рассматриваемой реализации хранится построчно и построчно же разделяется между процессорами. Также в целях исключения влияния недетерминированности алгоритма этот процесс изменен так, что при вычислении всех ненулевых собственных значений матрицы <math>A</math> работа алгоритма продолжается над фиктивными данными, пока не будет завершено заранее заданное число итераций. При этом в реализации используется OpenMP для распараллеливания в пределах одного узла. <br />
<br />
[[File:Ланцош_масштабы.png|1000px|thumb|center|Рис. 3: Производительность алгоритма для указанных значений размера задачи и числа MPI процессов (по 4 OpenMP нити каждый). Потенциальная максимальная производительность, которая может быть достигнута - 13.6 GFLOPS на 1 MPI процесс. Тогда, например, эффективность вычислений на 100 процессах при размере задачи 4000<math>{\cdot}</math>4000 составляет приблизительно 14.33%]]<br />
<br />
Набор изменяемых параметров запуска реализации алгоритма и границы значений параметров алгоритма:<br />
<br />
*Число MPI процессов [1 : 200]<br />
*Число OpenMP нитей [1 : 4] (Запуск с 1 OpenMP нитью производился только для 1 MPI процесса, ускорение замерялось относительно этого запуска).<br />
*Размерность матрицы [400 : 4000]<br />
<br />
Эффективность выполнения реализации алгоритма:<br />
<br />
*Минимальная эффективность - <1% (неактуально, так как в исследовании достигнут предел масштабируемости алгоритма).<br />
*Максимальная эффективность - ~30% (получается на 2-8 процессов при ~640000 ячеек матрицы на процесс; в случае 8 процессов это входная матрица 1600<math>{\cdot}</math>1600). <br />
<br />
Оценка масштабируемости:<br />
<br />
*По числу процессов - при увеличении числа процессов эффективность уменьшается на всей области рассмотренных значений, но не слишком интенсивно. Это связано с небольшим объёмом пересылок данных, реализованных через достаточно эффективные операции редукции.<br />
*По размеру задачи - при увеличении размера задачи эффективность вычислений вначале кратковременно возрастает, но затем начинает относительно равномерно убывать на всей рассматриваемой области.<br />
*По двум направлениям - при рассмотрении увеличения, как вычислительной сложности, так и числа процессов по всей рассмотренной области значений наибольшая эффективность наблюдается при соответствии числа процессов и размера задачи. По этой "диагонали" эффективность вначале кратковременно возрастает, затем начинает убывать.<br />
<br />
Интересно, что для разного числа процессоров положительные скачки производительности (зачастую - довольно заметные) имеют место на разных объёмах входных данных.<br />
<br />
Далее приводятся графики ускорения программы для разных объёмов входных данных. Ускорение считалось относительно варианта с 1 MPI процессом без дальнейшнего распараллеливания посредством OpenMP. На оси абсцисс отмечается число MPI процессов, число OpenMP нитей во всех случаях равно четырём.<br />
<br />
[[File:Speedup_2000.png|600px|thumb|center|Рис. 4: Ускорение для размера задачи 2000<math>{\cdot}</math>2000]]<br />
[[File:Speedup_3000.png|600px|thumb|center|Рис. 5: Ускорение для размера задачи 3000<math>{\cdot}</math>3000]]<br />
[[File:Speedup_4000.png|600px|thumb|center|Рис. 6: Ускорение для размера задачи 4000<math>{\cdot}</math>4000]]<br />
[[File:Speedup total.png|600px|thumb|center|Рис. 6: Сравнение ускорения для различных размеров задачи]]<br />
<br />
[https://github.com/VolkovNikita94/Lanczos Используемая реализация алгоритма]<br />
Реализация алгоритма гибридная, использует как MPI версии 2.2 (использование MPI_IN_PLACE), так и OpenMP. Указанный код компилировался с помощью компилятора IBM (mpixlcxx_r) с опцией -qsmp=omp (она включает в себя оптимизацию -O2). Реализация алгоритма полностью собственная и не использует встроенные библиотеки. Каждая "quadcore node" - это 1 узел системы BG/P, содержащий 4 микропроцессорных ядра IBM PowerPC 450 c суммарной пиковой производительностью 13.6 GFLOPS на узел и 2 GB общей памяти с пропускной способностью 13.6 GB/sec.<br />
<br />
==== Реализация 2 ====<br />
<br />
Для исследования масштабируемости рассматриваемого алгоритма Ланцоша без переортогонализации автором была реализована программа на языке С++ с использованием технологии MPI. <ref>https://goo.gl/pNOFmW или альтернативная ссылка с прикреплённым кодом: https://goo.gl/9AapM6 , по запросу автор реализации готов предоставить желающим доступ к проекту с этим кодом на ресурсе Bitbucket</ref> Дальнейшее исследование было проведено на суперкомпьютере "Ломоносов" Суперкомпьютерного комплекса МГУ.<ref>https://parallel.ru/cluster/lomonosov.html</ref> <br> Важно отметить, что реализованная программа выполняет только первую часть алгоритма - формирование трёхдиагональной матрицы <math >T_j</math>. Вторая часть алгоритма (задача поиска собственных значений матрицы <math >T_j</math>) может быть решена несколькими методами, каждый из которых обладает своей эффективностью распараллеливания и характерными свойствами. Подробно этот вопрос уже обсуждался в статье ранее, поэтому не будем ещё раз заострять на этом внимание и останавливаться.<br />
<br><br><br />
Сборка осуществлялась со следующими параметрами:<br />
* Компилятор intel/15.0.090 <br />
* Openmpi/1.8.4-icc<br />
В серии проведённых расчётов рассматривались следующие числовые значения параметров:<br />
* Размер <math style="vertical-align:0%;>n</math> задачи и, соответственно, матрицы <math style="vertical-align:0%;>A</math>, задействованной в операции умножения на вектор: [2000 : 30000] с шагом 1000<br />
* Число <math style="vertical-align:-20%;>p</math> процессоров: 1, 2, 4, 16, 32, 48, 64, 96, 128, 192, 256<br />
* Число итераций в алгоритме: <math style="vertical-align:0%;>k=350</math><br />
Число <math style="vertical-align:0%;>k</math> взято таким вопреки информации в начале статьи, где говорится, что значение <math style="vertical-align:0%;>k</math> редко превышает <math style="vertical-align:-10%;>10^2</math> (хоть мы и имеем право брать его любым по нашему усмотрению). Это сделано исключительно для лучшей визуализации пиков на графиках в данном разделе и наглядности получаемых результатов, так как при меньших на порядок значениях параметра <math style="vertical-align:0%;>k</math> времена выполнения программ были слишком малы даже при минимальных числах процессоров и максимальных размерах <math style="vertical-align:0%;>n</math> задачи.<br />
<br><br />
На рис. 2 изображён график зависимости времени выполнения программы от числа процессоров и размера <math style="vertical-align:0%;>n</math> задачи. <br><br />
{|align="center"<br />
|-valign="top"<br />
|[[file:Levin_task1_execTIME.png|620px|thumb|center|Рис. 2 График зависимости времени выполнения программы.]]<br />
|[[file:Levin604 task1 result EFFICIENCY.png|630px|thumb|center|Рис. 3 График зависимости эффективности распараллеливания программы.]]<br />
|}<br />
Многочисленные тесты показали при малых количествах ядер уменьшение времени выполнения программы почти в 2 раза при увеличении числа ядер в 2 раза на всём диапазоне размеров матриц, что и отражает сильный излом на первом графике. Изменение значений времени можно легко оценить по предоставленной цветовой шкале.<br />
Был построен график эффективности распараллеливания (parallel efficiency)<ref>https://goo.gl/WCNEjR</ref> в зависимости от числа процессоров и размера задачи, изображённый на рис. 3.<br />
Получены следующие результаты численных экспериментов:<br />
* Средняя эффективность распараллеливания программы составила: 42.5576% ;<br />
* Минимальная эффективность реализации: 0.674% (число процессоров 256 и минимальный размер матрицы 2000);<br />
* Максимальная эффективность реализации: 77.773% (число процессоров 2, размер матрицы 20000).<br />
Нужно понимать, что это лишь экстремумы, соответствующие двум экспериментам, которые сами по себе не дают полной картины и не позволяют однозначно судить о параллельных качествах алгоритма и реализующей его программы при столь большом числе параметров и проводимых расчётов. <br>На основании рис. 3 можно отметить следующие значимые и важные факты:<br />
*Имеется ярко выраженный спад эффективности при минимальном рассматриваемом размере матрицы <math style="vertical-align:0%;>n=2000</math> вдоль всей оси OY, отвечающей за число процессоров;<br />
*Присутствуют два пика эффективности (жёлтого цвета на рис. 3): при числе <math style="vertical-align:-20%;>p</math> процессоров 2 и 128. Затем при дальнейшем увеличении числа процессоров с 128 до 256 наблюдается постепенное уменьшение эффективности реализации. Это связано с увеличением накладных расходов и затрат времени на обмены сообщениями между процессами.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масшабируемости алгоритма была написана реализация[https://github.com/danyanya/ss16-task1] на языке C++ с использованием MPI. Реализация была протестирована на суперкомпьютере Ломоносов[http://parallel.ru/cluster/].<br />
<br />
Были исследованы:<br />
* время выполнения программы в зависимости от размера входных данных (матрицы);<br />
* время выполнения в зависимости от размера параллельных нод.<br />
<br />
Дополнительно было измерено время работы исходя из оптимизационных флагов компилятора GCC [https://gcc.gnu.org]. <br />
<br />
Параметры запуска алгоритма:<br />
* размер матрицы от 20000 до 175000 с шагом 2500;<br />
* количество процессоров от 8 до 128 с шагом 8.<br />
<br />
Программа запускалась на суперкомьютере "Ломоносов" со следующими характеристиками:<br />
* Компилятор GCC 5.2.1;<br />
* Версия MPI 1.8.4;<br />
* Сборка проводилась командой: <pre>mpic++ -std=c++0x -O2 <CPP_FILE> -lm -static-libstdc++</pre><br />
<br />
Полученная эффективность реализации варьируется в пределах от 2% (на маленьких входных данных) до 45% (на больших входных данных и максимальном числе нодов).<br />
<br />
На рисунке 4 представлена эффективность программы при отсутствии флага оптимизации O2. <br />
<br />
[[Файл:Grigorev_lanczos_eff_no_02.png|thumb|center|600px|Рисунок 4. Эффективность параллельной реализации алгоритма Ланцоща на MPI.]]<br />
<br />
На рисунке 5 представлена эффективность алгоритма с оптимизацией компилятора GCC (-O2).<br />
<br />
[[Файл:Grigorev_lanczos_eff_o2.png|thumb|center|600px|Рисунок 5. Эффективность параллельной реализации алгоритма Ланцоща на MPI с использованием оптимизации компилятора -O2.]]<br />
<br />
Как видно из графиков выше, средняя эффективность минимальна при малых входных данных (на размере матрицы в 10000). <br />
В среднем данный показатель держится между 9% и 14%.<br />
<br />
При этом, оптимизация компилятора MPIC++ позволяет получить выигрыш в эффективности более чем в 3 раза (45,5% против 14,7%). Однако, как можно заметить из графиков, при оптимизации алгоритм ведет себя более нестабильно, скорее всего из-за значительной траты времени на работу с памятью (более плавные участки свидетельствует о том, что необходимые данные нашлись в кеше).<br />
<br />
==== Реализация 4 ====<br />
<br />
Для исследования масшабируемости алгоритма была написана реализация[https://github.com/xrstalker/lanczos] на языке C с использованием MPI. Реализация была протестирована на суперкомпьютере Ломоносов[http://parallel.ru/cluster/].<br />
<br />
Сборка осуществлялась со следующими параметрами:<br />
<br />
* gcc-5.2.0<br />
* openmpi-1.8.4<br />
* аргументы компилятора: -std=c11 -Ofast<br />
<br />
Набор и границы значений изменяемых параметров реализации алгоритма: <br />
<br />
* число процессоров [32 : 256] с шагом 16;<br />
* размер матрицы [5000 : 200000] с шагом 5000.<br />
<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации 1.92%;<br />
* максимальная эффективность реализации 59.47%.<br />
<br />
График полученного распределения эффективности:<br />
<br />
[[file:Lanczos Without Orthogonalization Eff2Dist.png|thumb|center|600px|Рисунок 4. Параллельная реализация алгоритма Ланцоша. Распределение производительности.]]<br />
<br />
На следующих рисунках приведены графики производительности и эффективности данной реализации в зависимости от изменяемых параметров запуска.<br />
{|align="center"<br />
|-valign="top"<br />
|[[file:Lanczos Without Orthogonalization Perf2.png|thumb|center|600px|Рисунок 5. Параллельная реализация алгоритма Ланцоша. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
|[[file:Lanczos Without Orthogonalization Eff2.png|thumb|center|600px|Рисунок 6. Параллельная реализация алгоритма Ланцоша. Изменение эффективности в зависимости от числа процессоров и размера матрицы.]]<br />
|}<br />
<br />
Средняя эффективность для матриц с размером больше 25000 составила 52.7%.<br />
<br />
==== Реализация 5 ====<br />
<br />
Для изучения свойств масштабируемости алгоритма Ланцоша без переортогонализации былы исследованы результаты выполненных на с/к Ломоносов вычислений.<br />
<br />
В рамках алгоритма не решается проблема поиска собственных значений, т.к. эта задача является самостоятельной задачей.<br />
<br />
Для компиляции программы использовались компиляторы intel/15.0.090 и Openmpi/1.8.4-icc.<br />
<br />
Для проведения расчетов и получения полноценной картины поведения алгоритма в зависимости от входных данных и числа процессоров, программа была запущена на следующих параметрах:<br />
<br />
* в качестве размера входной матрицы подавались значения в диапазоне [2000:30000] c шагом 2000.<br />
* Число процессоров варьировалось от 1 до 256. <br />
* В качестве числа, отвечающего за количество выполняемых методом итераций бралось значение 100.<br />
<br />
Ниже на рисунке изображен трехмерный график, показывающий зависимость времени выполнения программы от входных данных/<br />
<br />
[[Файл:Lanczos_time_matrix.png|1000px|thumb|center| График зависимости времени выполнения программы от входных данных]]<br />
<br />
Также была исследована эффективность распараллеливания:<br />
* Средняя эффективность выполнения алгоритма составила порядка 12%<br />
* Наивысший показатель равен 43%<br />
* Минимальное значение составило менее 1%<br />
<br />
==== Реализация 6 ====<br />
<br />
Исследование проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster/].<br />
<br />
Исследовалась реализация<ref>http://pastebin.com/3W4uUVGj</ref> на языке C++ с использованием MPI. Компилятор GCC 5.2.1; версия MPI 1.8.4.<br />
<br />
Для компиляции и запуска использовались следующие команды:<br />
<pre>$ mpic++ -std=c++0x -O2 <CPP_FILE> -lm -static-libstdc++ -o <EXE_FILE><br />
$ mpirun -np <N_PROCESSORS> <EXE_FILE><br />
</pre><br />
<br />
* Число процессоров: 2, 4, 8, 16, 32, 64, 128.<br />
* Размерность матрицы <math>N</math> от 5 000 до 50 000, шаг 5 000.<br />
* Количество итераций алгоритма Ланцоша для всех экспериментов 200.<br />
<br />
Результаты:<br />
<br />
{| class="wikitable"<br />
|+ Время выполнения алгоритма<br />
|-<br />
!<br />
! 2<br />
! 4<br />
! 8<br />
! 16 <br />
! 32<br />
! 48<br />
! 64 <br />
! 96<br />
! 128<br />
|-<br />
! 5000<br />
| 1,5243<br />
| 1,0951<br />
| 0,9334<br />
| 0,6631<br />
| 0,7456<br />
| 0,7038<br />
| 0,7989<br />
| 0,9985<br />
| 0,985<br />
|-<br />
! 10 000<br />
| 5,667<br />
| 4,2625<br />
| 2,7502<br />
| 3,6762<br />
| 3,4435<br />
| 3,6192<br />
| 3,5194<br />
| 3,8486<br />
| 3,2956<br />
|-<br />
! 15 000<br />
| 12,5207<br />
| 6,5995<br />
| 7,2897<br />
| 8,5086<br />
| 8,539<br />
| 9,0018<br />
| 8,9427<br />
| 15,0132<br />
| 7,7088<br />
|-<br />
! 20 000<br />
| 23,8143<br />
| 15,851<br />
| 10,6313<br />
| 13,7241<br />
| 12,8051<br />
| 14,3183<br />
| 13,2946<br />
| 14,0864<br />
| 13,7499<br />
|-<br />
! 25 000<br />
| 37,5419<br />
| 17,9436<br />
| 15,5451<br />
| 18,3944<br />
| 19,6076<br />
| 18,5444<br />
| 19,1347<br />
| 21,8366<br />
| 21,4454<br />
|-<br />
! 30 000<br />
| 55,014<br />
| 26,2702<br />
| 22,3943<br />
| 26,4935<br />
| 25,5618<br />
| 29,6784<br />
| 28,0853<br />
| 27,8872<br />
| 31,2415<br />
|-<br />
! 35 000<br />
| 71,3969<br />
| 37,4811<br />
| 30,2274<br />
| 34,0041<br />
| 38,1028<br />
| 38,4912<br />
| 38,0174<br />
| 37,1407<br />
| 40,4463<br />
|-<br />
! 40 000<br />
| 100,326<br />
| 47,4248<br />
| 40,2295<br />
| 44,0332<br />
| 50,0015<br />
| 52,9428<br />
| 46,2961<br />
| 51,4372<br />
| 49,7504<br />
|-<br />
! 45 000<br />
| 142,93<br />
| 66,4829<br />
| 52,1382<br />
| 55,4683<br />
| 62,3042<br />
| 65,0885<br />
| 60,0512<br />
| 57,8891<br />
| 64,3764<br />
|}<br />
<br />
Ниже приведен график зависимости времени выполнения программы от числа процессоров и размера исходной матрицы.<br />
<br />
[[File:Lanzcos.png|600px|thumb|center|Рисунок 2. Время работы программы в зависимости от количества процессоров и размера матрицы.<br/><br />
<br />
<math>Procs</math> — количество процессоров, <br/><br />
<math>Size</math> — размерность матрицы <math>N</math>, <br/><br />
<math>Time</math> — время работы алгоритма в сек. <br/>]]<br />
<br />
===Динамические характеристики и эффективность реализации алгоритма===<br />
===Выводы для классов архитектур===<br />
===Существующие реализации алгоритма===<br />
Подробный анализ алгоритмов нахождения собственных значений, включая алгоритм Ланцоша, был проведен в <ref>V. Hernandez, J. E. Roman, A. Tomas, V. Vidal. A Survey of Software for Sparse Eigenvalue Problems ([http://slepc.upv.es/documentation/reports/str6.pdf см.])</ref>. Существует несколько как последовательных, так и параллельных реализаций алгоритма Ланцоша, включенных в программные библиотеки для поиска собственных значений матриц. Свойства этих реализаций можно увидеть в таблицах ниже. Первая таблица — относительно недавние реализации, вторая - «музейные экспонаты». Следует уделить особенное внимание столбцам «тип метода» и «параллелизация». В первом из них значение только N соответствует описанному варианту без реортогонализации, F – полной реортогонализации, P – частичной реортогонализации, S – выборочной реортогонализации. Во втором значние «none» соответствует отсутствию параллельной реализации, M – параллельной реализации посредством MPI, O – параллельной реализации посредством OpenMP. Их приведение целесообразно, так как на практике алгоритм Ланцоша без переортогонализации неустойчив. Также указываются библиотеки, в которых реализованы более глубокие модификации метода Ланцоша, с указанием изменений в графе «тип метода».<br />
<br />
{| class="wikitable"<br />
|+<b>Таблица 1 - «современные» реализации.</b><br />
!Название библиотеки<br />
!Язык программирования<br />
!Дата появления, версия библиотки<br />
!Тип метода<br />
!Параллелизация<br />
|-<br />
|BLKLAN<br />
|C/Matlab<br />
|2003<br />
|P<br />
|none<br />
|-<br />
|BLZPACK<br />
|F77<br />
|2000, 04/00<br />
|P + S<br />
|M<br />
|-<br />
|IETL<br />
|C++<br />
|2006, 2.2<br />
|N<br />
|none<br />
|-<br />
|SLEPc<br />
|C/F77<br />
|2009, 3.0.0<br />
|All<br />
|M<br />
|-<br />
|TRLAN<br />
|F90<br />
|2006<br />
|Dynamic thick-restart<br />
|M<br />
|-<br />
|PROPACK<br />
|F77/Matlab<br />
|2005, 2.1 / 1.1<br />
|P, finds SVD<br />
|O<br />
|-<br />
|IRBLEIGS<br />
|Matlab<br />
|2000, 1.0<br />
|Indefinitie symmetric<br />
|none<br />
|}<br />
<br />
{| class="wikitable"<br />
|+<b>Таблица 2 - «исторические» реализации.</b><br />
!Название библиотеки<br />
!Язык программирования<br />
!Дата появления, версия библиотки<br />
!Тип метода<br />
!Параллелизация<br />
|-<br />
|ARPACK<br />
|F77<br />
|1995, 2.1<br />
|Arnoldi iterations, impliicit restart<br />
|M<br />
|-<br />
|ARPACK++<br />
|C++<br />
|1998, 1.1<br />
|Arnoldi iterations, impliicit restart<br />
|none<br />
|-<br />
|LANCZOS<br />
|F77<br />
|1992<br />
|N<br />
|none<br />
|-<br />
|LANZ<br />
|F77<br />
|1991, 1.0<br />
|P<br />
|none<br />
|-<br />
|LASO<br />
|F77<br />
|1983, 2<br />
|S<br />
|none<br />
|-<br />
|NAPACK<br />
|F77<br />
|1987<br />
|N<br />
|none<br />
|-<br />
|QMRPACK<br />
|F77<br />
|1996<br />
|Designed for nonsymmetrical matrices (lookahead)<br />
|none<br />
|-<br />
|SVDPACK<br />
|C/F77<br />
|1992<br />
|P, finds SVD<br />
|none<br />
|-<br />
|Underwood<br />
|F77<br />
|1975<br />
|F, block version<br />
|none<br />
|}<br />
<br />
В настоящее время алгоритм Ланцоша поиска собственных значений квадратной симметричной матрицы включён в несколько библиотек и реализован на различных языках, среди которых такие наиболее распространенные как '''C''', '''C++''', '''FORTRAN77/90''', '''MathLab'''. Ссылки на наиболее проверенные и часто используемые реализации алгоритма:<br />
<br />
* Так, например, в языке '''MathLab''' во встроенном пакете '''ARPACK''' найти собственные значения методом Ланцоша можно при помощи вызова функции ''eigs()''. <br />
<br />
* На языке '''С''' существует библиотека '''[http://www.cs.wm.edu/~andreas/software/ PRIMME]''', название которой расшифровывается как PReconditioned Iterative MultiMethod Eigensolver (итерационные методы поиска собственных значений с предусловием).<br />
<br />
* Библиотека '''[http://www.nag.co.uk NAG] Numerical Library''' также содержит обширный набор функций, позволяющих проводить математический анализ, среди которых есть и реализация алгоритма Ланцоша. Библиотека доступна в трех пакетах: NAG C Library, NAG Fortran Library и NAG Library for .NET, совместна со многими языками и с основными операционными системами (Windows, Linux и OS X, а также Solaris, AIX и HP-UX).<br />
<br />
Заслуживают внимания также реализации в проектах IETL<ref>http://www.comp-phys.org/software/ietl/lanczos.html</ref>, ARPACK <ref>http://www.caam.rice.edu/software/ARPACK/</ref> и SLEPc<ref>Hernandez V., Roman J. E., Vidal V. SLEPc: A scalable and flexible toolkit for the solution of eigenvalue problems //ACM Transactions on Mathematical Software (TOMS).– Т. 31. – №. 3. – С. 351-362.</ref>.<br />
<br />
==Литература==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Konshin&diff=25269Обсуждение участника:Konshin2018-01-16T19:08:55Z<p>Konshin: /* Другие (5): */</p>
<hr />
<div>* явных списываний нет ни в одном из алгоритмов!<br />
* правда в алгоритме 19 (Алгоритм k средних) у 3-х из 4-х авторов использована одна и та же открытая реализация<br />
* все работы очень хорошие! только 4 оценки 4, остальные 5<br />
* в принципе для оценки выполнены все работы, хотя в 3-х было бы неплохо дождаться мелкой правки...<br />
<br />
Обозначения:<br />
<br />
* ... - ждем ответа на замечания<br />
* +++ - работа выполнена<br />
* +++!!! - выполнена и является претендентом на внесение в AlgoWiki<br />
* (X)(Y) - оценки по разделам 1 и 2, соответственно<br />
<br />
<br />
=== Алгоритм_4, Алгоритм Ланцоша (итерационный метод вычисления собственных значений симметричной матрицы) для точной арифметики (без переортогонализации) (6): ===<br />
<br />
https://algowiki-project.org/ru/Участник:Alexbashirov/Алгоритм_Ланцоша_для_точной_арифметики<br />
<br />
+++ (4)(4) по сравнению с другими замечательными описаниями и из-за задержки с п.2.4 оценка снижена, хотя формально все сделано, можно поставить и 5-<br />
<br />
https://algowiki-project.org/ru/Участник:A.Freeman/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (5)(5) собственная реализация на 256 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Danyanya/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации) <br />
<br />
+++ (5-)(5-) собственная реализация на 128 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:AleksLevin/Алгоритм_Ланцоша_вычисления_собственных_значений_симметричной_матрицы_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (5)(5) п.3! собственная реализация на 256 проц. Ломоносова; не очень понятно, открыт ли полный доступ к коду...<br />
<br />
https://algowiki-project.org/ru/Участник:VolkovNikita94/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++!!! (5-)(5+) Никита Волков; п.2.7 лучший!; реализация MPI+OpenMP тоже лучшая! должны еще поправить рис.4-6...<br />
<br />
https://algowiki-project.org/ru/Участник:Shostix/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (4)(4) нормально<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
=== Алгоритм_14, Решение системы нелинейных уравнений методом Ньютона (4): ===<br />
<br />
https://algowiki-project.org/ru/Участник:N_Zakharov/Метод_Ньютона_для_решения_систем_нелинейных_уравнений<br />
<br />
+++ (4+)(4+) пример из PETSc до 128 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Oleggium/Метод_Ньютона_для_решения_систем_нелинейных_уравнений(2)<br />
<br />
+++ (5)(5-) красивый граф, самостоятельная реализация на CUDA до 400 потока на персоналке, но бледно описано; все сделано, местами лучшие описания, но несколько лаконично :( <br />
<br />
https://algowiki-project.org/ru/Участник:SKirill/Метод_Ньютона_решения_систем_нелинейных_уравнений<br />
<br />
+++ (5-)(5) необычные и интересные блоксхемы п.1.5! и 1.7!; самостоятельная MPI реализация до 128 нитей; лучший п.2.7!<br />
<br />
[https://algowiki-project.org/ru/Участник:Арутюнов_А.В. https://algowiki-project.org/ru/Участник:Арутюнов_А.В.] (!!! осторожно с точкой в конце адреса !!!)<br />
<br />
+++ (4)(4) нормально<br />
<br />
...В РАБОТЕ: https://algowiki-project.org/ru/Метод_Ньютона_для_систем_нелинейных_уравнений<br />
<br />
=== Алгоритм_19, Алгоритм k средних (4): ===<br />
<br />
https://algowiki-project.org/ru/Участник:IanaV/Алгоритм_k_means<br />
<br />
+++ (5)(5) хороший текст, особенно п.1.10, отличные графы, но витиеватый язык, видимо списано из учебника, чужая прогр. до 512 MPI проц. Ломоносова (там же имеются OpenMP и CUDA версии, но они не исследовались, собственно, как и 2-ми и 3-ми авторами)<br />
<br />
https://algowiki-project.org/ru/Участник:Parkhomenko/Алгоритм_k_средних<br />
<br />
+++ (5-)(5) специфически, но интересно представлены результаты в п.2.4!, дважды переделывали по моей просьбе, молодцы: и чужие и свои прогоны готовой реализации на 512 MPI проц. Blue Gene/P<br />
<br />
https://algowiki-project.org/ru/Участник:Бротиковская_Данута/Алгоритм_k-means<br />
<br />
+++!!! (5)(5) готовая реализация до 512 на Ломоносове, хороший 2.4, лучший 2.7 и 3, написан даже п.2.6!!<br />
<br />
https://algowiki-project.org/ru/Участник:Илья_Егоров/Алгоритм_k-средних<br />
<br />
+++ (5-)(5-) самостоятельная реализация OpenMP до 16 нитей Ломоносова (жаль что считали на головном узле?! поэтому-то и минус); очень красивые графы!<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Алгоритм_k_средних_(k-means)<br />
<br />
=== Другие (5): ===<br />
<br />
https://algowiki-project.org/ru/Участник:Bagnikita/Face_Recognition<br />
<br />
... (5+)(5+) заполнены все! пункты; лучшая работа, несмотря на некоторое смешивание рассмотрения "метода" и используемых в нем "алгоритмов", но по другому здесь сделать и не возможно (ждем мелкой правки...)(давно уже ждем...)(несмотря на ожидание все равно оценка 5+, но в расчете на исправления пока так и не поставил метку "принято")<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Распознование_лиц<br />
<br />
<br />
https://algowiki-project.org/ru/Участник:EasyBreezy/Стабилизированный_метод_биспоряженных_градиентов_(BiCGSTAB)<br />
<br />
... (4)(4) единственное описание на эту важную тему, вопросов много, хотелось бы довести до конца (тогда возможна и оценка 5); реализация HYPRE на 16 проц. Ломоносова (ждем уже давно... кое-что поправлено, но мало... без их помощи труднее будет довести статью до включения в основную вики, пока не ставлю метку "принято")<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Стабилизированный_метод_бисопряженных_градиентов_(BiCGStab)<br />
<br />
<br />
https://algowiki-project.org/ru/Участница:Александра/Метод_встречи_посередине<br />
<br />
+++!!! (5+)(5+) лучшее описание по этой теме; слегка изменила чужую реализацию и запустила на 8 ядрах Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Огнева_Мария/Метод_встречи_посередине <br />
<br />
... (4)(--) нет прогонов для п.2.4, хотя реализация ей и известна... (все-таки оценка 4, особенно по сравнению с другими работами... хотя выполняла она одна, а за первую часть можно было бы поставить и 5-)<br />
<br />
ГОТОВО (Батарина): https://algowiki-project.org/ru/Метод_встречи_посередине<br />
<br />
<br />
https://algowiki-project.org/ru/VladimirDobrovolsky611/Алгоритм_SDDP<br />
<br />
+++ (4)(4) нормально описано, после небольшой правки можно выкладывать. Рисунки не совсем законченные. Собственная реализация автором не выложена.<br />
<br />
ГОТОВО: http://algowiki-project.org/ru/Стохастическое_двойственное_динамическое_программирование_(SDDP)</div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Konshin&diff=25268Обсуждение участника:Konshin2018-01-16T19:05:45Z<p>Konshin: /* Алгоритм_4, Алгоритм Ланцоша (итерационный метод вычисления собственных значений симметричной матрицы) для точной арифметики (без пере…</p>
<hr />
<div>* явных списываний нет ни в одном из алгоритмов!<br />
* правда в алгоритме 19 (Алгоритм k средних) у 3-х из 4-х авторов использована одна и та же открытая реализация<br />
* все работы очень хорошие! только 4 оценки 4, остальные 5<br />
* в принципе для оценки выполнены все работы, хотя в 3-х было бы неплохо дождаться мелкой правки...<br />
<br />
Обозначения:<br />
<br />
* ... - ждем ответа на замечания<br />
* +++ - работа выполнена<br />
* +++!!! - выполнена и является претендентом на внесение в AlgoWiki<br />
* (X)(Y) - оценки по разделам 1 и 2, соответственно<br />
<br />
<br />
=== Алгоритм_4, Алгоритм Ланцоша (итерационный метод вычисления собственных значений симметричной матрицы) для точной арифметики (без переортогонализации) (6): ===<br />
<br />
https://algowiki-project.org/ru/Участник:Alexbashirov/Алгоритм_Ланцоша_для_точной_арифметики<br />
<br />
+++ (4)(4) по сравнению с другими замечательными описаниями и из-за задержки с п.2.4 оценка снижена, хотя формально все сделано, можно поставить и 5-<br />
<br />
https://algowiki-project.org/ru/Участник:A.Freeman/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (5)(5) собственная реализация на 256 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Danyanya/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации) <br />
<br />
+++ (5-)(5-) собственная реализация на 128 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:AleksLevin/Алгоритм_Ланцоша_вычисления_собственных_значений_симметричной_матрицы_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (5)(5) п.3! собственная реализация на 256 проц. Ломоносова; не очень понятно, открыт ли полный доступ к коду...<br />
<br />
https://algowiki-project.org/ru/Участник:VolkovNikita94/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++!!! (5-)(5+) Никита Волков; п.2.7 лучший!; реализация MPI+OpenMP тоже лучшая! должны еще поправить рис.4-6...<br />
<br />
https://algowiki-project.org/ru/Участник:Shostix/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (4)(4) нормально<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
=== Алгоритм_14, Решение системы нелинейных уравнений методом Ньютона (4): ===<br />
<br />
https://algowiki-project.org/ru/Участник:N_Zakharov/Метод_Ньютона_для_решения_систем_нелинейных_уравнений<br />
<br />
+++ (4+)(4+) пример из PETSc до 128 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Oleggium/Метод_Ньютона_для_решения_систем_нелинейных_уравнений(2)<br />
<br />
+++ (5)(5-) красивый граф, самостоятельная реализация на CUDA до 400 потока на персоналке, но бледно описано; все сделано, местами лучшие описания, но несколько лаконично :( <br />
<br />
https://algowiki-project.org/ru/Участник:SKirill/Метод_Ньютона_решения_систем_нелинейных_уравнений<br />
<br />
+++ (5-)(5) необычные и интересные блоксхемы п.1.5! и 1.7!; самостоятельная MPI реализация до 128 нитей; лучший п.2.7!<br />
<br />
[https://algowiki-project.org/ru/Участник:Арутюнов_А.В. https://algowiki-project.org/ru/Участник:Арутюнов_А.В.] (!!! осторожно с точкой в конце адреса !!!)<br />
<br />
+++ (4)(4) нормально<br />
<br />
...В РАБОТЕ: https://algowiki-project.org/ru/Метод_Ньютона_для_систем_нелинейных_уравнений<br />
<br />
=== Алгоритм_19, Алгоритм k средних (4): ===<br />
<br />
https://algowiki-project.org/ru/Участник:IanaV/Алгоритм_k_means<br />
<br />
+++ (5)(5) хороший текст, особенно п.1.10, отличные графы, но витиеватый язык, видимо списано из учебника, чужая прогр. до 512 MPI проц. Ломоносова (там же имеются OpenMP и CUDA версии, но они не исследовались, собственно, как и 2-ми и 3-ми авторами)<br />
<br />
https://algowiki-project.org/ru/Участник:Parkhomenko/Алгоритм_k_средних<br />
<br />
+++ (5-)(5) специфически, но интересно представлены результаты в п.2.4!, дважды переделывали по моей просьбе, молодцы: и чужие и свои прогоны готовой реализации на 512 MPI проц. Blue Gene/P<br />
<br />
https://algowiki-project.org/ru/Участник:Бротиковская_Данута/Алгоритм_k-means<br />
<br />
+++!!! (5)(5) готовая реализация до 512 на Ломоносове, хороший 2.4, лучший 2.7 и 3, написан даже п.2.6!!<br />
<br />
https://algowiki-project.org/ru/Участник:Илья_Егоров/Алгоритм_k-средних<br />
<br />
+++ (5-)(5-) самостоятельная реализация OpenMP до 16 нитей Ломоносова (жаль что считали на головном узле?! поэтому-то и минус); очень красивые графы!<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Алгоритм_k_средних_(k-means)<br />
<br />
=== Другие (5): ===<br />
<br />
https://algowiki-project.org/ru/Участник:Bagnikita/Face_Recognition<br />
<br />
... (5+)(5+) заполнены все! пункты; лучшая работа, несмотря на некоторое смешивание рассмотрения "метода" и используемых в нем "алгоритмов", но по другому здесь сделать и не возможно (ждем мелкой правки...)(давно уже ждем...)(несмотря на ожидание все равно оценка 5+, но в расчете на исправления пока так и не поставил метку "принято")<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Распознование_лиц<br />
<br />
<br />
https://algowiki-project.org/ru/Участник:EasyBreezy/Стабилизированный_метод_биспоряженных_градиентов_(BiCGSTAB)<br />
<br />
... (4)(4) единственное описание на эту важную тему, вопросов много, хотелось бы довести до конца (тогда возможна и оценка 5); реализация HYPRE на 16 проц. Ломоносова (ждем уже давно... кое-что поправлено, но мало... без их помощи труднее будет довести статью до включения в основную вики, пока не ставлю метку "принято")<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Стабилизированный_метод_бисопряженных_градиентов_(BiCGStab)<br />
<br />
<br />
https://algowiki-project.org/ru/Участница:Александра/Метод_встречи_посередине<br />
<br />
+++!!! (5+)(5+) лучшее описание по этой теме; слегка изменила чужую реализацию и запустила на 8 ядрах Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Огнева_Мария/Метод_встречи_посередине <br />
<br />
... (4)(--) нет прогонов для п.2.4, хотя реализация ей и известна... (все-таки оценка 4, особенно по сравнению с другими работами... хотя выполняла она одна, а за первую часть можно было бы поставить и 5-)<br />
<br />
ГОТОВО (Батарина): https://algowiki-project.org/w/ru/index.php?title=Метод_встречи_посередине<br />
<br />
<br />
https://algowiki-project.org/ru/VladimirDobrovolsky611/Алгоритм_SDDP<br />
<br />
+++ (4)(4) нормально описано, после небольшой правки можно выкладывать. Рисунки не совсем законченные. Собственная реализация автором не выложена.<br />
<br />
ГОТОВО: http://algowiki-project.org/ru/Стохастическое_двойственное_динамическое_программирование_(SDDP)</div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Konshin&diff=25267Обсуждение участника:Konshin2018-01-16T18:57:45Z<p>Konshin: /* Алгоритм_14, Решение системы нелинейных уравнений методом Ньютона (4): */</p>
<hr />
<div>* явных списываний нет ни в одном из алгоритмов!<br />
* правда в алгоритме 19 (Алгоритм k средних) у 3-х из 4-х авторов использована одна и та же открытая реализация<br />
* все работы очень хорошие! только 4 оценки 4, остальные 5<br />
* в принципе для оценки выполнены все работы, хотя в 3-х было бы неплохо дождаться мелкой правки...<br />
<br />
Обозначения:<br />
<br />
* ... - ждем ответа на замечания<br />
* +++ - работа выполнена<br />
* +++!!! - выполнена и является претендентом на внесение в AlgoWiki<br />
* (X)(Y) - оценки по разделам 1 и 2, соответственно<br />
<br />
<br />
=== Алгоритм_4, Алгоритм Ланцоша (итерационный метод вычисления собственных значений симметричной матрицы) для точной арифметики (без переортогонализации) (6): ===<br />
<br />
https://algowiki-project.org/ru/Участник:Alexbashirov/Алгоритм_Ланцоша_для_точной_арифметики<br />
<br />
+++ (4)(4) по сравнению с другими замечательными описаниями и из-за задержки с п.2.4 оценка снижена, хотя формально все сделано, можно поставить и 5-<br />
<br />
https://algowiki-project.org/ru/Участник:A.Freeman/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (5)(5) собственная реализация на 256 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Danyanya/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации) <br />
<br />
+++ (5-)(5-) собственная реализация на 128 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:AleksLevin/Алгоритм_Ланцоша_вычисления_собственных_значений_симметричной_матрицы_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (5)(5) п.3! собственная реализация на 256 проц. Ломоносова; не очень понятно, открыт ли полный доступ к коду...<br />
<br />
https://algowiki-project.org/ru/Участник:VolkovNikita94/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++!!! (5-)(5+) Никита Волков; п.2.7 лучший!; реализация MPI+OpenMP тоже лучшая! должны еще поправить рис.4-6...<br />
<br />
https://algowiki-project.org/ru/Участник:Shostix/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (4)(4) нормально<br />
<br />
ГОТОВО: https://algowiki-project.org/w/ru/index.php?title=Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
https://algowiki-project.org/ru/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
=== Алгоритм_14, Решение системы нелинейных уравнений методом Ньютона (4): ===<br />
<br />
https://algowiki-project.org/ru/Участник:N_Zakharov/Метод_Ньютона_для_решения_систем_нелинейных_уравнений<br />
<br />
+++ (4+)(4+) пример из PETSc до 128 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Oleggium/Метод_Ньютона_для_решения_систем_нелинейных_уравнений(2)<br />
<br />
+++ (5)(5-) красивый граф, самостоятельная реализация на CUDA до 400 потока на персоналке, но бледно описано; все сделано, местами лучшие описания, но несколько лаконично :( <br />
<br />
https://algowiki-project.org/ru/Участник:SKirill/Метод_Ньютона_решения_систем_нелинейных_уравнений<br />
<br />
+++ (5-)(5) необычные и интересные блоксхемы п.1.5! и 1.7!; самостоятельная MPI реализация до 128 нитей; лучший п.2.7!<br />
<br />
[https://algowiki-project.org/ru/Участник:Арутюнов_А.В. https://algowiki-project.org/ru/Участник:Арутюнов_А.В.] (!!! осторожно с точкой в конце адреса !!!)<br />
<br />
+++ (4)(4) нормально<br />
<br />
...В РАБОТЕ: https://algowiki-project.org/ru/Метод_Ньютона_для_систем_нелинейных_уравнений<br />
<br />
=== Алгоритм_19, Алгоритм k средних (4): ===<br />
<br />
https://algowiki-project.org/ru/Участник:IanaV/Алгоритм_k_means<br />
<br />
+++ (5)(5) хороший текст, особенно п.1.10, отличные графы, но витиеватый язык, видимо списано из учебника, чужая прогр. до 512 MPI проц. Ломоносова (там же имеются OpenMP и CUDA версии, но они не исследовались, собственно, как и 2-ми и 3-ми авторами)<br />
<br />
https://algowiki-project.org/ru/Участник:Parkhomenko/Алгоритм_k_средних<br />
<br />
+++ (5-)(5) специфически, но интересно представлены результаты в п.2.4!, дважды переделывали по моей просьбе, молодцы: и чужие и свои прогоны готовой реализации на 512 MPI проц. Blue Gene/P<br />
<br />
https://algowiki-project.org/ru/Участник:Бротиковская_Данута/Алгоритм_k-means<br />
<br />
+++!!! (5)(5) готовая реализация до 512 на Ломоносове, хороший 2.4, лучший 2.7 и 3, написан даже п.2.6!!<br />
<br />
https://algowiki-project.org/ru/Участник:Илья_Егоров/Алгоритм_k-средних<br />
<br />
+++ (5-)(5-) самостоятельная реализация OpenMP до 16 нитей Ломоносова (жаль что считали на головном узле?! поэтому-то и минус); очень красивые графы!<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Алгоритм_k_средних_(k-means)<br />
<br />
=== Другие (5): ===<br />
<br />
https://algowiki-project.org/ru/Участник:Bagnikita/Face_Recognition<br />
<br />
... (5+)(5+) заполнены все! пункты; лучшая работа, несмотря на некоторое смешивание рассмотрения "метода" и используемых в нем "алгоритмов", но по другому здесь сделать и не возможно (ждем мелкой правки...)(давно уже ждем...)(несмотря на ожидание все равно оценка 5+, но в расчете на исправления пока так и не поставил метку "принято")<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Распознование_лиц<br />
<br />
<br />
https://algowiki-project.org/ru/Участник:EasyBreezy/Стабилизированный_метод_биспоряженных_градиентов_(BiCGSTAB)<br />
<br />
... (4)(4) единственное описание на эту важную тему, вопросов много, хотелось бы довести до конца (тогда возможна и оценка 5); реализация HYPRE на 16 проц. Ломоносова (ждем уже давно... кое-что поправлено, но мало... без их помощи труднее будет довести статью до включения в основную вики, пока не ставлю метку "принято")<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Стабилизированный_метод_бисопряженных_градиентов_(BiCGStab)<br />
<br />
<br />
https://algowiki-project.org/ru/Участница:Александра/Метод_встречи_посередине<br />
<br />
+++!!! (5+)(5+) лучшее описание по этой теме; слегка изменила чужую реализацию и запустила на 8 ядрах Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Огнева_Мария/Метод_встречи_посередине <br />
<br />
... (4)(--) нет прогонов для п.2.4, хотя реализация ей и известна... (все-таки оценка 4, особенно по сравнению с другими работами... хотя выполняла она одна, а за первую часть можно было бы поставить и 5-)<br />
<br />
ГОТОВО (Батарина): https://algowiki-project.org/w/ru/index.php?title=Метод_встречи_посередине<br />
<br />
<br />
https://algowiki-project.org/ru/VladimirDobrovolsky611/Алгоритм_SDDP<br />
<br />
+++ (4)(4) нормально описано, после небольшой правки можно выкладывать. Рисунки не совсем законченные. Собственная реализация автором не выложена.<br />
<br />
ГОТОВО: http://algowiki-project.org/ru/Стохастическое_двойственное_динамическое_программирование_(SDDP)</div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25266Метод Ньютона для систем нелинейных уравнений2018-01-16T18:55:20Z<p>Konshin: </p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует чья-то среднего качества страница без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<br />
<br />
Основные авторы описания: [[Участник:SKirill|Шохин К.О.]](1.1,1.2,1.4,1.6,1.8,1.10,2.7), [[Участник:Лебедев Артём|Лебедев А.А.]](1.1,1.3,1.5,1.7,1.9,2.7) 2.4.X<br />
<br />
Авторы: Гирняк О.Р., Васильков Д.А. (1.7, 2.4.X)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>max\_iter</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
==== Масштабируемость алгоритма ====<br />
<br />
Так как алгоритм по сути своей является последовательным, то масштабируемость конкретной реализации алгоритма определяется масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также известно начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25265Метод Ньютона для систем нелинейных уравнений2018-01-16T18:52:39Z<p>Konshin: /* Масштабируемость реализации алгоритма */</p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует чья-то среднего качества страница без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<br />
<br />
Основные авторы описания: [[Участник:SKirill|Шохин К.О.]](1.1,1.2,1.4,1.6,1.8,1.10,2.7), [[Участник:Лебедев Артём|Лебедев А.А.]](1.1,1.3,1.5,1.7,1.9,2.7)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>max\_iter</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
==== Масштабируемость алгоритма ====<br />
<br />
Так как алгоритм по сути своей является последовательным, то масштабируемость конкретной реализации алгоритма определяется масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также известно начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 5 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.5 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25264Метод Ньютона для систем нелинейных уравнений2018-01-16T18:51:55Z<p>Konshin: /* Информационный граф */</p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует чья-то среднего качества страница без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<br />
<br />
Основные авторы описания: [[Участник:SKirill|Шохин К.О.]](1.1,1.2,1.4,1.6,1.8,1.10,2.7), [[Участник:Лебедев Артём|Лебедев А.А.]](1.1,1.3,1.5,1.7,1.9,2.7)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>max\_iter</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
Информационный граф метода Ньютона может быть также представлен в виде следующего рисунка:<br />
<br />
[[Файл:Graph_newton004.png|1000px|Рис.4 Информационный граф алгоритма.|мини|центр]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
==== Масштабируемость алгоритма ====<br />
<br />
Так как алгоритм по сути своей является последовательным, то масштабируемость конкретной реализации алгоритма определяется масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также известно начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 3 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.3 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25263Метод Ньютона для систем нелинейных уравнений2018-01-16T18:48:23Z<p>Konshin: </p>
<hr />
<div>...пока в процессе работы (Игорь Коньшин)... (используются описания студентов и 4 их реализации)<br />
<br />
??? существует чья-то среднего качества страница без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<br />
<br />
Основные авторы описания: [[Участник:SKirill|Шохин К.О.]](1.1,1.2,1.4,1.6,1.8,1.10,2.7), [[Участник:Лебедев Артём|Лебедев А.А.]](1.1,1.3,1.5,1.7,1.9,2.7)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>max\_iter</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
==== Масштабируемость алгоритма ====<br />
<br />
Так как алгоритм по сути своей является последовательным, то масштабируемость конкретной реализации алгоритма определяется масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также известно начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 3 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.3 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25262Метод Ньютона для систем нелинейных уравнений2018-01-16T18:44:52Z<p>Konshin: </p>
<hr />
<div>В процессе работы (Игорь Коньшин)... (описания студентов и 4 реализации)<br />
<br />
??? существует чья-то среднего качества страница без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<br />
<br />
Основные авторы описания: [[Участник:SKirill|Шохин К.О.]](1.1,1.2,1.4,1.6,1.8,1.10,2.7), [[Участник:Лебедев Артём|Лебедев А.А.]](1.1,1.3,1.5,1.7,1.9,2.7)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>max\_iter</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(1)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(1)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(1)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(1)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
==== Масштабируемость алгоритма ====<br />
<br />
Так как алгоритм по сути своей является последовательным, то масштабируемость конкретной реализации алгоритма определяется масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad (2)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также известно начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 3 показаны результаты измерений времени решения системы <math>(2)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.3 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25261Метод Ньютона для систем нелинейных уравнений2018-01-16T18:43:19Z<p>Konshin: /* Вычислительное ядро алгоритма */</p>
<hr />
<div>В процессе работы (Игорь Коньшин)... (описания студентов и 4 реализации)<br />
<br />
??? существует чья-то среднего качества страница без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<br />
<br />
Основные авторы описания: [[Участник:SKirill|Шохин К.О.]](1.1,1.2,1.4,1.6,1.8,1.10,2.7), [[Участник:Лебедев Артём|Лебедев А.А.]](1.1,1.3,1.5,1.7,1.9,2.7)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>max\_iter</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad (1)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(*)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(*)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(*)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(*)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
==== Масштабируемость алгоритма ====<br />
<br />
Так как алгоритм по сути своей является последовательным, то масштабируемость конкретной реализации алгоритма определяется масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad(**)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также известно начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 3 показаны результаты измерений времени решения системы <math>(**)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.3 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25260Метод Ньютона для систем нелинейных уравнений2018-01-16T18:42:12Z<p>Konshin: /* Математическое описание алгоритма */</p>
<hr />
<div>В процессе работы (Игорь Коньшин)... (описания студентов и 4 реализации)<br />
<br />
??? существует чья-то среднего качества страница без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<br />
<br />
Основные авторы описания: [[Участник:SKirill|Шохин К.О.]](1.1,1.2,1.4,1.6,1.8,1.10,2.7), [[Участник:Лебедев Артём|Лебедев А.А.]](1.1,1.3,1.5,1.7,1.9,2.7)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>max\_iter</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : {\mathbb R}^n \to {\mathbb R}, \ i = 1,\ldots,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset {\mathbb R}^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad(*)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(*)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(*)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(*)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(*)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
==== Масштабируемость алгоритма ====<br />
<br />
Так как алгоритм по сути своей является последовательным, то масштабируемость конкретной реализации алгоритма определяется масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad(**)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также известно начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 3 показаны результаты измерений времени решения системы <math>(**)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.3 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25259Метод Ньютона для систем нелинейных уравнений2018-01-16T18:40:56Z<p>Konshin: /* Общее описание алгоритма */</p>
<hr />
<div>В процессе работы (Игорь Коньшин)... (описания студентов и 4 реализации)<br />
<br />
??? существует чья-то среднего качества страница без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<br />
<br />
Основные авторы описания: [[Участник:SKirill|Шохин К.О.]](1.1,1.2,1.4,1.6,1.8,1.10,2.7), [[Участник:Лебедев Артём|Лебедев А.А.]](1.1,1.3,1.5,1.7,1.9,2.7)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>max\_iter</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : {\mathbb R}^1 \to {\mathbb R}^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^\prime(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^\prime (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^\prime(x_k)}^{-1}F(x_k), \ k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : \R^n \to \R, i = 1, \ldots ,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset \R^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad(*)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(*)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(*)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(*)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(*)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
==== Масштабируемость алгоритма ====<br />
<br />
Так как алгоритм по сути своей является последовательным, то масштабируемость конкретной реализации алгоритма определяется масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad(**)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также известно начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 3 показаны результаты измерений времени решения системы <math>(**)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.3 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25258Метод Ньютона для систем нелинейных уравнений2018-01-16T18:38:51Z<p>Konshin: </p>
<hr />
<div>В процессе работы (Игорь Коньшин)... (описания студентов и 4 реализации)<br />
<br />
??? существует чья-то среднего качества страница без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона<br />
<br />
<br />
<br />
Основные авторы описания: [[Участник:SKirill|Шохин К.О.]](1.1,1.2,1.4,1.6,1.8,1.10,2.7), [[Участник:Лебедев Артём|Лебедев А.А.]](1.1,1.3,1.5,1.7,1.9,2.7)<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>max\_iter</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : \R^1 \to \R^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^'(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^' (x_0 )h =0</math>.<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^'(x_k)}^{-1}F(x_k) , k = 0,1,\ldots </math> .<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : \R^n \to \R, i = 1, \ldots ,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой области <math>G \subset \R^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad(*)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(*)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. На рис 1. представлена блок-схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Блок-схема последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(*)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рис. 2 изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма, блок-схема итерации алгоритма представлена на рис. 3. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
[[file:NewtoneIteration.png|thumb|center|700px|Рис.3 Блок-схема итерации алгоритма]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(*)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(*)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
==== Масштабируемость алгоритма ====<br />
<br />
Так как алгоритм по сути своей является последовательным, то масштабируемость конкретной реализации алгоритма определяется масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad(**)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также известно начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 3 показаны результаты измерений времени решения системы <math>(**)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.3 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался оптимизационный ключ -O2, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25257Метод Ньютона для систем нелинейных уравнений2018-01-16T18:25:23Z<p>Konshin: </p>
<hr />
<div>В процессе работы (Игорь Коньшин)... (описания студентов и 4 реализации)<br />
<br />
??? существует чья-то среднего качества страница без реализаций :::<br />
https://algowiki-project.org/ru/Метод_Ньютона</div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=25256Метод Ньютона для систем нелинейных уравнений2018-01-16T18:23:46Z<p>Konshin: Новая страница: «В процессе работы (Игорь Коньшин)... (описания студентов и 4 реализации) ??? существует чья-…»</p>
<hr />
<div>В процессе работы (Игорь Коньшин)... (описания студентов и 4 реализации)<br />
<br />
??? существует чья-то страница<br />
https://algowiki-project.org/w/ru/index.php?title=Метод_Ньютона</div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9A%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D1%8F_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%BE%D0%B2&diff=25255Классификация алгоритмов2018-01-16T18:10:38Z<p>Konshin: </p>
<hr />
<div># <div id="Векторные операции">'''Векторные операции'''</div><br />
## {{level|Суммирование сдваиванием}}<br />
### {{level|Нахождение суммы элементов массива сдваиванием}}<br />
### {{level|Нахождение частных сумм элементов массива сдваиванием}}<br />
## {{level|Равномерная норма вектора, вещественная версия, последовательно-параллельный вариант}}<br />
## {{level|Скалярное произведение векторов, вещественная версия, последовательно-параллельный вариант}}<br />
## {{level|Последовательно-параллельный метод суммирования}}<br />
# <div id="Матрично-векторные операции">'''Матрично-векторные операции'''</div><br />
## {{level|Умножение плотной матрицы на вектор}}<br />
### {{level|Умножение плотной неособенной матрицы на вектор (последовательный вещественный вариант)}}<br />
# <div id="Матричные операции">'''Матричные операции'''</div><br />
## {{level|Умножение плотных матриц}}<br />
### {{level|Перемножение плотных неособенных матриц (последовательный вещественный вариант)}}<br />
### {{level|Метод Штрассена}}<br />
# {{level|Разложения матриц}}<br />
## {{level|Треугольные разложения}}<br />
### {{level|Метод Гаусса (нахождение LU-разложения)}}<br />
#### {{level|LU-разложение методом Гаусса без перестановок}}<br />
##### {{level|LU-разложение методом Гаусса}}<br />
##### {{level|Компактная схема метода Гаусса и её модификации}}<br />
###### {{level|Компактная схема метода Гаусса для плотной матрицы}}<br />
###### {{level|Компактная схема метода Гаусса для трёхдиагональной матрицы и её модификации}}<br />
####### {{level|Компактная схема метода Гаусса для трёхдиагональной матрицы, последовательный вариант}}<br />
####### {{level|Алгоритм сдваивания Стоуна для LU-разложения трёхдиагональной матрицы}}<br />
####### {{level|Последовательно-параллельный алгоритм для LU-разложения трёхдиагональной матрицы}}<br />
#### {{level|LU-разложение методом Гаусса с перестановками}}<br />
##### {{level|LU-разложение методом Гаусса с выбором ведущего элемента по столбцу}}<br />
##### {{level|LU-разложение методом Гаусса с выбором ведущего элемента по строке}}<br />
##### {{level|LU-разложение методом Гаусса с выбором ведущего элемента по главной диагонали}}<br />
##### {{level|LU-разложение методом Гаусса с выбором ведущего элемента по всей матрице}}<br />
### {{level|Метод Холецкого (нахождение симметричного треугольного разложения)}}<br />
#### {{level|Разложение Холецкого (метод квадратного корня)}} базовый точечный вещественный вариант для плотной симметричной положительно-определённой матрицы<br />
### {{level|Известные треугольные разложения для матриц специального вида}}<br />
## {{level|Унитарно-треугольные разложения}}<br />
### {{level|QR-разложения плотных неособенных матриц}}<br />
#### {{level|Метод Гивенса (вращений) QR-разложения матрицы}}<br />
##### {{level|Метод Гивенса (вращений) QR-разложения квадратной матрицы (вещественный точечный вариант)}}<br />
#### {{level|Метод Хаусхолдера (отражений) QR-разложения матрицы}}<br />
##### {{level|Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный точечный вариант}}<br />
#### {{level|Метод ортогонализации}}<br />
##### {{level|Классический метод ортогонализации}}<br />
##### {{level|Метод ортогонализации с переортогонализацией}}<br />
#### {{level|Метод треугольного разложения матрицы Грама}}<br />
### {{level|Методы QR-разложения плотных хессенберговых матриц}}<br />
#### {{level|Метод Гивенса (вращений) QR-разложения хессенберговой матрицы (вещественный вариант)}}<br />
#### {{level|Метод Хаусхолдера (отражений) QR-разложения хессенберговой матрицы (вещественный вариант)}}<br />
## {{level|Подобные разложения}}<br />
### {{level|Подобные разложения на унитарные и хессенберговы матрицы}}<br />
#### {{level|Метод Хаусхолдера (отражений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
##### {{level|Классический точечный метод Хаусхолдера (отражений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
#### {{level|Метод Гивенса (вращений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
##### {{level|Классический точечный метод Гивенса (вращений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
### {{level|Симметричные разложения на унитарные и трёхдиагональные матрицы}}<br />
#### {{level|Метод Хаусхолдера (отражений) приведения к трёхдиагональному виду}}<br />
##### {{level|Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду}}<br />
##### {{level|Метод Хаусхолдера (отражений) для приведения комплексных эрмитовых матриц к трёхдиагональному симметричному виду}}<br />
#### {{level|Метод Гивенса (вращений) приведения матрицы к трёхдиагональной форме}}<br />
### {{level|Спектральное разложение (нахождение собственных значений и векторов)}}<br />
## {{level|Неподобные унитарные разложения}}<br />
### {{level|Неподобные разложения на унитарные и двухдиагональные матрицы}}<br />
#### {{level|Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме}}<br />
#### {{level|Метод Гивенса (вращений) приведения матрицы к двухдиагональной форме}}<br />
### {{level|Разложения на унитарные и диагональные матрицы}}<br />
#### {{level|Сингулярное разложение (нахождение сингулярных значений и векторов)}}<br />
##### {{level|Методы нахождения сингулярных чисел двухдиагональных матриц}}<br />
###### {{level|Алгоритм dqds нахождения сингулярных чисел двухдиагональной матрицы}}<br />
####### {{level|Итерация алгоритма dqds}}<br />
# {{level|Решение систем линейных уравнений}}<br />
## {{level|Прямые методы решения СЛАУ}}<br />
### {{level|Linpack benchmark}}<br />
### {{level|Методы решения СЛАУ с матрицами специального вида}}<br />
#### {{level|Методы решения СЛАУ с треугольными матрицами}}<br />
##### {{level|Прямая подстановка (вещественный вариант)|Прямая подстановка}}<br />
##### {{level|Обратная подстановка (вещественный вариант)|Обратная подстановка}}<br />
##### {{level|Методы решения СЛАУ с двудиагональными матрицами}}<br />
###### {{level|Прямая и обратная подстановка в СЛАУ с двухдиагональной матрицей}}<br />
###### {{level|Метод сдваивания Стоуна для решения двудиагональных СЛАУ}}<br />
###### {{level|Последовательно-параллельный вариант обратной подстановки}}<br />
#### {{level|Методы решения СЛАУ с трёхдиагональными матрицами}}<br />
##### {{level|Методы, основанные на стандартном LU-разложении матрицы}}<br />
###### {{level|Прогонка}}<br />
####### {{level|Прогонка, точечный вариант}}<br />
####### {{level|Классическая монотонная прогонка, повторный вариант}}<br />
###### {{level|Метод сдваивания Стоуна}}<br />
####### {{level|Алгоритм сдваивания Стоуна для LU-разложения трёхдиагональной матрицы}}<br />
####### {{level|Метод сдваивания Стоуна для решения двудиагональных СЛАУ}}<br />
###### {{level|Последовательно-параллельный вариант решения трёхдиагональной СЛАУ с LU-разложением и обратными подстановками}}<br />
##### Другие методы<br />
###### {{level|Метод редукции}}<br />
####### {{level|Полный метод редукции}}<br />
####### {{level|Повторный метод редукции для новой правой части}}<br />
###### {{level|Встречная прогонка}}<br />
####### {{level|Встречная прогонка, точечный вариант}}<br />
####### {{level|Повторная встречная прогонка, точечный вариант}}<br />
###### {{level|Метод циклической редукции}}<br />
####### {{level|Полный метод циклической редукции}}<br />
####### {{level|Повторный метод циклической редукции для новой правой части}}<br />
###### {{level|Метод окаймления}}<br />
#### Методы решения СЛАУ с блочно-треугольными матрицами<br />
##### {{level|Блочная прямая подстановка (вещественный вариант)|Блочная прямая подстановка}}<br />
##### {{level|Блочная обратная подстановка (вещественный вариант)|Блочная обратная подстановка}}<br />
##### Методы решения СЛАУ с блочно-двухдиагональными матрицами<br />
###### {{level|Прямая и обратная подстановка в СЛАУ с блочно-двухдиагональной матрицей}}<br />
###### {{level|Метод сдваивания Стоуна для решения блочно-двухдиагональных СЛАУ}}<br />
###### {{level|Блочный последовательно-параллельный вариант обратной подстановки для решения блочно-двухдиагональных СЛАУ}}<br />
#### {{level|Методы решения СЛАУ с блочно-трёхдиагональными матрицами}}<br />
##### Методы, основанные на стандартном LU-разложении матрицы<br />
###### {{level|Блочная прогонка}}<br />
###### {{level|Блочный последовательно-параллельный вариант решения с LU-разложением и обратными подстановками}}<br />
##### Другие методы<br />
###### {{level|Встречная прогонка, блочный вариант}}<br />
###### {{level|Блочный метод циклической редукции}}<br />
###### {{level|Блочный метод окаймления}}<br />
### {{level|Решения СЛАУ с матрицами специального вида, имеющими известные обратные матрицы}}<br />
## Итерационные методы решения СЛАУ<br />
### {{level|High Performance Conjugate Gradient (HPCG) benchmark}}<br />
### {{level|Стабилизированный метод бисопряженных градиентов (BiCGStab)}}<br />
### {{level|Алгоритм_Качмажа}}<br />
# {{level|Решение систем нелинейных уравнений}}<br />
## {{level|Метод Ньютона для систем нелинейных уравнений}}<br />
# <div id="Решения спектральных задач">'''Решения спектральных задач'''</div><br />
## {{level|Спектральное разложение (нахождение собственных значений и векторов)}}<br />
### {{level|QR-алгоритм}}<br />
#### {{level|QR-алгоритм, используемый в SCALAPACK}}<br />
##### {{level|Классический точечный метод Хаусхолдера (отражений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
##### {{level|QR-алгоритм для хессенберговой матрицы, используемый в SCALAPACK}}<br />
#### {{level|QR-алгоритм для симметричных матриц, используемый в SCALAPACK}}<br />
##### {{level|Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду}}<br />
##### {{level|QR-алгоритм для симметричных трёхдиагональных матриц, используемый в SCALAPACK}}<br />
#### {{level|QR-алгоритм для комплексных эрмитовых матриц, используемый в SCALAPACK}}<br />
##### {{level|Метод Хаусхолдера (отражений) для приведения комплексных эрмитовых матриц к трёхдиагональному симметричному виду}}<br />
##### {{level|QR-алгоритм для симметричных трёхдиагональных матриц, используемый в SCALAPACK}}<br />
### {{level|Метод Якоби (вращений) для решения спектральной задачи у симметричных матриц}}<br />
#### {{level|Классический метод Якоби (вращений) для симметричных матриц с выбором по всей матрице}}<br />
#### {{level|Метод Якоби (вращений) для симметричных матриц с циклическим исключением}}<br />
#### {{level|Метод Якоби (вращений) для симметричных матриц с циклическим исключением и барьерами}}<br />
### {{level|Метод Ланцоша}}<br />
#### {{level|Алгоритм Ланцоша для точной арифметики (без переортогонализации)}}<br />
## {{level|Частичная спектральная задача}}<br />
### {{level|Метод бисекций}}<br />
## {{level|Сингулярное разложение (нахождение сингулярных значений и векторов)}}<br />
### {{level|Метод Якоби (вращений) для нахождения сингулярных значений неособенных матриц}}<br />
#### {{level|Метод Якоби (вращений) для нахождения сингулярных значений с циклическим перебором}}<br />
#### {{level|Метод Якоби для нахождения сингулярных значений со специальным подбором вращений}}<br />
### {{level|QR-алгоритм в приложении к сингулярному разложению}}<br />
# <div id="Тесты производительности компьютеров">'''Тесты производительности компьютеров'''</div><br />
## {{level|High Performance Conjugate Gradient (HPCG) benchmark}}<br />
## {{level|Linpack benchmark}}<br />
# {{level|Преобразование Фурье}}<br />
## {{level|Быстрое преобразование Фурье}}<br />
### {{level|Быстрое преобразование Фурье для степеней двойки}}<br />
# <div id="Алгебра многочленов">'''Алгебра многочленов'''</div><br />
## {{level|Схема Горнера, вещественная версия, последовательный вариант}}<br />
# <div id="Численные методы интегрирования">'''Численные методы интегрирования'''</div><br />
## {{level|Квадратурные формулы}}<br />
## {{level|Квадратурные (кубатурные) методы численного интегрирования по отрезку (многомерному кубу)}}<br />
### [[Квадратурные_(кубатурные)_методы_численного_интегрирования_по_отрезку_(многомерному_кубу)#Метод прямоугольников|Метод прямоугольников]]<br />
### [[Квадратурные_(кубатурные)_методы_численного_интегрирования_по_отрезку_(многомерному_кубу)#Метод трапеций|Метод трапеций]]<br />
### [[Квадратурные_(кубатурные)_методы_численного_интегрирования_по_отрезку_(многомерному_кубу)#Метод парабол (метод Симпсона)|Метод парабол (метод Симпсона)]]<br />
### [[Квадратурные_(кубатурные)_методы_численного_интегрирования_по_отрезку_(многомерному_кубу)#Метод Гаусса|Метод Гаусса]]<br />
# <div id="Алгоритмы на графах">'''Алгоритмы на графах'''</div><br />
## Обход графа<br />
### {{level|Поиск в ширину (BFS)}}<br />
### {{level|Поиск в глубину (DFS)}}<br />
## {{level|Поиск кратчайшего пути от одной вершины (SSSP)}}<br />
### {{level|Поиск в ширину (BFS)}} (для невзвешенных графов)<br />
### {{level|Алгоритм Дейкстры}}<br />
### {{level|Алгоритм Беллмана-Форда}}<br />
### {{level|Алгоритм Δ-шагания}}<br />
## {{level|Поиск кратчайшего пути для всех пар вершин (APSP)}}<br />
### {{level|Алгоритм Джонсона}}<br />
### {{level|Алгоритм Флойда-Уоршелла}}<br />
## {{level|Поиск транзитивного замыкания орграфа}}<br />
### {{level|Алгоритм Пурдома}}<br />
## {{level|Определение диаметра графа}}<br />
## {{level|Построение минимального остовного дерева (MST)}}<br />
### {{level|Алгоритм Борувки}}<br />
### {{level|Алгоритм Крускала}}<br />
### {{level|Алгоритм Прима}}<br />
### {{level|Алгоритм GHS}}<br />
## {{level|Поиск изоморфных подграфов}}<br />
### {{level|Алгоритм Ульмана}}<br />
### {{level|Алгоритм VF2}}<br />
## {{level|Связность в графах}}<br />
### {{level|Алгоритм Шилоаха-Вишкина поиска компонент связности}}<br />
### {{level|Система непересекающихся множеств}}<br />
### {{level|Алгоритм Тарьяна поиска компонент сильной связности}}<br />
### {{level|Алгоритм DCSC поиска компонент сильной связности}}<br />
### {{level|Алгоритм Тарьяна поиска компонент двусвязности}}<br />
### {{level|Алгоритм Тарьяна-Вишкина поиска компонент двусвязности}}<br />
### {{level|Алгоритм Тарьяна поиска «мостов» в графе}}<br />
### {{level|Определение вершинной связности графа}}<br />
### {{level|Алгоритм Габова определения рёберной связности графа}}<br />
## {{level|Поиск максимального потока в транспортной сети}}<br />
### {{level|Алгоритм Форда-Фалкерсона}}<br />
### {{level|Алгоритм проталкивания предпотока}}<br />
## {{level|Поиск потока минимальной стоимости в транспортной сети}}<br />
## {{level|Задача о назначениях}}<br />
### {{level|Венгерский алгоритм}}<br />
### {{level|Алгоритм аукциона}}<br />
### {{level|Алгоритм Гопкрофта-Карпа}}<br />
## {{level|Вычисление betweenness centrality|Вычисление центральности вершин}}<br />
# <div id="Алгоритмы поиска">'''Алгоритмы поиска'''</div><br />
## {{level|Линейный поиск - находит элемент в любом списке|Линейный поиск}}, <math>O(n)</math><br />
## {{level|Двоичный поиск - находит элемент в отсортированном списке|Двоичный поиск}}, <math>O(\log(n))</math><br />
# <div id="Алгоритмы сортировки">'''Алгоритмы сортировки'''</div><br />
## {{level|Сортировка с помощью двоичного дерева}}<br />
## {{level|Сортировка пузырьком}}<br />
## {{level|Сортировка слиянием (последовательный и параллельный варианты)}}<br />
# <div id="Вычислительная геометрия">'''Вычислительная геометрия'''</div><br />
## {{level|Поиск диаметра множества точек}}<br />
## {{level|Построение выпуклой оболочки набора точек}}<br />
## {{level|Триангуляция Делоне}}<br />
## {{level|Диаграмма Вороного}}<br />
## {{level|Принадлежность точки многоугольнику}}<br />
## {{level|Пересечения выпуклых многоугольников}} - трудоёмкость <math>O(n_1 + n_2)</math><br />
## {{level|Пересечение звёздных многоугольников}} - трудоёмкость <math>O(n_1 \cdot n_2)</math><br />
# <div id="Компьютерная графика">'''Компьютерная графика'''</div><br />
## {{level|Алгоритмы построения отрезка - алгоритмы для аппроксимации отрезка на дискретной графической поверхности}}<br />
## {{level|Алгоритм определения видимых частей трёхмерной сцены}}<br />
## {{level|Трассировка лучей - рендеринг реалистичных изображений}}<br />
## {{level|Глобальное освещение - рассматривает прямое освещение и отражение от других объектов}}<br />
# <div id="Криптографические алгоритмы">'''Криптографические алгоритмы'''</div><br />
## {{level|Метод встречи посередине}}<br />
# <div id="Нейронные сети">'''Нейронные сети'''</div><br />
## {{level|Распознование образов}}<br />
### {{level|Распознование текста}}<br />
### {{level|Распознование речи}}<br />
### {{level|Распознование лиц}}<br />
# <div id="Алгоритмы оптимизации">'''Алгоритмы оптимизации'''</div><br />
## {{level|Линейное программирование}}<br />
## {{level|Симплекс-метод}}<br />
## {{level|Метод ветвей и границ}}<br />
## {{level|Генетические алгоритмы}}<br />
## {{level|Муравьиные алгоритмы}}<br />
## {{level|Комбинированные алгоритмы}}<br />
## {{level|Стохастическое двойственное динамическое программирование (SDDP)}}<br />
# <div id="Алгоритмы машинного обучения">'''Алгоритмы машинного обучения'''</div><br />
## {{level|Алгоритм k средних (k-means)}}<br />
# <div id="Алгоритмы теории игр">'''Алгоритмы теории игр'''</div><br />
# <div id="Алгоритмы моделирования квантовых систем">'''Алгоритмы моделирования квантовых систем'''</div><br />
## ''Алгоритмы моделирования квантовых вычислений''<br />
### {{level|Однокубитное преобразование вектора-состояния}}<br />
### {{level|Двухкубитное преобразование вектора-состояния}}<br />
### {{level|Моделирование квантового преобразования Фурье}}<br />
# <div id="Алгоритмы решения уравнений математической физики">'''Алгоритмы решения уравнений математической физики'''</div><br />
## {{level|Уравнение Пуассона, решение дискретным преобразованием Фурье}}<br />
# <div id="Другие алгоритмы">'''Другие алгоритмы'''</div><br />
<br />
[[en:Algorithm classification]]<br />
<br />
[[Категория:Алгоритмы|*]]</div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9A%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D1%8F_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%BE%D0%B2&diff=25254Классификация алгоритмов2018-01-16T18:00:32Z<p>Konshin: </p>
<hr />
<div># <div id="Векторные операции">'''Векторные операции'''</div><br />
## {{level|Суммирование сдваиванием}}<br />
### {{level|Нахождение суммы элементов массива сдваиванием}}<br />
### {{level|Нахождение частных сумм элементов массива сдваиванием}}<br />
## {{level|Равномерная норма вектора, вещественная версия, последовательно-параллельный вариант}}<br />
## {{level|Скалярное произведение векторов, вещественная версия, последовательно-параллельный вариант}}<br />
## {{level|Последовательно-параллельный метод суммирования}}<br />
# <div id="Матрично-векторные операции">'''Матрично-векторные операции'''</div><br />
## {{level|Умножение плотной матрицы на вектор}}<br />
### {{level|Умножение плотной неособенной матрицы на вектор (последовательный вещественный вариант)}}<br />
# <div id="Матричные операции">'''Матричные операции'''</div><br />
## {{level|Умножение плотных матриц}}<br />
### {{level|Перемножение плотных неособенных матриц (последовательный вещественный вариант)}}<br />
### {{level|Метод Штрассена}}<br />
# {{level|Разложения матриц}}<br />
## {{level|Треугольные разложения}}<br />
### {{level|Метод Гаусса (нахождение LU-разложения)}}<br />
#### {{level|LU-разложение методом Гаусса без перестановок}}<br />
##### {{level|LU-разложение методом Гаусса}}<br />
##### {{level|Компактная схема метода Гаусса и её модификации}}<br />
###### {{level|Компактная схема метода Гаусса для плотной матрицы}}<br />
###### {{level|Компактная схема метода Гаусса для трёхдиагональной матрицы и её модификации}}<br />
####### {{level|Компактная схема метода Гаусса для трёхдиагональной матрицы, последовательный вариант}}<br />
####### {{level|Алгоритм сдваивания Стоуна для LU-разложения трёхдиагональной матрицы}}<br />
####### {{level|Последовательно-параллельный алгоритм для LU-разложения трёхдиагональной матрицы}}<br />
#### {{level|LU-разложение методом Гаусса с перестановками}}<br />
##### {{level|LU-разложение методом Гаусса с выбором ведущего элемента по столбцу}}<br />
##### {{level|LU-разложение методом Гаусса с выбором ведущего элемента по строке}}<br />
##### {{level|LU-разложение методом Гаусса с выбором ведущего элемента по главной диагонали}}<br />
##### {{level|LU-разложение методом Гаусса с выбором ведущего элемента по всей матрице}}<br />
### {{level|Метод Холецкого (нахождение симметричного треугольного разложения)}}<br />
#### {{level|Разложение Холецкого (метод квадратного корня)}} базовый точечный вещественный вариант для плотной симметричной положительно-определённой матрицы<br />
### {{level|Известные треугольные разложения для матриц специального вида}}<br />
## {{level|Унитарно-треугольные разложения}}<br />
### {{level|QR-разложения плотных неособенных матриц}}<br />
#### {{level|Метод Гивенса (вращений) QR-разложения матрицы}}<br />
##### {{level|Метод Гивенса (вращений) QR-разложения квадратной матрицы (вещественный точечный вариант)}}<br />
#### {{level|Метод Хаусхолдера (отражений) QR-разложения матрицы}}<br />
##### {{level|Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный точечный вариант}}<br />
#### {{level|Метод ортогонализации}}<br />
##### {{level|Классический метод ортогонализации}}<br />
##### {{level|Метод ортогонализации с переортогонализацией}}<br />
#### {{level|Метод треугольного разложения матрицы Грама}}<br />
### {{level|Методы QR-разложения плотных хессенберговых матриц}}<br />
#### {{level|Метод Гивенса (вращений) QR-разложения хессенберговой матрицы (вещественный вариант)}}<br />
#### {{level|Метод Хаусхолдера (отражений) QR-разложения хессенберговой матрицы (вещественный вариант)}}<br />
## {{level|Подобные разложения}}<br />
### {{level|Подобные разложения на унитарные и хессенберговы матрицы}}<br />
#### {{level|Метод Хаусхолдера (отражений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
##### {{level|Классический точечный метод Хаусхолдера (отражений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
#### {{level|Метод Гивенса (вращений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
##### {{level|Классический точечный метод Гивенса (вращений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
### {{level|Симметричные разложения на унитарные и трёхдиагональные матрицы}}<br />
#### {{level|Метод Хаусхолдера (отражений) приведения к трёхдиагональному виду}}<br />
##### {{level|Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду}}<br />
##### {{level|Метод Хаусхолдера (отражений) для приведения комплексных эрмитовых матриц к трёхдиагональному симметричному виду}}<br />
#### {{level|Метод Гивенса (вращений) приведения матрицы к трёхдиагональной форме}}<br />
### {{level|Спектральное разложение (нахождение собственных значений и векторов)}}<br />
## {{level|Неподобные унитарные разложения}}<br />
### {{level|Неподобные разложения на унитарные и двухдиагональные матрицы}}<br />
#### {{level|Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме}}<br />
#### {{level|Метод Гивенса (вращений) приведения матрицы к двухдиагональной форме}}<br />
### {{level|Разложения на унитарные и диагональные матрицы}}<br />
#### {{level|Сингулярное разложение (нахождение сингулярных значений и векторов)}}<br />
##### {{level|Методы нахождения сингулярных чисел двухдиагональных матриц}}<br />
###### {{level|Алгоритм dqds нахождения сингулярных чисел двухдиагональной матрицы}}<br />
####### {{level|Итерация алгоритма dqds}}<br />
# {{level|Решение систем линейных уравнений}}<br />
## {{level|Прямые методы решения СЛАУ}}<br />
### {{level|Linpack benchmark}}<br />
### {{level|Методы решения СЛАУ с матрицами специального вида}}<br />
#### {{level|Методы решения СЛАУ с треугольными матрицами}}<br />
##### {{level|Прямая подстановка (вещественный вариант)|Прямая подстановка}}<br />
##### {{level|Обратная подстановка (вещественный вариант)|Обратная подстановка}}<br />
##### {{level|Методы решения СЛАУ с двудиагональными матрицами}}<br />
###### {{level|Прямая и обратная подстановка в СЛАУ с двухдиагональной матрицей}}<br />
###### {{level|Метод сдваивания Стоуна для решения двудиагональных СЛАУ}}<br />
###### {{level|Последовательно-параллельный вариант обратной подстановки}}<br />
#### {{level|Методы решения СЛАУ с трёхдиагональными матрицами}}<br />
##### {{level|Методы, основанные на стандартном LU-разложении матрицы}}<br />
###### {{level|Прогонка}}<br />
####### {{level|Прогонка, точечный вариант}}<br />
####### {{level|Классическая монотонная прогонка, повторный вариант}}<br />
###### {{level|Метод сдваивания Стоуна}}<br />
####### {{level|Алгоритм сдваивания Стоуна для LU-разложения трёхдиагональной матрицы}}<br />
####### {{level|Метод сдваивания Стоуна для решения двудиагональных СЛАУ}}<br />
###### {{level|Последовательно-параллельный вариант решения трёхдиагональной СЛАУ с LU-разложением и обратными подстановками}}<br />
##### Другие методы<br />
###### {{level|Метод редукции}}<br />
####### {{level|Полный метод редукции}}<br />
####### {{level|Повторный метод редукции для новой правой части}}<br />
###### {{level|Встречная прогонка}}<br />
####### {{level|Встречная прогонка, точечный вариант}}<br />
####### {{level|Повторная встречная прогонка, точечный вариант}}<br />
###### {{level|Метод циклической редукции}}<br />
####### {{level|Полный метод циклической редукции}}<br />
####### {{level|Повторный метод циклической редукции для новой правой части}}<br />
###### {{level|Метод окаймления}}<br />
#### Методы решения СЛАУ с блочно-треугольными матрицами<br />
##### {{level|Блочная прямая подстановка (вещественный вариант)|Блочная прямая подстановка}}<br />
##### {{level|Блочная обратная подстановка (вещественный вариант)|Блочная обратная подстановка}}<br />
##### Методы решения СЛАУ с блочно-двухдиагональными матрицами<br />
###### {{level|Прямая и обратная подстановка в СЛАУ с блочно-двухдиагональной матрицей}}<br />
###### {{level|Метод сдваивания Стоуна для решения блочно-двухдиагональных СЛАУ}}<br />
###### {{level|Блочный последовательно-параллельный вариант обратной подстановки для решения блочно-двухдиагональных СЛАУ}}<br />
#### {{level|Методы решения СЛАУ с блочно-трёхдиагональными матрицами}}<br />
##### Методы, основанные на стандартном LU-разложении матрицы<br />
###### {{level|Блочная прогонка}}<br />
###### {{level|Блочный последовательно-параллельный вариант решения с LU-разложением и обратными подстановками}}<br />
##### Другие методы<br />
###### {{level|Встречная прогонка, блочный вариант}}<br />
###### {{level|Блочный метод циклической редукции}}<br />
###### {{level|Блочный метод окаймления}}<br />
### {{level|Решения СЛАУ с матрицами специального вида, имеющими известные обратные матрицы}}<br />
## Итерационные методы решения СЛАУ<br />
### {{level|High Performance Conjugate Gradient (HPCG) benchmark}}<br />
### {{level|Стабилизированный метод бисопряженных градиентов (BiCGStab)}}<br />
### {{level|Алгоритм_Качмажа}}<br />
# {{level|Решение систем нелинейных уравнений}}<br />
## {{level|Метод Ньютона}}<br />
# <div id="Решения спектральных задач">'''Решения спектральных задач'''</div><br />
## {{level|Спектральное разложение (нахождение собственных значений и векторов)}}<br />
### {{level|QR-алгоритм}}<br />
#### {{level|QR-алгоритм, используемый в SCALAPACK}}<br />
##### {{level|Классический точечный метод Хаусхолдера (отражений) приведения матрицы к хессенберговой (почти треугольной) форме}}<br />
##### {{level|QR-алгоритм для хессенберговой матрицы, используемый в SCALAPACK}}<br />
#### {{level|QR-алгоритм для симметричных матриц, используемый в SCALAPACK}}<br />
##### {{level|Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду}}<br />
##### {{level|QR-алгоритм для симметричных трёхдиагональных матриц, используемый в SCALAPACK}}<br />
#### {{level|QR-алгоритм для комплексных эрмитовых матриц, используемый в SCALAPACK}}<br />
##### {{level|Метод Хаусхолдера (отражений) для приведения комплексных эрмитовых матриц к трёхдиагональному симметричному виду}}<br />
##### {{level|QR-алгоритм для симметричных трёхдиагональных матриц, используемый в SCALAPACK}}<br />
### {{level|Метод Якоби (вращений) для решения спектральной задачи у симметричных матриц}}<br />
#### {{level|Классический метод Якоби (вращений) для симметричных матриц с выбором по всей матрице}}<br />
#### {{level|Метод Якоби (вращений) для симметричных матриц с циклическим исключением}}<br />
#### {{level|Метод Якоби (вращений) для симметричных матриц с циклическим исключением и барьерами}}<br />
### {{level|Метод Ланцоша}}<br />
#### {{level|Алгоритм Ланцоша для точной арифметики (без переортогонализации)}}<br />
## {{level|Частичная спектральная задача}}<br />
### {{level|Метод бисекций}}<br />
## {{level|Сингулярное разложение (нахождение сингулярных значений и векторов)}}<br />
### {{level|Метод Якоби (вращений) для нахождения сингулярных значений неособенных матриц}}<br />
#### {{level|Метод Якоби (вращений) для нахождения сингулярных значений с циклическим перебором}}<br />
#### {{level|Метод Якоби для нахождения сингулярных значений со специальным подбором вращений}}<br />
### {{level|QR-алгоритм в приложении к сингулярному разложению}}<br />
# <div id="Тесты производительности компьютеров">'''Тесты производительности компьютеров'''</div><br />
## {{level|High Performance Conjugate Gradient (HPCG) benchmark}}<br />
## {{level|Linpack benchmark}}<br />
# {{level|Преобразование Фурье}}<br />
## {{level|Быстрое преобразование Фурье}}<br />
### {{level|Быстрое преобразование Фурье для степеней двойки}}<br />
# <div id="Алгебра многочленов">'''Алгебра многочленов'''</div><br />
## {{level|Схема Горнера, вещественная версия, последовательный вариант}}<br />
# <div id="Численные методы интегрирования">'''Численные методы интегрирования'''</div><br />
## {{level|Квадратурные формулы}}<br />
## {{level|Квадратурные (кубатурные) методы численного интегрирования по отрезку (многомерному кубу)}}<br />
### [[Квадратурные_(кубатурные)_методы_численного_интегрирования_по_отрезку_(многомерному_кубу)#Метод прямоугольников|Метод прямоугольников]]<br />
### [[Квадратурные_(кубатурные)_методы_численного_интегрирования_по_отрезку_(многомерному_кубу)#Метод трапеций|Метод трапеций]]<br />
### [[Квадратурные_(кубатурные)_методы_численного_интегрирования_по_отрезку_(многомерному_кубу)#Метод парабол (метод Симпсона)|Метод парабол (метод Симпсона)]]<br />
### [[Квадратурные_(кубатурные)_методы_численного_интегрирования_по_отрезку_(многомерному_кубу)#Метод Гаусса|Метод Гаусса]]<br />
# <div id="Алгоритмы на графах">'''Алгоритмы на графах'''</div><br />
## Обход графа<br />
### {{level|Поиск в ширину (BFS)}}<br />
### {{level|Поиск в глубину (DFS)}}<br />
## {{level|Поиск кратчайшего пути от одной вершины (SSSP)}}<br />
### {{level|Поиск в ширину (BFS)}} (для невзвешенных графов)<br />
### {{level|Алгоритм Дейкстры}}<br />
### {{level|Алгоритм Беллмана-Форда}}<br />
### {{level|Алгоритм Δ-шагания}}<br />
## {{level|Поиск кратчайшего пути для всех пар вершин (APSP)}}<br />
### {{level|Алгоритм Джонсона}}<br />
### {{level|Алгоритм Флойда-Уоршелла}}<br />
## {{level|Поиск транзитивного замыкания орграфа}}<br />
### {{level|Алгоритм Пурдома}}<br />
## {{level|Определение диаметра графа}}<br />
## {{level|Построение минимального остовного дерева (MST)}}<br />
### {{level|Алгоритм Борувки}}<br />
### {{level|Алгоритм Крускала}}<br />
### {{level|Алгоритм Прима}}<br />
### {{level|Алгоритм GHS}}<br />
## {{level|Поиск изоморфных подграфов}}<br />
### {{level|Алгоритм Ульмана}}<br />
### {{level|Алгоритм VF2}}<br />
## {{level|Связность в графах}}<br />
### {{level|Алгоритм Шилоаха-Вишкина поиска компонент связности}}<br />
### {{level|Система непересекающихся множеств}}<br />
### {{level|Алгоритм Тарьяна поиска компонент сильной связности}}<br />
### {{level|Алгоритм DCSC поиска компонент сильной связности}}<br />
### {{level|Алгоритм Тарьяна поиска компонент двусвязности}}<br />
### {{level|Алгоритм Тарьяна-Вишкина поиска компонент двусвязности}}<br />
### {{level|Алгоритм Тарьяна поиска «мостов» в графе}}<br />
### {{level|Определение вершинной связности графа}}<br />
### {{level|Алгоритм Габова определения рёберной связности графа}}<br />
## {{level|Поиск максимального потока в транспортной сети}}<br />
### {{level|Алгоритм Форда-Фалкерсона}}<br />
### {{level|Алгоритм проталкивания предпотока}}<br />
## {{level|Поиск потока минимальной стоимости в транспортной сети}}<br />
## {{level|Задача о назначениях}}<br />
### {{level|Венгерский алгоритм}}<br />
### {{level|Алгоритм аукциона}}<br />
### {{level|Алгоритм Гопкрофта-Карпа}}<br />
## {{level|Вычисление betweenness centrality|Вычисление центральности вершин}}<br />
# <div id="Алгоритмы поиска">'''Алгоритмы поиска'''</div><br />
## {{level|Линейный поиск - находит элемент в любом списке|Линейный поиск}}, <math>O(n)</math><br />
## {{level|Двоичный поиск - находит элемент в отсортированном списке|Двоичный поиск}}, <math>O(\log(n))</math><br />
# <div id="Алгоритмы сортировки">'''Алгоритмы сортировки'''</div><br />
## {{level|Сортировка с помощью двоичного дерева}}<br />
## {{level|Сортировка пузырьком}}<br />
## {{level|Сортировка слиянием (последовательный и параллельный варианты)}}<br />
# <div id="Вычислительная геометрия">'''Вычислительная геометрия'''</div><br />
## {{level|Поиск диаметра множества точек}}<br />
## {{level|Построение выпуклой оболочки набора точек}}<br />
## {{level|Триангуляция Делоне}}<br />
## {{level|Диаграмма Вороного}}<br />
## {{level|Принадлежность точки многоугольнику}}<br />
## {{level|Пересечения выпуклых многоугольников}} - трудоёмкость <math>O(n_1 + n_2)</math><br />
## {{level|Пересечение звёздных многоугольников}} - трудоёмкость <math>O(n_1 \cdot n_2)</math><br />
# <div id="Компьютерная графика">'''Компьютерная графика'''</div><br />
## {{level|Алгоритмы построения отрезка - алгоритмы для аппроксимации отрезка на дискретной графической поверхности}}<br />
## {{level|Алгоритм определения видимых частей трёхмерной сцены}}<br />
## {{level|Трассировка лучей - рендеринг реалистичных изображений}}<br />
## {{level|Глобальное освещение - рассматривает прямое освещение и отражение от других объектов}}<br />
# <div id="Криптографические алгоритмы">'''Криптографические алгоритмы'''</div><br />
## {{level|Метод встречи посередине}}<br />
# <div id="Нейронные сети">'''Нейронные сети'''</div><br />
## {{level|Распознование образов}}<br />
### {{level|Распознование текста}}<br />
### {{level|Распознование речи}}<br />
### {{level|Распознование лиц}}<br />
# <div id="Алгоритмы оптимизации">'''Алгоритмы оптимизации'''</div><br />
## {{level|Линейное программирование}}<br />
## {{level|Симплекс-метод}}<br />
## {{level|Метод ветвей и границ}}<br />
## {{level|Генетические алгоритмы}}<br />
## {{level|Муравьиные алгоритмы}}<br />
## {{level|Комбинированные алгоритмы}}<br />
## {{level|Стохастическое двойственное динамическое программирование (SDDP)}}<br />
# <div id="Алгоритмы машинного обучения">'''Алгоритмы машинного обучения'''</div><br />
## {{level|Алгоритм k средних (k-means)}}<br />
# <div id="Алгоритмы теории игр">'''Алгоритмы теории игр'''</div><br />
# <div id="Алгоритмы моделирования квантовых систем">'''Алгоритмы моделирования квантовых систем'''</div><br />
## ''Алгоритмы моделирования квантовых вычислений''<br />
### {{level|Однокубитное преобразование вектора-состояния}}<br />
### {{level|Двухкубитное преобразование вектора-состояния}}<br />
### {{level|Моделирование квантового преобразования Фурье}}<br />
# <div id="Алгоритмы решения уравнений математической физики">'''Алгоритмы решения уравнений математической физики'''</div><br />
## {{level|Уравнение Пуассона, решение дискретным преобразованием Фурье}}<br />
# <div id="Другие алгоритмы">'''Другие алгоритмы'''</div><br />
<br />
[[en:Algorithm classification]]<br />
<br />
[[Категория:Алгоритмы|*]]</div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Konshin&diff=25253Обсуждение участника:Konshin2018-01-16T17:42:21Z<p>Konshin: /* Алгоритм_19, Алгоритм k средних (4): */</p>
<hr />
<div>* явных списываний нет ни в одном из алгоритмов!<br />
* правда в алгоритме 19 (Алгоритм k средних) у 3-х из 4-х авторов использована одна и та же открытая реализация<br />
* все работы очень хорошие! только 4 оценки 4, остальные 5<br />
* в принципе для оценки выполнены все работы, хотя в 3-х было бы неплохо дождаться мелкой правки...<br />
<br />
Обозначения:<br />
<br />
* ... - ждем ответа на замечания<br />
* +++ - работа выполнена<br />
* +++!!! - выполнена и является претендентом на внесение в AlgoWiki<br />
* (X)(Y) - оценки по разделам 1 и 2, соответственно<br />
<br />
<br />
=== Алгоритм_4, Алгоритм Ланцоша (итерационный метод вычисления собственных значений симметричной матрицы) для точной арифметики (без переортогонализации) (6): ===<br />
<br />
https://algowiki-project.org/ru/Участник:Alexbashirov/Алгоритм_Ланцоша_для_точной_арифметики<br />
<br />
+++ (4)(4) по сравнению с другими замечательными описаниями и из-за задержки с п.2.4 оценка снижена, хотя формально все сделано, можно поставить и 5-<br />
<br />
https://algowiki-project.org/ru/Участник:A.Freeman/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (5)(5) собственная реализация на 256 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Danyanya/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации) <br />
<br />
+++ (5-)(5-) собственная реализация на 128 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:AleksLevin/Алгоритм_Ланцоша_вычисления_собственных_значений_симметричной_матрицы_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (5)(5) п.3! собственная реализация на 256 проц. Ломоносова; не очень понятно, открыт ли полный доступ к коду...<br />
<br />
https://algowiki-project.org/ru/Участник:VolkovNikita94/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++!!! (5-)(5+) Никита Волков; п.2.7 лучший!; реализация MPI+OpenMP тоже лучшая! должны еще поправить рис.4-6...<br />
<br />
https://algowiki-project.org/ru/Участник:Shostix/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
+++ (4)(4) нормально<br />
<br />
ГОТОВО: https://algowiki-project.org/w/ru/index.php?title=Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
https://algowiki-project.org/ru/Алгоритм_Ланцоша_для_точной_арифметики_(без_переортогонализации)<br />
<br />
=== Алгоритм_14, Решение системы нелинейных уравнений методом Ньютона (4): ===<br />
<br />
https://algowiki-project.org/ru/Участник:N_Zakharov/Метод_Ньютона_для_решения_систем_нелинейных_уравнений<br />
<br />
+++ (4+)(4+) пример из PETSc до 128 проц. Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Oleggium/Метод_Ньютона_для_решения_систем_нелинейных_уравнений(2)<br />
<br />
+++ (5)(5-) красивый граф, самостоятельная реализация на CUDA до 400 потока на персоналке, но бледно описано; все сделано, местами лучшие описания, но несколько лаконично :( <br />
<br />
https://algowiki-project.org/ru/Участник:SKirill/Метод_Ньютона_решения_систем_нелинейных_уравнений<br />
<br />
+++ (5-)(5) необычные и интересные блоксхемы п.1.5! и 1.7!; самостоятельная MPI реализация до 128 нитей; лучший п.2.7!<br />
<br />
[https://algowiki-project.org/ru/Участник:Арутюнов_А.В. https://algowiki-project.org/ru/Участник:Арутюнов_А.В.] (!!! осторожно с точкой в конце адреса !!!)<br />
<br />
+++ (4)(4) нормально<br />
<br />
-: http<br />
<br />
=== Алгоритм_19, Алгоритм k средних (4): ===<br />
<br />
https://algowiki-project.org/ru/Участник:IanaV/Алгоритм_k_means<br />
<br />
+++ (5)(5) хороший текст, особенно п.1.10, отличные графы, но витиеватый язык, видимо списано из учебника, чужая прогр. до 512 MPI проц. Ломоносова (там же имеются OpenMP и CUDA версии, но они не исследовались, собственно, как и 2-ми и 3-ми авторами)<br />
<br />
https://algowiki-project.org/ru/Участник:Parkhomenko/Алгоритм_k_средних<br />
<br />
+++ (5-)(5) специфически, но интересно представлены результаты в п.2.4!, дважды переделывали по моей просьбе, молодцы: и чужие и свои прогоны готовой реализации на 512 MPI проц. Blue Gene/P<br />
<br />
https://algowiki-project.org/ru/Участник:Бротиковская_Данута/Алгоритм_k-means<br />
<br />
+++!!! (5)(5) готовая реализация до 512 на Ломоносове, хороший 2.4, лучший 2.7 и 3, написан даже п.2.6!!<br />
<br />
https://algowiki-project.org/ru/Участник:Илья_Егоров/Алгоритм_k-средних<br />
<br />
+++ (5-)(5-) самостоятельная реализация OpenMP до 16 нитей Ломоносова (жаль что считали на головном узле?! поэтому-то и минус); очень красивые графы!<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Алгоритм_k_средних_(k-means)<br />
<br />
=== Другие (5): ===<br />
<br />
https://algowiki-project.org/ru/Участник:Bagnikita/Face_Recognition<br />
<br />
... (5+)(5+) заполнены все! пункты; лучшая работа, несмотря на некоторое смешивание рассмотрения "метода" и используемых в нем "алгоритмов", но по другому здесь сделать и не возможно (ждем мелкой правки...)(давно уже ждем...)(несмотря на ожидание все равно оценка 5+, но в расчете на исправления пока так и не поставил метку "принято")<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Распознование_лиц<br />
<br />
<br />
https://algowiki-project.org/ru/Участник:EasyBreezy/Стабилизированный_метод_биспоряженных_градиентов_(BiCGSTAB)<br />
<br />
... (4)(4) единственное описание на эту важную тему, вопросов много, хотелось бы довести до конца (тогда возможна и оценка 5); реализация HYPRE на 16 проц. Ломоносова (ждем уже давно... кое-что поправлено, но мало... без их помощи труднее будет довести статью до включения в основную вики, пока не ставлю метку "принято")<br />
<br />
ГОТОВО: https://algowiki-project.org/ru/Стабилизированный_метод_бисопряженных_градиентов_(BiCGStab)<br />
<br />
<br />
https://algowiki-project.org/ru/Участница:Александра/Метод_встречи_посередине<br />
<br />
+++!!! (5+)(5+) лучшее описание по этой теме; слегка изменила чужую реализацию и запустила на 8 ядрах Ломоносова<br />
<br />
https://algowiki-project.org/ru/Участник:Огнева_Мария/Метод_встречи_посередине <br />
<br />
... (4)(--) нет прогонов для п.2.4, хотя реализация ей и известна... (все-таки оценка 4, особенно по сравнению с другими работами... хотя выполняла она одна, а за первую часть можно было бы поставить и 5-)<br />
<br />
ГОТОВО (Батарина): https://algowiki-project.org/w/ru/index.php?title=Метод_встречи_посередине<br />
<br />
<br />
https://algowiki-project.org/ru/VladimirDobrovolsky611/Алгоритм_SDDP<br />
<br />
+++ (4)(4) нормально описано, после небольшой правки можно выкладывать. Рисунки не совсем законченные. Собственная реализация автором не выложена.<br />
<br />
ГОТОВО: http://algowiki-project.org/ru/Стохастическое_двойственное_динамическое_программирование_(SDDP)</div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_k_%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D1%85_(k-means)&diff=25252Алгоритм k средних (k-means)2018-01-16T17:40:36Z<p>Konshin: </p>
<hr />
<div>Основные авторы статьи (разделы 1, 2.4.1, 2.6-2.7, 3):<br />
[https://algowiki-project.org/ru/Участник:Бротиковская_Данута<b>Д.Бротиковская</b>] и<br />
[https://algowiki-project.org/ru/Участник:DennZo1993<b>Д.Зобнин</b>]<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:IanaV<b>Я.А.Валуйская</b>] и<br />
[https://algowiki-project.org/ru/Участник:GlotovES<b>Е.С.Глотов</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Parkhomenko<b>П.А.Пархоменко</b>] и<br />
[https://algowiki-project.org/ru/Участник:Ivan.mashonskiy<b>И.Д.Машонский</b>] (раздел 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Илья_Егоров<b>И.Егоров</b>] и<br />
[https://algowiki-project.org/ru/Участник:Богомазов_Евгений<b>Е.Богомазов</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Алгоритм <math>k</math> средних (<i>k</i>-means)<br />
| serial_complexity = <math>O(ikdn)</math><br />
| input_data = <math> dn </math><br />
| output_data = <math> n </math><br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Алгоритм <b><i>k средних</i></b> (англ. <i>k</i>-means) - один из алгоритмов машинного обучения, решающий задачу кластеризации.<br />
Этот алгоритм является неиерархическим<ref>"https://ru.wikipedia.org/wiki/Иерархическая_кластеризация"</ref>, итерационным методом кластеризации<ref>"https://ru.wikipedia.org/wiki/Кластерный_анализ"</ref>, он получил большую популярность благодаря своей простоте, наглядности реализации и достаточно высокому качеству работы. <br />
Был изобретен в 1950-х годах математиком <i>Гуго Штейнгаузом</i><ref>Steinhaus, Hugo. "Sur la division des corp materiels en parties." Bull. Acad. Polon. Sci 1.804 (1956): 801.</ref> и почти одновременно <i>Стюартом Ллойдом</i><ref>Lloyd, S. P. "Least square quantization in PCM. Bell Telephone Laboratories Paper. Published in journal much later: Lloyd, SP: Least squares quantization in PCM." IEEE Trans. Inform. Theor.(1957/1982).</ref>. Особую популярность приобрел после публикации работы <i>МакКуина</i><ref>MacQueen, James. "Some methods for classification and analysis of multivariate observations." Proceedings of the fifth Berkeley symposium on mathematical statistics and probability. Vol. 1. No. 14. 1967.</ref> в 1967.<br />
<br />
Алгоритм представляет собой версию EM-алгоритма<ref>"https://ru.wikipedia.org/wiki/EM-алгоритм"</ref>, применяемого также для разделения смеси гауссиан. Основная идея алгоритма <i>k</i>-means заключается в том, что данные произвольно разбиваются на кластеры, после чего итеративно перевычисляется центр масс для каждого кластера, полученного на предыдущем шаге, затем векторы разбиваются на кластеры вновь в соответствии с тем, какой из новых центров оказался ближе по выбранной метрике.<br />
<br />
Цель алгоритма заключается в разделении <math>n</math> наблюдений на <math>k</math> кластеров таким образом, чтобы каждое наблюдение принадлежало ровно одному кластеру, расположенному на наименьшем расстоянии от наблюдения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
<b>Дано:</b><br />
* набор из <math>n</math> наблюдений <math>X=\{\mathbf{x}_1, \mathbf{x}_2, ..., \mathbf{x}_n\}, \mathbf{x}_i \in \mathbb{R}^d, \ i=1,...,n</math>;<br />
* <math>k</math> - требуемое число кластеров, <math>k \in \mathbb{N}, \ k \leq n</math>.<br />
<br />
<b>Требуется:</b><br />
<br />
Разделить множество наблюдений <math>X</math> на <math>k</math> кластеров <math>S_1, S_2, ..., S_k</math>:<br />
* <math>S_i \cap S_j= \varnothing, \quad i \ne j</math><br />
<br />
* <math>\bigcup_{i=1}^{k} S_i = X</math><br />
<br />
<b>Действие алгоритма:</b><br />
<p>Алгоритм <i>k</i>-means разбивает набор <math>X</math> на <math>k</math> наборов <math>S_1, S_2, ..., S_k,</math> таким образом, чтобы минимизировать сумму квадратов расстояний от каждой точки кластера до его центра (центр масс кластера). Введем обозначение, <math>S=\{S_1, S_2, ..., S_k\}</math>. Тогда действие алгоритма <i>k</i>-means равносильно поиску:</p><br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math>\arg\min_{S} \sum\limits_{i=1}^k \sum\limits_{\mathbf{x} \in S_i} \rho(\mathbf{x}, \mathbf{\mu}_i )^2,</math></td><br />
<td align="right"><math>(1)</math></td><br />
</tr><br />
</table><br />
где <math>\mathbf{\mu}_i</math> &ndash; центры кластеров, <math>i=1,...,k, \quad \rho(\mathbf{x}, \mathbf{\mu}_i)</math> &ndash; функция расстояния между <math>\mathbf{x}</math> и <math>\mu_i</math><br />
<br />
<b>Шаги алгоритма:</b><br />
<ol><br />
<li><br />
<b>Начальный шаг: инициализация кластеров</b><br />
<p>Выбирается произвольное множество точек <math>\mu_i, \ i=1,...,k,</math> рассматриваемых как начальные центры кластеров: <math>\mu_i^{(0)} = \mu_i, \quad i=1,...,k</math></p><br />
</li><br />
<li><br />
<b>Распределение векторов по кластерам</b><br />
<p><b>Шаг</b> <math>t: \forall \mathbf{x}_i \in X, \ i=1,...,n: \mathbf{x}_i \in S_j \iff j=\arg\min_{k}\rho(\mathbf{x}_i,\mathbf{\mu}_k^{(t-1)})^2</math></p><br />
</li><br />
<li><br />
<b>Пересчет центров кластеров</b><br />
<p><b>Шаг </b> <math>t: \forall i=1,...,k: \mu_i^{(t)} = \cfrac{1}{|S_i|}\sum_{\mathbf{x}\in S_i}\mathbf{x}</math></p><br />
</li><br />
<li><br />
<b>Проверка условия останова:</b><br />
<p></p><br />
* '''if''' <math>\exists i\in \overline{1,k}: \mu_i^{(t)} \ne \mu_i^{(t-1)}</math> '''then'''<br />
** <math>t = t + 1</math>;<br />
** goto 2;<br />
* '''else'''<br />
** '''stop'''<br />
</li><br />
</ol><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром являются шаги 2 и 3 приведенного выше алгоритма: <b><i>распределение векторов по кластерам</i></b> и <b><i>пересчет центров кластеров</i></b>. <br />
<br />
<p><br />
<b><i>Распределение векторов</i></b> по кластерам предполагает вычисление расстояний между каждым вектором <math>\mathbf{x}_i \in X, \ i= 1,...,n</math> и центрами кластера <math>\mathbf{\mu}_j, \ j= 1,...,k</math>. Таким образом, данный шаг предполагает <math>kn</math> вычислений расстояний между <math>d</math>-мерными векторами. <br />
</p><br />
<p><br />
<b><i>Пересчет центров кластеров</i></b> предполагает <math>k</math> вычислений центров масс <math>\mathbf{\mu}_i</math> множеств <math>S_i, \ i=1,...,k,</math> представленных выражением в шаге 3 представленного выше алгоритма.<br />
</p><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
<b>Инициализация центров масс <math>\mu_1, ..., \mu_k</math></b>. <br />
<br />
Наиболее распространенными являются следующие стратегии:<br />
<ul><br />
<li><br />
<b>Метод Forgy</b><br>В качестве начальных значений <math>\mu_1, ..., \mu_k</math> берутся случайно выбранные векторы.<br />
</li><br />
<li><br />
<b>Метод случайно разделения (Random Partitioning)</b><br>Для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> выбирается случайным образом кластер <math>S_1, ..., S_k</math>, после чего для каждого полученного кластера вычисляются значения <math>\mu_1, ..., \mu_k</math>.<br />
</li><br />
</ul><br />
<br />
<b>Распределение векторов по кластерам</b><br />
<br />
Для этого шага алгоритма между векторами <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> и центрами кластеров <math>\mu_1,...,\mu_k</math> вычисляются <b>расстояния</b> по формуле (как правило, используется Евлидово расстояние):<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mathbf{v}_1, \mathbf{v}_2 \in \mathbb{R}^d, \quad \rho(\mathbf{v}_1, \mathbf{v}_2) = \lVert \mathbf{v}_1- \mathbf{v}_2 \rVert= \sqrt{\sum_{i=1}^{d}(\mathbf{v}_{1,i} - \mathbf{v}_{2,i})^2}</math></td><br />
<td align="right"><math>(2)</math></td><br />
</tr><br />
</table><br />
<br />
<b>Пересчет центров кластеров</b><br />
<br />
Для этого шага алгоритма производится пересчет центров кластера по <b>формуле вычисления центра масс</b>:<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mu = \cfrac{1}{|S|}\sum_{\mathbf{x}\in S}\mathbf{x}</math></td><br />
<td align="right"><math>(3)</math></td><br />
</tr><br />
</table><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
'''1.''' Инициализировать центры кластеров <math>\mathbf{\mu}_i^{(1)}, \ i=1,...,k</math><br><br />
'''2.''' <math>t \leftarrow 1</math><br><br />
'''3.''' Распределение по кластерам<br><br />
<math>\quad S_i^{(t)}=\{\mathbf{x}_p: \lVert\mathbf{x}_p-\mathbf{\mu}_i^{(t)}\rVert^2 \leq \lVert\mathbf{x}_p-\mathbf{\mu}_j^{(t)}\rVert^2 \quad \forall j=1,...,k\},</math><br><br />
<math>\quad</math>где каждый вектор <math>\mathbf{x}_p</math> соотносится единственному кластеру <math>S^{(t)}</math><br><br />
'''4.''' Обновление центров кластеров<br><br />
<math>\quad \mathbf{\mu}_i^{(t+1)} = \frac{1}{|S^{(t)}_i|} \sum_{\mathbf{x}_j \in S^{(t)}_i} \mathbf{x}_j </math><br><br />
'''5.''' '''if''' <math>\exists i \in \overline{1,k}: \mathbf{\mu}_i^{(t+1)} \ne \mathbf{\mu}_i^{(t)}</math> '''then'''<br><br />
<math>\quad t = t + 1</math>;<br><br />
<math>\quad</math>goto '''3''';<br><br />
<math>~~~</math>'''else'''<br><br />
<math>\quad</math>'''stop'''<br><br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
<div style="padding-bottom: 20px"><br />
<p>Обозначим <math>\Theta_{\rm centroid}^{d, m}</math> временную сложность вычисления центорида кластера, число элементов которого равна <math>m</math>, в d-мерном пространстве.</p><br />
<p>Аналогично <math>\Theta_{\rm distance}^d</math> &ndash; временная сложность вычисления расстояния между двумя d-мерными векторами.</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<b>Сложность шага инициализации <math>k</math> кластеров мощности <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm init}^{k, d, m}</math></b><br />
<ul><br />
<li><i>Стратерия Forgy</i>: вычисления не требуются, <math>\Theta_{\rm init}^{k, d, m} = 0</math></li><br />
<li><i>Стратегия случайного разбиения</i>: вычисление центров <math>k</math> кластеров, <math>\Theta_{\rm init}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}, m \le n</math></li><br />
</ul><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Cложность шага распределения d мерных векторов по <math>k</math> кластерам &ndash; <math>\Theta_{\rm distribute}^{k, d}</math></b></p><br />
<p><br />
На этом шаге для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> вычисляется <math>k</math> расстояний до центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math><br />
</p><br />
<p align="center"><br />
<math>\Theta_{\rm distribute}^{k, d} = n \cdot k \cdot \Theta_{\rm distance}^d</math><br />
</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><br />
<b>Сложность шага пересчета центров <math>k</math> кластеров размера <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm recenter}^{k, d, m}</math></b><br />
</p><br />
<p>На этом шаге вычисляется <math>k</math> центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math></p><br />
<p align="center"><math>\Theta_{\rm recenter}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}</math> </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm centroid}^{d, m}</math> для кластера, число элементов которого равно <math>m</math></b></p><br />
<p align="center"><math>\Theta_{\rm centroid}^{d, m}</math> = <math>m \cdot d</math> сложений + <math>d</math> делений </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm distance}^d</math> в соответствие с формулой <math>(2)</math></b></p><br />
<p align="center"><math>\Theta_{\rm distance}^d</math> = <math>d</math> вычитаний + <math>d</math> умножений + <math>(d-1)</math> сложение</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Предположим, что алгоритм сошелся за <math>i</math> итераций, тогда временная сложность алгоритма <math>\Theta_{\rm k-means}^{d, n}</math></p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le \Theta_{\rm init}^{k, d, n} + i(\Theta_{\rm distribute}^{k, d} + \Theta_{\rm recenter}^{k, d, n})</math></b></p><br />
</div><br />
<div style="padding-bottom: 5px"><br />
<p>Операции сложения/вычитания:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le knd+ i(kn(2d-1) + knd) = knd+ i(kn(3d-1)) \thicksim O(ikdn)</math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Операции умножения/деления:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le kd + i(knd + kd) = kd + ikd(n+1) \thicksim O(ikdn) </math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Получаем, что <b>временная сложность</b> алгоритма <b><i>k</i>-means</b> кластеризации <math>n</math> <b>d-мерных</b> векторов на <math>k</math> кластеров за <math>i</math> итераций:</p><br />
<p align="center"><b><math> \Theta_{\rm k-means}^{d, n} \thicksim O(ikdn) </math></b></p><br />
</div><br />
<br />
=== Информационный граф ===<br />
<br />
Рассмотрим информационный граф алгоритма. Алгоритм <i>k</i>-means начинается с этапа инициализации, после которого следуют итерации, на каждой из которых выполняется два последовательных шага (см. [[#Схема реализации последовательного алгоритма|"Схема реализации последовательного алгоритма"]]): <br />
* распределение векторов по кластерам<br />
* перерасчет центров кластеров<br />
<br />
Поскольку основная часть вычислений приходится на шаги итераций, распишем информационные графы данных шагов.<br />
<br />
<p><b>Распределение векторов по кластерам</b></p><br />
Информационный граф шага распределения векторов по кластерам представлен на ''рисунке 1''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также центры кластеров <math>\mathbf{\mu}_1, ... \mathbf{\mu}_k</math>, вычисленные ранее (на шаге инициализации, если рассматривается первая итерация алгоритма, или на шаге пересчета центров кластеров предыдущей итерации в противном случае). Каждая пара векторов данных <math>\mathbf{x}_i, \ i=1,...,n,</math> и центров кластера <math>\mathbf{\mu}_j, \ j=1,...,k</math> : (<math>\mathbf{x}_i</math>, <math>\mathbf{\mu}_j</math>) подаются на независимые узлы <i>"d"</i> вычисления расстояния между векторами (более подробная схема вычисления расстояния представлена далее, ''рисунок 2''). Далее узлы вычисления расстояния <i>"d"</i>, соответствующие одному и тому же исходному вектору <math>\mathbf{x}_i</math> передаются на один узел <i>"m"</i>, где далее происходит вычисление новой метки кластера для каждого вектора <math>\mathbf{x}_i</math> (берется кластер с минимальным результатом вычисления расстояния). На выходе графа выдаются метки кластеров , <math>L_1, ..., L_n</math>, такие что <math>\forall \mathbf{x}_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>. <br />
<br />
[[file:Clusterization.png|thumb|center|800px|Рис. 1. Схема распределения векторов по кластерам. ''d'' &ndash; вычисление расстояния между векторами; ''m'' &ndash; вычисление минимума.]]<br />
<br />
<p><b>Вычисление расстояния между векторами</b></p><br />
Подробная схема вычисления расстояния между векторами <math>\mathbf{x}_i, \mathbf{\mu}_j</math> представлена на ''рисунке 2''. Как показано на графе, узел вычисления расстояния между векторами <i>"d"</i> состоит из шага взятия разности между векторами (узел "<math>-</math>") и взятия нормы получившегося вектора разности (узел "<math>||\cdot||^2</math>"). Более подробно, вычисление расстояния между векторами <math>\mathbf{x}_i = {x_{i1,}, ...,{x_{in}}}, \mathbf{\mu}_j = {\mu_{j1}, ...,\mu_{jn}}</math> может быть представлено как вычисление разности между каждой парой компонент <math>(x_{iz}, \mu_{jz}), \ z=1,...,d</math> (узел "<math>-</math>"), далее возведение в квадрат для каждого узла "<math>-</math>" (узел "<math>()^2</math>") и суммирования выходов всех узлов "<math>()^2</math>" (узел "<math>+</math>"). <br />
<br />
[[file:dist_calc.png|thumb|center|800px|Рис. 2. Схема вычисления расстояния между вектором и центром кластера.]]<br />
<br />
<p><b>Пересчет центров кластеров</b></p><br />
Информационный граф шага пересчета центров кластеров представлен на ''рисунке 3''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также им соответствующие метки кластера, <math>L_1, ..., L_n</math>, такие что <math>\forall x_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>, вычисленные на этапе распределения векторов по кластерам. Все векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math> подаются в узлы <math>+_1, ...+_k</math>, каждый узел <math>+_m, \ m = 1,...,k,</math> соответствует операции сложения векторов кластера с номером <math>m</math>. Метки кластера <math>L_1, ..., L_n</math> также совместно передаются на узлы <math>S_m, \ m=1,...,k</math>, на каждом из которых вычисляется количество векторов в соответствующем кластере (количество меток с соответствующим значением). Далее каждая пара выходов узлов <math>+_m</math> и <math>S_m</math> подается на узел "<math>/</math>", где производится деление суммы векторов кластера на количество элементов в нем. Значения, вычисленные на узлах "<math>/</math>", присваиваются новым центрам кластеров (выходные значения графа).<br />
<br />
[[file:Recluster.png|thumb|center|800px|Рис. 3. Схема пересчета центров кластеров]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
<p><br />
Работа алгоритма состоит из <math>i</math> итераций, в каждой из которых происходит <b>распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b>, а также <b>пересчет центров кластеров в <math>d</math>-мерном пространстве</b>. В шаге <b>распределения <math>d</math>-мерных векторов по <math>k</math> кластерам</b> расстояния между вектором и центрами кластеров вычисляются независимо (отсутствуют информационные зависимости). <b>Центры масс кластеров</b> также пересчитываются независимо друг от друга. Таким образом, имеет место [[глоссарий#Массовый параллелизм|''массовый параллелизм'']]. Вычислим параллельную сложность <math>\Psi_*</math> каждого из шагов, а также параллельную сложность всего алгоритма, <math>\Psi_{\rm k-means}</math>. Будем исходить из предположения, что может быть использовано любое необходимое число потоков.<br />
</p><br />
<br />
<p><b>Распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b></p> <br />
<p><br />
Поскольку на данном шаге для каждой пары векторов <math>\mathbf{x}_i, \ i=1,...,n</math> и <math>\mathbf{\mu}_j, \ j=1,...,k,</math> операции вычисления расстояния не зависят друг от друга, они могут выполняться параллельно. Тогда, разделив все вычисление расстояний на <math>n</math> потоков, получим, что в каждом потоке будет выполняться только одна операция вычисления расстояния между векторами размерности <math>d</math>. При этом каждому вычислительному потоку передаются координаты центров всех кластеров <math>\mathbf{\mu}_1, ..., \mathbf{\mu}_k</math>. Таким образом, параллельная сложность данного шага определяется <i> сложностью параллельной операции вычисления расстояния между <math>d</math>-мерными векторами</i>, <math>\Psi_{\rm distance}^d</math> и <i>сложностью определения наиболее близкого кластера</i> (паралельное взятие минимума по расстояниям), <math>\Psi_{\rm min}^k</math>. Для оценки <math>\Psi_{\rm distance}^d</math> воспользуемся [[Нахождение_частных_сумм_элементов_массива_сдваиванием | параллельной реализацией нахождения частичной суммы элементов массива путем сдваивания]]. Аналогично, <math>\Psi_{\rm min}^k = \log(k)</math>. В результате, <math>\Psi_{\rm distance}^d = O(\log(d))</math>. Таким образом: <br />
</p><br />
<p align="center"><math>\Psi_{\rm distribute}^{k, d} = \Psi_{\rm distance}^d + \Psi_{\rm min}^k = O(\log(d))+O(\log(k)) = O(\log(kd))</math></p><br />
<br />
<p><b>Пересчет центров кластеров в d-мерном пространстве</b></p><br />
<p><br />
Поскольку на данном шаге для каждого из <math>k</math> кластеров центр масс может быть вычислен независимо, данные операции могут быть выполнены в отдельных потоках. Таким образом, параллельная сложность данного шага, <math>\Psi_{\rm recenter}^{k, d}</math>, будет определяться <i>параллельной сложностью вычисления одного центра масс кластера размера <math>m</math></i>, <math>\Psi_{\rm recenter}^{k, d}</math>, а так как <math>m \le n \Rightarrow \Psi_{\rm recenter}^{d, m} \le \Psi_{\rm recenter}^{d, n}</math>. Сложность вычисления центра масс кластера <math>d</math>-мерных векторов размера n аналогично предыдущим вычислениям равна <math>O(\log(n))</math>. Тогда: <br />
</p><br />
<p align="center"><math>\Psi_{\rm recenter}^{k, d} \le \Psi_{\rm recenter}^{d, n} = O(\log(n))</math></p><br />
<br />
<p><b>Общая параллельная сложность алгоритма</b></p><br />
<p><br />
На каждой итерации необходимо обновление центров кластеров, которые будут использованы на следующей итерации. Таким образом, итерационный процесс выполняется последовательно<ref>Zhao, Weizhong, Huifang Ma, and Qing He. "Parallel k-means clustering based on mapreduce." IEEE International Conference on Cloud Computing. Springer Berlin Heidelberg, 2009.</ref>. Тогда, поскольку сложность каждой итерации определяется <math>\Psi_{\rm distribute}^{k, d}</math> и <math>\Psi_{\rm recenter}</math>, сложность всего алгоритма, <math>\Psi_{\rm k-means}</math> в предположении, что было сделано <math>i</math> операций определяется выражением<br />
</p><br />
<p align="center"><math>\Psi_{\rm k-means} \approx i \cdot (\Psi_{\rm distribute}^{k, d} + \Psi_{\rm recenter}^{k, d}) \le i \cdot O(\log(kdn))</math></p><br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
<p><br />
<b>Входные данные</b><br><br />
<ul><br />
<li>Матрица из <math>n \cdot d</math> элементов <math>x_{i, j} \in \mathbb{R}, \ i=1,...,n, \ j=1,...,d,</math> &ndash; координат векторов (наблюдений).</li><br />
<li>Целое положительное число <math>k, \ k \le n</math> &ndash; количество кластеров.</li><br />
</ul><br />
</p><br />
<p><br />
<b>Объем входных данных</b><br><br />
<math>1</math> целое число + <math>n \cdot d</math> вещественных чисел (при условии, что координаты &ndash; вещественные числа).<br />
</p><br />
<p><br />
<b>Выходные данные</b><br><br />
<math>n</math> целых положительных чисел <math>L_1, ..., L_n</math>&ndash; номера кластеров, соотвествующие каждому вектору (при условии, что нумерация кластеров начинается с <math>1</math>).<br />
</p><br />
<p><br />
<b>Объем выходных данных</b><br><br />
<math>n</math> целых положительных чисел.<br />
</p><br />
<br />
=== Свойства алгоритма ===<br />
<br />
<p><b>[[глоссарий#Вычислительная мощность|''Вычислительная мощность'']]</b></p><br />
Вычислительная мощность алгоритма <i>k</i>-means равна <math>\frac{ikdn}{nd} = ki </math>, где <math>k</math> &ndash; число кластеров, <math>i</math> &ndash; число итераций алгоритма.<br />
<br />
<p><b>[[глоссарий#Детерминированность |''Детерминированность'']] и [[глоссарий#Устойчивость |''Устойчивость'']] </b></p><br />
Алгоритм <i>k</i>-means является итерационным. Количество итераций алгоритма в общем случае не фиксируется и зависит от начального расположения объектов в пространстве, параметра <math>k</math>, а также от начального приближения центров кластеров, <math>\mu_1, ..., \mu_k</math>. В результате этого может варьироваться результат работы алгоритма. При неудачном выборе начальных параметров итерационный процесс может сойтись к локальному оптимуму<ref>Von Luxburg, Ulrike. Clustering Stability. Now Publishers Inc, 2010.</ref>. По этим причинам алгоритм не является ни <b>детермирированным</b>, ни <b>устойчивым</b>.<br />
<br />
<p><b>Соотношение последовательной и параллельной сложности алгоритма</b></p><br />
<br />
<math>\frac{\Theta_{\rm k-means}}{\Psi_{\rm k-means}} = \frac{O(ikdn)}{O(i \cdot \log(kdn))}</math><br />
<br />
<b>Сильные стороны алгоритма</b>:<br />
<ul><br />
<li><i>Сравнительно высокая эффективность при простоте реализации</i></li><br />
<li><i>Высокое качество кластеризации</i></li><br />
<li><i>Возможность распараллеливания</i></li><br />
<li><i>Существование множества модификаций</i></li><br />
</ul><br />
<b>Недостатки алгоритма</b><ref>Ortega, Joaquín Pérez, Ma Del Rocío Boone Rojas, and María J. Somodevilla. "Research issues on K-means Algorithm: An Experimental Trial Using Matlab."</ref>:<br />
<ul><br />
<li><i>Количество кластеров является параметром алгоритма</i></li><br />
<li><br />
<p><i>Чувствительность к начальным условиям</i></p><br />
<p>Инициализация центров кластеров в значительной степени влияет на результат кластеризации.</p><br />
</li><br />
<li><br />
<p><i>Чувствительность к выбросам и шумам</i></p><br />
<p>Выбросы, далекие от центров настоящих кластеров, все равно учитываются при вычислении их центров.</p><br />
</li><br />
<li><br />
<p><i>Возможность сходимости к локальному оптимуму</i></p><br />
<p>Итеративный подход не дает гарантии сходимости к оптимальному решению.</p><br />
</li><br />
<li><br />
<p><i>Использование понятия "среднего"</i></p><br />
<p>Алгоритм неприменим к данным, для которых не определено понятие "среднего", например, категориальным данным.</p><br />
</li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализации алгоритма <math>k</math> средних согласно [[Scalability methodology|методике]] AlgoWiki.<br />
<br />
==== Реализация 1 ====<br />
<br />
Исследование масштабируемости параллельной реализации алгоритма <i>k</i>-means проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Алгоритм реализован на языке C с использованием средств MPI.<br />
Для исследования масштабируемости проводилось множество запусков программы с разным значением параметра (количество векторов для кластеризации), а также с различным числом процессоров. Фиксировались результаты запусков &ndash; время работы <math>t</math> и количество произведенных итераций алгоритма <math>i</math>.<br />
<br />
Параметры запусков для экспериментальной оценки:<br />
<ul><br />
<li>Значения <b>количества векторов</b> <math>n</math>: 20'000, 30'000, 50'000, 100'000, 200'000, 300'000, 500'000, 700'000, 1'000'000, 1'500'000, 2'000'000.</li><br />
<li>Значения <b>количества процессоров</b> <math>p</math>: 1, 8, 16, 32, 64, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512.</li><br />
<li>Значение <b>количества кластеров</b> <math>k</math>: 100.</li><br />
<li>Значение <b>размерности векторов</b> <math>d</math>: 10.</li><br />
</ul><br />
<br />
Для проведения экспериментов были сгенерированы нормально распределенные псевдослучайные данные (с использованием Python библиотеки [http://scikit-learn.org/ scikit-learn]):<br />
<syntaxhighlight lang="python"><br />
from sklearn.datasets import make_classification<br />
X1, Y1 = make_classification(n_features=10, n_redundant=2, n_informative=8,<br />
n_clusters_per_class=1, n_classes=100,<br />
n_samples=2000000)<br />
</syntaxhighlight><br />
<br />
Для заданной конфигурации эксперимента (<math>n, d, p, k</math>) и полученных результатов (<math>t, i</math>) [[Глоссарий#Производительность|производительность]] и эффективность реализации расчитывались по формулам:<br />
<p><br />
<ul><li><math>{\rm Performance} = \frac{N_{\rm k-means}^{d, n}}{t}\ \ ({\rm FLOPS}),</math></li></ul><br />
где <math>N_{\rm k-means}^{d, n}</math> &ndash; точное число операций с плавающей точкой (операции с памятью, а также целочисленные операции не учитывались), вычисленное в соответствие с разделом [[#Последовательная сложность алгоритма| "Последовательная сложность алгоритма"]];<br />
</p><br />
<p><br />
<ul><li><math>{\rm Efficiency} = \frac{100 \cdot {\rm Performance}}{{\rm Performance}_{\rm Peak}^{p}}\ \ (\%),</math></li></ul><br />
где <math>{\rm Performance}_{\rm Peak}^{p}</math> &ndash; пиковая производительность суперкомпьютера при <math>p</math> процессорах, вычисленная согласно спецификациям Intel<sup>&reg;</sup> XEON<sup>&reg;</sup> X5670<ref>"http://ark.intel.com/ru/products/47920/Intel-Xeon-Processor-X5670-12M-Cache-2_93-GHz-6_40-GTs-Intel-QPI"</ref>.<br />
</p><br />
<br />
Графики зависимости производительности и эффективности параллельной реализации <i>k</i>-means от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>) представлены на рисунках 4 и 5, соответственно.<br />
<br />
[[file:Flops.png|thumb|center|800px|Рис. 4. Параллельная реализация <i>k</i>-means. График зависимости производительности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
[[file:Efficiency.png|thumb|center|800px|Рис. 5. Параллельная реализация <i>k</i>-means. График зависимости эффективности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
<br />
<p><br />
<b>В результате</b> экспериментальной оценки были получены следующие оценки эффективности реализации:<br />
<ul><br />
<li><b>Минимальное</b> значение: 0.000409 % достигается при <math>n=20'000, p=480</math></li><br />
<li><b>Максимальное</b> значение: 0.741119 % достигается при <math>n=300'000, p=1</math></li><br />
</ul><br />
</p><br />
<br />
<p><br />
Оценки масштабируемости реализации алгоритма <i>k</i>-means:<br />
<ul><br />
<li><b>По числу процессоров:</b> -0.002683 &ndash; эффективность убывает с ростом числа процессоров. Данный результат вызван ростом накладных расходов для обеспечения параллельного выполнения алгоритма.</li><br />
<li><b>По размеру задачи:</b> 0.002779 &ndash; эффективность растет с ростом числа векторов. Данный результат вызван тем, что при увеличении размера задачи, количество вычислений растет по сравнению с временем, затрачиваемым на пересылку данных.</li><br />
<li><b>Общая оценка:</b> -0.000058 &ndash; можно сделать вывод, что в целом эффективность реализации незначительно уменьшается с ростом размера задачи и числа процессоров.</li><br />
</ul><br />
</p><br />
<br />
[https://github.com/serban/kmeans Использованная параллельная реализация алгоритма <i>k</i>-means]<br />
<br />
==== Реализация 2 ====<br />
<br />
Исследование также проводилось на суперкомпьютере "Ломоносов".<br />
<br />
<p>Набор данных для тестирования состоял из 946000 векторов размерности 2 (координаты на сфере)</p><br />
<p>Набор и границы значений изменяемых параметров запуска реализации алгоритма:</p><br />
<br />
* число процессов (виртуальных ядер) [8 : 512];<br />
* число кластеров [128 : 384].<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации <math>2,47%</math> достигается при делении исходных данных на 128 кластеров с использованием 512 процессов;<br />
* максимальная эффективность реализации <math>7,13%</math> достигается при делении исходных данных на 352 кластера с использованием 8 процессов.<br />
На рисунках 6 и 7, соответственно, представлены графики зависимости производительности и эффективности параллельной реализации <i>k</i>-means от числа кластеров и числа процессов.<br />
<br />
[[file:kmeans_performance.jpg|thumb|center|720px|Рис. 6. График зависимости производительности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
По рис. 6 можно отметить практически полное отсутствие роста производительности с увеличением числа процессов от 256 до 512 при минимальном размере задачи. Это связано с быстрым ростом накладных расходов по отношению к крайне низкому объёму вычислений. При росте размерности задачи данный эффект пропадает, и при одновременном пропорциональном увеличении числа кластеров и числа процессов рост производительности становится близким к линейному.<br />
<br />
[[file:kmeans_efficiency.jpg|thumb|center|720px|Рис. 7. График зависимости эффективности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
Исследовалась [https://github.com/like-a-bauss/kmeans параллельная реализация алгоритма <i>k</i>-means на MPI]. <br />
<p>Были получены следующие оценки масштабируемости реализации алгоритма <i>k</i>-means:</p><br />
*<i>По числу процессов:</i> <math>-0.02209</math>. Следовательно, с ростом числа процессов эффективность уменьшается. На рис. 7 можно наблюдать плавное и равномерное снижение производительности по мере увеличения числа процессов при неизменном числе кластеров, что свидетельствует об относительно невысоком росте накладных расходов на передачу данных между процессами и преобладании объёма вычислений над объёмом пересылок данных по сети.<br />
*<i>По размеру задачи:</i> <math>0.01252</math>. Следовательно, с ростом размера задачи (числа кластеров) эффективность увеличивается. При этом объём пересылок данных по сети пропорционален <math>(n + k) \cdot p</math> (где <math>k</math> - число кластеров, <math>n</math> - число входных векторов, <math>p</math> - число процессов) таким образом, поскольку <math>k << n</math>, рост накладных расходов с ростом числа кластеров при неизменном числе процессов и входных векторов представляет собой незначительную величину.<br />
*<i>Общая оценка:</i> <math>-0.00081</math>. Таким образом, с ростом и размера задачи, и числа процессов эффективность уменьшается. Это связано с тем, что отношение объёма вычислений к объёму передаваемых данных изменяется пропорционально <math>{kn \over (n + k) \cdot p} \thicksim {k \over p}</math>, что представляет собой невысокий коэффициент, но при этом позволяет параллельной реализации не деградировать до нулевой эффективности при значительном увеличении числа процессов.<br />
<br />
==== Реализация 3 ====<br />
<br />
Исследование масштабируемости алгоритма k-means в зависимости от количества используемых процессов было проведено в статье Кумара<ref>Kumar, J., Mills, R. T., Hoffman, F. M., & Hargrove, W. W. (2011). Parallel k-means clustering for quantitative ecoregion delineation using large data sets. Procedia Computer Science, 4, 1602-1611.</ref>. Исследование происходило на суперкомпьютере Jaguar - Cray XT5<ref>https://www.top500.org/system/176029</ref>. На момент экспериментов данный суперкомпьютер имел следующую конфигурацию: 18,688 вычислительных узлов с двумя шестнадцатиядерными процессорами AMD Opteron 2435 (Istanbul) 2.6 GHz, 16 GB of DDR2-800 оперативной памяти, и SeaStar 2+ роутер. Всего он состоял из 224,256 вычислительных ядер, 300 TB памяти, и пиковой производительностью 2.3 petaflops.<br />
<br />
Реализация алгоритма была выполнена на языке программирования C с использованием MPI.<br />
<br />
Объем данных составлял 84 ГБ, количество объектов (d-мерных векторов) n равнялось 1,024,767,667, размерность векторов <math>d</math> равнялась 22, количество кластеров <math>k</math> равнялось 1000. <br />
<br />
На рис. 8 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров. Можно отметить, что время, затраченное на чтение данных и запись результатов кластеризации, практически не изменяется с увеличением количества задействованных процессоров. Время же работы самого алгоритма кластеризации уменьшается с увеличением количества процессоров.<br />
<br />
[[Файл:k-means-proc-scalability.png|thumb|center|700px|Рис. 8. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (из работы: Kumar etc. 2011).]]<br />
<br />
Также было произведено самостоятельное исследование масштабируемости алгоритма k-means. Исследование производилось на суперкомпьютере "Blue Gene/P"<ref>http://hpc.cmc.msu.ru/bgp</ref>.<br />
<br />
Набор и границы значений изменяемых параметров запуска реализации алгоритма:<br />
<br />
* число процессоров [1, 2, 4, 8, 16, 32, 64, 128, 256, 512];<br />
* количество объектов [5000, 10000, 25000, 50000].<br />
<br />
Был использован набор данных ''Dataset for Sensorless Drive Diagnosis Data Set''<ref>PASCHKE, Fabian ; BAYER, Christian ; BATOR, Martyna ; MÖNKS, Uwe ; DICKS, Alexander ; ENGE-ROSENBLATT, Olaf ; LOHWEG, Volker: Sensorlose Zustandsüberwachung an Synchronmotoren, Bd. 46. In: HOFFMANN, Frank; HÃœLLERMEIER, Eyke (Hrsg.): Proceedings 23. Workshop Computational Intelligence. Karlsruhe : KIT Scientific Publishing, 2013 (Schriftenreihe des Instituts für Angewandte Informatik - Automatisierungstechnik am Karlsruher Institut für Technologie, 46), S. 211-225</ref> из репозитория ''Machine learning repository''<ref>https://archive.ics.uci.edu/ml/datasets/Dataset+for+Sensorless+Drive+Diagnosis</ref>.<br />
<br />
Исследуемый набор данных содержит векторы, размерность которых равна 49. Компоненты векторов являются вещественными числами. Количество кластеров равно 11. Пропущенные значения отсутствуют.<br />
<br />
Для исследования масштабируемости алгоритма была использована реализация на языке C с использованием MPI<ref>http://users.eecs.northwestern.edu/~wkliao/Kmeans/index.html</ref>. Код можно найти здесь: https://github.com/serban/kmeans. Данная реализация предоставляет возможность распараллеливать решение задачи с помощью технологий MPI, OpenMP И CUDA. Для запуска MPI-версии программы использовалась цель "mpi_main" Makefile.<br />
<br />
На рис. 9 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (использовались логарифмические оси). Разными цветами помечены запуски, соответствующие разным количествам объектам, участвующих в кластеризации. Можно видеть близкое к линейному увеличение времени работы программы в зависимости от количества процессоров. Также можно видеть увеличение времени работы алгоритма при увеличении количества объектов.<br />
[[Файл:Plot_1.png|thumb|center|900px|Рис. 9. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
На рис. 10 показана эта же зависимость, только в трехмерном пространстве. По аналогии с рис. 9, были использованы логарифмические оси. Как и в случае двумерного рисунка, можно видеть близкое к линейному увеличение времени работы программы.<br />
[[Файл:Kmeans-3d.png|thumb|center|900px|Рис. 10. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
==== Реализация 4 ====<br />
<br />
Исследование масштабируемости данной параллельной реализации алгоритма k-средних также проводилось на суперкомпьютере "Ломоносов". Параллельная реализация была написана самостоятельно на языке C, [http://git.algowiki-project.org/iaegorov/Egorov-Bogomazov-k-means/tree/master ссылка на реализацию]. Так как на каждой итерации число действий на единицу данных не велико и данные должны быть собраны вместе при перерасчете центроидов, было решено для ускорения вычислений воспользоваться только OpenMP без использовании MPI.<br />
<br />
Код собирался под gcc c опцией -fopenmp. Код считался на одном процессоре, технология hyperthreading не использовалась.<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 16] с увеличением в 2 раза;<br />
* размер данных [100000 : 1600000] с увеличением в 2 раза.<br />
<br />
В результате проведённых экспериментов были получены следующие данные:<br />
<br />
* Максимальная эффективность в точке достигается при переходе от 1 потока на 4 при минимальном размере данных, она равна <math>87,5%</math>.<br />
* Усредненная максимальная эффективность достигается при переходе с одного потока на два. Среднее время вычислений на всех рассмотренных потока снижается с 16,33 до 11.87 секунд, поэтому формально эффективность <math>= 16.33 / 11.87 / 2 \approx 68,4\%</math><br />
* Минимальная эффективность в точке достигается при переходе от 1 потока на 16 при размере данных 800000, она равна <math>11,1\%</math>.<br />
* Усредненная минимальная эффективность наблюдается при переходе с одного на максимальное рассматриваемое в эксперименте число потоков, равное 16. Время вычисления изменяется с 16,33 до 7,6 секунд, поэтому формально эффективность <math> = 16.33 / 7.6 / 16 \approx 14,9\%</math><br />
<br />
Ниже приведены графики зависимости вычислительного времени алгоритма и его эффективности от изменяемых параметров запуска — размера данных и числа процессоров:<br />
<br />
[[file:Egorov_Bogomazov_Time.jpg|thumb|center|700px|Рис. 11. Параллельная реализация алгоритма k-средних. Изменение вычислительного времени алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь видно, что время выполнения операций алгоритма плавно убывает по каждому из параметров, причем скорость убывания по параметру числа процессоров выше, чем в зависимости от размерности задачи.<br />
<br />
[[file:Egorov_Bogomazov_Efficiency.jpg|thumb|center|700px|Рис. 12. Параллельная реализация алгоритма k-средних. Изменение эффективности алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь построена эффективность перехода от последовательной реализации к параллельной. Рассчитывается она по формуле ''Время вычисления на 1 потоке / Время вычисления на <math>T</math> потоках / <math>T</math>'', где <math>T</math> — это число потоков. При вычислении на 1 процессоре она равна 100 \% в силу используемой формулы, что и отражено на графике.<br />
<br />
Проведем оценки масштабируемости:<br />
<br />
'''По числу процессов''' — при увеличении числа процессов эффективность уменьшается на всей области рассматриваемых значений, причем темп убывания замедляется с ростом числа процессов.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений вначале кратковременно возрастает, но затем начинает относительно равномерно убывать на всей рассматриваемой области.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений в общем случае постепенно убывает. На малых данных она выходит на пик мощности, являющийся максимумом эффективности в исследуемых условиях, но затем возвращается к процессу убывания.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
В однопоточном режиме на наборах данных, представляющих практический интерес (порядка нескольких десятков тысяч векторов и выше), время работы алгоритма неприемлемо велико. Благодаря свойству массового параллелизма должно наблюдаться значительное ускорение алгоритма на многоядерных архитектурах (Intel Xeon), а также на графических процессорах, даже на мобильных вычислительных системах (ноутбуках), оснащенных видеокартой. Алгоритм <i>k</i>-means также будет демонстрировать значительное ускорение на сверхмощных вычислительных комплексах (суперкомпьютерах, системах облачных вычислений<ref>"Issa. "Performance characterization and analysis for Hadoop K-means iteration". Journal of Cloud Computing, 2016"</ref>).<br />
<br />
На сегодняшний день существует множество реализаций алгоритма <i>k</i>-means, в частности, направленных на оптимизацию параллельной работы на различных архитектурах<ref>"Raghavan R. A fast and scalable hardware architecture for K-means clustering for big data analysis : дис. – University of Colorado Colorado Springs. Kraemer Family Library, 2016."</ref><ref>"Yang, Luobin, et al. "High performance data clustering: a comparative analysis of performance for GPU, RASC, MPI, and OpenMP implementations." The Journal of supercomputing 70.1 (2014): 284-300."</ref><ref>"Li, You, et al. "Speeding up k-means algorithm by GPUs." Computer and Information Technology (CIT), 2010 IEEE 10th International Conference on. IEEE, 2010."</ref>. Предлагается множество адаптаций алгоритма под конкретные архитектуры. Например, авторы работы<ref>"Kanan, Gebali, Ibrahim. "Fast and Area-Efficient Hardware Implementation of the K-means<br />
Clustering Algorithm". WSEAS Transactions on circuits and systems. Vol. 15. 2016"</ref> производят перерасчет центров кластеров на этапе распределения векторов по кластерам.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
==== Открытое программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.icpsr.umich.edu/CrimeStat/ CrimeStat]</li> Программное обеспечение, созданное для операционных систем Windows, предоставляющее инструменты статистического и пространственного анализа для решения задачи картирования преступности.<br />
<li>[http://juliastats.github.io Julia]</li> Высокоуровневый высокопроизводительный свободный язык программирования с динамической типизацией, созданный для математических вычислений, содержит реализацию k-means.<br />
<li>[https://mahout.apache.org Mahout]</li> Apache Mahout - Java библиотека для работы с алгоритмами машинного обучения с использованием MapReduce. Содержит реализацию k-means.<br />
<li>[https://www.gnu.org/software/octave/ Octave]</li> Написанная на C++ свободная система для математических вычислений, использующая совместимый с MATLAB язык высокого уровня, содержит реализацию k-means.<br />
<li>[http://spark.apache.org/docs/latest/mllib-clustering.html Spark]</li> Распределенная реализация k-means содержится в библиотеке Mlib для работы с алгоритмами машинного обучения, взаимодействующая с Python библиотекой NumPy и библиотека R.<br />
<li>[http://torch.ch Torch]</li> MATLAB-подобная библиотека для языка программирования Lua с открытым исходным кодом, предоставляет большое количество алгоритмов для глубинного обучения и научных расчётов. Ядро написано на Си, прикладная часть выполняется на LuaJIT, поддерживается распараллеливание вычислений средствами CUDA и OpenMP. Существуют реализации k-means.<br />
<li>[http://www.cs.waikato.ac.nz/ml/weka/ Weka]</li> Cвободное программное обеспечение для анализа данных, написанное на Java. Содержит k-means и x-means.<br />
<li>[http://accord-framework.net/docs/html/T_Accord_MachineLearning_KMeans.htm Accord.NET]</li> C# реализация алгоритмов k-means, k-means++, k-modes.<br />
<li>[http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html OpenCV]</li> Написанная на С++ библиотека, направленная в основном на решение задач компьютерного зрения. Содержит реализацию k-means.<br />
<li>[http://mlpack.org/ MLPACK]</li> Масштабируемая С++ библиотека для работы с алгоритмами машинного обучения, содержит реализацию k-means.<br />
<li>[https://www.scipy.org/ SciPy]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[http://scikit-learn.org/ scikit-learn]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[https://www.r-bloggers.com/k-means-clustering-in-r/ R]</li> Язык программирования для статистической обработки данных и работы с графикой, а также свободная программная среда вычислений с открытым исходным кодом в рамках проекта GNU, содержит три реализации k-means.<br />
<li>[http://elki.dbs.ifi.lmu.de ELKI]</li> Java фреймворк, содержащий реализацию k-means, а также множество других алгоритмов кластеризации.<br />
</ol><br />
<br />
==== Проприетарное программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.ayasdi.com/blog/bigdata/topological-data-analysis-of-oil-and-gas-petrophysical-data/ Ayasdi]</li><br />
<li>[http://www.stata.com/features/cluster-analysis/ Stata]</li><br />
<li>[http://mathworld.wolfram.com/K-MeansClusteringAlgorithm.html Mathematica]</li><br />
<li>[http://www.mathworks.com/help/stats/kmeans.html?requestedDomain=www.mathworks.com MATLAB]</li><br />
<li>[https://support.sas.com/rnd/app/stat/procedures/fastclus.html SAS]</li><br />
<li>[http://docs.rapidminer.com/studio/operators/modeling/segmentation/k_means.html RapidMiner]</li><br />
<li>[https://blogs.sap.com/2013/03/28/sap-hana-pal-k-means-algorithm-or-how-to-do-customer-segmentation-for-the-telecommunications-industry/ SAP HANA]</li><br />
</ol><br />
<br />
== Литература ==<br />
<br />
<references /></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_k_%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D1%85_(k-means)&diff=25251Алгоритм k средних (k-means)2018-01-16T17:39:31Z<p>Konshin: /* Ресурс параллелизма алгоритма */</p>
<hr />
<div>Основные авторы статьи (разделы 1, 2.4.1, 2.6-2.7, 3):<br />
[https://algowiki-project.org/ru/Участник:Бротиковская_Данута<b>Д.Бротиковская</b>] и<br />
[https://algowiki-project.org/ru/Участник:DennZo1993<b>Д.Зобнин</b>]<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:IanaV<b>Я.А.Валуйская</b>] и<br />
[https://algowiki-project.org/ru/Участник:GlotovES<b>Е.С.Глотов</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Parkhomenko<b>П.А.Пархоменко</b>] и<br />
[https://algowiki-project.org/ru/Участник:Ivan.mashonskiy<b>И.Д.Машонский</b>] (раздел 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Илья_Егоров<b>И.Егоров</b>] и<br />
[https://algowiki-project.org/ru/Участник:Богомазов_Евгений<b>Е.Богомазов</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Алгоритм <math>k</math> средних (<i>k</i>-means)<br />
| serial_complexity = <math>O(ikdn)</math><br />
| input_data = <math> dn </math><br />
| output_data = <math> n </math><br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Алгоритм <b><i>k средних</i></b> (англ. <i>k</i>-means) - один из алгоритмов машинного обучения, решающий задачу кластеризации.<br />
Этот алгоритм является неиерархическим<ref>"https://ru.wikipedia.org/wiki/Иерархическая_кластеризация"</ref>, итерационным методом кластеризации<ref>"https://ru.wikipedia.org/wiki/Кластерный_анализ"</ref>, он получил большую популярность благодаря своей простоте, наглядности реализации и достаточно высокому качеству работы. <br />
Был изобретен в 1950-х годах математиком <i>Гуго Штейнгаузом</i><ref>Steinhaus, Hugo. "Sur la division des corp materiels en parties." Bull. Acad. Polon. Sci 1.804 (1956): 801.</ref> и почти одновременно <i>Стюартом Ллойдом</i><ref>Lloyd, S. P. "Least square quantization in PCM. Bell Telephone Laboratories Paper. Published in journal much later: Lloyd, SP: Least squares quantization in PCM." IEEE Trans. Inform. Theor.(1957/1982).</ref>. Особую популярность приобрел после публикации работы <i>МакКуина</i><ref>MacQueen, James. "Some methods for classification and analysis of multivariate observations." Proceedings of the fifth Berkeley symposium on mathematical statistics and probability. Vol. 1. No. 14. 1967.</ref> в 1967.<br />
<br />
Алгоритм представляет собой версию EM-алгоритма<ref>"https://ru.wikipedia.org/wiki/EM-алгоритм"</ref>, применяемого также для разделения смеси гауссиан. Основная идея алгоритма <i>k</i>-means заключается в том, что данные произвольно разбиваются на кластеры, после чего итеративно перевычисляется центр масс для каждого кластера, полученного на предыдущем шаге, затем векторы разбиваются на кластеры вновь в соответствии с тем, какой из новых центров оказался ближе по выбранной метрике.<br />
<br />
Цель алгоритма заключается в разделении <math>n</math> наблюдений на <math>k</math> кластеров таким образом, чтобы каждое наблюдение принадлежало ровно одному кластеру, расположенному на наименьшем расстоянии от наблюдения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
<b>Дано:</b><br />
* набор из <math>n</math> наблюдений <math>X=\{\mathbf{x}_1, \mathbf{x}_2, ..., \mathbf{x}_n\}, \mathbf{x}_i \in \mathbb{R}^d, \ i=1,...,n</math>;<br />
* <math>k</math> - требуемое число кластеров, <math>k \in \mathbb{N}, \ k \leq n</math>.<br />
<br />
<b>Требуется:</b><br />
<br />
Разделить множество наблюдений <math>X</math> на <math>k</math> кластеров <math>S_1, S_2, ..., S_k</math>:<br />
* <math>S_i \cap S_j= \varnothing, \quad i \ne j</math><br />
<br />
* <math>\bigcup_{i=1}^{k} S_i = X</math><br />
<br />
<b>Действие алгоритма:</b><br />
<p>Алгоритм <i>k</i>-means разбивает набор <math>X</math> на <math>k</math> наборов <math>S_1, S_2, ..., S_k,</math> таким образом, чтобы минимизировать сумму квадратов расстояний от каждой точки кластера до его центра (центр масс кластера). Введем обозначение, <math>S=\{S_1, S_2, ..., S_k\}</math>. Тогда действие алгоритма <i>k</i>-means равносильно поиску:</p><br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math>\arg\min_{S} \sum\limits_{i=1}^k \sum\limits_{\mathbf{x} \in S_i} \rho(\mathbf{x}, \mathbf{\mu}_i )^2,</math></td><br />
<td align="right"><math>(1)</math></td><br />
</tr><br />
</table><br />
где <math>\mathbf{\mu}_i</math> &ndash; центры кластеров, <math>i=1,...,k, \quad \rho(\mathbf{x}, \mathbf{\mu}_i)</math> &ndash; функция расстояния между <math>\mathbf{x}</math> и <math>\mu_i</math><br />
<br />
<b>Шаги алгоритма:</b><br />
<ol><br />
<li><br />
<b>Начальный шаг: инициализация кластеров</b><br />
<p>Выбирается произвольное множество точек <math>\mu_i, \ i=1,...,k,</math> рассматриваемых как начальные центры кластеров: <math>\mu_i^{(0)} = \mu_i, \quad i=1,...,k</math></p><br />
</li><br />
<li><br />
<b>Распределение векторов по кластерам</b><br />
<p><b>Шаг</b> <math>t: \forall \mathbf{x}_i \in X, \ i=1,...,n: \mathbf{x}_i \in S_j \iff j=\arg\min_{k}\rho(\mathbf{x}_i,\mathbf{\mu}_k^{(t-1)})^2</math></p><br />
</li><br />
<li><br />
<b>Пересчет центров кластеров</b><br />
<p><b>Шаг </b> <math>t: \forall i=1,...,k: \mu_i^{(t)} = \cfrac{1}{|S_i|}\sum_{\mathbf{x}\in S_i}\mathbf{x}</math></p><br />
</li><br />
<li><br />
<b>Проверка условия останова:</b><br />
<p></p><br />
* '''if''' <math>\exists i\in \overline{1,k}: \mu_i^{(t)} \ne \mu_i^{(t-1)}</math> '''then'''<br />
** <math>t = t + 1</math>;<br />
** goto 2;<br />
* '''else'''<br />
** '''stop'''<br />
</li><br />
</ol><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром являются шаги 2 и 3 приведенного выше алгоритма: <b><i>распределение векторов по кластерам</i></b> и <b><i>пересчет центров кластеров</i></b>. <br />
<br />
<p><br />
<b><i>Распределение векторов</i></b> по кластерам предполагает вычисление расстояний между каждым вектором <math>\mathbf{x}_i \in X, \ i= 1,...,n</math> и центрами кластера <math>\mathbf{\mu}_j, \ j= 1,...,k</math>. Таким образом, данный шаг предполагает <math>kn</math> вычислений расстояний между <math>d</math>-мерными векторами. <br />
</p><br />
<p><br />
<b><i>Пересчет центров кластеров</i></b> предполагает <math>k</math> вычислений центров масс <math>\mathbf{\mu}_i</math> множеств <math>S_i, \ i=1,...,k,</math> представленных выражением в шаге 3 представленного выше алгоритма.<br />
</p><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
<b>Инициализация центров масс <math>\mu_1, ..., \mu_k</math></b>. <br />
<br />
Наиболее распространенными являются следующие стратегии:<br />
<ul><br />
<li><br />
<b>Метод Forgy</b><br>В качестве начальных значений <math>\mu_1, ..., \mu_k</math> берутся случайно выбранные векторы.<br />
</li><br />
<li><br />
<b>Метод случайно разделения (Random Partitioning)</b><br>Для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> выбирается случайным образом кластер <math>S_1, ..., S_k</math>, после чего для каждого полученного кластера вычисляются значения <math>\mu_1, ..., \mu_k</math>.<br />
</li><br />
</ul><br />
<br />
<b>Распределение векторов по кластерам</b><br />
<br />
Для этого шага алгоритма между векторами <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> и центрами кластеров <math>\mu_1,...,\mu_k</math> вычисляются <b>расстояния</b> по формуле (как правило, используется Евлидово расстояние):<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mathbf{v}_1, \mathbf{v}_2 \in \mathbb{R}^d, \quad \rho(\mathbf{v}_1, \mathbf{v}_2) = \lVert \mathbf{v}_1- \mathbf{v}_2 \rVert= \sqrt{\sum_{i=1}^{d}(\mathbf{v}_{1,i} - \mathbf{v}_{2,i})^2}</math></td><br />
<td align="right"><math>(2)</math></td><br />
</tr><br />
</table><br />
<br />
<b>Пересчет центров кластеров</b><br />
<br />
Для этого шага алгоритма производится пересчет центров кластера по <b>формуле вычисления центра масс</b>:<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mu = \cfrac{1}{|S|}\sum_{\mathbf{x}\in S}\mathbf{x}</math></td><br />
<td align="right"><math>(3)</math></td><br />
</tr><br />
</table><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
'''1.''' Инициализировать центры кластеров <math>\mathbf{\mu}_i^{(1)}, \ i=1,...,k</math><br><br />
'''2.''' <math>t \leftarrow 1</math><br><br />
'''3.''' Распределение по кластерам<br><br />
<math>\quad S_i^{(t)}=\{\mathbf{x}_p: \lVert\mathbf{x}_p-\mathbf{\mu}_i^{(t)}\rVert^2 \leq \lVert\mathbf{x}_p-\mathbf{\mu}_j^{(t)}\rVert^2 \quad \forall j=1,...,k\},</math><br><br />
<math>\quad</math>где каждый вектор <math>\mathbf{x}_p</math> соотносится единственному кластеру <math>S^{(t)}</math><br><br />
'''4.''' Обновление центров кластеров<br><br />
<math>\quad \mathbf{\mu}_i^{(t+1)} = \frac{1}{|S^{(t)}_i|} \sum_{\mathbf{x}_j \in S^{(t)}_i} \mathbf{x}_j </math><br><br />
'''5.''' '''if''' <math>\exists i \in \overline{1,k}: \mathbf{\mu}_i^{(t+1)} \ne \mathbf{\mu}_i^{(t)}</math> '''then'''<br><br />
<math>\quad t = t + 1</math>;<br><br />
<math>\quad</math>goto '''3''';<br><br />
<math>~~~</math>'''else'''<br><br />
<math>\quad</math>'''stop'''<br><br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
<div style="padding-bottom: 20px"><br />
<p>Обозначим <math>\Theta_{\rm centroid}^{d, m}</math> временную сложность вычисления центорида кластера, число элементов которого равна <math>m</math>, в d-мерном пространстве.</p><br />
<p>Аналогично <math>\Theta_{\rm distance}^d</math> &ndash; временная сложность вычисления расстояния между двумя d-мерными векторами.</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<b>Сложность шага инициализации <math>k</math> кластеров мощности <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm init}^{k, d, m}</math></b><br />
<ul><br />
<li><i>Стратерия Forgy</i>: вычисления не требуются, <math>\Theta_{\rm init}^{k, d, m} = 0</math></li><br />
<li><i>Стратегия случайного разбиения</i>: вычисление центров <math>k</math> кластеров, <math>\Theta_{\rm init}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}, m \le n</math></li><br />
</ul><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Cложность шага распределения d мерных векторов по <math>k</math> кластерам &ndash; <math>\Theta_{\rm distribute}^{k, d}</math></b></p><br />
<p><br />
На этом шаге для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> вычисляется <math>k</math> расстояний до центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math><br />
</p><br />
<p align="center"><br />
<math>\Theta_{\rm distribute}^{k, d} = n \cdot k \cdot \Theta_{\rm distance}^d</math><br />
</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><br />
<b>Сложность шага пересчета центров <math>k</math> кластеров размера <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm recenter}^{k, d, m}</math></b><br />
</p><br />
<p>На этом шаге вычисляется <math>k</math> центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math></p><br />
<p align="center"><math>\Theta_{\rm recenter}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}</math> </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm centroid}^{d, m}</math> для кластера, число элементов которого равно <math>m</math></b></p><br />
<p align="center"><math>\Theta_{\rm centroid}^{d, m}</math> = <math>m \cdot d</math> сложений + <math>d</math> делений </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm distance}^d</math> в соответствие с формулой <math>(2)</math></b></p><br />
<p align="center"><math>\Theta_{\rm distance}^d</math> = <math>d</math> вычитаний + <math>d</math> умножений + <math>(d-1)</math> сложение</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Предположим, что алгоритм сошелся за <math>i</math> итераций, тогда временная сложность алгоритма <math>\Theta_{\rm k-means}^{d, n}</math></p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le \Theta_{\rm init}^{k, d, n} + i(\Theta_{\rm distribute}^{k, d} + \Theta_{\rm recenter}^{k, d, n})</math></b></p><br />
</div><br />
<div style="padding-bottom: 5px"><br />
<p>Операции сложения/вычитания:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le knd+ i(kn(2d-1) + knd) = knd+ i(kn(3d-1)) \thicksim O(ikdn)</math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Операции умножения/деления:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le kd + i(knd + kd) = kd + ikd(n+1) \thicksim O(ikdn) </math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Получаем, что <b>временная сложность</b> алгоритма <b><i>k</i>-means</b> кластеризации <math>n</math> <b>d-мерных</b> векторов на <math>k</math> кластеров за <math>i</math> итераций:</p><br />
<p align="center"><b><math> \Theta_{\rm k-means}^{d, n} \thicksim O(ikdn) </math></b></p><br />
</div><br />
<br />
=== Информационный граф ===<br />
<br />
Рассмотрим информационный граф алгоритма. Алгоритм <i>k</i>-means начинается с этапа инициализации, после которого следуют итерации, на каждой из которых выполняется два последовательных шага (см. [[#Схема реализации последовательного алгоритма|"Схема реализации последовательного алгоритма"]]): <br />
* распределение векторов по кластерам<br />
* перерасчет центров кластеров<br />
<br />
Поскольку основная часть вычислений приходится на шаги итераций, распишем информационные графы данных шагов.<br />
<br />
<p><b>Распределение векторов по кластерам</b></p><br />
Информационный граф шага распределения векторов по кластерам представлен на ''рисунке 1''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также центры кластеров <math>\mathbf{\mu}_1, ... \mathbf{\mu}_k</math>, вычисленные ранее (на шаге инициализации, если рассматривается первая итерация алгоритма, или на шаге пересчета центров кластеров предыдущей итерации в противном случае). Каждая пара векторов данных <math>\mathbf{x}_i, \ i=1,...,n,</math> и центров кластера <math>\mathbf{\mu}_j, \ j=1,...,k</math> : (<math>\mathbf{x}_i</math>, <math>\mathbf{\mu}_j</math>) подаются на независимые узлы <i>"d"</i> вычисления расстояния между векторами (более подробная схема вычисления расстояния представлена далее, ''рисунок 2''). Далее узлы вычисления расстояния <i>"d"</i>, соответствующие одному и тому же исходному вектору <math>\mathbf{x}_i</math> передаются на один узел <i>"m"</i>, где далее происходит вычисление новой метки кластера для каждого вектора <math>\mathbf{x}_i</math> (берется кластер с минимальным результатом вычисления расстояния). На выходе графа выдаются метки кластеров , <math>L_1, ..., L_n</math>, такие что <math>\forall \mathbf{x}_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>. <br />
<br />
[[file:Clusterization.png|thumb|center|800px|Рис. 1. Схема распределения векторов по кластерам. ''d'' &ndash; вычисление расстояния между векторами; ''m'' &ndash; вычисление минимума.]]<br />
<br />
<p><b>Вычисление расстояния между векторами</b></p><br />
Подробная схема вычисления расстояния между векторами <math>\mathbf{x}_i, \mathbf{\mu}_j</math> представлена на ''рисунке 2''. Как показано на графе, узел вычисления расстояния между векторами <i>"d"</i> состоит из шага взятия разности между векторами (узел "<math>-</math>") и взятия нормы получившегося вектора разности (узел "<math>||\cdot||^2</math>"). Более подробно, вычисление расстояния между векторами <math>\mathbf{x}_i = {x_{i1,}, ...,{x_{in}}}, \mathbf{\mu}_j = {\mu_{j1}, ...,\mu_{jn}}</math> может быть представлено как вычисление разности между каждой парой компонент <math>(x_{iz}, \mu_{jz}), \ z=1,...,d</math> (узел "<math>-</math>"), далее возведение в квадрат для каждого узла "<math>-</math>" (узел "<math>()^2</math>") и суммирования выходов всех узлов "<math>()^2</math>" (узел "<math>+</math>"). <br />
<br />
[[file:dist_calc.png|thumb|center|800px|Рис. 2. Схема вычисления расстояния между вектором и центром кластера.]]<br />
<br />
<p><b>Пересчет центров кластеров</b></p><br />
Информационный граф шага пересчета центров кластеров представлен на ''рисунке 3''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также им соответствующие метки кластера, <math>L_1, ..., L_n</math>, такие что <math>\forall x_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>, вычисленные на этапе распределения векторов по кластерам. Все векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math> подаются в узлы <math>+_1, ...+_k</math>, каждый узел <math>+_m, \ m = 1,...,k,</math> соответствует операции сложения векторов кластера с номером <math>m</math>. Метки кластера <math>L_1, ..., L_n</math> также совместно передаются на узлы <math>S_m, \ m=1,...,k</math>, на каждом из которых вычисляется количество векторов в соответствующем кластере (количество меток с соответствующим значением). Далее каждая пара выходов узлов <math>+_m</math> и <math>S_m</math> подается на узел "<math>/</math>", где производится деление суммы векторов кластера на количество элементов в нем. Значения, вычисленные на узлах "<math>/</math>", присваиваются новым центрам кластеров (выходные значения графа).<br />
<br />
[[file:Recluster.png|thumb|center|800px|Рис. 3. Схема пересчета центров кластеров]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
<p><br />
Работа алгоритма состоит из <math>i</math> итераций, в каждой из которых происходит <b>распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b>, а также <b>пересчет центров кластеров в <math>d</math>-мерном пространстве</b>. В шаге <b>распределения <math>d</math>-мерных векторов по <math>k</math> кластерам</b> расстояния между вектором и центрами кластеров вычисляются независимо (отсутствуют информационные зависимости). <b>Центры масс кластеров</b> также пересчитываются независимо друг от друга. Таким образом, имеет место [[глоссарий#Массовый параллелизм|''массовый параллелизм'']]. Вычислим параллельную сложность <math>\Psi_*</math> каждого из шагов, а также параллельную сложность всего алгоритма, <math>\Psi_{\rm k-means}</math>. Будем исходить из предположения, что может быть использовано любое необходимое число потоков.<br />
</p><br />
<br />
<p><b>Распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b></p> <br />
<p><br />
Поскольку на данном шаге для каждой пары векторов <math>\mathbf{x}_i, \ i=1,...,n</math> и <math>\mathbf{\mu}_j, \ j=1,...,k,</math> операции вычисления расстояния не зависят друг от друга, они могут выполняться параллельно. Тогда, разделив все вычисление расстояний на <math>n</math> потоков, получим, что в каждом потоке будет выполняться только одна операция вычисления расстояния между векторами размерности <math>d</math>. При этом каждому вычислительному потоку передаются координаты центров всех кластеров <math>\mathbf{\mu}_1, ..., \mathbf{\mu}_k</math>. Таким образом, параллельная сложность данного шага определяется <i> сложностью параллельной операции вычисления расстояния между <math>d</math>-мерными векторами</i>, <math>\Psi_{\rm distance}^d</math> и <i>сложностью определения наиболее близкого кластера</i> (паралельное взятие минимума по расстояниям), <math>\Psi_{\rm min}^k</math>. Для оценки <math>\Psi_{\rm distance}^d</math> воспользуемся [[Нахождение_частных_сумм_элементов_массива_сдваиванием | параллельной реализацией нахождения частичной суммы элементов массива путем сдваивания]]. Аналогично, <math>\Psi_{\rm min}^k = log(k)</math>. В результате, <math>\Psi_{\rm distance}^d = O(\log(d))</math>. Таким образом: <br />
</p><br />
<p align="center"><math>\Psi_{\rm distribute}^{k, d} = \Psi_{\rm distance}^d + \Psi_{\rm min}^k = O(log(d))+O(log(k)) = O(log(kd))</math></p><br />
<br />
<p><b>Пересчет центров кластеров в d-мерном пространстве</b></p><br />
<p><br />
Поскольку на данном шаге для каждого из <math>k</math> кластеров центр масс может быть вычислен независимо, данные операции могут быть выполнены в отдельных потоках. Таким образом, параллельная сложность данного шага, <math>\Psi_{\rm recenter}^{k, d}</math>, будет определяться <i>параллельной сложностью вычисления одного центра масс кластера размера <math>m</math></i>, <math>\Psi_{\rm recenter}^{k, d}</math>, а так как <math>m \le n \Rightarrow \Psi_{\rm recenter}^{d, m} \le \Psi_{\rm recenter}^{d, n}</math>. Сложность вычисления центра масс кластера <math>d</math>-мерных векторов размера n аналогично предыдущим вычислениям равна <math>O(log(n))</math>. Тогда: <br />
</p><br />
<p align="center"><math>\Psi_{\rm recenter}^{k, d} \le \Psi_{\rm recenter}^{d, n} = O(log(n))</math></p><br />
<br />
<p><b>Общая параллельная сложность алгоритма</b></p><br />
<p><br />
На каждой итерации необходимо обновление центров кластеров, которые будут использованы на следующей итерации. Таким образом, итерационный процесс выполняется последовательно<ref>Zhao, Weizhong, Huifang Ma, and Qing He. "Parallel k-means clustering based on mapreduce." IEEE International Conference on Cloud Computing. Springer Berlin Heidelberg, 2009.</ref>. Тогда, поскольку сложность каждой итерации определяется <math>\Psi_{\rm distribute}^{k, d}</math> и <math>\Psi_{\rm recenter}</math>, сложность всего алгоритма, <math>\Psi_{\rm k-means}</math> в предположении, что было сделано <math>i</math> операций определяется выражением<br />
</p><br />
<p align="center"><math>\Psi_{\rm k-means} \approx i \cdot (\Psi_{\rm distribute}^{k, d} + \Psi_{\rm recenter}^{k, d}) \le i \cdot O(log(kdn))</math></p><br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
<p><br />
<b>Входные данные</b><br><br />
<ul><br />
<li>Матрица из <math>n \cdot d</math> элементов <math>x_{i, j} \in \mathbb{R}, \ i=1,...,n, \ j=1,...,d,</math> &ndash; координат векторов (наблюдений).</li><br />
<li>Целое положительное число <math>k, \ k \le n</math> &ndash; количество кластеров.</li><br />
</ul><br />
</p><br />
<p><br />
<b>Объем входных данных</b><br><br />
<math>1</math> целое число + <math>n \cdot d</math> вещественных чисел (при условии, что координаты &ndash; вещественные числа).<br />
</p><br />
<p><br />
<b>Выходные данные</b><br><br />
<math>n</math> целых положительных чисел <math>L_1, ..., L_n</math>&ndash; номера кластеров, соотвествующие каждому вектору (при условии, что нумерация кластеров начинается с <math>1</math>).<br />
</p><br />
<p><br />
<b>Объем выходных данных</b><br><br />
<math>n</math> целых положительных чисел.<br />
</p><br />
<br />
=== Свойства алгоритма ===<br />
<br />
<p><b>[[глоссарий#Вычислительная мощность|''Вычислительная мощность'']]</b></p><br />
Вычислительная мощность алгоритма <i>k</i>-means равна <math>\frac{ikdn}{nd} = ki </math>, где <math>k</math> &ndash; число кластеров, <math>i</math> &ndash; число итераций алгоритма.<br />
<br />
<p><b>[[глоссарий#Детерминированность |''Детерминированность'']] и [[глоссарий#Устойчивость |''Устойчивость'']] </b></p><br />
Алгоритм <i>k</i>-means является итерационным. Количество итераций алгоритма в общем случае не фиксируется и зависит от начального расположения объектов в пространстве, параметра <math>k</math>, а также от начального приближения центров кластеров, <math>\mu_1, ..., \mu_k</math>. В результате этого может варьироваться результат работы алгоритма. При неудачном выборе начальных параметров итерационный процесс может сойтись к локальному оптимуму<ref>Von Luxburg, Ulrike. Clustering Stability. Now Publishers Inc, 2010.</ref>. По этим причинам алгоритм не является ни <b>детермирированным</b>, ни <b>устойчивым</b>.<br />
<br />
<p><b>Соотношение последовательной и параллельной сложности алгоритма</b></p><br />
<br />
<math>\frac{\Theta_{\rm k-means}}{\Psi_{\rm k-means}} = \frac{O(ikdn)}{O(i \cdot log(kdn))}</math><br />
<br />
<b>Сильные стороны алгоритма</b>:<br />
<ul><br />
<li><i>Сравнительно высокая эффективность при простоте реализации</i></li><br />
<li><i>Высокое качество кластеризации</i></li><br />
<li><i>Возможность распараллеливания</i></li><br />
<li><i>Существование множества модификаций</i></li><br />
</ul><br />
<b>Недостатки алгоритма</b><ref>Ortega, Joaquín Pérez, Ma Del Rocío Boone Rojas, and María J. Somodevilla. "Research issues on K-means Algorithm: An Experimental Trial Using Matlab."</ref>:<br />
<ul><br />
<li><i>Количество кластеров является параметром алгоритма</i></li><br />
<li><br />
<p><i>Чувствительность к начальным условиям</i></p><br />
<p>Инициализация центров кластеров в значительной степени влияет на результат кластеризации.</p><br />
</li><br />
<li><br />
<p><i>Чувствительность к выбросам и шумам</i></p><br />
<p>Выбросы, далекие от центров настоящих кластеров, все равно учитываются при вычислении их центров.</p><br />
</li><br />
<li><br />
<p><i>Возможность сходимости к локальному оптимуму</i></p><br />
<p>Итеративный подход не дает гарантии сходимости к оптимальному решению.</p><br />
</li><br />
<li><br />
<p><i>Использование понятия "среднего"</i></p><br />
<p>Алгоритм неприменим к данным, для которых не определено понятие "среднего", например, категориальным данным.</p><br />
</li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализации алгоритма <math>k</math> средних согласно [[Scalability methodology|методике]] AlgoWiki.<br />
<br />
==== Реализация 1 ====<br />
<br />
Исследование масштабируемости параллельной реализации алгоритма <i>k</i>-means проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Алгоритм реализован на языке C с использованием средств MPI.<br />
Для исследования масштабируемости проводилось множество запусков программы с разным значением параметра (количество векторов для кластеризации), а также с различным числом процессоров. Фиксировались результаты запусков &ndash; время работы <math>t</math> и количество произведенных итераций алгоритма <math>i</math>.<br />
<br />
Параметры запусков для экспериментальной оценки:<br />
<ul><br />
<li>Значения <b>количества векторов</b> <math>n</math>: 20'000, 30'000, 50'000, 100'000, 200'000, 300'000, 500'000, 700'000, 1'000'000, 1'500'000, 2'000'000.</li><br />
<li>Значения <b>количества процессоров</b> <math>p</math>: 1, 8, 16, 32, 64, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512.</li><br />
<li>Значение <b>количества кластеров</b> <math>k</math>: 100.</li><br />
<li>Значение <b>размерности векторов</b> <math>d</math>: 10.</li><br />
</ul><br />
<br />
Для проведения экспериментов были сгенерированы нормально распределенные псевдослучайные данные (с использованием Python библиотеки [http://scikit-learn.org/ scikit-learn]):<br />
<syntaxhighlight lang="python"><br />
from sklearn.datasets import make_classification<br />
X1, Y1 = make_classification(n_features=10, n_redundant=2, n_informative=8,<br />
n_clusters_per_class=1, n_classes=100,<br />
n_samples=2000000)<br />
</syntaxhighlight><br />
<br />
Для заданной конфигурации эксперимента (<math>n, d, p, k</math>) и полученных результатов (<math>t, i</math>) [[Глоссарий#Производительность|производительность]] и эффективность реализации расчитывались по формулам:<br />
<p><br />
<ul><li><math>{\rm Performance} = \frac{N_{\rm k-means}^{d, n}}{t}\ \ ({\rm FLOPS}),</math></li></ul><br />
где <math>N_{\rm k-means}^{d, n}</math> &ndash; точное число операций с плавающей точкой (операции с памятью, а также целочисленные операции не учитывались), вычисленное в соответствие с разделом [[#Последовательная сложность алгоритма| "Последовательная сложность алгоритма"]];<br />
</p><br />
<p><br />
<ul><li><math>{\rm Efficiency} = \frac{100 \cdot {\rm Performance}}{{\rm Performance}_{\rm Peak}^{p}}\ \ (\%),</math></li></ul><br />
где <math>{\rm Performance}_{\rm Peak}^{p}</math> &ndash; пиковая производительность суперкомпьютера при <math>p</math> процессорах, вычисленная согласно спецификациям Intel<sup>&reg;</sup> XEON<sup>&reg;</sup> X5670<ref>"http://ark.intel.com/ru/products/47920/Intel-Xeon-Processor-X5670-12M-Cache-2_93-GHz-6_40-GTs-Intel-QPI"</ref>.<br />
</p><br />
<br />
Графики зависимости производительности и эффективности параллельной реализации <i>k</i>-means от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>) представлены на рисунках 4 и 5, соответственно.<br />
<br />
[[file:Flops.png|thumb|center|800px|Рис. 4. Параллельная реализация <i>k</i>-means. График зависимости производительности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
[[file:Efficiency.png|thumb|center|800px|Рис. 5. Параллельная реализация <i>k</i>-means. График зависимости эффективности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
<br />
<p><br />
<b>В результате</b> экспериментальной оценки были получены следующие оценки эффективности реализации:<br />
<ul><br />
<li><b>Минимальное</b> значение: 0.000409 % достигается при <math>n=20'000, p=480</math></li><br />
<li><b>Максимальное</b> значение: 0.741119 % достигается при <math>n=300'000, p=1</math></li><br />
</ul><br />
</p><br />
<br />
<p><br />
Оценки масштабируемости реализации алгоритма <i>k</i>-means:<br />
<ul><br />
<li><b>По числу процессоров:</b> -0.002683 &ndash; эффективность убывает с ростом числа процессоров. Данный результат вызван ростом накладных расходов для обеспечения параллельного выполнения алгоритма.</li><br />
<li><b>По размеру задачи:</b> 0.002779 &ndash; эффективность растет с ростом числа векторов. Данный результат вызван тем, что при увеличении размера задачи, количество вычислений растет по сравнению с временем, затрачиваемым на пересылку данных.</li><br />
<li><b>Общая оценка:</b> -0.000058 &ndash; можно сделать вывод, что в целом эффективность реализации незначительно уменьшается с ростом размера задачи и числа процессоров.</li><br />
</ul><br />
</p><br />
<br />
[https://github.com/serban/kmeans Использованная параллельная реализация алгоритма <i>k</i>-means]<br />
<br />
==== Реализация 2 ====<br />
<br />
Исследование также проводилось на суперкомпьютере "Ломоносов".<br />
<br />
<p>Набор данных для тестирования состоял из 946000 векторов размерности 2 (координаты на сфере)</p><br />
<p>Набор и границы значений изменяемых параметров запуска реализации алгоритма:</p><br />
<br />
* число процессов (виртуальных ядер) [8 : 512];<br />
* число кластеров [128 : 384].<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации <math>2,47%</math> достигается при делении исходных данных на 128 кластеров с использованием 512 процессов;<br />
* максимальная эффективность реализации <math>7,13%</math> достигается при делении исходных данных на 352 кластера с использованием 8 процессов.<br />
На рисунках 6 и 7, соответственно, представлены графики зависимости производительности и эффективности параллельной реализации <i>k</i>-means от числа кластеров и числа процессов.<br />
<br />
[[file:kmeans_performance.jpg|thumb|center|720px|Рис. 6. График зависимости производительности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
По рис. 6 можно отметить практически полное отсутствие роста производительности с увеличением числа процессов от 256 до 512 при минимальном размере задачи. Это связано с быстрым ростом накладных расходов по отношению к крайне низкому объёму вычислений. При росте размерности задачи данный эффект пропадает, и при одновременном пропорциональном увеличении числа кластеров и числа процессов рост производительности становится близким к линейному.<br />
<br />
[[file:kmeans_efficiency.jpg|thumb|center|720px|Рис. 7. График зависимости эффективности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
Исследовалась [https://github.com/like-a-bauss/kmeans параллельная реализация алгоритма <i>k</i>-means на MPI]. <br />
<p>Были получены следующие оценки масштабируемости реализации алгоритма <i>k</i>-means:</p><br />
*<i>По числу процессов:</i> <math>-0.02209</math>. Следовательно, с ростом числа процессов эффективность уменьшается. На рис. 7 можно наблюдать плавное и равномерное снижение производительности по мере увеличения числа процессов при неизменном числе кластеров, что свидетельствует об относительно невысоком росте накладных расходов на передачу данных между процессами и преобладании объёма вычислений над объёмом пересылок данных по сети.<br />
*<i>По размеру задачи:</i> <math>0.01252</math>. Следовательно, с ростом размера задачи (числа кластеров) эффективность увеличивается. При этом объём пересылок данных по сети пропорционален <math>(n + k) \cdot p</math> (где <math>k</math> - число кластеров, <math>n</math> - число входных векторов, <math>p</math> - число процессов) таким образом, поскольку <math>k << n</math>, рост накладных расходов с ростом числа кластеров при неизменном числе процессов и входных векторов представляет собой незначительную величину.<br />
*<i>Общая оценка:</i> <math>-0.00081</math>. Таким образом, с ростом и размера задачи, и числа процессов эффективность уменьшается. Это связано с тем, что отношение объёма вычислений к объёму передаваемых данных изменяется пропорционально <math>{kn \over (n + k) \cdot p} \thicksim {k \over p}</math>, что представляет собой невысокий коэффициент, но при этом позволяет параллельной реализации не деградировать до нулевой эффективности при значительном увеличении числа процессов.<br />
<br />
==== Реализация 3 ====<br />
<br />
Исследование масштабируемости алгоритма k-means в зависимости от количества используемых процессов было проведено в статье Кумара<ref>Kumar, J., Mills, R. T., Hoffman, F. M., & Hargrove, W. W. (2011). Parallel k-means clustering for quantitative ecoregion delineation using large data sets. Procedia Computer Science, 4, 1602-1611.</ref>. Исследование происходило на суперкомпьютере Jaguar - Cray XT5<ref>https://www.top500.org/system/176029</ref>. На момент экспериментов данный суперкомпьютер имел следующую конфигурацию: 18,688 вычислительных узлов с двумя шестнадцатиядерными процессорами AMD Opteron 2435 (Istanbul) 2.6 GHz, 16 GB of DDR2-800 оперативной памяти, и SeaStar 2+ роутер. Всего он состоял из 224,256 вычислительных ядер, 300 TB памяти, и пиковой производительностью 2.3 petaflops.<br />
<br />
Реализация алгоритма была выполнена на языке программирования C с использованием MPI.<br />
<br />
Объем данных составлял 84 ГБ, количество объектов (d-мерных векторов) n равнялось 1,024,767,667, размерность векторов <math>d</math> равнялась 22, количество кластеров <math>k</math> равнялось 1000. <br />
<br />
На рис. 8 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров. Можно отметить, что время, затраченное на чтение данных и запись результатов кластеризации, практически не изменяется с увеличением количества задействованных процессоров. Время же работы самого алгоритма кластеризации уменьшается с увеличением количества процессоров.<br />
<br />
[[Файл:k-means-proc-scalability.png|thumb|center|700px|Рис. 8. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (из работы: Kumar etc. 2011).]]<br />
<br />
Также было произведено самостоятельное исследование масштабируемости алгоритма k-means. Исследование производилось на суперкомпьютере "Blue Gene/P"<ref>http://hpc.cmc.msu.ru/bgp</ref>.<br />
<br />
Набор и границы значений изменяемых параметров запуска реализации алгоритма:<br />
<br />
* число процессоров [1, 2, 4, 8, 16, 32, 64, 128, 256, 512];<br />
* количество объектов [5000, 10000, 25000, 50000].<br />
<br />
Был использован набор данных ''Dataset for Sensorless Drive Diagnosis Data Set''<ref>PASCHKE, Fabian ; BAYER, Christian ; BATOR, Martyna ; MÖNKS, Uwe ; DICKS, Alexander ; ENGE-ROSENBLATT, Olaf ; LOHWEG, Volker: Sensorlose Zustandsüberwachung an Synchronmotoren, Bd. 46. In: HOFFMANN, Frank; HÃœLLERMEIER, Eyke (Hrsg.): Proceedings 23. Workshop Computational Intelligence. Karlsruhe : KIT Scientific Publishing, 2013 (Schriftenreihe des Instituts für Angewandte Informatik - Automatisierungstechnik am Karlsruher Institut für Technologie, 46), S. 211-225</ref> из репозитория ''Machine learning repository''<ref>https://archive.ics.uci.edu/ml/datasets/Dataset+for+Sensorless+Drive+Diagnosis</ref>.<br />
<br />
Исследуемый набор данных содержит векторы, размерность которых равна 49. Компоненты векторов являются вещественными числами. Количество кластеров равно 11. Пропущенные значения отсутствуют.<br />
<br />
Для исследования масштабируемости алгоритма была использована реализация на языке C с использованием MPI<ref>http://users.eecs.northwestern.edu/~wkliao/Kmeans/index.html</ref>. Код можно найти здесь: https://github.com/serban/kmeans. Данная реализация предоставляет возможность распараллеливать решение задачи с помощью технологий MPI, OpenMP И CUDA. Для запуска MPI-версии программы использовалась цель "mpi_main" Makefile.<br />
<br />
На рис. 9 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (использовались логарифмические оси). Разными цветами помечены запуски, соответствующие разным количествам объектам, участвующих в кластеризации. Можно видеть близкое к линейному увеличение времени работы программы в зависимости от количества процессоров. Также можно видеть увеличение времени работы алгоритма при увеличении количества объектов.<br />
[[Файл:Plot_1.png|thumb|center|900px|Рис. 9. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
На рис. 10 показана эта же зависимость, только в трехмерном пространстве. По аналогии с рис. 9, были использованы логарифмические оси. Как и в случае двумерного рисунка, можно видеть близкое к линейному увеличение времени работы программы.<br />
[[Файл:Kmeans-3d.png|thumb|center|900px|Рис. 10. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
==== Реализация 4 ====<br />
<br />
Исследование масштабируемости данной параллельной реализации алгоритма k-средних также проводилось на суперкомпьютере "Ломоносов". Параллельная реализация была написана самостоятельно на языке C, [http://git.algowiki-project.org/iaegorov/Egorov-Bogomazov-k-means/tree/master ссылка на реализацию]. Так как на каждой итерации число действий на единицу данных не велико и данные должны быть собраны вместе при перерасчете центроидов, было решено для ускорения вычислений воспользоваться только OpenMP без использовании MPI.<br />
<br />
Код собирался под gcc c опцией -fopenmp. Код считался на одном процессоре, технология hyperthreading не использовалась.<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 16] с увеличением в 2 раза;<br />
* размер данных [100000 : 1600000] с увеличением в 2 раза.<br />
<br />
В результате проведённых экспериментов были получены следующие данные:<br />
<br />
* Максимальная эффективность в точке достигается при переходе от 1 потока на 4 при минимальном размере данных, она равна <math>87,5%</math>.<br />
* Усредненная максимальная эффективность достигается при переходе с одного потока на два. Среднее время вычислений на всех рассмотренных потока снижается с 16,33 до 11.87 секунд, поэтому формально эффективность <math>= 16.33 / 11.87 / 2 \approx 68,4\%</math><br />
* Минимальная эффективность в точке достигается при переходе от 1 потока на 16 при размере данных 800000, она равна <math>11,1\%</math>.<br />
* Усредненная минимальная эффективность наблюдается при переходе с одного на максимальное рассматриваемое в эксперименте число потоков, равное 16. Время вычисления изменяется с 16,33 до 7,6 секунд, поэтому формально эффективность <math> = 16.33 / 7.6 / 16 \approx 14,9\%</math><br />
<br />
Ниже приведены графики зависимости вычислительного времени алгоритма и его эффективности от изменяемых параметров запуска — размера данных и числа процессоров:<br />
<br />
[[file:Egorov_Bogomazov_Time.jpg|thumb|center|700px|Рис. 11. Параллельная реализация алгоритма k-средних. Изменение вычислительного времени алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь видно, что время выполнения операций алгоритма плавно убывает по каждому из параметров, причем скорость убывания по параметру числа процессоров выше, чем в зависимости от размерности задачи.<br />
<br />
[[file:Egorov_Bogomazov_Efficiency.jpg|thumb|center|700px|Рис. 12. Параллельная реализация алгоритма k-средних. Изменение эффективности алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь построена эффективность перехода от последовательной реализации к параллельной. Рассчитывается она по формуле ''Время вычисления на 1 потоке / Время вычисления на <math>T</math> потоках / <math>T</math>'', где <math>T</math> — это число потоков. При вычислении на 1 процессоре она равна 100 \% в силу используемой формулы, что и отражено на графике.<br />
<br />
Проведем оценки масштабируемости:<br />
<br />
'''По числу процессов''' — при увеличении числа процессов эффективность уменьшается на всей области рассматриваемых значений, причем темп убывания замедляется с ростом числа процессов.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений вначале кратковременно возрастает, но затем начинает относительно равномерно убывать на всей рассматриваемой области.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений в общем случае постепенно убывает. На малых данных она выходит на пик мощности, являющийся максимумом эффективности в исследуемых условиях, но затем возвращается к процессу убывания.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
В однопоточном режиме на наборах данных, представляющих практический интерес (порядка нескольких десятков тысяч векторов и выше), время работы алгоритма неприемлемо велико. Благодаря свойству массового параллелизма должно наблюдаться значительное ускорение алгоритма на многоядерных архитектурах (Intel Xeon), а также на графических процессорах, даже на мобильных вычислительных системах (ноутбуках), оснащенных видеокартой. Алгоритм <i>k</i>-means также будет демонстрировать значительное ускорение на сверхмощных вычислительных комплексах (суперкомпьютерах, системах облачных вычислений<ref>"Issa. "Performance characterization and analysis for Hadoop K-means iteration". Journal of Cloud Computing, 2016"</ref>).<br />
<br />
На сегодняшний день существует множество реализаций алгоритма <i>k</i>-means, в частности, направленных на оптимизацию параллельной работы на различных архитектурах<ref>"Raghavan R. A fast and scalable hardware architecture for K-means clustering for big data analysis : дис. – University of Colorado Colorado Springs. Kraemer Family Library, 2016."</ref><ref>"Yang, Luobin, et al. "High performance data clustering: a comparative analysis of performance for GPU, RASC, MPI, and OpenMP implementations." The Journal of supercomputing 70.1 (2014): 284-300."</ref><ref>"Li, You, et al. "Speeding up k-means algorithm by GPUs." Computer and Information Technology (CIT), 2010 IEEE 10th International Conference on. IEEE, 2010."</ref>. Предлагается множество адаптаций алгоритма под конкретные архитектуры. Например, авторы работы<ref>"Kanan, Gebali, Ibrahim. "Fast and Area-Efficient Hardware Implementation of the K-means<br />
Clustering Algorithm". WSEAS Transactions on circuits and systems. Vol. 15. 2016"</ref> производят перерасчет центров кластеров на этапе распределения векторов по кластерам.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
==== Открытое программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.icpsr.umich.edu/CrimeStat/ CrimeStat]</li> Программное обеспечение, созданное для операционных систем Windows, предоставляющее инструменты статистического и пространственного анализа для решения задачи картирования преступности.<br />
<li>[http://juliastats.github.io Julia]</li> Высокоуровневый высокопроизводительный свободный язык программирования с динамической типизацией, созданный для математических вычислений, содержит реализацию k-means.<br />
<li>[https://mahout.apache.org Mahout]</li> Apache Mahout - Java библиотека для работы с алгоритмами машинного обучения с использованием MapReduce. Содержит реализацию k-means.<br />
<li>[https://www.gnu.org/software/octave/ Octave]</li> Написанная на C++ свободная система для математических вычислений, использующая совместимый с MATLAB язык высокого уровня, содержит реализацию k-means.<br />
<li>[http://spark.apache.org/docs/latest/mllib-clustering.html Spark]</li> Распределенная реализация k-means содержится в библиотеке Mlib для работы с алгоритмами машинного обучения, взаимодействующая с Python библиотекой NumPy и библиотека R.<br />
<li>[http://torch.ch Torch]</li> MATLAB-подобная библиотека для языка программирования Lua с открытым исходным кодом, предоставляет большое количество алгоритмов для глубинного обучения и научных расчётов. Ядро написано на Си, прикладная часть выполняется на LuaJIT, поддерживается распараллеливание вычислений средствами CUDA и OpenMP. Существуют реализации k-means.<br />
<li>[http://www.cs.waikato.ac.nz/ml/weka/ Weka]</li> Cвободное программное обеспечение для анализа данных, написанное на Java. Содержит k-means и x-means.<br />
<li>[http://accord-framework.net/docs/html/T_Accord_MachineLearning_KMeans.htm Accord.NET]</li> C# реализация алгоритмов k-means, k-means++, k-modes.<br />
<li>[http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html OpenCV]</li> Написанная на С++ библиотека, направленная в основном на решение задач компьютерного зрения. Содержит реализацию k-means.<br />
<li>[http://mlpack.org/ MLPACK]</li> Масштабируемая С++ библиотека для работы с алгоритмами машинного обучения, содержит реализацию k-means.<br />
<li>[https://www.scipy.org/ SciPy]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[http://scikit-learn.org/ scikit-learn]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[https://www.r-bloggers.com/k-means-clustering-in-r/ R]</li> Язык программирования для статистической обработки данных и работы с графикой, а также свободная программная среда вычислений с открытым исходным кодом в рамках проекта GNU, содержит три реализации k-means.<br />
<li>[http://elki.dbs.ifi.lmu.de ELKI]</li> Java фреймворк, содержащий реализацию k-means, а также множество других алгоритмов кластеризации.<br />
</ol><br />
<br />
==== Проприетарное программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.ayasdi.com/blog/bigdata/topological-data-analysis-of-oil-and-gas-petrophysical-data/ Ayasdi]</li><br />
<li>[http://www.stata.com/features/cluster-analysis/ Stata]</li><br />
<li>[http://mathworld.wolfram.com/K-MeansClusteringAlgorithm.html Mathematica]</li><br />
<li>[http://www.mathworks.com/help/stats/kmeans.html?requestedDomain=www.mathworks.com MATLAB]</li><br />
<li>[https://support.sas.com/rnd/app/stat/procedures/fastclus.html SAS]</li><br />
<li>[http://docs.rapidminer.com/studio/operators/modeling/segmentation/k_means.html RapidMiner]</li><br />
<li>[https://blogs.sap.com/2013/03/28/sap-hana-pal-k-means-algorithm-or-how-to-do-customer-segmentation-for-the-telecommunications-industry/ SAP HANA]</li><br />
</ol><br />
<br />
== Литература ==<br />
<br />
<references /></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_k_%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D1%85_(k-means)&diff=25250Алгоритм k средних (k-means)2018-01-16T17:32:30Z<p>Konshin: /* Математическое описание алгоритма */</p>
<hr />
<div>Основные авторы статьи (разделы 1, 2.4.1, 2.6-2.7, 3):<br />
[https://algowiki-project.org/ru/Участник:Бротиковская_Данута<b>Д.Бротиковская</b>] и<br />
[https://algowiki-project.org/ru/Участник:DennZo1993<b>Д.Зобнин</b>]<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:IanaV<b>Я.А.Валуйская</b>] и<br />
[https://algowiki-project.org/ru/Участник:GlotovES<b>Е.С.Глотов</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Parkhomenko<b>П.А.Пархоменко</b>] и<br />
[https://algowiki-project.org/ru/Участник:Ivan.mashonskiy<b>И.Д.Машонский</b>] (раздел 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Илья_Егоров<b>И.Егоров</b>] и<br />
[https://algowiki-project.org/ru/Участник:Богомазов_Евгений<b>Е.Богомазов</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Алгоритм <math>k</math> средних (<i>k</i>-means)<br />
| serial_complexity = <math>O(ikdn)</math><br />
| input_data = <math> dn </math><br />
| output_data = <math> n </math><br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Алгоритм <b><i>k средних</i></b> (англ. <i>k</i>-means) - один из алгоритмов машинного обучения, решающий задачу кластеризации.<br />
Этот алгоритм является неиерархическим<ref>"https://ru.wikipedia.org/wiki/Иерархическая_кластеризация"</ref>, итерационным методом кластеризации<ref>"https://ru.wikipedia.org/wiki/Кластерный_анализ"</ref>, он получил большую популярность благодаря своей простоте, наглядности реализации и достаточно высокому качеству работы. <br />
Был изобретен в 1950-х годах математиком <i>Гуго Штейнгаузом</i><ref>Steinhaus, Hugo. "Sur la division des corp materiels en parties." Bull. Acad. Polon. Sci 1.804 (1956): 801.</ref> и почти одновременно <i>Стюартом Ллойдом</i><ref>Lloyd, S. P. "Least square quantization in PCM. Bell Telephone Laboratories Paper. Published in journal much later: Lloyd, SP: Least squares quantization in PCM." IEEE Trans. Inform. Theor.(1957/1982).</ref>. Особую популярность приобрел после публикации работы <i>МакКуина</i><ref>MacQueen, James. "Some methods for classification and analysis of multivariate observations." Proceedings of the fifth Berkeley symposium on mathematical statistics and probability. Vol. 1. No. 14. 1967.</ref> в 1967.<br />
<br />
Алгоритм представляет собой версию EM-алгоритма<ref>"https://ru.wikipedia.org/wiki/EM-алгоритм"</ref>, применяемого также для разделения смеси гауссиан. Основная идея алгоритма <i>k</i>-means заключается в том, что данные произвольно разбиваются на кластеры, после чего итеративно перевычисляется центр масс для каждого кластера, полученного на предыдущем шаге, затем векторы разбиваются на кластеры вновь в соответствии с тем, какой из новых центров оказался ближе по выбранной метрике.<br />
<br />
Цель алгоритма заключается в разделении <math>n</math> наблюдений на <math>k</math> кластеров таким образом, чтобы каждое наблюдение принадлежало ровно одному кластеру, расположенному на наименьшем расстоянии от наблюдения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
<b>Дано:</b><br />
* набор из <math>n</math> наблюдений <math>X=\{\mathbf{x}_1, \mathbf{x}_2, ..., \mathbf{x}_n\}, \mathbf{x}_i \in \mathbb{R}^d, \ i=1,...,n</math>;<br />
* <math>k</math> - требуемое число кластеров, <math>k \in \mathbb{N}, \ k \leq n</math>.<br />
<br />
<b>Требуется:</b><br />
<br />
Разделить множество наблюдений <math>X</math> на <math>k</math> кластеров <math>S_1, S_2, ..., S_k</math>:<br />
* <math>S_i \cap S_j= \varnothing, \quad i \ne j</math><br />
<br />
* <math>\bigcup_{i=1}^{k} S_i = X</math><br />
<br />
<b>Действие алгоритма:</b><br />
<p>Алгоритм <i>k</i>-means разбивает набор <math>X</math> на <math>k</math> наборов <math>S_1, S_2, ..., S_k,</math> таким образом, чтобы минимизировать сумму квадратов расстояний от каждой точки кластера до его центра (центр масс кластера). Введем обозначение, <math>S=\{S_1, S_2, ..., S_k\}</math>. Тогда действие алгоритма <i>k</i>-means равносильно поиску:</p><br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math>\arg\min_{S} \sum\limits_{i=1}^k \sum\limits_{\mathbf{x} \in S_i} \rho(\mathbf{x}, \mathbf{\mu}_i )^2,</math></td><br />
<td align="right"><math>(1)</math></td><br />
</tr><br />
</table><br />
где <math>\mathbf{\mu}_i</math> &ndash; центры кластеров, <math>i=1,...,k, \quad \rho(\mathbf{x}, \mathbf{\mu}_i)</math> &ndash; функция расстояния между <math>\mathbf{x}</math> и <math>\mu_i</math><br />
<br />
<b>Шаги алгоритма:</b><br />
<ol><br />
<li><br />
<b>Начальный шаг: инициализация кластеров</b><br />
<p>Выбирается произвольное множество точек <math>\mu_i, \ i=1,...,k,</math> рассматриваемых как начальные центры кластеров: <math>\mu_i^{(0)} = \mu_i, \quad i=1,...,k</math></p><br />
</li><br />
<li><br />
<b>Распределение векторов по кластерам</b><br />
<p><b>Шаг</b> <math>t: \forall \mathbf{x}_i \in X, \ i=1,...,n: \mathbf{x}_i \in S_j \iff j=\arg\min_{k}\rho(\mathbf{x}_i,\mathbf{\mu}_k^{(t-1)})^2</math></p><br />
</li><br />
<li><br />
<b>Пересчет центров кластеров</b><br />
<p><b>Шаг </b> <math>t: \forall i=1,...,k: \mu_i^{(t)} = \cfrac{1}{|S_i|}\sum_{\mathbf{x}\in S_i}\mathbf{x}</math></p><br />
</li><br />
<li><br />
<b>Проверка условия останова:</b><br />
<p></p><br />
* '''if''' <math>\exists i\in \overline{1,k}: \mu_i^{(t)} \ne \mu_i^{(t-1)}</math> '''then'''<br />
** <math>t = t + 1</math>;<br />
** goto 2;<br />
* '''else'''<br />
** '''stop'''<br />
</li><br />
</ol><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром являются шаги 2 и 3 приведенного выше алгоритма: <b><i>распределение векторов по кластерам</i></b> и <b><i>пересчет центров кластеров</i></b>. <br />
<br />
<p><br />
<b><i>Распределение векторов</i></b> по кластерам предполагает вычисление расстояний между каждым вектором <math>\mathbf{x}_i \in X, \ i= 1,...,n</math> и центрами кластера <math>\mathbf{\mu}_j, \ j= 1,...,k</math>. Таким образом, данный шаг предполагает <math>kn</math> вычислений расстояний между <math>d</math>-мерными векторами. <br />
</p><br />
<p><br />
<b><i>Пересчет центров кластеров</i></b> предполагает <math>k</math> вычислений центров масс <math>\mathbf{\mu}_i</math> множеств <math>S_i, \ i=1,...,k,</math> представленных выражением в шаге 3 представленного выше алгоритма.<br />
</p><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
<b>Инициализация центров масс <math>\mu_1, ..., \mu_k</math></b>. <br />
<br />
Наиболее распространенными являются следующие стратегии:<br />
<ul><br />
<li><br />
<b>Метод Forgy</b><br>В качестве начальных значений <math>\mu_1, ..., \mu_k</math> берутся случайно выбранные векторы.<br />
</li><br />
<li><br />
<b>Метод случайно разделения (Random Partitioning)</b><br>Для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> выбирается случайным образом кластер <math>S_1, ..., S_k</math>, после чего для каждого полученного кластера вычисляются значения <math>\mu_1, ..., \mu_k</math>.<br />
</li><br />
</ul><br />
<br />
<b>Распределение векторов по кластерам</b><br />
<br />
Для этого шага алгоритма между векторами <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> и центрами кластеров <math>\mu_1,...,\mu_k</math> вычисляются <b>расстояния</b> по формуле (как правило, используется Евлидово расстояние):<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mathbf{v}_1, \mathbf{v}_2 \in \mathbb{R}^d, \quad \rho(\mathbf{v}_1, \mathbf{v}_2) = \lVert \mathbf{v}_1- \mathbf{v}_2 \rVert= \sqrt{\sum_{i=1}^{d}(\mathbf{v}_{1,i} - \mathbf{v}_{2,i})^2}</math></td><br />
<td align="right"><math>(2)</math></td><br />
</tr><br />
</table><br />
<br />
<b>Пересчет центров кластеров</b><br />
<br />
Для этого шага алгоритма производится пересчет центров кластера по <b>формуле вычисления центра масс</b>:<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mu = \cfrac{1}{|S|}\sum_{\mathbf{x}\in S}\mathbf{x}</math></td><br />
<td align="right"><math>(3)</math></td><br />
</tr><br />
</table><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
'''1.''' Инициализировать центры кластеров <math>\mathbf{\mu}_i^{(1)}, \ i=1,...,k</math><br><br />
'''2.''' <math>t \leftarrow 1</math><br><br />
'''3.''' Распределение по кластерам<br><br />
<math>\quad S_i^{(t)}=\{\mathbf{x}_p: \lVert\mathbf{x}_p-\mathbf{\mu}_i^{(t)}\rVert^2 \leq \lVert\mathbf{x}_p-\mathbf{\mu}_j^{(t)}\rVert^2 \quad \forall j=1,...,k\},</math><br><br />
<math>\quad</math>где каждый вектор <math>\mathbf{x}_p</math> соотносится единственному кластеру <math>S^{(t)}</math><br><br />
'''4.''' Обновление центров кластеров<br><br />
<math>\quad \mathbf{\mu}_i^{(t+1)} = \frac{1}{|S^{(t)}_i|} \sum_{\mathbf{x}_j \in S^{(t)}_i} \mathbf{x}_j </math><br><br />
'''5.''' '''if''' <math>\exists i \in \overline{1,k}: \mathbf{\mu}_i^{(t+1)} \ne \mathbf{\mu}_i^{(t)}</math> '''then'''<br><br />
<math>\quad t = t + 1</math>;<br><br />
<math>\quad</math>goto '''3''';<br><br />
<math>~~~</math>'''else'''<br><br />
<math>\quad</math>'''stop'''<br><br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
<div style="padding-bottom: 20px"><br />
<p>Обозначим <math>\Theta_{\rm centroid}^{d, m}</math> временную сложность вычисления центорида кластера, число элементов которого равна <math>m</math>, в d-мерном пространстве.</p><br />
<p>Аналогично <math>\Theta_{\rm distance}^d</math> &ndash; временная сложность вычисления расстояния между двумя d-мерными векторами.</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<b>Сложность шага инициализации <math>k</math> кластеров мощности <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm init}^{k, d, m}</math></b><br />
<ul><br />
<li><i>Стратерия Forgy</i>: вычисления не требуются, <math>\Theta_{\rm init}^{k, d, m} = 0</math></li><br />
<li><i>Стратегия случайного разбиения</i>: вычисление центров <math>k</math> кластеров, <math>\Theta_{\rm init}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}, m \le n</math></li><br />
</ul><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Cложность шага распределения d мерных векторов по <math>k</math> кластерам &ndash; <math>\Theta_{\rm distribute}^{k, d}</math></b></p><br />
<p><br />
На этом шаге для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> вычисляется <math>k</math> расстояний до центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math><br />
</p><br />
<p align="center"><br />
<math>\Theta_{\rm distribute}^{k, d} = n \cdot k \cdot \Theta_{\rm distance}^d</math><br />
</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><br />
<b>Сложность шага пересчета центров <math>k</math> кластеров размера <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm recenter}^{k, d, m}</math></b><br />
</p><br />
<p>На этом шаге вычисляется <math>k</math> центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math></p><br />
<p align="center"><math>\Theta_{\rm recenter}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}</math> </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm centroid}^{d, m}</math> для кластера, число элементов которого равно <math>m</math></b></p><br />
<p align="center"><math>\Theta_{\rm centroid}^{d, m}</math> = <math>m \cdot d</math> сложений + <math>d</math> делений </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm distance}^d</math> в соответствие с формулой <math>(2)</math></b></p><br />
<p align="center"><math>\Theta_{\rm distance}^d</math> = <math>d</math> вычитаний + <math>d</math> умножений + <math>(d-1)</math> сложение</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Предположим, что алгоритм сошелся за <math>i</math> итераций, тогда временная сложность алгоритма <math>\Theta_{\rm k-means}^{d, n}</math></p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le \Theta_{\rm init}^{k, d, n} + i(\Theta_{\rm distribute}^{k, d} + \Theta_{\rm recenter}^{k, d, n})</math></b></p><br />
</div><br />
<div style="padding-bottom: 5px"><br />
<p>Операции сложения/вычитания:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le knd+ i(kn(2d-1) + knd) = knd+ i(kn(3d-1)) \thicksim O(ikdn)</math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Операции умножения/деления:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le kd + i(knd + kd) = kd + ikd(n+1) \thicksim O(ikdn) </math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Получаем, что <b>временная сложность</b> алгоритма <b><i>k</i>-means</b> кластеризации <math>n</math> <b>d-мерных</b> векторов на <math>k</math> кластеров за <math>i</math> итераций:</p><br />
<p align="center"><b><math> \Theta_{\rm k-means}^{d, n} \thicksim O(ikdn) </math></b></p><br />
</div><br />
<br />
=== Информационный граф ===<br />
<br />
Рассмотрим информационный граф алгоритма. Алгоритм <i>k</i>-means начинается с этапа инициализации, после которого следуют итерации, на каждой из которых выполняется два последовательных шага (см. [[#Схема реализации последовательного алгоритма|"Схема реализации последовательного алгоритма"]]): <br />
* распределение векторов по кластерам<br />
* перерасчет центров кластеров<br />
<br />
Поскольку основная часть вычислений приходится на шаги итераций, распишем информационные графы данных шагов.<br />
<br />
<p><b>Распределение векторов по кластерам</b></p><br />
Информационный граф шага распределения векторов по кластерам представлен на ''рисунке 1''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также центры кластеров <math>\mathbf{\mu}_1, ... \mathbf{\mu}_k</math>, вычисленные ранее (на шаге инициализации, если рассматривается первая итерация алгоритма, или на шаге пересчета центров кластеров предыдущей итерации в противном случае). Каждая пара векторов данных <math>\mathbf{x}_i, \ i=1,...,n,</math> и центров кластера <math>\mathbf{\mu}_j, \ j=1,...,k</math> : (<math>\mathbf{x}_i</math>, <math>\mathbf{\mu}_j</math>) подаются на независимые узлы <i>"d"</i> вычисления расстояния между векторами (более подробная схема вычисления расстояния представлена далее, ''рисунок 2''). Далее узлы вычисления расстояния <i>"d"</i>, соответствующие одному и тому же исходному вектору <math>\mathbf{x}_i</math> передаются на один узел <i>"m"</i>, где далее происходит вычисление новой метки кластера для каждого вектора <math>\mathbf{x}_i</math> (берется кластер с минимальным результатом вычисления расстояния). На выходе графа выдаются метки кластеров , <math>L_1, ..., L_n</math>, такие что <math>\forall \mathbf{x}_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>. <br />
<br />
[[file:Clusterization.png|thumb|center|800px|Рис. 1. Схема распределения векторов по кластерам. ''d'' &ndash; вычисление расстояния между векторами; ''m'' &ndash; вычисление минимума.]]<br />
<br />
<p><b>Вычисление расстояния между векторами</b></p><br />
Подробная схема вычисления расстояния между векторами <math>\mathbf{x}_i, \mathbf{\mu}_j</math> представлена на ''рисунке 2''. Как показано на графе, узел вычисления расстояния между векторами <i>"d"</i> состоит из шага взятия разности между векторами (узел "<math>-</math>") и взятия нормы получившегося вектора разности (узел "<math>||\cdot||^2</math>"). Более подробно, вычисление расстояния между векторами <math>\mathbf{x}_i = {x_{i1,}, ...,{x_{in}}}, \mathbf{\mu}_j = {\mu_{j1}, ...,\mu_{jn}}</math> может быть представлено как вычисление разности между каждой парой компонент <math>(x_{iz}, \mu_{jz}), \ z=1,...,d</math> (узел "<math>-</math>"), далее возведение в квадрат для каждого узла "<math>-</math>" (узел "<math>()^2</math>") и суммирования выходов всех узлов "<math>()^2</math>" (узел "<math>+</math>"). <br />
<br />
[[file:dist_calc.png|thumb|center|800px|Рис. 2. Схема вычисления расстояния между вектором и центром кластера.]]<br />
<br />
<p><b>Пересчет центров кластеров</b></p><br />
Информационный граф шага пересчета центров кластеров представлен на ''рисунке 3''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также им соответствующие метки кластера, <math>L_1, ..., L_n</math>, такие что <math>\forall x_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>, вычисленные на этапе распределения векторов по кластерам. Все векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math> подаются в узлы <math>+_1, ...+_k</math>, каждый узел <math>+_m, \ m = 1,...,k,</math> соответствует операции сложения векторов кластера с номером <math>m</math>. Метки кластера <math>L_1, ..., L_n</math> также совместно передаются на узлы <math>S_m, \ m=1,...,k</math>, на каждом из которых вычисляется количество векторов в соответствующем кластере (количество меток с соответствующим значением). Далее каждая пара выходов узлов <math>+_m</math> и <math>S_m</math> подается на узел "<math>/</math>", где производится деление суммы векторов кластера на количество элементов в нем. Значения, вычисленные на узлах "<math>/</math>", присваиваются новым центрам кластеров (выходные значения графа).<br />
<br />
[[file:Recluster.png|thumb|center|800px|Рис. 3. Схема пересчета центров кластеров]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
<p><br />
Работа алгоритма состоит из <math>i</math> итераций, в каждой из которых происходит <b>распределение d-мерных векторов по <math>k</math> кластерам</b>, а также <b>пересчет центров кластеров в d-мерном пространстве</b>. В шаге <b>распределения d-мерных векторов по <math>k</math> кластерам</b> расстояния между вектором и центрами кластеров вычисляются независимо (отсутствуют информационные зависимости). <b>Центры масс кластеров</b> также пересчитываются независимо друг от друга. Таким образом, имеет место [[глоссарий#Массовый параллелизм|''массовый параллелизм'']]. Вычислим параллельную сложность <math>\Psi_*</math> каждого из шагов, а также параллельную сложность всего алгоритма, <math>\Psi_{\rm k-means}</math>. Будем исходить из предположения, что может быть использовано любое необходимое число потоков.<br />
</p><br />
<br />
<p><b>Распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b></p> <br />
<p><br />
Поскольку на данном шаге для каждой пары векторов <math>\mathbf{x}_i, \ i=1,...,n</math> и <math>\mathbf{\mu}_j, \ j=1,...,k,</math> операции вычисления расстояния не зависят друг от друга, они могут выполняться параллельно. Тогда, разделив все вычисление расстояний на <math>n</math> потоков, получим, что в каждом потоке будет выполняться только одна операция вычисления расстояния между векторами размерности d. При этом каждому вычислительному потоку передаются координаты центров всех кластеров <math>\mathbf{\mu}_1, ..., \mathbf{\mu}_k</math>. Таким образом, параллельная сложность данного шага определяется <i> сложностью параллельной операции вычисления расстояния между d-мерными векторами</i>, <math>\Psi_{\rm distance}^d</math> и <i>сложностью определения наиболее близкого кластера</i> (паралельное взятие минимума по расстояниям), <math>\Psi_{\rm min}^k</math>. Для оценки <math>\Psi_{\rm distance}^d</math> воспользуемся [[Нахождение_частных_сумм_элементов_массива_сдваиванием | параллельной реализацией нахождения частичной суммы элементов массива путем сдваивания]]. Аналогично, <math>\Psi_{\rm min}^k = log(k)</math>. В результате, <math>\Psi_{\rm distance}^d = O(log(d))</math>. Таким образом: <br />
</p><br />
<p align="center"><math>\Psi_{\rm distribute}^{k, d} = \Psi_{\rm distance}^d + \Psi_{\rm min}^k = O(log(d))+O(log(k)) = O(log(kd))</math></p><br />
<br />
<p><b>Пересчет центров кластеров в d-мерном пространстве</b></p><br />
<p><br />
Поскольку на данном шаге для каждого из <math>k</math> кластеров центр масс может быть вычислен независимо, данные операции могут быть выполнены в отдельных потоках. Таким образом, параллельная сложность данного шага, <math>\Psi_{\rm recenter}^{k, d}</math>, будет определяться <i>параллельной сложностью вычисления одного центра масс кластера размера <math>m</math></i>, <math>\Psi_{\rm recenter}^{k, d}</math>, а так как <math>m \le n \Rightarrow \Psi_{\rm recenter}^{d, m} \le \Psi_{\rm recenter}^{d, n}</math>. Сложность вычисления центра масс кластера d-мерных векторов размера n аналогично предыдущим вычислениям равна <math>O(log(n))</math>. Тогда: <br />
</p><br />
<p align="center"><math>\Psi_{\rm recenter}^{k, d} \le \Psi_{\rm recenter}^{d, n} = O(log(n))</math></p><br />
<br />
<p><b>Общая параллельная сложность алгоритма</b></p><br />
<p><br />
На каждой итерации необходимо обновление центров кластеров, которые будут использованы на следующей итерации. Таким образом, итерационный процесс выполняется последовательно<ref>Zhao, Weizhong, Huifang Ma, and Qing He. "Parallel k-means clustering based on mapreduce." IEEE International Conference on Cloud Computing. Springer Berlin Heidelberg, 2009.</ref>. Тогда, поскольку сложность каждой итерации определяется <math>\Psi_{\rm distribute}^{k, d}</math> и <math>\Psi_{\rm recenter}</math>, сложность всего алгоритма, <math>\Psi_{\rm k-means}</math> в предположении, что было сделано <math>i</math> операций определяется выражением<br />
</p><br />
<p align="center"><math>\Psi_{\rm k-means} \approx i \cdot (\Psi_{\rm distribute}^{k, d} + \Psi_{\rm recenter}^{k, d}) \le i \cdot O(log(kdn))</math></p><br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
<p><br />
<b>Входные данные</b><br><br />
<ul><br />
<li>Матрица из <math>n \cdot d</math> элементов <math>x_{i, j} \in \mathbb{R}, \ i=1,...,n, \ j=1,...,d,</math> &ndash; координат векторов (наблюдений).</li><br />
<li>Целое положительное число <math>k, \ k \le n</math> &ndash; количество кластеров.</li><br />
</ul><br />
</p><br />
<p><br />
<b>Объем входных данных</b><br><br />
<math>1</math> целое число + <math>n \cdot d</math> вещественных чисел (при условии, что координаты &ndash; вещественные числа).<br />
</p><br />
<p><br />
<b>Выходные данные</b><br><br />
<math>n</math> целых положительных чисел <math>L_1, ..., L_n</math>&ndash; номера кластеров, соотвествующие каждому вектору (при условии, что нумерация кластеров начинается с <math>1</math>).<br />
</p><br />
<p><br />
<b>Объем выходных данных</b><br><br />
<math>n</math> целых положительных чисел.<br />
</p><br />
<br />
=== Свойства алгоритма ===<br />
<br />
<p><b>[[глоссарий#Вычислительная мощность|''Вычислительная мощность'']]</b></p><br />
Вычислительная мощность алгоритма <i>k</i>-means равна <math>\frac{ikdn}{nd} = ki </math>, где <math>k</math> &ndash; число кластеров, <math>i</math> &ndash; число итераций алгоритма.<br />
<br />
<p><b>[[глоссарий#Детерминированность |''Детерминированность'']] и [[глоссарий#Устойчивость |''Устойчивость'']] </b></p><br />
Алгоритм <i>k</i>-means является итерационным. Количество итераций алгоритма в общем случае не фиксируется и зависит от начального расположения объектов в пространстве, параметра <math>k</math>, а также от начального приближения центров кластеров, <math>\mu_1, ..., \mu_k</math>. В результате этого может варьироваться результат работы алгоритма. При неудачном выборе начальных параметров итерационный процесс может сойтись к локальному оптимуму<ref>Von Luxburg, Ulrike. Clustering Stability. Now Publishers Inc, 2010.</ref>. По этим причинам алгоритм не является ни <b>детермирированным</b>, ни <b>устойчивым</b>.<br />
<br />
<p><b>Соотношение последовательной и параллельной сложности алгоритма</b></p><br />
<br />
<math>\frac{\Theta_{\rm k-means}}{\Psi_{\rm k-means}} = \frac{O(ikdn)}{O(i \cdot log(kdn))}</math><br />
<br />
<b>Сильные стороны алгоритма</b>:<br />
<ul><br />
<li><i>Сравнительно высокая эффективность при простоте реализации</i></li><br />
<li><i>Высокое качество кластеризации</i></li><br />
<li><i>Возможность распараллеливания</i></li><br />
<li><i>Существование множества модификаций</i></li><br />
</ul><br />
<b>Недостатки алгоритма</b><ref>Ortega, Joaquín Pérez, Ma Del Rocío Boone Rojas, and María J. Somodevilla. "Research issues on K-means Algorithm: An Experimental Trial Using Matlab."</ref>:<br />
<ul><br />
<li><i>Количество кластеров является параметром алгоритма</i></li><br />
<li><br />
<p><i>Чувствительность к начальным условиям</i></p><br />
<p>Инициализация центров кластеров в значительной степени влияет на результат кластеризации.</p><br />
</li><br />
<li><br />
<p><i>Чувствительность к выбросам и шумам</i></p><br />
<p>Выбросы, далекие от центров настоящих кластеров, все равно учитываются при вычислении их центров.</p><br />
</li><br />
<li><br />
<p><i>Возможность сходимости к локальному оптимуму</i></p><br />
<p>Итеративный подход не дает гарантии сходимости к оптимальному решению.</p><br />
</li><br />
<li><br />
<p><i>Использование понятия "среднего"</i></p><br />
<p>Алгоритм неприменим к данным, для которых не определено понятие "среднего", например, категориальным данным.</p><br />
</li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализации алгоритма <math>k</math> средних согласно [[Scalability methodology|методике]] AlgoWiki.<br />
<br />
==== Реализация 1 ====<br />
<br />
Исследование масштабируемости параллельной реализации алгоритма <i>k</i>-means проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Алгоритм реализован на языке C с использованием средств MPI.<br />
Для исследования масштабируемости проводилось множество запусков программы с разным значением параметра (количество векторов для кластеризации), а также с различным числом процессоров. Фиксировались результаты запусков &ndash; время работы <math>t</math> и количество произведенных итераций алгоритма <math>i</math>.<br />
<br />
Параметры запусков для экспериментальной оценки:<br />
<ul><br />
<li>Значения <b>количества векторов</b> <math>n</math>: 20'000, 30'000, 50'000, 100'000, 200'000, 300'000, 500'000, 700'000, 1'000'000, 1'500'000, 2'000'000.</li><br />
<li>Значения <b>количества процессоров</b> <math>p</math>: 1, 8, 16, 32, 64, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512.</li><br />
<li>Значение <b>количества кластеров</b> <math>k</math>: 100.</li><br />
<li>Значение <b>размерности векторов</b> <math>d</math>: 10.</li><br />
</ul><br />
<br />
Для проведения экспериментов были сгенерированы нормально распределенные псевдослучайные данные (с использованием Python библиотеки [http://scikit-learn.org/ scikit-learn]):<br />
<syntaxhighlight lang="python"><br />
from sklearn.datasets import make_classification<br />
X1, Y1 = make_classification(n_features=10, n_redundant=2, n_informative=8,<br />
n_clusters_per_class=1, n_classes=100,<br />
n_samples=2000000)<br />
</syntaxhighlight><br />
<br />
Для заданной конфигурации эксперимента (<math>n, d, p, k</math>) и полученных результатов (<math>t, i</math>) [[Глоссарий#Производительность|производительность]] и эффективность реализации расчитывались по формулам:<br />
<p><br />
<ul><li><math>{\rm Performance} = \frac{N_{\rm k-means}^{d, n}}{t}\ \ ({\rm FLOPS}),</math></li></ul><br />
где <math>N_{\rm k-means}^{d, n}</math> &ndash; точное число операций с плавающей точкой (операции с памятью, а также целочисленные операции не учитывались), вычисленное в соответствие с разделом [[#Последовательная сложность алгоритма| "Последовательная сложность алгоритма"]];<br />
</p><br />
<p><br />
<ul><li><math>{\rm Efficiency} = \frac{100 \cdot {\rm Performance}}{{\rm Performance}_{\rm Peak}^{p}}\ \ (\%),</math></li></ul><br />
где <math>{\rm Performance}_{\rm Peak}^{p}</math> &ndash; пиковая производительность суперкомпьютера при <math>p</math> процессорах, вычисленная согласно спецификациям Intel<sup>&reg;</sup> XEON<sup>&reg;</sup> X5670<ref>"http://ark.intel.com/ru/products/47920/Intel-Xeon-Processor-X5670-12M-Cache-2_93-GHz-6_40-GTs-Intel-QPI"</ref>.<br />
</p><br />
<br />
Графики зависимости производительности и эффективности параллельной реализации <i>k</i>-means от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>) представлены на рисунках 4 и 5, соответственно.<br />
<br />
[[file:Flops.png|thumb|center|800px|Рис. 4. Параллельная реализация <i>k</i>-means. График зависимости производительности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
[[file:Efficiency.png|thumb|center|800px|Рис. 5. Параллельная реализация <i>k</i>-means. График зависимости эффективности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
<br />
<p><br />
<b>В результате</b> экспериментальной оценки были получены следующие оценки эффективности реализации:<br />
<ul><br />
<li><b>Минимальное</b> значение: 0.000409 % достигается при <math>n=20'000, p=480</math></li><br />
<li><b>Максимальное</b> значение: 0.741119 % достигается при <math>n=300'000, p=1</math></li><br />
</ul><br />
</p><br />
<br />
<p><br />
Оценки масштабируемости реализации алгоритма <i>k</i>-means:<br />
<ul><br />
<li><b>По числу процессоров:</b> -0.002683 &ndash; эффективность убывает с ростом числа процессоров. Данный результат вызван ростом накладных расходов для обеспечения параллельного выполнения алгоритма.</li><br />
<li><b>По размеру задачи:</b> 0.002779 &ndash; эффективность растет с ростом числа векторов. Данный результат вызван тем, что при увеличении размера задачи, количество вычислений растет по сравнению с временем, затрачиваемым на пересылку данных.</li><br />
<li><b>Общая оценка:</b> -0.000058 &ndash; можно сделать вывод, что в целом эффективность реализации незначительно уменьшается с ростом размера задачи и числа процессоров.</li><br />
</ul><br />
</p><br />
<br />
[https://github.com/serban/kmeans Использованная параллельная реализация алгоритма <i>k</i>-means]<br />
<br />
==== Реализация 2 ====<br />
<br />
Исследование также проводилось на суперкомпьютере "Ломоносов".<br />
<br />
<p>Набор данных для тестирования состоял из 946000 векторов размерности 2 (координаты на сфере)</p><br />
<p>Набор и границы значений изменяемых параметров запуска реализации алгоритма:</p><br />
<br />
* число процессов (виртуальных ядер) [8 : 512];<br />
* число кластеров [128 : 384].<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации <math>2,47%</math> достигается при делении исходных данных на 128 кластеров с использованием 512 процессов;<br />
* максимальная эффективность реализации <math>7,13%</math> достигается при делении исходных данных на 352 кластера с использованием 8 процессов.<br />
На рисунках 6 и 7, соответственно, представлены графики зависимости производительности и эффективности параллельной реализации <i>k</i>-means от числа кластеров и числа процессов.<br />
<br />
[[file:kmeans_performance.jpg|thumb|center|720px|Рис. 6. График зависимости производительности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
По рис. 6 можно отметить практически полное отсутствие роста производительности с увеличением числа процессов от 256 до 512 при минимальном размере задачи. Это связано с быстрым ростом накладных расходов по отношению к крайне низкому объёму вычислений. При росте размерности задачи данный эффект пропадает, и при одновременном пропорциональном увеличении числа кластеров и числа процессов рост производительности становится близким к линейному.<br />
<br />
[[file:kmeans_efficiency.jpg|thumb|center|720px|Рис. 7. График зависимости эффективности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
Исследовалась [https://github.com/like-a-bauss/kmeans параллельная реализация алгоритма <i>k</i>-means на MPI]. <br />
<p>Были получены следующие оценки масштабируемости реализации алгоритма <i>k</i>-means:</p><br />
*<i>По числу процессов:</i> <math>-0.02209</math>. Следовательно, с ростом числа процессов эффективность уменьшается. На рис. 7 можно наблюдать плавное и равномерное снижение производительности по мере увеличения числа процессов при неизменном числе кластеров, что свидетельствует об относительно невысоком росте накладных расходов на передачу данных между процессами и преобладании объёма вычислений над объёмом пересылок данных по сети.<br />
*<i>По размеру задачи:</i> <math>0.01252</math>. Следовательно, с ростом размера задачи (числа кластеров) эффективность увеличивается. При этом объём пересылок данных по сети пропорционален <math>(n + k) \cdot p</math> (где <math>k</math> - число кластеров, <math>n</math> - число входных векторов, <math>p</math> - число процессов) таким образом, поскольку <math>k << n</math>, рост накладных расходов с ростом числа кластеров при неизменном числе процессов и входных векторов представляет собой незначительную величину.<br />
*<i>Общая оценка:</i> <math>-0.00081</math>. Таким образом, с ростом и размера задачи, и числа процессов эффективность уменьшается. Это связано с тем, что отношение объёма вычислений к объёму передаваемых данных изменяется пропорционально <math>{kn \over (n + k) \cdot p} \thicksim {k \over p}</math>, что представляет собой невысокий коэффициент, но при этом позволяет параллельной реализации не деградировать до нулевой эффективности при значительном увеличении числа процессов.<br />
<br />
==== Реализация 3 ====<br />
<br />
Исследование масштабируемости алгоритма k-means в зависимости от количества используемых процессов было проведено в статье Кумара<ref>Kumar, J., Mills, R. T., Hoffman, F. M., & Hargrove, W. W. (2011). Parallel k-means clustering for quantitative ecoregion delineation using large data sets. Procedia Computer Science, 4, 1602-1611.</ref>. Исследование происходило на суперкомпьютере Jaguar - Cray XT5<ref>https://www.top500.org/system/176029</ref>. На момент экспериментов данный суперкомпьютер имел следующую конфигурацию: 18,688 вычислительных узлов с двумя шестнадцатиядерными процессорами AMD Opteron 2435 (Istanbul) 2.6 GHz, 16 GB of DDR2-800 оперативной памяти, и SeaStar 2+ роутер. Всего он состоял из 224,256 вычислительных ядер, 300 TB памяти, и пиковой производительностью 2.3 petaflops.<br />
<br />
Реализация алгоритма была выполнена на языке программирования C с использованием MPI.<br />
<br />
Объем данных составлял 84 ГБ, количество объектов (d-мерных векторов) n равнялось 1,024,767,667, размерность векторов <math>d</math> равнялась 22, количество кластеров <math>k</math> равнялось 1000. <br />
<br />
На рис. 8 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров. Можно отметить, что время, затраченное на чтение данных и запись результатов кластеризации, практически не изменяется с увеличением количества задействованных процессоров. Время же работы самого алгоритма кластеризации уменьшается с увеличением количества процессоров.<br />
<br />
[[Файл:k-means-proc-scalability.png|thumb|center|700px|Рис. 8. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (из работы: Kumar etc. 2011).]]<br />
<br />
Также было произведено самостоятельное исследование масштабируемости алгоритма k-means. Исследование производилось на суперкомпьютере "Blue Gene/P"<ref>http://hpc.cmc.msu.ru/bgp</ref>.<br />
<br />
Набор и границы значений изменяемых параметров запуска реализации алгоритма:<br />
<br />
* число процессоров [1, 2, 4, 8, 16, 32, 64, 128, 256, 512];<br />
* количество объектов [5000, 10000, 25000, 50000].<br />
<br />
Был использован набор данных ''Dataset for Sensorless Drive Diagnosis Data Set''<ref>PASCHKE, Fabian ; BAYER, Christian ; BATOR, Martyna ; MÖNKS, Uwe ; DICKS, Alexander ; ENGE-ROSENBLATT, Olaf ; LOHWEG, Volker: Sensorlose Zustandsüberwachung an Synchronmotoren, Bd. 46. In: HOFFMANN, Frank; HÃœLLERMEIER, Eyke (Hrsg.): Proceedings 23. Workshop Computational Intelligence. Karlsruhe : KIT Scientific Publishing, 2013 (Schriftenreihe des Instituts für Angewandte Informatik - Automatisierungstechnik am Karlsruher Institut für Technologie, 46), S. 211-225</ref> из репозитория ''Machine learning repository''<ref>https://archive.ics.uci.edu/ml/datasets/Dataset+for+Sensorless+Drive+Diagnosis</ref>.<br />
<br />
Исследуемый набор данных содержит векторы, размерность которых равна 49. Компоненты векторов являются вещественными числами. Количество кластеров равно 11. Пропущенные значения отсутствуют.<br />
<br />
Для исследования масштабируемости алгоритма была использована реализация на языке C с использованием MPI<ref>http://users.eecs.northwestern.edu/~wkliao/Kmeans/index.html</ref>. Код можно найти здесь: https://github.com/serban/kmeans. Данная реализация предоставляет возможность распараллеливать решение задачи с помощью технологий MPI, OpenMP И CUDA. Для запуска MPI-версии программы использовалась цель "mpi_main" Makefile.<br />
<br />
На рис. 9 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (использовались логарифмические оси). Разными цветами помечены запуски, соответствующие разным количествам объектам, участвующих в кластеризации. Можно видеть близкое к линейному увеличение времени работы программы в зависимости от количества процессоров. Также можно видеть увеличение времени работы алгоритма при увеличении количества объектов.<br />
[[Файл:Plot_1.png|thumb|center|900px|Рис. 9. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
На рис. 10 показана эта же зависимость, только в трехмерном пространстве. По аналогии с рис. 9, были использованы логарифмические оси. Как и в случае двумерного рисунка, можно видеть близкое к линейному увеличение времени работы программы.<br />
[[Файл:Kmeans-3d.png|thumb|center|900px|Рис. 10. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
==== Реализация 4 ====<br />
<br />
Исследование масштабируемости данной параллельной реализации алгоритма k-средних также проводилось на суперкомпьютере "Ломоносов". Параллельная реализация была написана самостоятельно на языке C, [http://git.algowiki-project.org/iaegorov/Egorov-Bogomazov-k-means/tree/master ссылка на реализацию]. Так как на каждой итерации число действий на единицу данных не велико и данные должны быть собраны вместе при перерасчете центроидов, было решено для ускорения вычислений воспользоваться только OpenMP без использовании MPI.<br />
<br />
Код собирался под gcc c опцией -fopenmp. Код считался на одном процессоре, технология hyperthreading не использовалась.<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 16] с увеличением в 2 раза;<br />
* размер данных [100000 : 1600000] с увеличением в 2 раза.<br />
<br />
В результате проведённых экспериментов были получены следующие данные:<br />
<br />
* Максимальная эффективность в точке достигается при переходе от 1 потока на 4 при минимальном размере данных, она равна <math>87,5%</math>.<br />
* Усредненная максимальная эффективность достигается при переходе с одного потока на два. Среднее время вычислений на всех рассмотренных потока снижается с 16,33 до 11.87 секунд, поэтому формально эффективность <math>= 16.33 / 11.87 / 2 \approx 68,4\%</math><br />
* Минимальная эффективность в точке достигается при переходе от 1 потока на 16 при размере данных 800000, она равна <math>11,1\%</math>.<br />
* Усредненная минимальная эффективность наблюдается при переходе с одного на максимальное рассматриваемое в эксперименте число потоков, равное 16. Время вычисления изменяется с 16,33 до 7,6 секунд, поэтому формально эффективность <math> = 16.33 / 7.6 / 16 \approx 14,9\%</math><br />
<br />
Ниже приведены графики зависимости вычислительного времени алгоритма и его эффективности от изменяемых параметров запуска — размера данных и числа процессоров:<br />
<br />
[[file:Egorov_Bogomazov_Time.jpg|thumb|center|700px|Рис. 11. Параллельная реализация алгоритма k-средних. Изменение вычислительного времени алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь видно, что время выполнения операций алгоритма плавно убывает по каждому из параметров, причем скорость убывания по параметру числа процессоров выше, чем в зависимости от размерности задачи.<br />
<br />
[[file:Egorov_Bogomazov_Efficiency.jpg|thumb|center|700px|Рис. 12. Параллельная реализация алгоритма k-средних. Изменение эффективности алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь построена эффективность перехода от последовательной реализации к параллельной. Рассчитывается она по формуле ''Время вычисления на 1 потоке / Время вычисления на <math>T</math> потоках / <math>T</math>'', где <math>T</math> — это число потоков. При вычислении на 1 процессоре она равна 100 \% в силу используемой формулы, что и отражено на графике.<br />
<br />
Проведем оценки масштабируемости:<br />
<br />
'''По числу процессов''' — при увеличении числа процессов эффективность уменьшается на всей области рассматриваемых значений, причем темп убывания замедляется с ростом числа процессов.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений вначале кратковременно возрастает, но затем начинает относительно равномерно убывать на всей рассматриваемой области.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений в общем случае постепенно убывает. На малых данных она выходит на пик мощности, являющийся максимумом эффективности в исследуемых условиях, но затем возвращается к процессу убывания.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
В однопоточном режиме на наборах данных, представляющих практический интерес (порядка нескольких десятков тысяч векторов и выше), время работы алгоритма неприемлемо велико. Благодаря свойству массового параллелизма должно наблюдаться значительное ускорение алгоритма на многоядерных архитектурах (Intel Xeon), а также на графических процессорах, даже на мобильных вычислительных системах (ноутбуках), оснащенных видеокартой. Алгоритм <i>k</i>-means также будет демонстрировать значительное ускорение на сверхмощных вычислительных комплексах (суперкомпьютерах, системах облачных вычислений<ref>"Issa. "Performance characterization and analysis for Hadoop K-means iteration". Journal of Cloud Computing, 2016"</ref>).<br />
<br />
На сегодняшний день существует множество реализаций алгоритма <i>k</i>-means, в частности, направленных на оптимизацию параллельной работы на различных архитектурах<ref>"Raghavan R. A fast and scalable hardware architecture for K-means clustering for big data analysis : дис. – University of Colorado Colorado Springs. Kraemer Family Library, 2016."</ref><ref>"Yang, Luobin, et al. "High performance data clustering: a comparative analysis of performance for GPU, RASC, MPI, and OpenMP implementations." The Journal of supercomputing 70.1 (2014): 284-300."</ref><ref>"Li, You, et al. "Speeding up k-means algorithm by GPUs." Computer and Information Technology (CIT), 2010 IEEE 10th International Conference on. IEEE, 2010."</ref>. Предлагается множество адаптаций алгоритма под конкретные архитектуры. Например, авторы работы<ref>"Kanan, Gebali, Ibrahim. "Fast and Area-Efficient Hardware Implementation of the K-means<br />
Clustering Algorithm". WSEAS Transactions on circuits and systems. Vol. 15. 2016"</ref> производят перерасчет центров кластеров на этапе распределения векторов по кластерам.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
==== Открытое программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.icpsr.umich.edu/CrimeStat/ CrimeStat]</li> Программное обеспечение, созданное для операционных систем Windows, предоставляющее инструменты статистического и пространственного анализа для решения задачи картирования преступности.<br />
<li>[http://juliastats.github.io Julia]</li> Высокоуровневый высокопроизводительный свободный язык программирования с динамической типизацией, созданный для математических вычислений, содержит реализацию k-means.<br />
<li>[https://mahout.apache.org Mahout]</li> Apache Mahout - Java библиотека для работы с алгоритмами машинного обучения с использованием MapReduce. Содержит реализацию k-means.<br />
<li>[https://www.gnu.org/software/octave/ Octave]</li> Написанная на C++ свободная система для математических вычислений, использующая совместимый с MATLAB язык высокого уровня, содержит реализацию k-means.<br />
<li>[http://spark.apache.org/docs/latest/mllib-clustering.html Spark]</li> Распределенная реализация k-means содержится в библиотеке Mlib для работы с алгоритмами машинного обучения, взаимодействующая с Python библиотекой NumPy и библиотека R.<br />
<li>[http://torch.ch Torch]</li> MATLAB-подобная библиотека для языка программирования Lua с открытым исходным кодом, предоставляет большое количество алгоритмов для глубинного обучения и научных расчётов. Ядро написано на Си, прикладная часть выполняется на LuaJIT, поддерживается распараллеливание вычислений средствами CUDA и OpenMP. Существуют реализации k-means.<br />
<li>[http://www.cs.waikato.ac.nz/ml/weka/ Weka]</li> Cвободное программное обеспечение для анализа данных, написанное на Java. Содержит k-means и x-means.<br />
<li>[http://accord-framework.net/docs/html/T_Accord_MachineLearning_KMeans.htm Accord.NET]</li> C# реализация алгоритмов k-means, k-means++, k-modes.<br />
<li>[http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html OpenCV]</li> Написанная на С++ библиотека, направленная в основном на решение задач компьютерного зрения. Содержит реализацию k-means.<br />
<li>[http://mlpack.org/ MLPACK]</li> Масштабируемая С++ библиотека для работы с алгоритмами машинного обучения, содержит реализацию k-means.<br />
<li>[https://www.scipy.org/ SciPy]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[http://scikit-learn.org/ scikit-learn]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[https://www.r-bloggers.com/k-means-clustering-in-r/ R]</li> Язык программирования для статистической обработки данных и работы с графикой, а также свободная программная среда вычислений с открытым исходным кодом в рамках проекта GNU, содержит три реализации k-means.<br />
<li>[http://elki.dbs.ifi.lmu.de ELKI]</li> Java фреймворк, содержащий реализацию k-means, а также множество других алгоритмов кластеризации.<br />
</ol><br />
<br />
==== Проприетарное программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.ayasdi.com/blog/bigdata/topological-data-analysis-of-oil-and-gas-petrophysical-data/ Ayasdi]</li><br />
<li>[http://www.stata.com/features/cluster-analysis/ Stata]</li><br />
<li>[http://mathworld.wolfram.com/K-MeansClusteringAlgorithm.html Mathematica]</li><br />
<li>[http://www.mathworks.com/help/stats/kmeans.html?requestedDomain=www.mathworks.com MATLAB]</li><br />
<li>[https://support.sas.com/rnd/app/stat/procedures/fastclus.html SAS]</li><br />
<li>[http://docs.rapidminer.com/studio/operators/modeling/segmentation/k_means.html RapidMiner]</li><br />
<li>[https://blogs.sap.com/2013/03/28/sap-hana-pal-k-means-algorithm-or-how-to-do-customer-segmentation-for-the-telecommunications-industry/ SAP HANA]</li><br />
</ol><br />
<br />
== Литература ==<br />
<br />
<references /></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_k_%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D1%85_(k-means)&diff=25249Алгоритм k средних (k-means)2018-01-16T17:25:01Z<p>Konshin: </p>
<hr />
<div>Основные авторы статьи (разделы 1, 2.4.1, 2.6-2.7, 3):<br />
[https://algowiki-project.org/ru/Участник:Бротиковская_Данута<b>Д.Бротиковская</b>] и<br />
[https://algowiki-project.org/ru/Участник:DennZo1993<b>Д.Зобнин</b>]<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:IanaV<b>Я.А.Валуйская</b>] и<br />
[https://algowiki-project.org/ru/Участник:GlotovES<b>Е.С.Глотов</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Parkhomenko<b>П.А.Пархоменко</b>] и<br />
[https://algowiki-project.org/ru/Участник:Ivan.mashonskiy<b>И.Д.Машонский</b>] (раздел 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Илья_Егоров<b>И.Егоров</b>] и<br />
[https://algowiki-project.org/ru/Участник:Богомазов_Евгений<b>Е.Богомазов</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Алгоритм <math>k</math> средних (<i>k</i>-means)<br />
| serial_complexity = <math>O(ikdn)</math><br />
| input_data = <math> dn </math><br />
| output_data = <math> n </math><br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Алгоритм <b><i>k средних</i></b> (англ. <i>k</i>-means) - один из алгоритмов машинного обучения, решающий задачу кластеризации.<br />
Этот алгоритм является неиерархическим<ref>"https://ru.wikipedia.org/wiki/Иерархическая_кластеризация"</ref>, итерационным методом кластеризации<ref>"https://ru.wikipedia.org/wiki/Кластерный_анализ"</ref>, он получил большую популярность благодаря своей простоте, наглядности реализации и достаточно высокому качеству работы. <br />
Был изобретен в 1950-х годах математиком <i>Гуго Штейнгаузом</i><ref>Steinhaus, Hugo. "Sur la division des corp materiels en parties." Bull. Acad. Polon. Sci 1.804 (1956): 801.</ref> и почти одновременно <i>Стюартом Ллойдом</i><ref>Lloyd, S. P. "Least square quantization in PCM. Bell Telephone Laboratories Paper. Published in journal much later: Lloyd, SP: Least squares quantization in PCM." IEEE Trans. Inform. Theor.(1957/1982).</ref>. Особую популярность приобрел после публикации работы <i>МакКуина</i><ref>MacQueen, James. "Some methods for classification and analysis of multivariate observations." Proceedings of the fifth Berkeley symposium on mathematical statistics and probability. Vol. 1. No. 14. 1967.</ref> в 1967.<br />
<br />
Алгоритм представляет собой версию EM-алгоритма<ref>"https://ru.wikipedia.org/wiki/EM-алгоритм"</ref>, применяемого также для разделения смеси гауссиан. Основная идея алгоритма <i>k</i>-means заключается в том, что данные произвольно разбиваются на кластеры, после чего итеративно перевычисляется центр масс для каждого кластера, полученного на предыдущем шаге, затем векторы разбиваются на кластеры вновь в соответствии с тем, какой из новых центров оказался ближе по выбранной метрике.<br />
<br />
Цель алгоритма заключается в разделении <math>n</math> наблюдений на <math>k</math> кластеров таким образом, чтобы каждое наблюдение принадлежало ровно одному кластеру, расположенному на наименьшем расстоянии от наблюдения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
<b>Дано:</b><br />
* набор из <math>n</math> наблюдений <math>X=\{\mathbf{x}_1, \mathbf{x}_2, ..., \mathbf{x}_n\}, \mathbf{x}_i \in \mathbb{R}^d, \ i=1,...,n</math>;<br />
* <math>k</math> - требуемое число кластеров, <math>k \in \mathbb{N}, \ k \leq n</math>.<br />
<br />
<b>Требуется:</b><br />
<br />
Разделить множество наблюдений <math>X</math> на <math>k</math> кластеров <math>S_1, S_2, ..., S_k</math>:<br />
* <math>S_i \cap S_j= \varnothing, \quad i \ne j</math><br />
<br />
* <math>\bigcup_{i=1}^{k} S_i = X</math><br />
<br />
<b>Действие алгоритма:</b><br />
<p>Алгоритм <i>k</i>-means разбивает набор <math>X</math> на <math>k</math> наборов <math>S_1, S_2, ..., S_k,</math> таким образом, чтобы минимизировать сумму квадратов расстояний от каждой точки кластера до его центра (центр масс кластера). Введем обозначение, <math>S=\{S_1, S_2, ..., S_k\}</math>. Тогда действие алгоритма <i>k</i>-means равносильно поиску:</p><br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math>\arg\min_{S} \sum\limits_{i=1}^k \sum\limits_{\mathbf{x} \in S_i} \rho(\mathbf{x}, \mathbf{\mu}_i )^2,</math></td><br />
<td align="right"><math>(1)</math></td><br />
</tr><br />
</table><br />
где <math>\mathbf{\mu}_i</math> &ndash; центры кластеров, <math>i=1,...,k, \quad \rho(\mathbf{x}, \mathbf{\mu}_i)</math> &ndash; функция расстояния между <math>\mathbf{x}</math> и <math>\mu_i</math><br />
<br />
<b>Шаги алгоритма:</b><br />
<ol><br />
<li><br />
<b>Начальный шаг: инициализация кластеров</b><br />
<p>Выбирается произвольное множество точек <math>\mu_i, \ i=1,...,k,</math> рассматриваемых как начальные центры кластеров: <math>\mu_i^{(0)} = \mu_i, \quad i=1,...,k</math></p><br />
</li><br />
<li><br />
<b>Распределение векторов по кластерам</b><br />
<p><b>Шаг</b> <math>t: \forall \mathbf{x}_i \in X, \ i=1,...,n: \mathbf{x}_i \in S_j \iff j=\arg\min_{k}\rho(\mathbf{x}_i,\mathbf{\mu}_k^{(t-1)})^2</math></p><br />
</li><br />
<li><br />
<b>Пересчет центров кластеров</b><br />
<p><b>Шаг </b> <math>t: \forall i=1,...,k: \mu_i^{(t)} = \cfrac{1}{|S_i|}\sum_{\mathbf{x}\in S_i}\mathbf{x}</math></p><br />
</li><br />
<li><br />
<b>Проверка условия останова:</b><br />
<p></p><br />
'''if''' <math>\exist i\in \overline{1,k}: \mu_i^{(t)} \ne \mu_i^{(t-1)}</math> '''then'''<br />
t = t + 1;<br />
goto 2;<br />
'''else'''<br />
'''stop'''<br />
</li><br />
</ol><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром являются шаги 2 и 3 приведенного выше алгоритма: <b><i>распределение векторов по кластерам</i></b> и <b><i>пересчет центров кластеров</i></b>. <br />
<br />
<p><br />
<b><i>Распределение векторов</i></b> по кластерам предполагает вычисление расстояний между каждым вектором <math>\mathbf{x}_i \in X, \ i= 1,...,n</math> и центрами кластера <math>\mathbf{\mu}_j, \ j= 1,...,k</math>. Таким образом, данный шаг предполагает <math>kn</math> вычислений расстояний между <math>d</math>-мерными векторами. <br />
</p><br />
<p><br />
<b><i>Пересчет центров кластеров</i></b> предполагает <math>k</math> вычислений центров масс <math>\mathbf{\mu}_i</math> множеств <math>S_i, \ i=1,...,k,</math> представленных выражением в шаге 3 представленного выше алгоритма.<br />
</p><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
<b>Инициализация центров масс <math>\mu_1, ..., \mu_k</math></b>. <br />
<br />
Наиболее распространенными являются следующие стратегии:<br />
<ul><br />
<li><br />
<b>Метод Forgy</b><br>В качестве начальных значений <math>\mu_1, ..., \mu_k</math> берутся случайно выбранные векторы.<br />
</li><br />
<li><br />
<b>Метод случайно разделения (Random Partitioning)</b><br>Для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> выбирается случайным образом кластер <math>S_1, ..., S_k</math>, после чего для каждого полученного кластера вычисляются значения <math>\mu_1, ..., \mu_k</math>.<br />
</li><br />
</ul><br />
<br />
<b>Распределение векторов по кластерам</b><br />
<br />
Для этого шага алгоритма между векторами <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> и центрами кластеров <math>\mu_1,...,\mu_k</math> вычисляются <b>расстояния</b> по формуле (как правило, используется Евлидово расстояние):<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mathbf{v}_1, \mathbf{v}_2 \in \mathbb{R}^d, \quad \rho(\mathbf{v}_1, \mathbf{v}_2) = \lVert \mathbf{v}_1- \mathbf{v}_2 \rVert= \sqrt{\sum_{i=1}^{d}(\mathbf{v}_{1,i} - \mathbf{v}_{2,i})^2}</math></td><br />
<td align="right"><math>(2)</math></td><br />
</tr><br />
</table><br />
<br />
<b>Пересчет центров кластеров</b><br />
<br />
Для этого шага алгоритма производится пересчет центров кластера по <b>формуле вычисления центра масс</b>:<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mu = \cfrac{1}{|S|}\sum_{\mathbf{x}\in S}\mathbf{x}</math></td><br />
<td align="right"><math>(3)</math></td><br />
</tr><br />
</table><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
'''1.''' Инициализировать центры кластеров <math>\mathbf{\mu}_i^{(1)}, \ i=1,...,k</math><br><br />
'''2.''' <math>t \leftarrow 1</math><br><br />
'''3.''' Распределение по кластерам<br><br />
<math>\quad S_i^{(t)}=\{\mathbf{x}_p: \lVert\mathbf{x}_p-\mathbf{\mu}_i^{(t)}\rVert^2 \leq \lVert\mathbf{x}_p-\mathbf{\mu}_j^{(t)}\rVert^2 \quad \forall j=1,...,k\},</math><br><br />
<math>\quad</math>где каждый вектор <math>\mathbf{x}_p</math> соотносится единственному кластеру <math>S^{(t)}</math><br><br />
'''4.''' Обновление центров кластеров<br><br />
<math>\quad \mathbf{\mu}_i^{(t+1)} = \frac{1}{|S^{(t)}_i|} \sum_{\mathbf{x}_j \in S^{(t)}_i} \mathbf{x}_j </math><br><br />
'''5.''' '''if''' <math>\exists i \in \overline{1,k}: \mathbf{\mu}_i^{(t+1)} \ne \mathbf{\mu}_i^{(t)}</math> '''then'''<br><br />
<math>\quad t = t + 1</math>;<br><br />
<math>\quad</math>goto '''3''';<br><br />
<math>~~~</math>'''else'''<br><br />
<math>\quad</math>'''stop'''<br><br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
<div style="padding-bottom: 20px"><br />
<p>Обозначим <math>\Theta_{\rm centroid}^{d, m}</math> временную сложность вычисления центорида кластера, число элементов которого равна <math>m</math>, в d-мерном пространстве.</p><br />
<p>Аналогично <math>\Theta_{\rm distance}^d</math> &ndash; временная сложность вычисления расстояния между двумя d-мерными векторами.</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<b>Сложность шага инициализации <math>k</math> кластеров мощности <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm init}^{k, d, m}</math></b><br />
<ul><br />
<li><i>Стратерия Forgy</i>: вычисления не требуются, <math>\Theta_{\rm init}^{k, d, m} = 0</math></li><br />
<li><i>Стратегия случайного разбиения</i>: вычисление центров <math>k</math> кластеров, <math>\Theta_{\rm init}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}, m \le n</math></li><br />
</ul><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Cложность шага распределения d мерных векторов по <math>k</math> кластерам &ndash; <math>\Theta_{\rm distribute}^{k, d}</math></b></p><br />
<p><br />
На этом шаге для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> вычисляется <math>k</math> расстояний до центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math><br />
</p><br />
<p align="center"><br />
<math>\Theta_{\rm distribute}^{k, d} = n \cdot k \cdot \Theta_{\rm distance}^d</math><br />
</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><br />
<b>Сложность шага пересчета центров <math>k</math> кластеров размера <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm recenter}^{k, d, m}</math></b><br />
</p><br />
<p>На этом шаге вычисляется <math>k</math> центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math></p><br />
<p align="center"><math>\Theta_{\rm recenter}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}</math> </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm centroid}^{d, m}</math> для кластера, число элементов которого равно <math>m</math></b></p><br />
<p align="center"><math>\Theta_{\rm centroid}^{d, m}</math> = <math>m \cdot d</math> сложений + <math>d</math> делений </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm distance}^d</math> в соответствие с формулой <math>(2)</math></b></p><br />
<p align="center"><math>\Theta_{\rm distance}^d</math> = <math>d</math> вычитаний + <math>d</math> умножений + <math>(d-1)</math> сложение</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Предположим, что алгоритм сошелся за <math>i</math> итераций, тогда временная сложность алгоритма <math>\Theta_{\rm k-means}^{d, n}</math></p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le \Theta_{\rm init}^{k, d, n} + i(\Theta_{\rm distribute}^{k, d} + \Theta_{\rm recenter}^{k, d, n})</math></b></p><br />
</div><br />
<div style="padding-bottom: 5px"><br />
<p>Операции сложения/вычитания:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le knd+ i(kn(2d-1) + knd) = knd+ i(kn(3d-1)) \thicksim O(ikdn)</math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Операции умножения/деления:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le kd + i(knd + kd) = kd + ikd(n+1) \thicksim O(ikdn) </math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Получаем, что <b>временная сложность</b> алгоритма <b><i>k</i>-means</b> кластеризации <math>n</math> <b>d-мерных</b> векторов на <math>k</math> кластеров за <math>i</math> итераций:</p><br />
<p align="center"><b><math> \Theta_{\rm k-means}^{d, n} \thicksim O(ikdn) </math></b></p><br />
</div><br />
<br />
=== Информационный граф ===<br />
<br />
Рассмотрим информационный граф алгоритма. Алгоритм <i>k</i>-means начинается с этапа инициализации, после которого следуют итерации, на каждой из которых выполняется два последовательных шага (см. [[#Схема реализации последовательного алгоритма|"Схема реализации последовательного алгоритма"]]): <br />
* распределение векторов по кластерам<br />
* перерасчет центров кластеров<br />
<br />
Поскольку основная часть вычислений приходится на шаги итераций, распишем информационные графы данных шагов.<br />
<br />
<p><b>Распределение векторов по кластерам</b></p><br />
Информационный граф шага распределения векторов по кластерам представлен на ''рисунке 1''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также центры кластеров <math>\mathbf{\mu}_1, ... \mathbf{\mu}_k</math>, вычисленные ранее (на шаге инициализации, если рассматривается первая итерация алгоритма, или на шаге пересчета центров кластеров предыдущей итерации в противном случае). Каждая пара векторов данных <math>\mathbf{x}_i, \ i=1,...,n,</math> и центров кластера <math>\mathbf{\mu}_j, \ j=1,...,k</math> : (<math>\mathbf{x}_i</math>, <math>\mathbf{\mu}_j</math>) подаются на независимые узлы <i>"d"</i> вычисления расстояния между векторами (более подробная схема вычисления расстояния представлена далее, ''рисунок 2''). Далее узлы вычисления расстояния <i>"d"</i>, соответствующие одному и тому же исходному вектору <math>\mathbf{x}_i</math> передаются на один узел <i>"m"</i>, где далее происходит вычисление новой метки кластера для каждого вектора <math>\mathbf{x}_i</math> (берется кластер с минимальным результатом вычисления расстояния). На выходе графа выдаются метки кластеров , <math>L_1, ..., L_n</math>, такие что <math>\forall \mathbf{x}_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>. <br />
<br />
[[file:Clusterization.png|thumb|center|800px|Рис. 1. Схема распределения векторов по кластерам. ''d'' &ndash; вычисление расстояния между векторами; ''m'' &ndash; вычисление минимума.]]<br />
<br />
<p><b>Вычисление расстояния между векторами</b></p><br />
Подробная схема вычисления расстояния между векторами <math>\mathbf{x}_i, \mathbf{\mu}_j</math> представлена на ''рисунке 2''. Как показано на графе, узел вычисления расстояния между векторами <i>"d"</i> состоит из шага взятия разности между векторами (узел "<math>-</math>") и взятия нормы получившегося вектора разности (узел "<math>||\cdot||^2</math>"). Более подробно, вычисление расстояния между векторами <math>\mathbf{x}_i = {x_{i1,}, ...,{x_{in}}}, \mathbf{\mu}_j = {\mu_{j1}, ...,\mu_{jn}}</math> может быть представлено как вычисление разности между каждой парой компонент <math>(x_{iz}, \mu_{jz}), \ z=1,...,d</math> (узел "<math>-</math>"), далее возведение в квадрат для каждого узла "<math>-</math>" (узел "<math>()^2</math>") и суммирования выходов всех узлов "<math>()^2</math>" (узел "<math>+</math>"). <br />
<br />
[[file:dist_calc.png|thumb|center|800px|Рис. 2. Схема вычисления расстояния между вектором и центром кластера.]]<br />
<br />
<p><b>Пересчет центров кластеров</b></p><br />
Информационный граф шага пересчета центров кластеров представлен на ''рисунке 3''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также им соответствующие метки кластера, <math>L_1, ..., L_n</math>, такие что <math>\forall x_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>, вычисленные на этапе распределения векторов по кластерам. Все векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math> подаются в узлы <math>+_1, ...+_k</math>, каждый узел <math>+_m, \ m = 1,...,k,</math> соответствует операции сложения векторов кластера с номером <math>m</math>. Метки кластера <math>L_1, ..., L_n</math> также совместно передаются на узлы <math>S_m, \ m=1,...,k</math>, на каждом из которых вычисляется количество векторов в соответствующем кластере (количество меток с соответствующим значением). Далее каждая пара выходов узлов <math>+_m</math> и <math>S_m</math> подается на узел "<math>/</math>", где производится деление суммы векторов кластера на количество элементов в нем. Значения, вычисленные на узлах "<math>/</math>", присваиваются новым центрам кластеров (выходные значения графа).<br />
<br />
[[file:Recluster.png|thumb|center|800px|Рис. 3. Схема пересчета центров кластеров]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
<p><br />
Работа алгоритма состоит из <math>i</math> итераций, в каждой из которых происходит <b>распределение d-мерных векторов по <math>k</math> кластерам</b>, а также <b>пересчет центров кластеров в d-мерном пространстве</b>. В шаге <b>распределения d-мерных векторов по <math>k</math> кластерам</b> расстояния между вектором и центрами кластеров вычисляются независимо (отсутствуют информационные зависимости). <b>Центры масс кластеров</b> также пересчитываются независимо друг от друга. Таким образом, имеет место [[глоссарий#Массовый параллелизм|''массовый параллелизм'']]. Вычислим параллельную сложность <math>\Psi_*</math> каждого из шагов, а также параллельную сложность всего алгоритма, <math>\Psi_{\rm k-means}</math>. Будем исходить из предположения, что может быть использовано любое необходимое число потоков.<br />
</p><br />
<br />
<p><b>Распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b></p> <br />
<p><br />
Поскольку на данном шаге для каждой пары векторов <math>\mathbf{x}_i, \ i=1,...,n</math> и <math>\mathbf{\mu}_j, \ j=1,...,k,</math> операции вычисления расстояния не зависят друг от друга, они могут выполняться параллельно. Тогда, разделив все вычисление расстояний на <math>n</math> потоков, получим, что в каждом потоке будет выполняться только одна операция вычисления расстояния между векторами размерности d. При этом каждому вычислительному потоку передаются координаты центров всех кластеров <math>\mathbf{\mu}_1, ..., \mathbf{\mu}_k</math>. Таким образом, параллельная сложность данного шага определяется <i> сложностью параллельной операции вычисления расстояния между d-мерными векторами</i>, <math>\Psi_{\rm distance}^d</math> и <i>сложностью определения наиболее близкого кластера</i> (паралельное взятие минимума по расстояниям), <math>\Psi_{\rm min}^k</math>. Для оценки <math>\Psi_{\rm distance}^d</math> воспользуемся [[Нахождение_частных_сумм_элементов_массива_сдваиванием | параллельной реализацией нахождения частичной суммы элементов массива путем сдваивания]]. Аналогично, <math>\Psi_{\rm min}^k = log(k)</math>. В результате, <math>\Psi_{\rm distance}^d = O(log(d))</math>. Таким образом: <br />
</p><br />
<p align="center"><math>\Psi_{\rm distribute}^{k, d} = \Psi_{\rm distance}^d + \Psi_{\rm min}^k = O(log(d))+O(log(k)) = O(log(kd))</math></p><br />
<br />
<p><b>Пересчет центров кластеров в d-мерном пространстве</b></p><br />
<p><br />
Поскольку на данном шаге для каждого из <math>k</math> кластеров центр масс может быть вычислен независимо, данные операции могут быть выполнены в отдельных потоках. Таким образом, параллельная сложность данного шага, <math>\Psi_{\rm recenter}^{k, d}</math>, будет определяться <i>параллельной сложностью вычисления одного центра масс кластера размера <math>m</math></i>, <math>\Psi_{\rm recenter}^{k, d}</math>, а так как <math>m \le n \Rightarrow \Psi_{\rm recenter}^{d, m} \le \Psi_{\rm recenter}^{d, n}</math>. Сложность вычисления центра масс кластера d-мерных векторов размера n аналогично предыдущим вычислениям равна <math>O(log(n))</math>. Тогда: <br />
</p><br />
<p align="center"><math>\Psi_{\rm recenter}^{k, d} \le \Psi_{\rm recenter}^{d, n} = O(log(n))</math></p><br />
<br />
<p><b>Общая параллельная сложность алгоритма</b></p><br />
<p><br />
На каждой итерации необходимо обновление центров кластеров, которые будут использованы на следующей итерации. Таким образом, итерационный процесс выполняется последовательно<ref>Zhao, Weizhong, Huifang Ma, and Qing He. "Parallel k-means clustering based on mapreduce." IEEE International Conference on Cloud Computing. Springer Berlin Heidelberg, 2009.</ref>. Тогда, поскольку сложность каждой итерации определяется <math>\Psi_{\rm distribute}^{k, d}</math> и <math>\Psi_{\rm recenter}</math>, сложность всего алгоритма, <math>\Psi_{\rm k-means}</math> в предположении, что было сделано <math>i</math> операций определяется выражением<br />
</p><br />
<p align="center"><math>\Psi_{\rm k-means} \approx i \cdot (\Psi_{\rm distribute}^{k, d} + \Psi_{\rm recenter}^{k, d}) \le i \cdot O(log(kdn))</math></p><br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
<p><br />
<b>Входные данные</b><br><br />
<ul><br />
<li>Матрица из <math>n \cdot d</math> элементов <math>x_{i, j} \in \mathbb{R}, \ i=1,...,n, \ j=1,...,d,</math> &ndash; координат векторов (наблюдений).</li><br />
<li>Целое положительное число <math>k, \ k \le n</math> &ndash; количество кластеров.</li><br />
</ul><br />
</p><br />
<p><br />
<b>Объем входных данных</b><br><br />
<math>1</math> целое число + <math>n \cdot d</math> вещественных чисел (при условии, что координаты &ndash; вещественные числа).<br />
</p><br />
<p><br />
<b>Выходные данные</b><br><br />
<math>n</math> целых положительных чисел <math>L_1, ..., L_n</math>&ndash; номера кластеров, соотвествующие каждому вектору (при условии, что нумерация кластеров начинается с <math>1</math>).<br />
</p><br />
<p><br />
<b>Объем выходных данных</b><br><br />
<math>n</math> целых положительных чисел.<br />
</p><br />
<br />
=== Свойства алгоритма ===<br />
<br />
<p><b>[[глоссарий#Вычислительная мощность|''Вычислительная мощность'']]</b></p><br />
Вычислительная мощность алгоритма <i>k</i>-means равна <math>\frac{ikdn}{nd} = ki </math>, где <math>k</math> &ndash; число кластеров, <math>i</math> &ndash; число итераций алгоритма.<br />
<br />
<p><b>[[глоссарий#Детерминированность |''Детерминированность'']] и [[глоссарий#Устойчивость |''Устойчивость'']] </b></p><br />
Алгоритм <i>k</i>-means является итерационным. Количество итераций алгоритма в общем случае не фиксируется и зависит от начального расположения объектов в пространстве, параметра <math>k</math>, а также от начального приближения центров кластеров, <math>\mu_1, ..., \mu_k</math>. В результате этого может варьироваться результат работы алгоритма. При неудачном выборе начальных параметров итерационный процесс может сойтись к локальному оптимуму<ref>Von Luxburg, Ulrike. Clustering Stability. Now Publishers Inc, 2010.</ref>. По этим причинам алгоритм не является ни <b>детермирированным</b>, ни <b>устойчивым</b>.<br />
<br />
<p><b>Соотношение последовательной и параллельной сложности алгоритма</b></p><br />
<br />
<math>\frac{\Theta_{\rm k-means}}{\Psi_{\rm k-means}} = \frac{O(ikdn)}{O(i \cdot log(kdn))}</math><br />
<br />
<b>Сильные стороны алгоритма</b>:<br />
<ul><br />
<li><i>Сравнительно высокая эффективность при простоте реализации</i></li><br />
<li><i>Высокое качество кластеризации</i></li><br />
<li><i>Возможность распараллеливания</i></li><br />
<li><i>Существование множества модификаций</i></li><br />
</ul><br />
<b>Недостатки алгоритма</b><ref>Ortega, Joaquín Pérez, Ma Del Rocío Boone Rojas, and María J. Somodevilla. "Research issues on K-means Algorithm: An Experimental Trial Using Matlab."</ref>:<br />
<ul><br />
<li><i>Количество кластеров является параметром алгоритма</i></li><br />
<li><br />
<p><i>Чувствительность к начальным условиям</i></p><br />
<p>Инициализация центров кластеров в значительной степени влияет на результат кластеризации.</p><br />
</li><br />
<li><br />
<p><i>Чувствительность к выбросам и шумам</i></p><br />
<p>Выбросы, далекие от центров настоящих кластеров, все равно учитываются при вычислении их центров.</p><br />
</li><br />
<li><br />
<p><i>Возможность сходимости к локальному оптимуму</i></p><br />
<p>Итеративный подход не дает гарантии сходимости к оптимальному решению.</p><br />
</li><br />
<li><br />
<p><i>Использование понятия "среднего"</i></p><br />
<p>Алгоритм неприменим к данным, для которых не определено понятие "среднего", например, категориальным данным.</p><br />
</li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализации алгоритма <math>k</math> средних согласно [[Scalability methodology|методике]] AlgoWiki.<br />
<br />
==== Реализация 1 ====<br />
<br />
Исследование масштабируемости параллельной реализации алгоритма <i>k</i>-means проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Алгоритм реализован на языке C с использованием средств MPI.<br />
Для исследования масштабируемости проводилось множество запусков программы с разным значением параметра (количество векторов для кластеризации), а также с различным числом процессоров. Фиксировались результаты запусков &ndash; время работы <math>t</math> и количество произведенных итераций алгоритма <math>i</math>.<br />
<br />
Параметры запусков для экспериментальной оценки:<br />
<ul><br />
<li>Значения <b>количества векторов</b> <math>n</math>: 20'000, 30'000, 50'000, 100'000, 200'000, 300'000, 500'000, 700'000, 1'000'000, 1'500'000, 2'000'000.</li><br />
<li>Значения <b>количества процессоров</b> <math>p</math>: 1, 8, 16, 32, 64, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512.</li><br />
<li>Значение <b>количества кластеров</b> <math>k</math>: 100.</li><br />
<li>Значение <b>размерности векторов</b> <math>d</math>: 10.</li><br />
</ul><br />
<br />
Для проведения экспериментов были сгенерированы нормально распределенные псевдослучайные данные (с использованием Python библиотеки [http://scikit-learn.org/ scikit-learn]):<br />
<syntaxhighlight lang="python"><br />
from sklearn.datasets import make_classification<br />
X1, Y1 = make_classification(n_features=10, n_redundant=2, n_informative=8,<br />
n_clusters_per_class=1, n_classes=100,<br />
n_samples=2000000)<br />
</syntaxhighlight><br />
<br />
Для заданной конфигурации эксперимента (<math>n, d, p, k</math>) и полученных результатов (<math>t, i</math>) [[Глоссарий#Производительность|производительность]] и эффективность реализации расчитывались по формулам:<br />
<p><br />
<ul><li><math>{\rm Performance} = \frac{N_{\rm k-means}^{d, n}}{t}\ \ ({\rm FLOPS}),</math></li></ul><br />
где <math>N_{\rm k-means}^{d, n}</math> &ndash; точное число операций с плавающей точкой (операции с памятью, а также целочисленные операции не учитывались), вычисленное в соответствие с разделом [[#Последовательная сложность алгоритма| "Последовательная сложность алгоритма"]];<br />
</p><br />
<p><br />
<ul><li><math>{\rm Efficiency} = \frac{100 \cdot {\rm Performance}}{{\rm Performance}_{\rm Peak}^{p}}\ \ (\%),</math></li></ul><br />
где <math>{\rm Performance}_{\rm Peak}^{p}</math> &ndash; пиковая производительность суперкомпьютера при <math>p</math> процессорах, вычисленная согласно спецификациям Intel<sup>&reg;</sup> XEON<sup>&reg;</sup> X5670<ref>"http://ark.intel.com/ru/products/47920/Intel-Xeon-Processor-X5670-12M-Cache-2_93-GHz-6_40-GTs-Intel-QPI"</ref>.<br />
</p><br />
<br />
Графики зависимости производительности и эффективности параллельной реализации <i>k</i>-means от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>) представлены на рисунках 4 и 5, соответственно.<br />
<br />
[[file:Flops.png|thumb|center|800px|Рис. 4. Параллельная реализация <i>k</i>-means. График зависимости производительности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
[[file:Efficiency.png|thumb|center|800px|Рис. 5. Параллельная реализация <i>k</i>-means. График зависимости эффективности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
<br />
<p><br />
<b>В результате</b> экспериментальной оценки были получены следующие оценки эффективности реализации:<br />
<ul><br />
<li><b>Минимальное</b> значение: 0.000409 % достигается при <math>n=20'000, p=480</math></li><br />
<li><b>Максимальное</b> значение: 0.741119 % достигается при <math>n=300'000, p=1</math></li><br />
</ul><br />
</p><br />
<br />
<p><br />
Оценки масштабируемости реализации алгоритма <i>k</i>-means:<br />
<ul><br />
<li><b>По числу процессоров:</b> -0.002683 &ndash; эффективность убывает с ростом числа процессоров. Данный результат вызван ростом накладных расходов для обеспечения параллельного выполнения алгоритма.</li><br />
<li><b>По размеру задачи:</b> 0.002779 &ndash; эффективность растет с ростом числа векторов. Данный результат вызван тем, что при увеличении размера задачи, количество вычислений растет по сравнению с временем, затрачиваемым на пересылку данных.</li><br />
<li><b>Общая оценка:</b> -0.000058 &ndash; можно сделать вывод, что в целом эффективность реализации незначительно уменьшается с ростом размера задачи и числа процессоров.</li><br />
</ul><br />
</p><br />
<br />
[https://github.com/serban/kmeans Использованная параллельная реализация алгоритма <i>k</i>-means]<br />
<br />
==== Реализация 2 ====<br />
<br />
Исследование также проводилось на суперкомпьютере "Ломоносов".<br />
<br />
<p>Набор данных для тестирования состоял из 946000 векторов размерности 2 (координаты на сфере)</p><br />
<p>Набор и границы значений изменяемых параметров запуска реализации алгоритма:</p><br />
<br />
* число процессов (виртуальных ядер) [8 : 512];<br />
* число кластеров [128 : 384].<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации <math>2,47%</math> достигается при делении исходных данных на 128 кластеров с использованием 512 процессов;<br />
* максимальная эффективность реализации <math>7,13%</math> достигается при делении исходных данных на 352 кластера с использованием 8 процессов.<br />
На рисунках 6 и 7, соответственно, представлены графики зависимости производительности и эффективности параллельной реализации <i>k</i>-means от числа кластеров и числа процессов.<br />
<br />
[[file:kmeans_performance.jpg|thumb|center|720px|Рис. 6. График зависимости производительности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
По рис. 6 можно отметить практически полное отсутствие роста производительности с увеличением числа процессов от 256 до 512 при минимальном размере задачи. Это связано с быстрым ростом накладных расходов по отношению к крайне низкому объёму вычислений. При росте размерности задачи данный эффект пропадает, и при одновременном пропорциональном увеличении числа кластеров и числа процессов рост производительности становится близким к линейному.<br />
<br />
[[file:kmeans_efficiency.jpg|thumb|center|720px|Рис. 7. График зависимости эффективности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
Исследовалась [https://github.com/like-a-bauss/kmeans параллельная реализация алгоритма <i>k</i>-means на MPI]. <br />
<p>Были получены следующие оценки масштабируемости реализации алгоритма <i>k</i>-means:</p><br />
*<i>По числу процессов:</i> <math>-0.02209</math>. Следовательно, с ростом числа процессов эффективность уменьшается. На рис. 7 можно наблюдать плавное и равномерное снижение производительности по мере увеличения числа процессов при неизменном числе кластеров, что свидетельствует об относительно невысоком росте накладных расходов на передачу данных между процессами и преобладании объёма вычислений над объёмом пересылок данных по сети.<br />
*<i>По размеру задачи:</i> <math>0.01252</math>. Следовательно, с ростом размера задачи (числа кластеров) эффективность увеличивается. При этом объём пересылок данных по сети пропорционален <math>(n + k) \cdot p</math> (где <math>k</math> - число кластеров, <math>n</math> - число входных векторов, <math>p</math> - число процессов) таким образом, поскольку <math>k << n</math>, рост накладных расходов с ростом числа кластеров при неизменном числе процессов и входных векторов представляет собой незначительную величину.<br />
*<i>Общая оценка:</i> <math>-0.00081</math>. Таким образом, с ростом и размера задачи, и числа процессов эффективность уменьшается. Это связано с тем, что отношение объёма вычислений к объёму передаваемых данных изменяется пропорционально <math>{kn \over (n + k) \cdot p} \thicksim {k \over p}</math>, что представляет собой невысокий коэффициент, но при этом позволяет параллельной реализации не деградировать до нулевой эффективности при значительном увеличении числа процессов.<br />
<br />
==== Реализация 3 ====<br />
<br />
Исследование масштабируемости алгоритма k-means в зависимости от количества используемых процессов было проведено в статье Кумара<ref>Kumar, J., Mills, R. T., Hoffman, F. M., & Hargrove, W. W. (2011). Parallel k-means clustering for quantitative ecoregion delineation using large data sets. Procedia Computer Science, 4, 1602-1611.</ref>. Исследование происходило на суперкомпьютере Jaguar - Cray XT5<ref>https://www.top500.org/system/176029</ref>. На момент экспериментов данный суперкомпьютер имел следующую конфигурацию: 18,688 вычислительных узлов с двумя шестнадцатиядерными процессорами AMD Opteron 2435 (Istanbul) 2.6 GHz, 16 GB of DDR2-800 оперативной памяти, и SeaStar 2+ роутер. Всего он состоял из 224,256 вычислительных ядер, 300 TB памяти, и пиковой производительностью 2.3 petaflops.<br />
<br />
Реализация алгоритма была выполнена на языке программирования C с использованием MPI.<br />
<br />
Объем данных составлял 84 ГБ, количество объектов (d-мерных векторов) n равнялось 1,024,767,667, размерность векторов <math>d</math> равнялась 22, количество кластеров <math>k</math> равнялось 1000. <br />
<br />
На рис. 8 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров. Можно отметить, что время, затраченное на чтение данных и запись результатов кластеризации, практически не изменяется с увеличением количества задействованных процессоров. Время же работы самого алгоритма кластеризации уменьшается с увеличением количества процессоров.<br />
<br />
[[Файл:k-means-proc-scalability.png|thumb|center|700px|Рис. 8. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (из работы: Kumar etc. 2011).]]<br />
<br />
Также было произведено самостоятельное исследование масштабируемости алгоритма k-means. Исследование производилось на суперкомпьютере "Blue Gene/P"<ref>http://hpc.cmc.msu.ru/bgp</ref>.<br />
<br />
Набор и границы значений изменяемых параметров запуска реализации алгоритма:<br />
<br />
* число процессоров [1, 2, 4, 8, 16, 32, 64, 128, 256, 512];<br />
* количество объектов [5000, 10000, 25000, 50000].<br />
<br />
Был использован набор данных ''Dataset for Sensorless Drive Diagnosis Data Set''<ref>PASCHKE, Fabian ; BAYER, Christian ; BATOR, Martyna ; MÖNKS, Uwe ; DICKS, Alexander ; ENGE-ROSENBLATT, Olaf ; LOHWEG, Volker: Sensorlose Zustandsüberwachung an Synchronmotoren, Bd. 46. In: HOFFMANN, Frank; HÃœLLERMEIER, Eyke (Hrsg.): Proceedings 23. Workshop Computational Intelligence. Karlsruhe : KIT Scientific Publishing, 2013 (Schriftenreihe des Instituts für Angewandte Informatik - Automatisierungstechnik am Karlsruher Institut für Technologie, 46), S. 211-225</ref> из репозитория ''Machine learning repository''<ref>https://archive.ics.uci.edu/ml/datasets/Dataset+for+Sensorless+Drive+Diagnosis</ref>.<br />
<br />
Исследуемый набор данных содержит векторы, размерность которых равна 49. Компоненты векторов являются вещественными числами. Количество кластеров равно 11. Пропущенные значения отсутствуют.<br />
<br />
Для исследования масштабируемости алгоритма была использована реализация на языке C с использованием MPI<ref>http://users.eecs.northwestern.edu/~wkliao/Kmeans/index.html</ref>. Код можно найти здесь: https://github.com/serban/kmeans. Данная реализация предоставляет возможность распараллеливать решение задачи с помощью технологий MPI, OpenMP И CUDA. Для запуска MPI-версии программы использовалась цель "mpi_main" Makefile.<br />
<br />
На рис. 9 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (использовались логарифмические оси). Разными цветами помечены запуски, соответствующие разным количествам объектам, участвующих в кластеризации. Можно видеть близкое к линейному увеличение времени работы программы в зависимости от количества процессоров. Также можно видеть увеличение времени работы алгоритма при увеличении количества объектов.<br />
[[Файл:Plot_1.png|thumb|center|900px|Рис. 9. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
На рис. 10 показана эта же зависимость, только в трехмерном пространстве. По аналогии с рис. 9, были использованы логарифмические оси. Как и в случае двумерного рисунка, можно видеть близкое к линейному увеличение времени работы программы.<br />
[[Файл:Kmeans-3d.png|thumb|center|900px|Рис. 10. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
==== Реализация 4 ====<br />
<br />
Исследование масштабируемости данной параллельной реализации алгоритма k-средних также проводилось на суперкомпьютере "Ломоносов". Параллельная реализация была написана самостоятельно на языке C, [http://git.algowiki-project.org/iaegorov/Egorov-Bogomazov-k-means/tree/master ссылка на реализацию]. Так как на каждой итерации число действий на единицу данных не велико и данные должны быть собраны вместе при перерасчете центроидов, было решено для ускорения вычислений воспользоваться только OpenMP без использовании MPI.<br />
<br />
Код собирался под gcc c опцией -fopenmp. Код считался на одном процессоре, технология hyperthreading не использовалась.<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 16] с увеличением в 2 раза;<br />
* размер данных [100000 : 1600000] с увеличением в 2 раза.<br />
<br />
В результате проведённых экспериментов были получены следующие данные:<br />
<br />
* Максимальная эффективность в точке достигается при переходе от 1 потока на 4 при минимальном размере данных, она равна <math>87,5%</math>.<br />
* Усредненная максимальная эффективность достигается при переходе с одного потока на два. Среднее время вычислений на всех рассмотренных потока снижается с 16,33 до 11.87 секунд, поэтому формально эффективность <math>= 16.33 / 11.87 / 2 \approx 68,4\%</math><br />
* Минимальная эффективность в точке достигается при переходе от 1 потока на 16 при размере данных 800000, она равна <math>11,1\%</math>.<br />
* Усредненная минимальная эффективность наблюдается при переходе с одного на максимальное рассматриваемое в эксперименте число потоков, равное 16. Время вычисления изменяется с 16,33 до 7,6 секунд, поэтому формально эффективность <math> = 16.33 / 7.6 / 16 \approx 14,9\%</math><br />
<br />
Ниже приведены графики зависимости вычислительного времени алгоритма и его эффективности от изменяемых параметров запуска — размера данных и числа процессоров:<br />
<br />
[[file:Egorov_Bogomazov_Time.jpg|thumb|center|700px|Рис. 11. Параллельная реализация алгоритма k-средних. Изменение вычислительного времени алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь видно, что время выполнения операций алгоритма плавно убывает по каждому из параметров, причем скорость убывания по параметру числа процессоров выше, чем в зависимости от размерности задачи.<br />
<br />
[[file:Egorov_Bogomazov_Efficiency.jpg|thumb|center|700px|Рис. 12. Параллельная реализация алгоритма k-средних. Изменение эффективности алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь построена эффективность перехода от последовательной реализации к параллельной. Рассчитывается она по формуле ''Время вычисления на 1 потоке / Время вычисления на <math>T</math> потоках / <math>T</math>'', где <math>T</math> — это число потоков. При вычислении на 1 процессоре она равна 100 \% в силу используемой формулы, что и отражено на графике.<br />
<br />
Проведем оценки масштабируемости:<br />
<br />
'''По числу процессов''' — при увеличении числа процессов эффективность уменьшается на всей области рассматриваемых значений, причем темп убывания замедляется с ростом числа процессов.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений вначале кратковременно возрастает, но затем начинает относительно равномерно убывать на всей рассматриваемой области.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений в общем случае постепенно убывает. На малых данных она выходит на пик мощности, являющийся максимумом эффективности в исследуемых условиях, но затем возвращается к процессу убывания.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
В однопоточном режиме на наборах данных, представляющих практический интерес (порядка нескольких десятков тысяч векторов и выше), время работы алгоритма неприемлемо велико. Благодаря свойству массового параллелизма должно наблюдаться значительное ускорение алгоритма на многоядерных архитектурах (Intel Xeon), а также на графических процессорах, даже на мобильных вычислительных системах (ноутбуках), оснащенных видеокартой. Алгоритм <i>k</i>-means также будет демонстрировать значительное ускорение на сверхмощных вычислительных комплексах (суперкомпьютерах, системах облачных вычислений<ref>"Issa. "Performance characterization and analysis for Hadoop K-means iteration". Journal of Cloud Computing, 2016"</ref>).<br />
<br />
На сегодняшний день существует множество реализаций алгоритма <i>k</i>-means, в частности, направленных на оптимизацию параллельной работы на различных архитектурах<ref>"Raghavan R. A fast and scalable hardware architecture for K-means clustering for big data analysis : дис. – University of Colorado Colorado Springs. Kraemer Family Library, 2016."</ref><ref>"Yang, Luobin, et al. "High performance data clustering: a comparative analysis of performance for GPU, RASC, MPI, and OpenMP implementations." The Journal of supercomputing 70.1 (2014): 284-300."</ref><ref>"Li, You, et al. "Speeding up k-means algorithm by GPUs." Computer and Information Technology (CIT), 2010 IEEE 10th International Conference on. IEEE, 2010."</ref>. Предлагается множество адаптаций алгоритма под конкретные архитектуры. Например, авторы работы<ref>"Kanan, Gebali, Ibrahim. "Fast and Area-Efficient Hardware Implementation of the K-means<br />
Clustering Algorithm". WSEAS Transactions on circuits and systems. Vol. 15. 2016"</ref> производят перерасчет центров кластеров на этапе распределения векторов по кластерам.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
==== Открытое программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.icpsr.umich.edu/CrimeStat/ CrimeStat]</li> Программное обеспечение, созданное для операционных систем Windows, предоставляющее инструменты статистического и пространственного анализа для решения задачи картирования преступности.<br />
<li>[http://juliastats.github.io Julia]</li> Высокоуровневый высокопроизводительный свободный язык программирования с динамической типизацией, созданный для математических вычислений, содержит реализацию k-means.<br />
<li>[https://mahout.apache.org Mahout]</li> Apache Mahout - Java библиотека для работы с алгоритмами машинного обучения с использованием MapReduce. Содержит реализацию k-means.<br />
<li>[https://www.gnu.org/software/octave/ Octave]</li> Написанная на C++ свободная система для математических вычислений, использующая совместимый с MATLAB язык высокого уровня, содержит реализацию k-means.<br />
<li>[http://spark.apache.org/docs/latest/mllib-clustering.html Spark]</li> Распределенная реализация k-means содержится в библиотеке Mlib для работы с алгоритмами машинного обучения, взаимодействующая с Python библиотекой NumPy и библиотека R.<br />
<li>[http://torch.ch Torch]</li> MATLAB-подобная библиотека для языка программирования Lua с открытым исходным кодом, предоставляет большое количество алгоритмов для глубинного обучения и научных расчётов. Ядро написано на Си, прикладная часть выполняется на LuaJIT, поддерживается распараллеливание вычислений средствами CUDA и OpenMP. Существуют реализации k-means.<br />
<li>[http://www.cs.waikato.ac.nz/ml/weka/ Weka]</li> Cвободное программное обеспечение для анализа данных, написанное на Java. Содержит k-means и x-means.<br />
<li>[http://accord-framework.net/docs/html/T_Accord_MachineLearning_KMeans.htm Accord.NET]</li> C# реализация алгоритмов k-means, k-means++, k-modes.<br />
<li>[http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html OpenCV]</li> Написанная на С++ библиотека, направленная в основном на решение задач компьютерного зрения. Содержит реализацию k-means.<br />
<li>[http://mlpack.org/ MLPACK]</li> Масштабируемая С++ библиотека для работы с алгоритмами машинного обучения, содержит реализацию k-means.<br />
<li>[https://www.scipy.org/ SciPy]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[http://scikit-learn.org/ scikit-learn]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[https://www.r-bloggers.com/k-means-clustering-in-r/ R]</li> Язык программирования для статистической обработки данных и работы с графикой, а также свободная программная среда вычислений с открытым исходным кодом в рамках проекта GNU, содержит три реализации k-means.<br />
<li>[http://elki.dbs.ifi.lmu.de ELKI]</li> Java фреймворк, содержащий реализацию k-means, а также множество других алгоритмов кластеризации.<br />
</ol><br />
<br />
==== Проприетарное программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.ayasdi.com/blog/bigdata/topological-data-analysis-of-oil-and-gas-petrophysical-data/ Ayasdi]</li><br />
<li>[http://www.stata.com/features/cluster-analysis/ Stata]</li><br />
<li>[http://mathworld.wolfram.com/K-MeansClusteringAlgorithm.html Mathematica]</li><br />
<li>[http://www.mathworks.com/help/stats/kmeans.html?requestedDomain=www.mathworks.com MATLAB]</li><br />
<li>[https://support.sas.com/rnd/app/stat/procedures/fastclus.html SAS]</li><br />
<li>[http://docs.rapidminer.com/studio/operators/modeling/segmentation/k_means.html RapidMiner]</li><br />
<li>[https://blogs.sap.com/2013/03/28/sap-hana-pal-k-means-algorithm-or-how-to-do-customer-segmentation-for-the-telecommunications-industry/ SAP HANA]</li><br />
</ol><br />
<br />
== Литература ==<br />
<br />
<references /></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_k_%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D1%85_(k-means)&diff=25248Алгоритм k средних (k-means)2018-01-16T17:23:03Z<p>Konshin: /* Общее описание алгоритма */</p>
<hr />
<div>Основные авторы статьи (разделы 1, 2.4.1, 2.6-2.7, 3):<br />
[https://algowiki-project.org/ru/Участник:Бротиковская_Данута<b>Д.Бротиковская</b>] и<br />
[https://algowiki-project.org/ru/Участник:DennZo1993<b>Д.Зобнин</b>]<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:IanaV<b>Я.А.Валуйская</b>] и<br />
[https://algowiki-project.org/ru/Участник:GlotovES<b>Е.С.Глотов</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Parkhomenko<b>П.А.Пархоменко</b>] и<br />
[https://algowiki-project.org/ru/Участник:Ivan.mashonskiy<b>И.Д.Машонский</b>] (раздел 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Илья_Егоров<b>И.Егоров</b>] и<br />
[https://algowiki-project.org/ru/Участник:Богомазов_Евгений<b>Е.Богомазов</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Алгоритм <math>k</math> средних (<math>k</math>-means)<br />
| serial_complexity = <math>O(ikdn)</math><br />
| input_data = <math> dn </math><br />
| output_data = <math> n </math><br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Алгоритм <b><i>k средних</i></b> (англ. <i>k</i>-means) - один из алгоритмов машинного обучения, решающий задачу кластеризации.<br />
Этот алгоритм является неиерархическим<ref>"https://ru.wikipedia.org/wiki/Иерархическая_кластеризация"</ref>, итерационным методом кластеризации<ref>"https://ru.wikipedia.org/wiki/Кластерный_анализ"</ref>, он получил большую популярность благодаря своей простоте, наглядности реализации и достаточно высокому качеству работы. <br />
Был изобретен в 1950-х годах математиком <i>Гуго Штейнгаузом</i><ref>Steinhaus, Hugo. "Sur la division des corp materiels en parties." Bull. Acad. Polon. Sci 1.804 (1956): 801.</ref> и почти одновременно <i>Стюартом Ллойдом</i><ref>Lloyd, S. P. "Least square quantization in PCM. Bell Telephone Laboratories Paper. Published in journal much later: Lloyd, SP: Least squares quantization in PCM." IEEE Trans. Inform. Theor.(1957/1982).</ref>. Особую популярность приобрел после публикации работы <i>МакКуина</i><ref>MacQueen, James. "Some methods for classification and analysis of multivariate observations." Proceedings of the fifth Berkeley symposium on mathematical statistics and probability. Vol. 1. No. 14. 1967.</ref> в 1967.<br />
<br />
Алгоритм представляет собой версию EM-алгоритма<ref>"https://ru.wikipedia.org/wiki/EM-алгоритм"</ref>, применяемого также для разделения смеси гауссиан. Основная идея алгоритма <i>k</i>-means заключается в том, что данные произвольно разбиваются на кластеры, после чего итеративно перевычисляется центр масс для каждого кластера, полученного на предыдущем шаге, затем векторы разбиваются на кластеры вновь в соответствии с тем, какой из новых центров оказался ближе по выбранной метрике.<br />
<br />
Цель алгоритма заключается в разделении <math>n</math> наблюдений на <math>k</math> кластеров таким образом, чтобы каждое наблюдение принадлежало ровно одному кластеру, расположенному на наименьшем расстоянии от наблюдения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
<b>Дано:</b><br />
* набор из <math>n</math> наблюдений <math>X=\{\mathbf{x}_1, \mathbf{x}_2, ..., \mathbf{x}_n\}, \mathbf{x}_i \in \mathbb{R}^d, \ i=1,...,n</math>;<br />
* <math>k</math> - требуемое число кластеров, <math>k \in \mathbb{N}, \ k \leq n</math>.<br />
<br />
<b>Требуется:</b><br />
<br />
Разделить множество наблюдений <math>X</math> на <math>k</math> кластеров <math>S_1, S_2, ..., S_k</math>:<br />
* <math>S_i \cap S_j= \varnothing, \quad i \ne j</math><br />
<br />
* <math>\bigcup_{i=1}^{k} S_i = X</math><br />
<br />
<b>Действие алгоритма:</b><br />
<p>Алгоритм <i>k</i>-means разбивает набор <math>X</math> на <math>k</math> наборов <math>S_1, S_2, ..., S_k,</math> таким образом, чтобы минимизировать сумму квадратов расстояний от каждой точки кластера до его центра (центр масс кластера). Введем обозначение, <math>S=\{S_1, S_2, ..., S_k\}</math>. Тогда действие алгоритма <i>k</i>-means равносильно поиску:</p><br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math>\arg\min_{S} \sum\limits_{i=1}^k \sum\limits_{\mathbf{x} \in S_i} \rho(\mathbf{x}, \mathbf{\mu}_i )^2,</math></td><br />
<td align="right"><math>(1)</math></td><br />
</tr><br />
</table><br />
где <math>\mathbf{\mu}_i</math> &ndash; центры кластеров, <math>i=1,...,k, \quad \rho(\mathbf{x}, \mathbf{\mu}_i)</math> &ndash; функция расстояния между <math>\mathbf{x}</math> и <math>\mu_i</math><br />
<br />
<b>Шаги алгоритма:</b><br />
<ol><br />
<li><br />
<b>Начальный шаг: инициализация кластеров</b><br />
<p>Выбирается произвольное множество точек <math>\mu_i, \ i=1,...,k,</math> рассматриваемых как начальные центры кластеров: <math>\mu_i^{(0)} = \mu_i, \quad i=1,...,k</math></p><br />
</li><br />
<li><br />
<b>Распределение векторов по кластерам</b><br />
<p><b>Шаг</b> <math>t: \forall \mathbf{x}_i \in X, \ i=1,...,n: \mathbf{x}_i \in S_j \iff j=\arg\min_{k}\rho(\mathbf{x}_i,\mathbf{\mu}_k^{(t-1)})^2</math></p><br />
</li><br />
<li><br />
<b>Пересчет центров кластеров</b><br />
<p><b>Шаг </b> <math>t: \forall i=1,...,k: \mu_i^{(t)} = \cfrac{1}{|S_i|}\sum_{\mathbf{x}\in S_i}\mathbf{x}</math></p><br />
</li><br />
<li><br />
<b>Проверка условия останова:</b><br />
<p></p><br />
'''if''' <math>\exist i\in \overline{1,k}: \mu_i^{(t)} \ne \mu_i^{(t-1)}</math> '''then'''<br />
t = t + 1;<br />
goto 2;<br />
'''else'''<br />
'''stop'''<br />
</li><br />
</ol><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром являются шаги 2 и 3 приведенного выше алгоритма: <b><i>распределение векторов по кластерам</i></b> и <b><i>пересчет центров кластеров</i></b>. <br />
<br />
<p><br />
<b><i>Распределение векторов</i></b> по кластерам предполагает вычисление расстояний между каждым вектором <math>\mathbf{x}_i \in X, \ i= 1,...,n</math> и центрами кластера <math>\mathbf{\mu}_j, \ j= 1,...,k</math>. Таким образом, данный шаг предполагает <math>kn</math> вычислений расстояний между <math>d</math>-мерными векторами. <br />
</p><br />
<p><br />
<b><i>Пересчет центров кластеров</i></b> предполагает <math>k</math> вычислений центров масс <math>\mathbf{\mu}_i</math> множеств <math>S_i, \ i=1,...,k,</math> представленных выражением в шаге 3 представленного выше алгоритма.<br />
</p><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
<b>Инициализация центров масс <math>\mu_1, ..., \mu_k</math></b>. <br />
<br />
Наиболее распространенными являются следующие стратегии:<br />
<ul><br />
<li><br />
<b>Метод Forgy</b><br>В качестве начальных значений <math>\mu_1, ..., \mu_k</math> берутся случайно выбранные векторы.<br />
</li><br />
<li><br />
<b>Метод случайно разделения (Random Partitioning)</b><br>Для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> выбирается случайным образом кластер <math>S_1, ..., S_k</math>, после чего для каждого полученного кластера вычисляются значения <math>\mu_1, ..., \mu_k</math>.<br />
</li><br />
</ul><br />
<br />
<b>Распределение векторов по кластерам</b><br />
<br />
Для этого шага алгоритма между векторами <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> и центрами кластеров <math>\mu_1,...,\mu_k</math> вычисляются <b>расстояния</b> по формуле (как правило, используется Евлидово расстояние):<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mathbf{v}_1, \mathbf{v}_2 \in \mathbb{R}^d, \quad \rho(\mathbf{v}_1, \mathbf{v}_2) = \lVert \mathbf{v}_1- \mathbf{v}_2 \rVert= \sqrt{\sum_{i=1}^{d}(\mathbf{v}_{1,i} - \mathbf{v}_{2,i})^2}</math></td><br />
<td align="right"><math>(2)</math></td><br />
</tr><br />
</table><br />
<br />
<b>Пересчет центров кластеров</b><br />
<br />
Для этого шага алгоритма производится пересчет центров кластера по <b>формуле вычисления центра масс</b>:<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mu = \cfrac{1}{|S|}\sum_{\mathbf{x}\in S}\mathbf{x}</math></td><br />
<td align="right"><math>(3)</math></td><br />
</tr><br />
</table><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
'''1.''' Инициализировать центры кластеров <math>\mathbf{\mu}_i^{(1)}, \ i=1,...,k</math><br><br />
'''2.''' <math>t \leftarrow 1</math><br><br />
'''3.''' Распределение по кластерам<br><br />
<math>\quad S_i^{(t)}=\{\mathbf{x}_p: \lVert\mathbf{x}_p-\mathbf{\mu}_i^{(t)}\rVert^2 \leq \lVert\mathbf{x}_p-\mathbf{\mu}_j^{(t)}\rVert^2 \quad \forall j=1,...,k\},</math><br><br />
<math>\quad</math>где каждый вектор <math>\mathbf{x}_p</math> соотносится единственному кластеру <math>S^{(t)}</math><br><br />
'''4.''' Обновление центров кластеров<br><br />
<math>\quad \mathbf{\mu}_i^{(t+1)} = \frac{1}{|S^{(t)}_i|} \sum_{\mathbf{x}_j \in S^{(t)}_i} \mathbf{x}_j </math><br><br />
'''5.''' '''if''' <math>\exists i \in \overline{1,k}: \mathbf{\mu}_i^{(t+1)} \ne \mathbf{\mu}_i^{(t)}</math> '''then'''<br><br />
<math>\quad t = t + 1</math>;<br><br />
<math>\quad</math>goto '''3''';<br><br />
<math>~~~</math>'''else'''<br><br />
<math>\quad</math>'''stop'''<br><br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
<div style="padding-bottom: 20px"><br />
<p>Обозначим <math>\Theta_{\rm centroid}^{d, m}</math> временную сложность вычисления центорида кластера, число элементов которого равна <math>m</math>, в d-мерном пространстве.</p><br />
<p>Аналогично <math>\Theta_{\rm distance}^d</math> &ndash; временная сложность вычисления расстояния между двумя d-мерными векторами.</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<b>Сложность шага инициализации <math>k</math> кластеров мощности <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm init}^{k, d, m}</math></b><br />
<ul><br />
<li><i>Стратерия Forgy</i>: вычисления не требуются, <math>\Theta_{\rm init}^{k, d, m} = 0</math></li><br />
<li><i>Стратегия случайного разбиения</i>: вычисление центров <math>k</math> кластеров, <math>\Theta_{\rm init}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}, m \le n</math></li><br />
</ul><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Cложность шага распределения d мерных векторов по <math>k</math> кластерам &ndash; <math>\Theta_{\rm distribute}^{k, d}</math></b></p><br />
<p><br />
На этом шаге для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> вычисляется <math>k</math> расстояний до центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math><br />
</p><br />
<p align="center"><br />
<math>\Theta_{\rm distribute}^{k, d} = n \cdot k \cdot \Theta_{\rm distance}^d</math><br />
</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><br />
<b>Сложность шага пересчета центров <math>k</math> кластеров размера <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm recenter}^{k, d, m}</math></b><br />
</p><br />
<p>На этом шаге вычисляется <math>k</math> центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math></p><br />
<p align="center"><math>\Theta_{\rm recenter}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}</math> </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm centroid}^{d, m}</math> для кластера, число элементов которого равно <math>m</math></b></p><br />
<p align="center"><math>\Theta_{\rm centroid}^{d, m}</math> = <math>m \cdot d</math> сложений + <math>d</math> делений </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm distance}^d</math> в соответствие с формулой <math>(2)</math></b></p><br />
<p align="center"><math>\Theta_{\rm distance}^d</math> = <math>d</math> вычитаний + <math>d</math> умножений + <math>(d-1)</math> сложение</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Предположим, что алгоритм сошелся за <math>i</math> итераций, тогда временная сложность алгоритма <math>\Theta_{\rm k-means}^{d, n}</math></p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le \Theta_{\rm init}^{k, d, n} + i(\Theta_{\rm distribute}^{k, d} + \Theta_{\rm recenter}^{k, d, n})</math></b></p><br />
</div><br />
<div style="padding-bottom: 5px"><br />
<p>Операции сложения/вычитания:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le knd+ i(kn(2d-1) + knd) = knd+ i(kn(3d-1)) \thicksim O(ikdn)</math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Операции умножения/деления:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le kd + i(knd + kd) = kd + ikd(n+1) \thicksim O(ikdn) </math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Получаем, что <b>временная сложность</b> алгоритма <b><i>k</i>-means</b> кластеризации <math>n</math> <b>d-мерных</b> векторов на <math>k</math> кластеров за <math>i</math> итераций:</p><br />
<p align="center"><b><math> \Theta_{\rm k-means}^{d, n} \thicksim O(ikdn) </math></b></p><br />
</div><br />
<br />
=== Информационный граф ===<br />
<br />
Рассмотрим информационный граф алгоритма. Алгоритм <i>k</i>-means начинается с этапа инициализации, после которого следуют итерации, на каждой из которых выполняется два последовательных шага (см. [[#Схема реализации последовательного алгоритма|"Схема реализации последовательного алгоритма"]]): <br />
* распределение векторов по кластерам<br />
* перерасчет центров кластеров<br />
<br />
Поскольку основная часть вычислений приходится на шаги итераций, распишем информационные графы данных шагов.<br />
<br />
<p><b>Распределение векторов по кластерам</b></p><br />
Информационный граф шага распределения векторов по кластерам представлен на ''рисунке 1''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также центры кластеров <math>\mathbf{\mu}_1, ... \mathbf{\mu}_k</math>, вычисленные ранее (на шаге инициализации, если рассматривается первая итерация алгоритма, или на шаге пересчета центров кластеров предыдущей итерации в противном случае). Каждая пара векторов данных <math>\mathbf{x}_i, \ i=1,...,n,</math> и центров кластера <math>\mathbf{\mu}_j, \ j=1,...,k</math> : (<math>\mathbf{x}_i</math>, <math>\mathbf{\mu}_j</math>) подаются на независимые узлы <i>"d"</i> вычисления расстояния между векторами (более подробная схема вычисления расстояния представлена далее, ''рисунок 2''). Далее узлы вычисления расстояния <i>"d"</i>, соответствующие одному и тому же исходному вектору <math>\mathbf{x}_i</math> передаются на один узел <i>"m"</i>, где далее происходит вычисление новой метки кластера для каждого вектора <math>\mathbf{x}_i</math> (берется кластер с минимальным результатом вычисления расстояния). На выходе графа выдаются метки кластеров , <math>L_1, ..., L_n</math>, такие что <math>\forall \mathbf{x}_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>. <br />
<br />
[[file:Clusterization.png|thumb|center|800px|Рис. 1. Схема распределения векторов по кластерам. ''d'' &ndash; вычисление расстояния между векторами; ''m'' &ndash; вычисление минимума.]]<br />
<br />
<p><b>Вычисление расстояния между векторами</b></p><br />
Подробная схема вычисления расстояния между векторами <math>\mathbf{x}_i, \mathbf{\mu}_j</math> представлена на ''рисунке 2''. Как показано на графе, узел вычисления расстояния между векторами <i>"d"</i> состоит из шага взятия разности между векторами (узел "<math>-</math>") и взятия нормы получившегося вектора разности (узел "<math>||\cdot||^2</math>"). Более подробно, вычисление расстояния между векторами <math>\mathbf{x}_i = {x_{i1,}, ...,{x_{in}}}, \mathbf{\mu}_j = {\mu_{j1}, ...,\mu_{jn}}</math> может быть представлено как вычисление разности между каждой парой компонент <math>(x_{iz}, \mu_{jz}), \ z=1,...,d</math> (узел "<math>-</math>"), далее возведение в квадрат для каждого узла "<math>-</math>" (узел "<math>()^2</math>") и суммирования выходов всех узлов "<math>()^2</math>" (узел "<math>+</math>"). <br />
<br />
[[file:dist_calc.png|thumb|center|800px|Рис. 2. Схема вычисления расстояния между вектором и центром кластера.]]<br />
<br />
<p><b>Пересчет центров кластеров</b></p><br />
Информационный граф шага пересчета центров кластеров представлен на ''рисунке 3''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также им соответствующие метки кластера, <math>L_1, ..., L_n</math>, такие что <math>\forall x_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>, вычисленные на этапе распределения векторов по кластерам. Все векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math> подаются в узлы <math>+_1, ...+_k</math>, каждый узел <math>+_m, \ m = 1,...,k,</math> соответствует операции сложения векторов кластера с номером <math>m</math>. Метки кластера <math>L_1, ..., L_n</math> также совместно передаются на узлы <math>S_m, \ m=1,...,k</math>, на каждом из которых вычисляется количество векторов в соответствующем кластере (количество меток с соответствующим значением). Далее каждая пара выходов узлов <math>+_m</math> и <math>S_m</math> подается на узел "<math>/</math>", где производится деление суммы векторов кластера на количество элементов в нем. Значения, вычисленные на узлах "<math>/</math>", присваиваются новым центрам кластеров (выходные значения графа).<br />
<br />
[[file:Recluster.png|thumb|center|800px|Рис. 3. Схема пересчета центров кластеров]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
<p><br />
Работа алгоритма состоит из <math>i</math> итераций, в каждой из которых происходит <b>распределение d-мерных векторов по <math>k</math> кластерам</b>, а также <b>пересчет центров кластеров в d-мерном пространстве</b>. В шаге <b>распределения d-мерных векторов по <math>k</math> кластерам</b> расстояния между вектором и центрами кластеров вычисляются независимо (отсутствуют информационные зависимости). <b>Центры масс кластеров</b> также пересчитываются независимо друг от друга. Таким образом, имеет место [[глоссарий#Массовый параллелизм|''массовый параллелизм'']]. Вычислим параллельную сложность <math>\Psi_*</math> каждого из шагов, а также параллельную сложность всего алгоритма, <math>\Psi_{\rm k-means}</math>. Будем исходить из предположения, что может быть использовано любое необходимое число потоков.<br />
</p><br />
<br />
<p><b>Распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b></p> <br />
<p><br />
Поскольку на данном шаге для каждой пары векторов <math>\mathbf{x}_i, \ i=1,...,n</math> и <math>\mathbf{\mu}_j, \ j=1,...,k,</math> операции вычисления расстояния не зависят друг от друга, они могут выполняться параллельно. Тогда, разделив все вычисление расстояний на <math>n</math> потоков, получим, что в каждом потоке будет выполняться только одна операция вычисления расстояния между векторами размерности d. При этом каждому вычислительному потоку передаются координаты центров всех кластеров <math>\mathbf{\mu}_1, ..., \mathbf{\mu}_k</math>. Таким образом, параллельная сложность данного шага определяется <i> сложностью параллельной операции вычисления расстояния между d-мерными векторами</i>, <math>\Psi_{\rm distance}^d</math> и <i>сложностью определения наиболее близкого кластера</i> (паралельное взятие минимума по расстояниям), <math>\Psi_{\rm min}^k</math>. Для оценки <math>\Psi_{\rm distance}^d</math> воспользуемся [[Нахождение_частных_сумм_элементов_массива_сдваиванием | параллельной реализацией нахождения частичной суммы элементов массива путем сдваивания]]. Аналогично, <math>\Psi_{\rm min}^k = log(k)</math>. В результате, <math>\Psi_{\rm distance}^d = O(log(d))</math>. Таким образом: <br />
</p><br />
<p align="center"><math>\Psi_{\rm distribute}^{k, d} = \Psi_{\rm distance}^d + \Psi_{\rm min}^k = O(log(d))+O(log(k)) = O(log(kd))</math></p><br />
<br />
<p><b>Пересчет центров кластеров в d-мерном пространстве</b></p><br />
<p><br />
Поскольку на данном шаге для каждого из <math>k</math> кластеров центр масс может быть вычислен независимо, данные операции могут быть выполнены в отдельных потоках. Таким образом, параллельная сложность данного шага, <math>\Psi_{\rm recenter}^{k, d}</math>, будет определяться <i>параллельной сложностью вычисления одного центра масс кластера размера <math>m</math></i>, <math>\Psi_{\rm recenter}^{k, d}</math>, а так как <math>m \le n \Rightarrow \Psi_{\rm recenter}^{d, m} \le \Psi_{\rm recenter}^{d, n}</math>. Сложность вычисления центра масс кластера d-мерных векторов размера n аналогично предыдущим вычислениям равна <math>O(log(n))</math>. Тогда: <br />
</p><br />
<p align="center"><math>\Psi_{\rm recenter}^{k, d} \le \Psi_{\rm recenter}^{d, n} = O(log(n))</math></p><br />
<br />
<p><b>Общая параллельная сложность алгоритма</b></p><br />
<p><br />
На каждой итерации необходимо обновление центров кластеров, которые будут использованы на следующей итерации. Таким образом, итерационный процесс выполняется последовательно<ref>Zhao, Weizhong, Huifang Ma, and Qing He. "Parallel k-means clustering based on mapreduce." IEEE International Conference on Cloud Computing. Springer Berlin Heidelberg, 2009.</ref>. Тогда, поскольку сложность каждой итерации определяется <math>\Psi_{\rm distribute}^{k, d}</math> и <math>\Psi_{\rm recenter}</math>, сложность всего алгоритма, <math>\Psi_{\rm k-means}</math> в предположении, что было сделано <math>i</math> операций определяется выражением<br />
</p><br />
<p align="center"><math>\Psi_{\rm k-means} \approx i \cdot (\Psi_{\rm distribute}^{k, d} + \Psi_{\rm recenter}^{k, d}) \le i \cdot O(log(kdn))</math></p><br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
<p><br />
<b>Входные данные</b><br><br />
<ul><br />
<li>Матрица из <math>n \cdot d</math> элементов <math>x_{i, j} \in \mathbb{R}, \ i=1,...,n, \ j=1,...,d,</math> &ndash; координат векторов (наблюдений).</li><br />
<li>Целое положительное число <math>k, \ k \le n</math> &ndash; количество кластеров.</li><br />
</ul><br />
</p><br />
<p><br />
<b>Объем входных данных</b><br><br />
<math>1</math> целое число + <math>n \cdot d</math> вещественных чисел (при условии, что координаты &ndash; вещественные числа).<br />
</p><br />
<p><br />
<b>Выходные данные</b><br><br />
<math>n</math> целых положительных чисел <math>L_1, ..., L_n</math>&ndash; номера кластеров, соотвествующие каждому вектору (при условии, что нумерация кластеров начинается с <math>1</math>).<br />
</p><br />
<p><br />
<b>Объем выходных данных</b><br><br />
<math>n</math> целых положительных чисел.<br />
</p><br />
<br />
=== Свойства алгоритма ===<br />
<br />
<p><b>[[глоссарий#Вычислительная мощность|''Вычислительная мощность'']]</b></p><br />
Вычислительная мощность алгоритма <i>k</i>-means равна <math>\frac{ikdn}{nd} = ki </math>, где <math>k</math> &ndash; число кластеров, <math>i</math> &ndash; число итераций алгоритма.<br />
<br />
<p><b>[[глоссарий#Детерминированность |''Детерминированность'']] и [[глоссарий#Устойчивость |''Устойчивость'']] </b></p><br />
Алгоритм <i>k</i>-means является итерационным. Количество итераций алгоритма в общем случае не фиксируется и зависит от начального расположения объектов в пространстве, параметра <math>k</math>, а также от начального приближения центров кластеров, <math>\mu_1, ..., \mu_k</math>. В результате этого может варьироваться результат работы алгоритма. При неудачном выборе начальных параметров итерационный процесс может сойтись к локальному оптимуму<ref>Von Luxburg, Ulrike. Clustering Stability. Now Publishers Inc, 2010.</ref>. По этим причинам алгоритм не является ни <b>детермирированным</b>, ни <b>устойчивым</b>.<br />
<br />
<p><b>Соотношение последовательной и параллельной сложности алгоритма</b></p><br />
<br />
<math>\frac{\Theta_{\rm k-means}}{\Psi_{\rm k-means}} = \frac{O(ikdn)}{O(i \cdot log(kdn))}</math><br />
<br />
<b>Сильные стороны алгоритма</b>:<br />
<ul><br />
<li><i>Сравнительно высокая эффективность при простоте реализации</i></li><br />
<li><i>Высокое качество кластеризации</i></li><br />
<li><i>Возможность распараллеливания</i></li><br />
<li><i>Существование множества модификаций</i></li><br />
</ul><br />
<b>Недостатки алгоритма</b><ref>Ortega, Joaquín Pérez, Ma Del Rocío Boone Rojas, and María J. Somodevilla. "Research issues on K-means Algorithm: An Experimental Trial Using Matlab."</ref>:<br />
<ul><br />
<li><i>Количество кластеров является параметром алгоритма</i></li><br />
<li><br />
<p><i>Чувствительность к начальным условиям</i></p><br />
<p>Инициализация центров кластеров в значительной степени влияет на результат кластеризации.</p><br />
</li><br />
<li><br />
<p><i>Чувствительность к выбросам и шумам</i></p><br />
<p>Выбросы, далекие от центров настоящих кластеров, все равно учитываются при вычислении их центров.</p><br />
</li><br />
<li><br />
<p><i>Возможность сходимости к локальному оптимуму</i></p><br />
<p>Итеративный подход не дает гарантии сходимости к оптимальному решению.</p><br />
</li><br />
<li><br />
<p><i>Использование понятия "среднего"</i></p><br />
<p>Алгоритм неприменим к данным, для которых не определено понятие "среднего", например, категориальным данным.</p><br />
</li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализации алгоритма <math>k</math> средних согласно [[Scalability methodology|методике]] AlgoWiki.<br />
<br />
==== Реализация 1 ====<br />
<br />
Исследование масштабируемости параллельной реализации алгоритма <i>k</i>-means проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Алгоритм реализован на языке C с использованием средств MPI.<br />
Для исследования масштабируемости проводилось множество запусков программы с разным значением параметра (количество векторов для кластеризации), а также с различным числом процессоров. Фиксировались результаты запусков &ndash; время работы <math>t</math> и количество произведенных итераций алгоритма <math>i</math>.<br />
<br />
Параметры запусков для экспериментальной оценки:<br />
<ul><br />
<li>Значения <b>количества векторов</b> <math>n</math>: 20'000, 30'000, 50'000, 100'000, 200'000, 300'000, 500'000, 700'000, 1'000'000, 1'500'000, 2'000'000.</li><br />
<li>Значения <b>количества процессоров</b> <math>p</math>: 1, 8, 16, 32, 64, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512.</li><br />
<li>Значение <b>количества кластеров</b> <math>k</math>: 100.</li><br />
<li>Значение <b>размерности векторов</b> <math>d</math>: 10.</li><br />
</ul><br />
<br />
Для проведения экспериментов были сгенерированы нормально распределенные псевдослучайные данные (с использованием Python библиотеки [http://scikit-learn.org/ scikit-learn]):<br />
<syntaxhighlight lang="python"><br />
from sklearn.datasets import make_classification<br />
X1, Y1 = make_classification(n_features=10, n_redundant=2, n_informative=8,<br />
n_clusters_per_class=1, n_classes=100,<br />
n_samples=2000000)<br />
</syntaxhighlight><br />
<br />
Для заданной конфигурации эксперимента (<math>n, d, p, k</math>) и полученных результатов (<math>t, i</math>) [[Глоссарий#Производительность|производительность]] и эффективность реализации расчитывались по формулам:<br />
<p><br />
<ul><li><math>{\rm Performance} = \frac{N_{\rm k-means}^{d, n}}{t}\ \ ({\rm FLOPS}),</math></li></ul><br />
где <math>N_{\rm k-means}^{d, n}</math> &ndash; точное число операций с плавающей точкой (операции с памятью, а также целочисленные операции не учитывались), вычисленное в соответствие с разделом [[#Последовательная сложность алгоритма| "Последовательная сложность алгоритма"]];<br />
</p><br />
<p><br />
<ul><li><math>{\rm Efficiency} = \frac{100 \cdot {\rm Performance}}{{\rm Performance}_{\rm Peak}^{p}}\ \ (\%),</math></li></ul><br />
где <math>{\rm Performance}_{\rm Peak}^{p}</math> &ndash; пиковая производительность суперкомпьютера при <math>p</math> процессорах, вычисленная согласно спецификациям Intel<sup>&reg;</sup> XEON<sup>&reg;</sup> X5670<ref>"http://ark.intel.com/ru/products/47920/Intel-Xeon-Processor-X5670-12M-Cache-2_93-GHz-6_40-GTs-Intel-QPI"</ref>.<br />
</p><br />
<br />
Графики зависимости производительности и эффективности параллельной реализации <i>k</i>-means от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>) представлены на рисунках 4 и 5, соответственно.<br />
<br />
[[file:Flops.png|thumb|center|800px|Рис. 4. Параллельная реализация <i>k</i>-means. График зависимости производительности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
[[file:Efficiency.png|thumb|center|800px|Рис. 5. Параллельная реализация <i>k</i>-means. График зависимости эффективности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
<br />
<p><br />
<b>В результате</b> экспериментальной оценки были получены следующие оценки эффективности реализации:<br />
<ul><br />
<li><b>Минимальное</b> значение: 0.000409 % достигается при <math>n=20'000, p=480</math></li><br />
<li><b>Максимальное</b> значение: 0.741119 % достигается при <math>n=300'000, p=1</math></li><br />
</ul><br />
</p><br />
<br />
<p><br />
Оценки масштабируемости реализации алгоритма <i>k</i>-means:<br />
<ul><br />
<li><b>По числу процессоров:</b> -0.002683 &ndash; эффективность убывает с ростом числа процессоров. Данный результат вызван ростом накладных расходов для обеспечения параллельного выполнения алгоритма.</li><br />
<li><b>По размеру задачи:</b> 0.002779 &ndash; эффективность растет с ростом числа векторов. Данный результат вызван тем, что при увеличении размера задачи, количество вычислений растет по сравнению с временем, затрачиваемым на пересылку данных.</li><br />
<li><b>Общая оценка:</b> -0.000058 &ndash; можно сделать вывод, что в целом эффективность реализации незначительно уменьшается с ростом размера задачи и числа процессоров.</li><br />
</ul><br />
</p><br />
<br />
[https://github.com/serban/kmeans Использованная параллельная реализация алгоритма <i>k</i>-means]<br />
<br />
==== Реализация 2 ====<br />
<br />
Исследование также проводилось на суперкомпьютере "Ломоносов".<br />
<br />
<p>Набор данных для тестирования состоял из 946000 векторов размерности 2 (координаты на сфере)</p><br />
<p>Набор и границы значений изменяемых параметров запуска реализации алгоритма:</p><br />
<br />
* число процессов (виртуальных ядер) [8 : 512];<br />
* число кластеров [128 : 384].<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации <math>2,47%</math> достигается при делении исходных данных на 128 кластеров с использованием 512 процессов;<br />
* максимальная эффективность реализации <math>7,13%</math> достигается при делении исходных данных на 352 кластера с использованием 8 процессов.<br />
На рисунках 6 и 7, соответственно, представлены графики зависимости производительности и эффективности параллельной реализации <i>k</i>-means от числа кластеров и числа процессов.<br />
<br />
[[file:kmeans_performance.jpg|thumb|center|720px|Рис. 6. График зависимости производительности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
По рис. 6 можно отметить практически полное отсутствие роста производительности с увеличением числа процессов от 256 до 512 при минимальном размере задачи. Это связано с быстрым ростом накладных расходов по отношению к крайне низкому объёму вычислений. При росте размерности задачи данный эффект пропадает, и при одновременном пропорциональном увеличении числа кластеров и числа процессов рост производительности становится близким к линейному.<br />
<br />
[[file:kmeans_efficiency.jpg|thumb|center|720px|Рис. 7. График зависимости эффективности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
Исследовалась [https://github.com/like-a-bauss/kmeans параллельная реализация алгоритма <i>k</i>-means на MPI]. <br />
<p>Были получены следующие оценки масштабируемости реализации алгоритма <i>k</i>-means:</p><br />
*<i>По числу процессов:</i> <math>-0.02209</math>. Следовательно, с ростом числа процессов эффективность уменьшается. На рис. 7 можно наблюдать плавное и равномерное снижение производительности по мере увеличения числа процессов при неизменном числе кластеров, что свидетельствует об относительно невысоком росте накладных расходов на передачу данных между процессами и преобладании объёма вычислений над объёмом пересылок данных по сети.<br />
*<i>По размеру задачи:</i> <math>0.01252</math>. Следовательно, с ростом размера задачи (числа кластеров) эффективность увеличивается. При этом объём пересылок данных по сети пропорционален <math>(n + k) \cdot p</math> (где <math>k</math> - число кластеров, <math>n</math> - число входных векторов, <math>p</math> - число процессов) таким образом, поскольку <math>k << n</math>, рост накладных расходов с ростом числа кластеров при неизменном числе процессов и входных векторов представляет собой незначительную величину.<br />
*<i>Общая оценка:</i> <math>-0.00081</math>. Таким образом, с ростом и размера задачи, и числа процессов эффективность уменьшается. Это связано с тем, что отношение объёма вычислений к объёму передаваемых данных изменяется пропорционально <math>{kn \over (n + k) \cdot p} \thicksim {k \over p}</math>, что представляет собой невысокий коэффициент, но при этом позволяет параллельной реализации не деградировать до нулевой эффективности при значительном увеличении числа процессов.<br />
<br />
==== Реализация 3 ====<br />
<br />
Исследование масштабируемости алгоритма k-means в зависимости от количества используемых процессов было проведено в статье Кумара<ref>Kumar, J., Mills, R. T., Hoffman, F. M., & Hargrove, W. W. (2011). Parallel k-means clustering for quantitative ecoregion delineation using large data sets. Procedia Computer Science, 4, 1602-1611.</ref>. Исследование происходило на суперкомпьютере Jaguar - Cray XT5<ref>https://www.top500.org/system/176029</ref>. На момент экспериментов данный суперкомпьютер имел следующую конфигурацию: 18,688 вычислительных узлов с двумя шестнадцатиядерными процессорами AMD Opteron 2435 (Istanbul) 2.6 GHz, 16 GB of DDR2-800 оперативной памяти, и SeaStar 2+ роутер. Всего он состоял из 224,256 вычислительных ядер, 300 TB памяти, и пиковой производительностью 2.3 petaflops.<br />
<br />
Реализация алгоритма была выполнена на языке программирования C с использованием MPI.<br />
<br />
Объем данных составлял 84 ГБ, количество объектов (d-мерных векторов) n равнялось 1,024,767,667, размерность векторов <math>d</math> равнялась 22, количество кластеров <math>k</math> равнялось 1000. <br />
<br />
На рис. 8 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров. Можно отметить, что время, затраченное на чтение данных и запись результатов кластеризации, практически не изменяется с увеличением количества задействованных процессоров. Время же работы самого алгоритма кластеризации уменьшается с увеличением количества процессоров.<br />
<br />
[[Файл:k-means-proc-scalability.png|thumb|center|700px|Рис. 8. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (из работы: Kumar etc. 2011).]]<br />
<br />
Также было произведено самостоятельное исследование масштабируемости алгоритма k-means. Исследование производилось на суперкомпьютере "Blue Gene/P"<ref>http://hpc.cmc.msu.ru/bgp</ref>.<br />
<br />
Набор и границы значений изменяемых параметров запуска реализации алгоритма:<br />
<br />
* число процессоров [1, 2, 4, 8, 16, 32, 64, 128, 256, 512];<br />
* количество объектов [5000, 10000, 25000, 50000].<br />
<br />
Был использован набор данных ''Dataset for Sensorless Drive Diagnosis Data Set''<ref>PASCHKE, Fabian ; BAYER, Christian ; BATOR, Martyna ; MÖNKS, Uwe ; DICKS, Alexander ; ENGE-ROSENBLATT, Olaf ; LOHWEG, Volker: Sensorlose Zustandsüberwachung an Synchronmotoren, Bd. 46. In: HOFFMANN, Frank; HÃœLLERMEIER, Eyke (Hrsg.): Proceedings 23. Workshop Computational Intelligence. Karlsruhe : KIT Scientific Publishing, 2013 (Schriftenreihe des Instituts für Angewandte Informatik - Automatisierungstechnik am Karlsruher Institut für Technologie, 46), S. 211-225</ref> из репозитория ''Machine learning repository''<ref>https://archive.ics.uci.edu/ml/datasets/Dataset+for+Sensorless+Drive+Diagnosis</ref>.<br />
<br />
Исследуемый набор данных содержит векторы, размерность которых равна 49. Компоненты векторов являются вещественными числами. Количество кластеров равно 11. Пропущенные значения отсутствуют.<br />
<br />
Для исследования масштабируемости алгоритма была использована реализация на языке C с использованием MPI<ref>http://users.eecs.northwestern.edu/~wkliao/Kmeans/index.html</ref>. Код можно найти здесь: https://github.com/serban/kmeans. Данная реализация предоставляет возможность распараллеливать решение задачи с помощью технологий MPI, OpenMP И CUDA. Для запуска MPI-версии программы использовалась цель "mpi_main" Makefile.<br />
<br />
На рис. 9 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (использовались логарифмические оси). Разными цветами помечены запуски, соответствующие разным количествам объектам, участвующих в кластеризации. Можно видеть близкое к линейному увеличение времени работы программы в зависимости от количества процессоров. Также можно видеть увеличение времени работы алгоритма при увеличении количества объектов.<br />
[[Файл:Plot_1.png|thumb|center|900px|Рис. 9. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
На рис. 10 показана эта же зависимость, только в трехмерном пространстве. По аналогии с рис. 9, были использованы логарифмические оси. Как и в случае двумерного рисунка, можно видеть близкое к линейному увеличение времени работы программы.<br />
[[Файл:Kmeans-3d.png|thumb|center|900px|Рис. 10. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
==== Реализация 4 ====<br />
<br />
Исследование масштабируемости данной параллельной реализации алгоритма k-средних также проводилось на суперкомпьютере "Ломоносов". Параллельная реализация была написана самостоятельно на языке C, [http://git.algowiki-project.org/iaegorov/Egorov-Bogomazov-k-means/tree/master ссылка на реализацию]. Так как на каждой итерации число действий на единицу данных не велико и данные должны быть собраны вместе при перерасчете центроидов, было решено для ускорения вычислений воспользоваться только OpenMP без использовании MPI.<br />
<br />
Код собирался под gcc c опцией -fopenmp. Код считался на одном процессоре, технология hyperthreading не использовалась.<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 16] с увеличением в 2 раза;<br />
* размер данных [100000 : 1600000] с увеличением в 2 раза.<br />
<br />
В результате проведённых экспериментов были получены следующие данные:<br />
<br />
* Максимальная эффективность в точке достигается при переходе от 1 потока на 4 при минимальном размере данных, она равна <math>87,5%</math>.<br />
* Усредненная максимальная эффективность достигается при переходе с одного потока на два. Среднее время вычислений на всех рассмотренных потока снижается с 16,33 до 11.87 секунд, поэтому формально эффективность <math>= 16.33 / 11.87 / 2 \approx 68,4\%</math><br />
* Минимальная эффективность в точке достигается при переходе от 1 потока на 16 при размере данных 800000, она равна <math>11,1\%</math>.<br />
* Усредненная минимальная эффективность наблюдается при переходе с одного на максимальное рассматриваемое в эксперименте число потоков, равное 16. Время вычисления изменяется с 16,33 до 7,6 секунд, поэтому формально эффективность <math> = 16.33 / 7.6 / 16 \approx 14,9\%</math><br />
<br />
Ниже приведены графики зависимости вычислительного времени алгоритма и его эффективности от изменяемых параметров запуска — размера данных и числа процессоров:<br />
<br />
[[file:Egorov_Bogomazov_Time.jpg|thumb|center|700px|Рис. 11. Параллельная реализация алгоритма k-средних. Изменение вычислительного времени алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь видно, что время выполнения операций алгоритма плавно убывает по каждому из параметров, причем скорость убывания по параметру числа процессоров выше, чем в зависимости от размерности задачи.<br />
<br />
[[file:Egorov_Bogomazov_Efficiency.jpg|thumb|center|700px|Рис. 12. Параллельная реализация алгоритма k-средних. Изменение эффективности алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь построена эффективность перехода от последовательной реализации к параллельной. Рассчитывается она по формуле ''Время вычисления на 1 потоке / Время вычисления на <math>T</math> потоках / <math>T</math>'', где <math>T</math> — это число потоков. При вычислении на 1 процессоре она равна 100 \% в силу используемой формулы, что и отражено на графике.<br />
<br />
Проведем оценки масштабируемости:<br />
<br />
'''По числу процессов''' — при увеличении числа процессов эффективность уменьшается на всей области рассматриваемых значений, причем темп убывания замедляется с ростом числа процессов.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений вначале кратковременно возрастает, но затем начинает относительно равномерно убывать на всей рассматриваемой области.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений в общем случае постепенно убывает. На малых данных она выходит на пик мощности, являющийся максимумом эффективности в исследуемых условиях, но затем возвращается к процессу убывания.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
В однопоточном режиме на наборах данных, представляющих практический интерес (порядка нескольких десятков тысяч векторов и выше), время работы алгоритма неприемлемо велико. Благодаря свойству массового параллелизма должно наблюдаться значительное ускорение алгоритма на многоядерных архитектурах (Intel Xeon), а также на графических процессорах, даже на мобильных вычислительных системах (ноутбуках), оснащенных видеокартой. Алгоритм <i>k</i>-means также будет демонстрировать значительное ускорение на сверхмощных вычислительных комплексах (суперкомпьютерах, системах облачных вычислений<ref>"Issa. "Performance characterization and analysis for Hadoop K-means iteration". Journal of Cloud Computing, 2016"</ref>).<br />
<br />
На сегодняшний день существует множество реализаций алгоритма <i>k</i>-means, в частности, направленных на оптимизацию параллельной работы на различных архитектурах<ref>"Raghavan R. A fast and scalable hardware architecture for K-means clustering for big data analysis : дис. – University of Colorado Colorado Springs. Kraemer Family Library, 2016."</ref><ref>"Yang, Luobin, et al. "High performance data clustering: a comparative analysis of performance for GPU, RASC, MPI, and OpenMP implementations." The Journal of supercomputing 70.1 (2014): 284-300."</ref><ref>"Li, You, et al. "Speeding up k-means algorithm by GPUs." Computer and Information Technology (CIT), 2010 IEEE 10th International Conference on. IEEE, 2010."</ref>. Предлагается множество адаптаций алгоритма под конкретные архитектуры. Например, авторы работы<ref>"Kanan, Gebali, Ibrahim. "Fast and Area-Efficient Hardware Implementation of the K-means<br />
Clustering Algorithm". WSEAS Transactions on circuits and systems. Vol. 15. 2016"</ref> производят перерасчет центров кластеров на этапе распределения векторов по кластерам.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
==== Открытое программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.icpsr.umich.edu/CrimeStat/ CrimeStat]</li> Программное обеспечение, созданное для операционных систем Windows, предоставляющее инструменты статистического и пространственного анализа для решения задачи картирования преступности.<br />
<li>[http://juliastats.github.io Julia]</li> Высокоуровневый высокопроизводительный свободный язык программирования с динамической типизацией, созданный для математических вычислений, содержит реализацию k-means.<br />
<li>[https://mahout.apache.org Mahout]</li> Apache Mahout - Java библиотека для работы с алгоритмами машинного обучения с использованием MapReduce. Содержит реализацию k-means.<br />
<li>[https://www.gnu.org/software/octave/ Octave]</li> Написанная на C++ свободная система для математических вычислений, использующая совместимый с MATLAB язык высокого уровня, содержит реализацию k-means.<br />
<li>[http://spark.apache.org/docs/latest/mllib-clustering.html Spark]</li> Распределенная реализация k-means содержится в библиотеке Mlib для работы с алгоритмами машинного обучения, взаимодействующая с Python библиотекой NumPy и библиотека R.<br />
<li>[http://torch.ch Torch]</li> MATLAB-подобная библиотека для языка программирования Lua с открытым исходным кодом, предоставляет большое количество алгоритмов для глубинного обучения и научных расчётов. Ядро написано на Си, прикладная часть выполняется на LuaJIT, поддерживается распараллеливание вычислений средствами CUDA и OpenMP. Существуют реализации k-means.<br />
<li>[http://www.cs.waikato.ac.nz/ml/weka/ Weka]</li> Cвободное программное обеспечение для анализа данных, написанное на Java. Содержит k-means и x-means.<br />
<li>[http://accord-framework.net/docs/html/T_Accord_MachineLearning_KMeans.htm Accord.NET]</li> C# реализация алгоритмов k-means, k-means++, k-modes.<br />
<li>[http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html OpenCV]</li> Написанная на С++ библиотека, направленная в основном на решение задач компьютерного зрения. Содержит реализацию k-means.<br />
<li>[http://mlpack.org/ MLPACK]</li> Масштабируемая С++ библиотека для работы с алгоритмами машинного обучения, содержит реализацию k-means.<br />
<li>[https://www.scipy.org/ SciPy]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[http://scikit-learn.org/ scikit-learn]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[https://www.r-bloggers.com/k-means-clustering-in-r/ R]</li> Язык программирования для статистической обработки данных и работы с графикой, а также свободная программная среда вычислений с открытым исходным кодом в рамках проекта GNU, содержит три реализации k-means.<br />
<li>[http://elki.dbs.ifi.lmu.de ELKI]</li> Java фреймворк, содержащий реализацию k-means, а также множество других алгоритмов кластеризации.<br />
</ol><br />
<br />
==== Проприетарное программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.ayasdi.com/blog/bigdata/topological-data-analysis-of-oil-and-gas-petrophysical-data/ Ayasdi]</li><br />
<li>[http://www.stata.com/features/cluster-analysis/ Stata]</li><br />
<li>[http://mathworld.wolfram.com/K-MeansClusteringAlgorithm.html Mathematica]</li><br />
<li>[http://www.mathworks.com/help/stats/kmeans.html?requestedDomain=www.mathworks.com MATLAB]</li><br />
<li>[https://support.sas.com/rnd/app/stat/procedures/fastclus.html SAS]</li><br />
<li>[http://docs.rapidminer.com/studio/operators/modeling/segmentation/k_means.html RapidMiner]</li><br />
<li>[https://blogs.sap.com/2013/03/28/sap-hana-pal-k-means-algorithm-or-how-to-do-customer-segmentation-for-the-telecommunications-industry/ SAP HANA]</li><br />
</ol><br />
<br />
== Литература ==<br />
<br />
<references /></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_k_%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D1%85_(k-means)&diff=25247Алгоритм k средних (k-means)2018-01-16T17:22:21Z<p>Konshin: </p>
<hr />
<div>Основные авторы статьи (разделы 1, 2.4.1, 2.6-2.7, 3):<br />
[https://algowiki-project.org/ru/Участник:Бротиковская_Данута<b>Д.Бротиковская</b>] и<br />
[https://algowiki-project.org/ru/Участник:DennZo1993<b>Д.Зобнин</b>]<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:IanaV<b>Я.А.Валуйская</b>] и<br />
[https://algowiki-project.org/ru/Участник:GlotovES<b>Е.С.Глотов</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Parkhomenko<b>П.А.Пархоменко</b>] и<br />
[https://algowiki-project.org/ru/Участник:Ivan.mashonskiy<b>И.Д.Машонский</b>] (раздел 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Илья_Егоров<b>И.Егоров</b>] и<br />
[https://algowiki-project.org/ru/Участник:Богомазов_Евгений<b>Е.Богомазов</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Алгоритм <math>k</math> средних (<math>k</math>-means)<br />
| serial_complexity = <math>O(ikdn)</math><br />
| input_data = <math> dn </math><br />
| output_data = <math> n </math><br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Алгоритм <b><i>k средних</i></b> (англ. k-means) - один из алгоритмов машинного обучения, решающий задачу кластеризации.<br />
Этот алгоритм является неиерархическим<ref>"https://ru.wikipedia.org/wiki/Иерархическая_кластеризация"</ref>, итерационным методом кластеризации<ref>"https://ru.wikipedia.org/wiki/Кластерный_анализ"</ref>, он получил большую популярность благодаря своей простоте, наглядности реализации и достаточно высокому качеству работы. <br />
Был изобретен в 1950-х годах математиком <i>Гуго Штейнгаузом</i><ref>Steinhaus, Hugo. "Sur la division des corp materiels en parties." Bull. Acad. Polon. Sci 1.804 (1956): 801.</ref> и почти одновременно <i>Стюартом Ллойдом</i><ref>Lloyd, S. P. "Least square quantization in PCM. Bell Telephone Laboratories Paper. Published in journal much later: Lloyd, SP: Least squares quantization in PCM." IEEE Trans. Inform. Theor.(1957/1982).</ref>. Особую популярность приобрел после публикации работы <i>МакКуина</i><ref>MacQueen, James. "Some methods for classification and analysis of multivariate observations." Proceedings of the fifth Berkeley symposium on mathematical statistics and probability. Vol. 1. No. 14. 1967.</ref> в 1967.<br />
<br />
Алгоритм представляет собой версию EM-алгоритма<ref>"https://ru.wikipedia.org/wiki/EM-алгоритм"</ref>, применяемого также для разделения смеси гауссиан. Основная идея алгоритма <i>k</i>-means заключается в том, что данные произвольно разбиваются на кластеры, после чего итеративно перевычисляется центр масс для каждого кластера, полученного на предыдущем шаге, затем векторы разбиваются на кластеры вновь в соответствии с тем, какой из новых центров оказался ближе по выбранной метрике.<br />
<br />
Цель алгоритма заключается в разделении <math>n</math> наблюдений на <math>k</math> кластеров таким образом, чтобы каждое наблюдение принадлежало ровно одному кластеру, расположенному на наименьшем расстоянии от наблюдения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
<b>Дано:</b><br />
* набор из <math>n</math> наблюдений <math>X=\{\mathbf{x}_1, \mathbf{x}_2, ..., \mathbf{x}_n\}, \mathbf{x}_i \in \mathbb{R}^d, \ i=1,...,n</math>;<br />
* <math>k</math> - требуемое число кластеров, <math>k \in \mathbb{N}, \ k \leq n</math>.<br />
<br />
<b>Требуется:</b><br />
<br />
Разделить множество наблюдений <math>X</math> на <math>k</math> кластеров <math>S_1, S_2, ..., S_k</math>:<br />
* <math>S_i \cap S_j= \varnothing, \quad i \ne j</math><br />
<br />
* <math>\bigcup_{i=1}^{k} S_i = X</math><br />
<br />
<b>Действие алгоритма:</b><br />
<p>Алгоритм <i>k</i>-means разбивает набор <math>X</math> на <math>k</math> наборов <math>S_1, S_2, ..., S_k,</math> таким образом, чтобы минимизировать сумму квадратов расстояний от каждой точки кластера до его центра (центр масс кластера). Введем обозначение, <math>S=\{S_1, S_2, ..., S_k\}</math>. Тогда действие алгоритма <i>k</i>-means равносильно поиску:</p><br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math>\arg\min_{S} \sum\limits_{i=1}^k \sum\limits_{\mathbf{x} \in S_i} \rho(\mathbf{x}, \mathbf{\mu}_i )^2,</math></td><br />
<td align="right"><math>(1)</math></td><br />
</tr><br />
</table><br />
где <math>\mathbf{\mu}_i</math> &ndash; центры кластеров, <math>i=1,...,k, \quad \rho(\mathbf{x}, \mathbf{\mu}_i)</math> &ndash; функция расстояния между <math>\mathbf{x}</math> и <math>\mu_i</math><br />
<br />
<b>Шаги алгоритма:</b><br />
<ol><br />
<li><br />
<b>Начальный шаг: инициализация кластеров</b><br />
<p>Выбирается произвольное множество точек <math>\mu_i, \ i=1,...,k,</math> рассматриваемых как начальные центры кластеров: <math>\mu_i^{(0)} = \mu_i, \quad i=1,...,k</math></p><br />
</li><br />
<li><br />
<b>Распределение векторов по кластерам</b><br />
<p><b>Шаг</b> <math>t: \forall \mathbf{x}_i \in X, \ i=1,...,n: \mathbf{x}_i \in S_j \iff j=\arg\min_{k}\rho(\mathbf{x}_i,\mathbf{\mu}_k^{(t-1)})^2</math></p><br />
</li><br />
<li><br />
<b>Пересчет центров кластеров</b><br />
<p><b>Шаг </b> <math>t: \forall i=1,...,k: \mu_i^{(t)} = \cfrac{1}{|S_i|}\sum_{\mathbf{x}\in S_i}\mathbf{x}</math></p><br />
</li><br />
<li><br />
<b>Проверка условия останова:</b><br />
<p></p><br />
'''if''' <math>\exist i\in \overline{1,k}: \mu_i^{(t)} \ne \mu_i^{(t-1)}</math> '''then'''<br />
t = t + 1;<br />
goto 2;<br />
'''else'''<br />
'''stop'''<br />
</li><br />
</ol><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром являются шаги 2 и 3 приведенного выше алгоритма: <b><i>распределение векторов по кластерам</i></b> и <b><i>пересчет центров кластеров</i></b>. <br />
<br />
<p><br />
<b><i>Распределение векторов</i></b> по кластерам предполагает вычисление расстояний между каждым вектором <math>\mathbf{x}_i \in X, \ i= 1,...,n</math> и центрами кластера <math>\mathbf{\mu}_j, \ j= 1,...,k</math>. Таким образом, данный шаг предполагает <math>kn</math> вычислений расстояний между <math>d</math>-мерными векторами. <br />
</p><br />
<p><br />
<b><i>Пересчет центров кластеров</i></b> предполагает <math>k</math> вычислений центров масс <math>\mathbf{\mu}_i</math> множеств <math>S_i, \ i=1,...,k,</math> представленных выражением в шаге 3 представленного выше алгоритма.<br />
</p><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
<b>Инициализация центров масс <math>\mu_1, ..., \mu_k</math></b>. <br />
<br />
Наиболее распространенными являются следующие стратегии:<br />
<ul><br />
<li><br />
<b>Метод Forgy</b><br>В качестве начальных значений <math>\mu_1, ..., \mu_k</math> берутся случайно выбранные векторы.<br />
</li><br />
<li><br />
<b>Метод случайно разделения (Random Partitioning)</b><br>Для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> выбирается случайным образом кластер <math>S_1, ..., S_k</math>, после чего для каждого полученного кластера вычисляются значения <math>\mu_1, ..., \mu_k</math>.<br />
</li><br />
</ul><br />
<br />
<b>Распределение векторов по кластерам</b><br />
<br />
Для этого шага алгоритма между векторами <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> и центрами кластеров <math>\mu_1,...,\mu_k</math> вычисляются <b>расстояния</b> по формуле (как правило, используется Евлидово расстояние):<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mathbf{v}_1, \mathbf{v}_2 \in \mathbb{R}^d, \quad \rho(\mathbf{v}_1, \mathbf{v}_2) = \lVert \mathbf{v}_1- \mathbf{v}_2 \rVert= \sqrt{\sum_{i=1}^{d}(\mathbf{v}_{1,i} - \mathbf{v}_{2,i})^2}</math></td><br />
<td align="right"><math>(2)</math></td><br />
</tr><br />
</table><br />
<br />
<b>Пересчет центров кластеров</b><br />
<br />
Для этого шага алгоритма производится пересчет центров кластера по <b>формуле вычисления центра масс</b>:<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mu = \cfrac{1}{|S|}\sum_{\mathbf{x}\in S}\mathbf{x}</math></td><br />
<td align="right"><math>(3)</math></td><br />
</tr><br />
</table><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
'''1.''' Инициализировать центры кластеров <math>\mathbf{\mu}_i^{(1)}, \ i=1,...,k</math><br><br />
'''2.''' <math>t \leftarrow 1</math><br><br />
'''3.''' Распределение по кластерам<br><br />
<math>\quad S_i^{(t)}=\{\mathbf{x}_p: \lVert\mathbf{x}_p-\mathbf{\mu}_i^{(t)}\rVert^2 \leq \lVert\mathbf{x}_p-\mathbf{\mu}_j^{(t)}\rVert^2 \quad \forall j=1,...,k\},</math><br><br />
<math>\quad</math>где каждый вектор <math>\mathbf{x}_p</math> соотносится единственному кластеру <math>S^{(t)}</math><br><br />
'''4.''' Обновление центров кластеров<br><br />
<math>\quad \mathbf{\mu}_i^{(t+1)} = \frac{1}{|S^{(t)}_i|} \sum_{\mathbf{x}_j \in S^{(t)}_i} \mathbf{x}_j </math><br><br />
'''5.''' '''if''' <math>\exists i \in \overline{1,k}: \mathbf{\mu}_i^{(t+1)} \ne \mathbf{\mu}_i^{(t)}</math> '''then'''<br><br />
<math>\quad t = t + 1</math>;<br><br />
<math>\quad</math>goto '''3''';<br><br />
<math>~~~</math>'''else'''<br><br />
<math>\quad</math>'''stop'''<br><br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
<div style="padding-bottom: 20px"><br />
<p>Обозначим <math>\Theta_{\rm centroid}^{d, m}</math> временную сложность вычисления центорида кластера, число элементов которого равна <math>m</math>, в d-мерном пространстве.</p><br />
<p>Аналогично <math>\Theta_{\rm distance}^d</math> &ndash; временная сложность вычисления расстояния между двумя d-мерными векторами.</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<b>Сложность шага инициализации <math>k</math> кластеров мощности <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm init}^{k, d, m}</math></b><br />
<ul><br />
<li><i>Стратерия Forgy</i>: вычисления не требуются, <math>\Theta_{\rm init}^{k, d, m} = 0</math></li><br />
<li><i>Стратегия случайного разбиения</i>: вычисление центров <math>k</math> кластеров, <math>\Theta_{\rm init}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}, m \le n</math></li><br />
</ul><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Cложность шага распределения d мерных векторов по <math>k</math> кластерам &ndash; <math>\Theta_{\rm distribute}^{k, d}</math></b></p><br />
<p><br />
На этом шаге для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> вычисляется <math>k</math> расстояний до центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math><br />
</p><br />
<p align="center"><br />
<math>\Theta_{\rm distribute}^{k, d} = n \cdot k \cdot \Theta_{\rm distance}^d</math><br />
</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><br />
<b>Сложность шага пересчета центров <math>k</math> кластеров размера <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm recenter}^{k, d, m}</math></b><br />
</p><br />
<p>На этом шаге вычисляется <math>k</math> центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math></p><br />
<p align="center"><math>\Theta_{\rm recenter}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}</math> </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm centroid}^{d, m}</math> для кластера, число элементов которого равно <math>m</math></b></p><br />
<p align="center"><math>\Theta_{\rm centroid}^{d, m}</math> = <math>m \cdot d</math> сложений + <math>d</math> делений </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm distance}^d</math> в соответствие с формулой <math>(2)</math></b></p><br />
<p align="center"><math>\Theta_{\rm distance}^d</math> = <math>d</math> вычитаний + <math>d</math> умножений + <math>(d-1)</math> сложение</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Предположим, что алгоритм сошелся за <math>i</math> итераций, тогда временная сложность алгоритма <math>\Theta_{\rm k-means}^{d, n}</math></p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le \Theta_{\rm init}^{k, d, n} + i(\Theta_{\rm distribute}^{k, d} + \Theta_{\rm recenter}^{k, d, n})</math></b></p><br />
</div><br />
<div style="padding-bottom: 5px"><br />
<p>Операции сложения/вычитания:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le knd+ i(kn(2d-1) + knd) = knd+ i(kn(3d-1)) \thicksim O(ikdn)</math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Операции умножения/деления:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le kd + i(knd + kd) = kd + ikd(n+1) \thicksim O(ikdn) </math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Получаем, что <b>временная сложность</b> алгоритма <b><i>k</i>-means</b> кластеризации <math>n</math> <b>d-мерных</b> векторов на <math>k</math> кластеров за <math>i</math> итераций:</p><br />
<p align="center"><b><math> \Theta_{\rm k-means}^{d, n} \thicksim O(ikdn) </math></b></p><br />
</div><br />
<br />
=== Информационный граф ===<br />
<br />
Рассмотрим информационный граф алгоритма. Алгоритм <i>k</i>-means начинается с этапа инициализации, после которого следуют итерации, на каждой из которых выполняется два последовательных шага (см. [[#Схема реализации последовательного алгоритма|"Схема реализации последовательного алгоритма"]]): <br />
* распределение векторов по кластерам<br />
* перерасчет центров кластеров<br />
<br />
Поскольку основная часть вычислений приходится на шаги итераций, распишем информационные графы данных шагов.<br />
<br />
<p><b>Распределение векторов по кластерам</b></p><br />
Информационный граф шага распределения векторов по кластерам представлен на ''рисунке 1''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также центры кластеров <math>\mathbf{\mu}_1, ... \mathbf{\mu}_k</math>, вычисленные ранее (на шаге инициализации, если рассматривается первая итерация алгоритма, или на шаге пересчета центров кластеров предыдущей итерации в противном случае). Каждая пара векторов данных <math>\mathbf{x}_i, \ i=1,...,n,</math> и центров кластера <math>\mathbf{\mu}_j, \ j=1,...,k</math> : (<math>\mathbf{x}_i</math>, <math>\mathbf{\mu}_j</math>) подаются на независимые узлы <i>"d"</i> вычисления расстояния между векторами (более подробная схема вычисления расстояния представлена далее, ''рисунок 2''). Далее узлы вычисления расстояния <i>"d"</i>, соответствующие одному и тому же исходному вектору <math>\mathbf{x}_i</math> передаются на один узел <i>"m"</i>, где далее происходит вычисление новой метки кластера для каждого вектора <math>\mathbf{x}_i</math> (берется кластер с минимальным результатом вычисления расстояния). На выходе графа выдаются метки кластеров , <math>L_1, ..., L_n</math>, такие что <math>\forall \mathbf{x}_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>. <br />
<br />
[[file:Clusterization.png|thumb|center|800px|Рис. 1. Схема распределения векторов по кластерам. ''d'' &ndash; вычисление расстояния между векторами; ''m'' &ndash; вычисление минимума.]]<br />
<br />
<p><b>Вычисление расстояния между векторами</b></p><br />
Подробная схема вычисления расстояния между векторами <math>\mathbf{x}_i, \mathbf{\mu}_j</math> представлена на ''рисунке 2''. Как показано на графе, узел вычисления расстояния между векторами <i>"d"</i> состоит из шага взятия разности между векторами (узел "<math>-</math>") и взятия нормы получившегося вектора разности (узел "<math>||\cdot||^2</math>"). Более подробно, вычисление расстояния между векторами <math>\mathbf{x}_i = {x_{i1,}, ...,{x_{in}}}, \mathbf{\mu}_j = {\mu_{j1}, ...,\mu_{jn}}</math> может быть представлено как вычисление разности между каждой парой компонент <math>(x_{iz}, \mu_{jz}), \ z=1,...,d</math> (узел "<math>-</math>"), далее возведение в квадрат для каждого узла "<math>-</math>" (узел "<math>()^2</math>") и суммирования выходов всех узлов "<math>()^2</math>" (узел "<math>+</math>"). <br />
<br />
[[file:dist_calc.png|thumb|center|800px|Рис. 2. Схема вычисления расстояния между вектором и центром кластера.]]<br />
<br />
<p><b>Пересчет центров кластеров</b></p><br />
Информационный граф шага пересчета центров кластеров представлен на ''рисунке 3''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также им соответствующие метки кластера, <math>L_1, ..., L_n</math>, такие что <math>\forall x_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>, вычисленные на этапе распределения векторов по кластерам. Все векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math> подаются в узлы <math>+_1, ...+_k</math>, каждый узел <math>+_m, \ m = 1,...,k,</math> соответствует операции сложения векторов кластера с номером <math>m</math>. Метки кластера <math>L_1, ..., L_n</math> также совместно передаются на узлы <math>S_m, \ m=1,...,k</math>, на каждом из которых вычисляется количество векторов в соответствующем кластере (количество меток с соответствующим значением). Далее каждая пара выходов узлов <math>+_m</math> и <math>S_m</math> подается на узел "<math>/</math>", где производится деление суммы векторов кластера на количество элементов в нем. Значения, вычисленные на узлах "<math>/</math>", присваиваются новым центрам кластеров (выходные значения графа).<br />
<br />
[[file:Recluster.png|thumb|center|800px|Рис. 3. Схема пересчета центров кластеров]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
<p><br />
Работа алгоритма состоит из <math>i</math> итераций, в каждой из которых происходит <b>распределение d-мерных векторов по <math>k</math> кластерам</b>, а также <b>пересчет центров кластеров в d-мерном пространстве</b>. В шаге <b>распределения d-мерных векторов по <math>k</math> кластерам</b> расстояния между вектором и центрами кластеров вычисляются независимо (отсутствуют информационные зависимости). <b>Центры масс кластеров</b> также пересчитываются независимо друг от друга. Таким образом, имеет место [[глоссарий#Массовый параллелизм|''массовый параллелизм'']]. Вычислим параллельную сложность <math>\Psi_*</math> каждого из шагов, а также параллельную сложность всего алгоритма, <math>\Psi_{\rm k-means}</math>. Будем исходить из предположения, что может быть использовано любое необходимое число потоков.<br />
</p><br />
<br />
<p><b>Распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b></p> <br />
<p><br />
Поскольку на данном шаге для каждой пары векторов <math>\mathbf{x}_i, \ i=1,...,n</math> и <math>\mathbf{\mu}_j, \ j=1,...,k,</math> операции вычисления расстояния не зависят друг от друга, они могут выполняться параллельно. Тогда, разделив все вычисление расстояний на <math>n</math> потоков, получим, что в каждом потоке будет выполняться только одна операция вычисления расстояния между векторами размерности d. При этом каждому вычислительному потоку передаются координаты центров всех кластеров <math>\mathbf{\mu}_1, ..., \mathbf{\mu}_k</math>. Таким образом, параллельная сложность данного шага определяется <i> сложностью параллельной операции вычисления расстояния между d-мерными векторами</i>, <math>\Psi_{\rm distance}^d</math> и <i>сложностью определения наиболее близкого кластера</i> (паралельное взятие минимума по расстояниям), <math>\Psi_{\rm min}^k</math>. Для оценки <math>\Psi_{\rm distance}^d</math> воспользуемся [[Нахождение_частных_сумм_элементов_массива_сдваиванием | параллельной реализацией нахождения частичной суммы элементов массива путем сдваивания]]. Аналогично, <math>\Psi_{\rm min}^k = log(k)</math>. В результате, <math>\Psi_{\rm distance}^d = O(log(d))</math>. Таким образом: <br />
</p><br />
<p align="center"><math>\Psi_{\rm distribute}^{k, d} = \Psi_{\rm distance}^d + \Psi_{\rm min}^k = O(log(d))+O(log(k)) = O(log(kd))</math></p><br />
<br />
<p><b>Пересчет центров кластеров в d-мерном пространстве</b></p><br />
<p><br />
Поскольку на данном шаге для каждого из <math>k</math> кластеров центр масс может быть вычислен независимо, данные операции могут быть выполнены в отдельных потоках. Таким образом, параллельная сложность данного шага, <math>\Psi_{\rm recenter}^{k, d}</math>, будет определяться <i>параллельной сложностью вычисления одного центра масс кластера размера <math>m</math></i>, <math>\Psi_{\rm recenter}^{k, d}</math>, а так как <math>m \le n \Rightarrow \Psi_{\rm recenter}^{d, m} \le \Psi_{\rm recenter}^{d, n}</math>. Сложность вычисления центра масс кластера d-мерных векторов размера n аналогично предыдущим вычислениям равна <math>O(log(n))</math>. Тогда: <br />
</p><br />
<p align="center"><math>\Psi_{\rm recenter}^{k, d} \le \Psi_{\rm recenter}^{d, n} = O(log(n))</math></p><br />
<br />
<p><b>Общая параллельная сложность алгоритма</b></p><br />
<p><br />
На каждой итерации необходимо обновление центров кластеров, которые будут использованы на следующей итерации. Таким образом, итерационный процесс выполняется последовательно<ref>Zhao, Weizhong, Huifang Ma, and Qing He. "Parallel k-means clustering based on mapreduce." IEEE International Conference on Cloud Computing. Springer Berlin Heidelberg, 2009.</ref>. Тогда, поскольку сложность каждой итерации определяется <math>\Psi_{\rm distribute}^{k, d}</math> и <math>\Psi_{\rm recenter}</math>, сложность всего алгоритма, <math>\Psi_{\rm k-means}</math> в предположении, что было сделано <math>i</math> операций определяется выражением<br />
</p><br />
<p align="center"><math>\Psi_{\rm k-means} \approx i \cdot (\Psi_{\rm distribute}^{k, d} + \Psi_{\rm recenter}^{k, d}) \le i \cdot O(log(kdn))</math></p><br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
<p><br />
<b>Входные данные</b><br><br />
<ul><br />
<li>Матрица из <math>n \cdot d</math> элементов <math>x_{i, j} \in \mathbb{R}, \ i=1,...,n, \ j=1,...,d,</math> &ndash; координат векторов (наблюдений).</li><br />
<li>Целое положительное число <math>k, \ k \le n</math> &ndash; количество кластеров.</li><br />
</ul><br />
</p><br />
<p><br />
<b>Объем входных данных</b><br><br />
<math>1</math> целое число + <math>n \cdot d</math> вещественных чисел (при условии, что координаты &ndash; вещественные числа).<br />
</p><br />
<p><br />
<b>Выходные данные</b><br><br />
<math>n</math> целых положительных чисел <math>L_1, ..., L_n</math>&ndash; номера кластеров, соотвествующие каждому вектору (при условии, что нумерация кластеров начинается с <math>1</math>).<br />
</p><br />
<p><br />
<b>Объем выходных данных</b><br><br />
<math>n</math> целых положительных чисел.<br />
</p><br />
<br />
=== Свойства алгоритма ===<br />
<br />
<p><b>[[глоссарий#Вычислительная мощность|''Вычислительная мощность'']]</b></p><br />
Вычислительная мощность алгоритма <i>k</i>-means равна <math>\frac{ikdn}{nd} = ki </math>, где <math>k</math> &ndash; число кластеров, <math>i</math> &ndash; число итераций алгоритма.<br />
<br />
<p><b>[[глоссарий#Детерминированность |''Детерминированность'']] и [[глоссарий#Устойчивость |''Устойчивость'']] </b></p><br />
Алгоритм <i>k</i>-means является итерационным. Количество итераций алгоритма в общем случае не фиксируется и зависит от начального расположения объектов в пространстве, параметра <math>k</math>, а также от начального приближения центров кластеров, <math>\mu_1, ..., \mu_k</math>. В результате этого может варьироваться результат работы алгоритма. При неудачном выборе начальных параметров итерационный процесс может сойтись к локальному оптимуму<ref>Von Luxburg, Ulrike. Clustering Stability. Now Publishers Inc, 2010.</ref>. По этим причинам алгоритм не является ни <b>детермирированным</b>, ни <b>устойчивым</b>.<br />
<br />
<p><b>Соотношение последовательной и параллельной сложности алгоритма</b></p><br />
<br />
<math>\frac{\Theta_{\rm k-means}}{\Psi_{\rm k-means}} = \frac{O(ikdn)}{O(i \cdot log(kdn))}</math><br />
<br />
<b>Сильные стороны алгоритма</b>:<br />
<ul><br />
<li><i>Сравнительно высокая эффективность при простоте реализации</i></li><br />
<li><i>Высокое качество кластеризации</i></li><br />
<li><i>Возможность распараллеливания</i></li><br />
<li><i>Существование множества модификаций</i></li><br />
</ul><br />
<b>Недостатки алгоритма</b><ref>Ortega, Joaquín Pérez, Ma Del Rocío Boone Rojas, and María J. Somodevilla. "Research issues on K-means Algorithm: An Experimental Trial Using Matlab."</ref>:<br />
<ul><br />
<li><i>Количество кластеров является параметром алгоритма</i></li><br />
<li><br />
<p><i>Чувствительность к начальным условиям</i></p><br />
<p>Инициализация центров кластеров в значительной степени влияет на результат кластеризации.</p><br />
</li><br />
<li><br />
<p><i>Чувствительность к выбросам и шумам</i></p><br />
<p>Выбросы, далекие от центров настоящих кластеров, все равно учитываются при вычислении их центров.</p><br />
</li><br />
<li><br />
<p><i>Возможность сходимости к локальному оптимуму</i></p><br />
<p>Итеративный подход не дает гарантии сходимости к оптимальному решению.</p><br />
</li><br />
<li><br />
<p><i>Использование понятия "среднего"</i></p><br />
<p>Алгоритм неприменим к данным, для которых не определено понятие "среднего", например, категориальным данным.</p><br />
</li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализации алгоритма <math>k</math> средних согласно [[Scalability methodology|методике]] AlgoWiki.<br />
<br />
==== Реализация 1 ====<br />
<br />
Исследование масштабируемости параллельной реализации алгоритма <i>k</i>-means проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Алгоритм реализован на языке C с использованием средств MPI.<br />
Для исследования масштабируемости проводилось множество запусков программы с разным значением параметра (количество векторов для кластеризации), а также с различным числом процессоров. Фиксировались результаты запусков &ndash; время работы <math>t</math> и количество произведенных итераций алгоритма <math>i</math>.<br />
<br />
Параметры запусков для экспериментальной оценки:<br />
<ul><br />
<li>Значения <b>количества векторов</b> <math>n</math>: 20'000, 30'000, 50'000, 100'000, 200'000, 300'000, 500'000, 700'000, 1'000'000, 1'500'000, 2'000'000.</li><br />
<li>Значения <b>количества процессоров</b> <math>p</math>: 1, 8, 16, 32, 64, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512.</li><br />
<li>Значение <b>количества кластеров</b> <math>k</math>: 100.</li><br />
<li>Значение <b>размерности векторов</b> <math>d</math>: 10.</li><br />
</ul><br />
<br />
Для проведения экспериментов были сгенерированы нормально распределенные псевдослучайные данные (с использованием Python библиотеки [http://scikit-learn.org/ scikit-learn]):<br />
<syntaxhighlight lang="python"><br />
from sklearn.datasets import make_classification<br />
X1, Y1 = make_classification(n_features=10, n_redundant=2, n_informative=8,<br />
n_clusters_per_class=1, n_classes=100,<br />
n_samples=2000000)<br />
</syntaxhighlight><br />
<br />
Для заданной конфигурации эксперимента (<math>n, d, p, k</math>) и полученных результатов (<math>t, i</math>) [[Глоссарий#Производительность|производительность]] и эффективность реализации расчитывались по формулам:<br />
<p><br />
<ul><li><math>{\rm Performance} = \frac{N_{\rm k-means}^{d, n}}{t}\ \ ({\rm FLOPS}),</math></li></ul><br />
где <math>N_{\rm k-means}^{d, n}</math> &ndash; точное число операций с плавающей точкой (операции с памятью, а также целочисленные операции не учитывались), вычисленное в соответствие с разделом [[#Последовательная сложность алгоритма| "Последовательная сложность алгоритма"]];<br />
</p><br />
<p><br />
<ul><li><math>{\rm Efficiency} = \frac{100 \cdot {\rm Performance}}{{\rm Performance}_{\rm Peak}^{p}}\ \ (\%),</math></li></ul><br />
где <math>{\rm Performance}_{\rm Peak}^{p}</math> &ndash; пиковая производительность суперкомпьютера при <math>p</math> процессорах, вычисленная согласно спецификациям Intel<sup>&reg;</sup> XEON<sup>&reg;</sup> X5670<ref>"http://ark.intel.com/ru/products/47920/Intel-Xeon-Processor-X5670-12M-Cache-2_93-GHz-6_40-GTs-Intel-QPI"</ref>.<br />
</p><br />
<br />
Графики зависимости производительности и эффективности параллельной реализации <i>k</i>-means от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>) представлены на рисунках 4 и 5, соответственно.<br />
<br />
[[file:Flops.png|thumb|center|800px|Рис. 4. Параллельная реализация <i>k</i>-means. График зависимости производительности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
[[file:Efficiency.png|thumb|center|800px|Рис. 5. Параллельная реализация <i>k</i>-means. График зависимости эффективности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
<br />
<p><br />
<b>В результате</b> экспериментальной оценки были получены следующие оценки эффективности реализации:<br />
<ul><br />
<li><b>Минимальное</b> значение: 0.000409 % достигается при <math>n=20'000, p=480</math></li><br />
<li><b>Максимальное</b> значение: 0.741119 % достигается при <math>n=300'000, p=1</math></li><br />
</ul><br />
</p><br />
<br />
<p><br />
Оценки масштабируемости реализации алгоритма <i>k</i>-means:<br />
<ul><br />
<li><b>По числу процессоров:</b> -0.002683 &ndash; эффективность убывает с ростом числа процессоров. Данный результат вызван ростом накладных расходов для обеспечения параллельного выполнения алгоритма.</li><br />
<li><b>По размеру задачи:</b> 0.002779 &ndash; эффективность растет с ростом числа векторов. Данный результат вызван тем, что при увеличении размера задачи, количество вычислений растет по сравнению с временем, затрачиваемым на пересылку данных.</li><br />
<li><b>Общая оценка:</b> -0.000058 &ndash; можно сделать вывод, что в целом эффективность реализации незначительно уменьшается с ростом размера задачи и числа процессоров.</li><br />
</ul><br />
</p><br />
<br />
[https://github.com/serban/kmeans Использованная параллельная реализация алгоритма <i>k</i>-means]<br />
<br />
==== Реализация 2 ====<br />
<br />
Исследование также проводилось на суперкомпьютере "Ломоносов".<br />
<br />
<p>Набор данных для тестирования состоял из 946000 векторов размерности 2 (координаты на сфере)</p><br />
<p>Набор и границы значений изменяемых параметров запуска реализации алгоритма:</p><br />
<br />
* число процессов (виртуальных ядер) [8 : 512];<br />
* число кластеров [128 : 384].<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации <math>2,47%</math> достигается при делении исходных данных на 128 кластеров с использованием 512 процессов;<br />
* максимальная эффективность реализации <math>7,13%</math> достигается при делении исходных данных на 352 кластера с использованием 8 процессов.<br />
На рисунках 6 и 7, соответственно, представлены графики зависимости производительности и эффективности параллельной реализации <i>k</i>-means от числа кластеров и числа процессов.<br />
<br />
[[file:kmeans_performance.jpg|thumb|center|720px|Рис. 6. График зависимости производительности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
По рис. 6 можно отметить практически полное отсутствие роста производительности с увеличением числа процессов от 256 до 512 при минимальном размере задачи. Это связано с быстрым ростом накладных расходов по отношению к крайне низкому объёму вычислений. При росте размерности задачи данный эффект пропадает, и при одновременном пропорциональном увеличении числа кластеров и числа процессов рост производительности становится близким к линейному.<br />
<br />
[[file:kmeans_efficiency.jpg|thumb|center|720px|Рис. 7. График зависимости эффективности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
Исследовалась [https://github.com/like-a-bauss/kmeans параллельная реализация алгоритма <i>k</i>-means на MPI]. <br />
<p>Были получены следующие оценки масштабируемости реализации алгоритма <i>k</i>-means:</p><br />
*<i>По числу процессов:</i> <math>-0.02209</math>. Следовательно, с ростом числа процессов эффективность уменьшается. На рис. 7 можно наблюдать плавное и равномерное снижение производительности по мере увеличения числа процессов при неизменном числе кластеров, что свидетельствует об относительно невысоком росте накладных расходов на передачу данных между процессами и преобладании объёма вычислений над объёмом пересылок данных по сети.<br />
*<i>По размеру задачи:</i> <math>0.01252</math>. Следовательно, с ростом размера задачи (числа кластеров) эффективность увеличивается. При этом объём пересылок данных по сети пропорционален <math>(n + k) \cdot p</math> (где <math>k</math> - число кластеров, <math>n</math> - число входных векторов, <math>p</math> - число процессов) таким образом, поскольку <math>k << n</math>, рост накладных расходов с ростом числа кластеров при неизменном числе процессов и входных векторов представляет собой незначительную величину.<br />
*<i>Общая оценка:</i> <math>-0.00081</math>. Таким образом, с ростом и размера задачи, и числа процессов эффективность уменьшается. Это связано с тем, что отношение объёма вычислений к объёму передаваемых данных изменяется пропорционально <math>{kn \over (n + k) \cdot p} \thicksim {k \over p}</math>, что представляет собой невысокий коэффициент, но при этом позволяет параллельной реализации не деградировать до нулевой эффективности при значительном увеличении числа процессов.<br />
<br />
==== Реализация 3 ====<br />
<br />
Исследование масштабируемости алгоритма k-means в зависимости от количества используемых процессов было проведено в статье Кумара<ref>Kumar, J., Mills, R. T., Hoffman, F. M., & Hargrove, W. W. (2011). Parallel k-means clustering for quantitative ecoregion delineation using large data sets. Procedia Computer Science, 4, 1602-1611.</ref>. Исследование происходило на суперкомпьютере Jaguar - Cray XT5<ref>https://www.top500.org/system/176029</ref>. На момент экспериментов данный суперкомпьютер имел следующую конфигурацию: 18,688 вычислительных узлов с двумя шестнадцатиядерными процессорами AMD Opteron 2435 (Istanbul) 2.6 GHz, 16 GB of DDR2-800 оперативной памяти, и SeaStar 2+ роутер. Всего он состоял из 224,256 вычислительных ядер, 300 TB памяти, и пиковой производительностью 2.3 petaflops.<br />
<br />
Реализация алгоритма была выполнена на языке программирования C с использованием MPI.<br />
<br />
Объем данных составлял 84 ГБ, количество объектов (d-мерных векторов) n равнялось 1,024,767,667, размерность векторов <math>d</math> равнялась 22, количество кластеров <math>k</math> равнялось 1000. <br />
<br />
На рис. 8 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров. Можно отметить, что время, затраченное на чтение данных и запись результатов кластеризации, практически не изменяется с увеличением количества задействованных процессоров. Время же работы самого алгоритма кластеризации уменьшается с увеличением количества процессоров.<br />
<br />
[[Файл:k-means-proc-scalability.png|thumb|center|700px|Рис. 8. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (из работы: Kumar etc. 2011).]]<br />
<br />
Также было произведено самостоятельное исследование масштабируемости алгоритма k-means. Исследование производилось на суперкомпьютере "Blue Gene/P"<ref>http://hpc.cmc.msu.ru/bgp</ref>.<br />
<br />
Набор и границы значений изменяемых параметров запуска реализации алгоритма:<br />
<br />
* число процессоров [1, 2, 4, 8, 16, 32, 64, 128, 256, 512];<br />
* количество объектов [5000, 10000, 25000, 50000].<br />
<br />
Был использован набор данных ''Dataset for Sensorless Drive Diagnosis Data Set''<ref>PASCHKE, Fabian ; BAYER, Christian ; BATOR, Martyna ; MÖNKS, Uwe ; DICKS, Alexander ; ENGE-ROSENBLATT, Olaf ; LOHWEG, Volker: Sensorlose Zustandsüberwachung an Synchronmotoren, Bd. 46. In: HOFFMANN, Frank; HÃœLLERMEIER, Eyke (Hrsg.): Proceedings 23. Workshop Computational Intelligence. Karlsruhe : KIT Scientific Publishing, 2013 (Schriftenreihe des Instituts für Angewandte Informatik - Automatisierungstechnik am Karlsruher Institut für Technologie, 46), S. 211-225</ref> из репозитория ''Machine learning repository''<ref>https://archive.ics.uci.edu/ml/datasets/Dataset+for+Sensorless+Drive+Diagnosis</ref>.<br />
<br />
Исследуемый набор данных содержит векторы, размерность которых равна 49. Компоненты векторов являются вещественными числами. Количество кластеров равно 11. Пропущенные значения отсутствуют.<br />
<br />
Для исследования масштабируемости алгоритма была использована реализация на языке C с использованием MPI<ref>http://users.eecs.northwestern.edu/~wkliao/Kmeans/index.html</ref>. Код можно найти здесь: https://github.com/serban/kmeans. Данная реализация предоставляет возможность распараллеливать решение задачи с помощью технологий MPI, OpenMP И CUDA. Для запуска MPI-версии программы использовалась цель "mpi_main" Makefile.<br />
<br />
На рис. 9 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (использовались логарифмические оси). Разными цветами помечены запуски, соответствующие разным количествам объектам, участвующих в кластеризации. Можно видеть близкое к линейному увеличение времени работы программы в зависимости от количества процессоров. Также можно видеть увеличение времени работы алгоритма при увеличении количества объектов.<br />
[[Файл:Plot_1.png|thumb|center|900px|Рис. 9. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
На рис. 10 показана эта же зависимость, только в трехмерном пространстве. По аналогии с рис. 9, были использованы логарифмические оси. Как и в случае двумерного рисунка, можно видеть близкое к линейному увеличение времени работы программы.<br />
[[Файл:Kmeans-3d.png|thumb|center|900px|Рис. 10. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
==== Реализация 4 ====<br />
<br />
Исследование масштабируемости данной параллельной реализации алгоритма k-средних также проводилось на суперкомпьютере "Ломоносов". Параллельная реализация была написана самостоятельно на языке C, [http://git.algowiki-project.org/iaegorov/Egorov-Bogomazov-k-means/tree/master ссылка на реализацию]. Так как на каждой итерации число действий на единицу данных не велико и данные должны быть собраны вместе при перерасчете центроидов, было решено для ускорения вычислений воспользоваться только OpenMP без использовании MPI.<br />
<br />
Код собирался под gcc c опцией -fopenmp. Код считался на одном процессоре, технология hyperthreading не использовалась.<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 16] с увеличением в 2 раза;<br />
* размер данных [100000 : 1600000] с увеличением в 2 раза.<br />
<br />
В результате проведённых экспериментов были получены следующие данные:<br />
<br />
* Максимальная эффективность в точке достигается при переходе от 1 потока на 4 при минимальном размере данных, она равна <math>87,5%</math>.<br />
* Усредненная максимальная эффективность достигается при переходе с одного потока на два. Среднее время вычислений на всех рассмотренных потока снижается с 16,33 до 11.87 секунд, поэтому формально эффективность <math>= 16.33 / 11.87 / 2 \approx 68,4\%</math><br />
* Минимальная эффективность в точке достигается при переходе от 1 потока на 16 при размере данных 800000, она равна <math>11,1\%</math>.<br />
* Усредненная минимальная эффективность наблюдается при переходе с одного на максимальное рассматриваемое в эксперименте число потоков, равное 16. Время вычисления изменяется с 16,33 до 7,6 секунд, поэтому формально эффективность <math> = 16.33 / 7.6 / 16 \approx 14,9\%</math><br />
<br />
Ниже приведены графики зависимости вычислительного времени алгоритма и его эффективности от изменяемых параметров запуска — размера данных и числа процессоров:<br />
<br />
[[file:Egorov_Bogomazov_Time.jpg|thumb|center|700px|Рис. 11. Параллельная реализация алгоритма k-средних. Изменение вычислительного времени алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь видно, что время выполнения операций алгоритма плавно убывает по каждому из параметров, причем скорость убывания по параметру числа процессоров выше, чем в зависимости от размерности задачи.<br />
<br />
[[file:Egorov_Bogomazov_Efficiency.jpg|thumb|center|700px|Рис. 12. Параллельная реализация алгоритма k-средних. Изменение эффективности алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь построена эффективность перехода от последовательной реализации к параллельной. Рассчитывается она по формуле ''Время вычисления на 1 потоке / Время вычисления на <math>T</math> потоках / <math>T</math>'', где <math>T</math> — это число потоков. При вычислении на 1 процессоре она равна 100 \% в силу используемой формулы, что и отражено на графике.<br />
<br />
Проведем оценки масштабируемости:<br />
<br />
'''По числу процессов''' — при увеличении числа процессов эффективность уменьшается на всей области рассматриваемых значений, причем темп убывания замедляется с ростом числа процессов.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений вначале кратковременно возрастает, но затем начинает относительно равномерно убывать на всей рассматриваемой области.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений в общем случае постепенно убывает. На малых данных она выходит на пик мощности, являющийся максимумом эффективности в исследуемых условиях, но затем возвращается к процессу убывания.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
В однопоточном режиме на наборах данных, представляющих практический интерес (порядка нескольких десятков тысяч векторов и выше), время работы алгоритма неприемлемо велико. Благодаря свойству массового параллелизма должно наблюдаться значительное ускорение алгоритма на многоядерных архитектурах (Intel Xeon), а также на графических процессорах, даже на мобильных вычислительных системах (ноутбуках), оснащенных видеокартой. Алгоритм <i>k</i>-means также будет демонстрировать значительное ускорение на сверхмощных вычислительных комплексах (суперкомпьютерах, системах облачных вычислений<ref>"Issa. "Performance characterization and analysis for Hadoop K-means iteration". Journal of Cloud Computing, 2016"</ref>).<br />
<br />
На сегодняшний день существует множество реализаций алгоритма <i>k</i>-means, в частности, направленных на оптимизацию параллельной работы на различных архитектурах<ref>"Raghavan R. A fast and scalable hardware architecture for K-means clustering for big data analysis : дис. – University of Colorado Colorado Springs. Kraemer Family Library, 2016."</ref><ref>"Yang, Luobin, et al. "High performance data clustering: a comparative analysis of performance for GPU, RASC, MPI, and OpenMP implementations." The Journal of supercomputing 70.1 (2014): 284-300."</ref><ref>"Li, You, et al. "Speeding up k-means algorithm by GPUs." Computer and Information Technology (CIT), 2010 IEEE 10th International Conference on. IEEE, 2010."</ref>. Предлагается множество адаптаций алгоритма под конкретные архитектуры. Например, авторы работы<ref>"Kanan, Gebali, Ibrahim. "Fast and Area-Efficient Hardware Implementation of the K-means<br />
Clustering Algorithm". WSEAS Transactions on circuits and systems. Vol. 15. 2016"</ref> производят перерасчет центров кластеров на этапе распределения векторов по кластерам.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
==== Открытое программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.icpsr.umich.edu/CrimeStat/ CrimeStat]</li> Программное обеспечение, созданное для операционных систем Windows, предоставляющее инструменты статистического и пространственного анализа для решения задачи картирования преступности.<br />
<li>[http://juliastats.github.io Julia]</li> Высокоуровневый высокопроизводительный свободный язык программирования с динамической типизацией, созданный для математических вычислений, содержит реализацию k-means.<br />
<li>[https://mahout.apache.org Mahout]</li> Apache Mahout - Java библиотека для работы с алгоритмами машинного обучения с использованием MapReduce. Содержит реализацию k-means.<br />
<li>[https://www.gnu.org/software/octave/ Octave]</li> Написанная на C++ свободная система для математических вычислений, использующая совместимый с MATLAB язык высокого уровня, содержит реализацию k-means.<br />
<li>[http://spark.apache.org/docs/latest/mllib-clustering.html Spark]</li> Распределенная реализация k-means содержится в библиотеке Mlib для работы с алгоритмами машинного обучения, взаимодействующая с Python библиотекой NumPy и библиотека R.<br />
<li>[http://torch.ch Torch]</li> MATLAB-подобная библиотека для языка программирования Lua с открытым исходным кодом, предоставляет большое количество алгоритмов для глубинного обучения и научных расчётов. Ядро написано на Си, прикладная часть выполняется на LuaJIT, поддерживается распараллеливание вычислений средствами CUDA и OpenMP. Существуют реализации k-means.<br />
<li>[http://www.cs.waikato.ac.nz/ml/weka/ Weka]</li> Cвободное программное обеспечение для анализа данных, написанное на Java. Содержит k-means и x-means.<br />
<li>[http://accord-framework.net/docs/html/T_Accord_MachineLearning_KMeans.htm Accord.NET]</li> C# реализация алгоритмов k-means, k-means++, k-modes.<br />
<li>[http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html OpenCV]</li> Написанная на С++ библиотека, направленная в основном на решение задач компьютерного зрения. Содержит реализацию k-means.<br />
<li>[http://mlpack.org/ MLPACK]</li> Масштабируемая С++ библиотека для работы с алгоритмами машинного обучения, содержит реализацию k-means.<br />
<li>[https://www.scipy.org/ SciPy]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[http://scikit-learn.org/ scikit-learn]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[https://www.r-bloggers.com/k-means-clustering-in-r/ R]</li> Язык программирования для статистической обработки данных и работы с графикой, а также свободная программная среда вычислений с открытым исходным кодом в рамках проекта GNU, содержит три реализации k-means.<br />
<li>[http://elki.dbs.ifi.lmu.de ELKI]</li> Java фреймворк, содержащий реализацию k-means, а также множество других алгоритмов кластеризации.<br />
</ol><br />
<br />
==== Проприетарное программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.ayasdi.com/blog/bigdata/topological-data-analysis-of-oil-and-gas-petrophysical-data/ Ayasdi]</li><br />
<li>[http://www.stata.com/features/cluster-analysis/ Stata]</li><br />
<li>[http://mathworld.wolfram.com/K-MeansClusteringAlgorithm.html Mathematica]</li><br />
<li>[http://www.mathworks.com/help/stats/kmeans.html?requestedDomain=www.mathworks.com MATLAB]</li><br />
<li>[https://support.sas.com/rnd/app/stat/procedures/fastclus.html SAS]</li><br />
<li>[http://docs.rapidminer.com/studio/operators/modeling/segmentation/k_means.html RapidMiner]</li><br />
<li>[https://blogs.sap.com/2013/03/28/sap-hana-pal-k-means-algorithm-or-how-to-do-customer-segmentation-for-the-telecommunications-industry/ SAP HANA]</li><br />
</ol><br />
<br />
== Литература ==<br />
<br />
<references /></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_k_%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D1%85_(k-means)&diff=25246Алгоритм k средних (k-means)2018-01-16T17:14:55Z<p>Konshin: /* Математическое описание алгоритма */</p>
<hr />
<div>Основные авторы статьи (разделы 1, 2.4.1, 2.6-2.7, 3):<br />
[https://algowiki-project.org/ru/Участник:Бротиковская_Данута<b>Д.Бротиковская</b>] и<br />
[https://algowiki-project.org/ru/Участник:DennZo1993<b>Д.Зобнин</b>]<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:IanaV<b>Я.А.Валуйская</b>] и<br />
[https://algowiki-project.org/ru/Участник:GlotovES<b>Е.С.Глотов</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Parkhomenko<b>П.А.Пархоменко</b>] и<br />
[https://algowiki-project.org/ru/Участник:Ivan.mashonskiy<b>И.Д.Машонский</b>] (раздел 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Илья_Егоров<b>И.Егоров</b>] и<br />
[https://algowiki-project.org/ru/Участник:Богомазов_Евгений<b>Е.Богомазов</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Алгоритм <math>k</math> средних (<math>k</math>-means)<br />
| serial_complexity = <math>O(ikdn)</math><br />
| input_data = <math> dn </math><br />
| output_data = <math> n </math><br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Алгоритм <b><i>k средних</i></b> (англ. k-means) - один из алгоритмов машинного обучения, решающий задачу кластеризации.<br />
Этот алгоритм является неиерархическим<ref>"https://ru.wikipedia.org/wiki/Иерархическая_кластеризация"</ref>, итерационным методом кластеризации<ref>"https://ru.wikipedia.org/wiki/Кластерный_анализ"</ref>, он получил большую популярность благодаря своей простоте, наглядности реализации и достаточно высокому качеству работы. <br />
Был изобретен в 1950-х годах математиком <i>Гуго Штейнгаузом</i><ref>Steinhaus, Hugo. "Sur la division des corp materiels en parties." Bull. Acad. Polon. Sci 1.804 (1956): 801.</ref> и почти одновременно <i>Стюартом Ллойдом</i><ref>Lloyd, S. P. "Least square quantization in PCM. Bell Telephone Laboratories Paper. Published in journal much later: Lloyd, SP: Least squares quantization in PCM." IEEE Trans. Inform. Theor.(1957/1982).</ref>. Особую популярность приобрел после публикации работы <i>МакКуина</i><ref>MacQueen, James. "Some methods for classification and analysis of multivariate observations." Proceedings of the fifth Berkeley symposium on mathematical statistics and probability. Vol. 1. No. 14. 1967.</ref> в 1967.<br />
<br />
Алгоритм представляет собой версию EM-алгоритма<ref>"https://ru.wikipedia.org/wiki/EM-алгоритм"</ref>, применяемого также для разделения смеси гауссиан. Основная идея <i>k means</i> заключается в том, что данные произвольно разбиваются на кластеры, после чего итеративно перевычисляется центр масс для каждого кластера, полученного на предыдущем шаге, затем векторы разбиваются на кластеры вновь в соответствии с тем, какой из новых центров оказался ближе по выбранной метрике.<br />
<br />
Цель алгоритма заключается в разделении <math>n</math> наблюдений на <math>k</math> кластеров таким образом, чтобы каждое наблюдение принадлежало ровно одному кластеру, расположенному на наименьшем расстоянии от наблюдения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
<b>Дано:</b><br />
* набор из <math>n</math> наблюдений <math>X=\{\mathbf{x}_1, \mathbf{x}_2, ..., \mathbf{x}_n\}, \mathbf{x}_i \in \mathbb{R}^d, \ i=1,...,n</math>;<br />
* <math>k</math> - требуемое число кластеров, <math>k \in \mathbb{N}, \ k \leq n</math>.<br />
<br />
<b>Требуется:</b><br />
<br />
Разделить множество наблюдений <math>X</math> на <math>k</math> кластеров <math>S_1, S_2, ..., S_k</math>:<br />
* <math>S_i \cap S_j= \varnothing, \quad i \ne j</math><br />
<br />
* <math>\bigcup_{i=1}^{k} S_i = X</math><br />
<br />
<b>Действие алгоритма:</b><br />
<p>Алгоритм <i>k means</i> разбивает набор <math>X</math> на <math>k</math> наборов <math>S_1, S_2, ..., S_k,</math> таким образом, чтобы минимизировать сумму квадратов расстояний от каждой точки кластера до его центра (центр масс кластера). Введем обозначение, <math>S=\{S_1, S_2, ..., S_k\}</math>. Тогда действие алгоритма <i>k means</i> равносильно поиску:</p><br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math>\arg\min_{S} \sum\limits_{i=1}^k \sum\limits_{\mathbf{x} \in S_i} \rho(\mathbf{x}, \mathbf{\mu}_i )^2,</math></td><br />
<td align="right"><math>(1)</math></td><br />
</tr><br />
</table><br />
где <math>\mathbf{\mu}_i</math> &ndash; центры кластеров, <math>i=1,...,k, \quad \rho(\mathbf{x}, \mathbf{\mu}_i)</math> &ndash; функция расстояния между <math>\mathbf{x}</math> и <math>\mu_i</math><br />
<br />
<b>Шаги алгоритма:</b><br />
<ol><br />
<li><br />
<b>Начальный шаг: инициализация кластеров</b><br />
<p>Выбирается произвольное множество точек <math>\mu_i, \ i=1,...,k,</math> рассматриваемых как начальные центры кластеров: <math>\mu_i^{(0)} = \mu_i, \quad i=1,...,k</math></p><br />
</li><br />
<li><br />
<b>Распределение векторов по кластерам</b><br />
<p><b>Шаг</b> <math>t: \forall \mathbf{x}_i \in X, \ i=1,...,n: \mathbf{x}_i \in S_j \iff j=\arg\min_{k}\rho(\mathbf{x}_i,\mathbf{\mu}_k^{(t-1)})^2</math></p><br />
</li><br />
<li><br />
<b>Пересчет центров кластеров</b><br />
<p><b>Шаг </b> <math>t: \forall i=1,...,k: \mu_i^{(t)} = \cfrac{1}{|S_i|}\sum_{\mathbf{x}\in S_i}\mathbf{x}</math></p><br />
</li><br />
<li><br />
<b>Проверка условия останова:</b><br />
<p></p><br />
'''if''' <math>\exist i\in \overline{1,k}: \mu_i^{(t)} \ne \mu_i^{(t-1)}</math> '''then'''<br />
t = t + 1;<br />
goto 2;<br />
'''else'''<br />
'''stop'''<br />
</li><br />
</ol><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром являются шаги 2 и 3 приведенного выше алгоритма: <b><i>распределение векторов по кластерам</i></b> и <b><i>пересчет центров кластеров</i></b>. <br />
<br />
<p><br />
<b><i>Распределение векторов</i></b> по кластерам предполагает вычисление расстояний между каждым вектором <math>\mathbf{x}_i \in X, \ i= 1,...,n</math> и центрами кластера <math>\mathbf{\mu}_j, \ j= 1,...,k</math>. Таким образом, данный шаг предполагает <math>kn</math> вычислений расстояний между <math>d</math>-мерными векторами. <br />
</p><br />
<p><br />
<b><i>Пересчет центров кластеров</i></b> предполагает <math>k</math> вычислений центров масс <math>\mathbf{\mu}_i</math> множеств <math>S_i, \ i=1,...,k,</math> представленных выражением в шаге 3 представленного выше алгоритма.<br />
</p><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
<b>Инициализация центров масс <math>\mu_1, ..., \mu_k</math></b>. <br />
<br />
Наиболее распространенными являются следующие стратегии:<br />
<ul><br />
<li><br />
<b>Метод Forgy</b><br>В качестве начальных значений <math>\mu_1, ..., \mu_k</math> берутся случайно выбранные векторы.<br />
</li><br />
<li><br />
<b>Метод случайно разделения (Random Partitioning)</b><br>Для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> выбирается случайным образом кластер <math>S_1, ..., S_k</math>, после чего для каждого полученного кластера вычисляются значения <math>\mu_1, ..., \mu_k</math>.<br />
</li><br />
</ul><br />
<br />
<b>Распределение векторов по кластерам</b><br />
<br />
Для этого шага алгоритма между векторами <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> и центрами кластеров <math>\mu_1,...,\mu_k</math> вычисляются <b>расстояния</b> по формуле (как правило, используется Евлидово расстояние):<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mathbf{v}_1, \mathbf{v}_2 \in \mathbb{R}^d, \quad \rho(\mathbf{v}_1, \mathbf{v}_2) = \lVert \mathbf{v}_1- \mathbf{v}_2 \rVert= \sqrt{\sum_{i=1}^{d}(\mathbf{v}_{1,i} - \mathbf{v}_{2,i})^2}</math></td><br />
<td align="right"><math>(2)</math></td><br />
</tr><br />
</table><br />
<br />
<b>Пересчет центров кластеров</b><br />
<br />
Для этого шага алгоритма производится пересчет центров кластера по <b>формуле вычисления центра масс</b>:<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mu = \cfrac{1}{|S|}\sum_{\mathbf{x}\in S}\mathbf{x}</math></td><br />
<td align="right"><math>(3)</math></td><br />
</tr><br />
</table><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
'''1.''' Инициализировать центры кластеров <math>\mathbf{\mu}_i^{(1)}, \ i=1,...,k</math><br><br />
'''2.''' <math>t \leftarrow 1</math><br><br />
'''3.''' Распределение по кластерам<br><br />
<math>\quad S_i^{(t)}=\{\mathbf{x}_p: \lVert\mathbf{x}_p-\mathbf{\mu}_i^{(t)}\rVert^2 \leq \lVert\mathbf{x}_p-\mathbf{\mu}_j^{(t)}\rVert^2 \quad \forall j=1,...,k\},</math><br><br />
<math>\quad</math>где каждый вектор <math>\mathbf{x}_p</math> соотносится единственному кластеру <math>S^{(t)}</math><br><br />
'''4.''' Обновление центров кластеров<br><br />
<math>\quad \mathbf{\mu}_i^{(t+1)} = \frac{1}{|S^{(t)}_i|} \sum_{\mathbf{x}_j \in S^{(t)}_i} \mathbf{x}_j </math><br><br />
'''5.''' '''if''' <math>\exists i \in \overline{1,k}: \mathbf{\mu}_i^{(t+1)} \ne \mathbf{\mu}_i^{(t)}</math> '''then'''<br><br />
<math>\quad t = t + 1</math>;<br><br />
<math>\quad</math>goto '''3''';<br><br />
<math>~~~</math>'''else'''<br><br />
<math>\quad</math>'''stop'''<br><br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
<div style="padding-bottom: 20px"><br />
<p>Обозначим <math>\Theta_{\rm centroid}^{d, m}</math> временную сложность вычисления центорида кластера, число элементов которого равна <math>m</math>, в d-мерном пространстве.</p><br />
<p>Аналогично <math>\Theta_{\rm distance}^d</math> &ndash; временная сложность вычисления расстояния между двумя d-мерными векторами.</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<b>Сложность шага инициализации <math>k</math> кластеров мощности <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm init}^{k, d, m}</math></b><br />
<ul><br />
<li><i>Стратерия Forgy</i>: вычисления не требуются, <math>\Theta_{\rm init}^{k, d, m} = 0</math></li><br />
<li><i>Стратегия случайного разбиения</i>: вычисление центров <math>k</math> кластеров, <math>\Theta_{\rm init}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}, m \le n</math></li><br />
</ul><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Cложность шага распределения d мерных векторов по <math>k</math> кластерам &ndash; <math>\Theta_{\rm distribute}^{k, d}</math></b></p><br />
<p><br />
На этом шаге для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> вычисляется <math>k</math> расстояний до центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math><br />
</p><br />
<p align="center"><br />
<math>\Theta_{\rm distribute}^{k, d} = n \cdot k \cdot \Theta_{\rm distance}^d</math><br />
</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><br />
<b>Сложность шага пересчета центров <math>k</math> кластеров размера <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm recenter}^{k, d, m}</math></b><br />
</p><br />
<p>На этом шаге вычисляется <math>k</math> центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math></p><br />
<p align="center"><math>\Theta_{\rm recenter}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}</math> </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm centroid}^{d, m}</math> для кластера, число элементов которого равно <math>m</math></b></p><br />
<p align="center"><math>\Theta_{\rm centroid}^{d, m}</math> = <math>m \cdot d</math> сложений + <math>d</math> делений </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm distance}^d</math> в соответствие с формулой <math>(2)</math></b></p><br />
<p align="center"><math>\Theta_{\rm distance}^d</math> = <math>d</math> вычитаний + <math>d</math> умножений + <math>(d-1)</math> сложение</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Предположим, что алгоритм сошелся за <math>i</math> итераций, тогда временная сложность алгоритма <math>\Theta_{\rm k-means}^{d, n}</math></p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le \Theta_{\rm init}^{k, d, n} + i(\Theta_{\rm distribute}^{k, d} + \Theta_{\rm recenter}^{k, d, n})</math></b></p><br />
</div><br />
<div style="padding-bottom: 5px"><br />
<p>Операции сложения/вычитания:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le knd+ i(kn(2d-1) + knd) = knd+ i(kn(3d-1)) \thicksim O(ikdn)</math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Операции умножения/деления:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le kd + i(knd + kd) = kd + ikd(n+1) \thicksim O(ikdn) </math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Получаем, что <b>временная сложность</b> алгоритма <b>k means</b> кластеризации <math>n</math> <b>d-мерных</b> векторов на <math>k</math> кластеров за <math>i</math> итераций:</p><br />
<p align="center"><b><math> \Theta_{\rm k-means}^{d, n} \thicksim O(ikdn) </math></b></p><br />
</div><br />
<br />
=== Информационный граф ===<br />
<br />
Рассмотрим информационный граф алгоритма. Алгоритм <i>k means</i> начинается с этапа инициализации, после которого следуют итерации, на каждой из которых выполняется два последовательных шага (см. [[#Схема реализации последовательного алгоритма|"Схема реализации последовательного алгоритма"]]): <br />
* распределение векторов по кластерам<br />
* перерасчет центров кластеров<br />
<br />
Поскольку основная часть вычислений приходится на шаги итераций, распишем информационные графы данных шагов.<br />
<br />
<p><b>Распределение векторов по кластерам</b></p><br />
Информационный граф шага распределения векторов по кластерам представлен на ''рисунке 1''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также центры кластеров <math>\mathbf{\mu}_1, ... \mathbf{\mu}_k</math>, вычисленные ранее (на шаге инициализации, если рассматривается первая итерация алгоритма, или на шаге пересчета центров кластеров предыдущей итерации в противном случае). Каждая пара векторов данных <math>\mathbf{x}_i, \ i=1,...,n,</math> и центров кластера <math>\mathbf{\mu}_j, \ j=1,...,k</math> : (<math>\mathbf{x}_i</math>, <math>\mathbf{\mu}_j</math>) подаются на независимые узлы <i>"d"</i> вычисления расстояния между векторами (более подробная схема вычисления расстояния представлена далее, ''рисунок 2''). Далее узлы вычисления расстояния <i>"d"</i>, соответствующие одному и тому же исходному вектору <math>\mathbf{x}_i</math> передаются на один узел <i>"m"</i>, где далее происходит вычисление новой метки кластера для каждого вектора <math>\mathbf{x}_i</math> (берется кластер с минимальным результатом вычисления расстояния). На выходе графа выдаются метки кластеров , <math>L_1, ..., L_n</math>, такие что <math>\forall \mathbf{x}_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>. <br />
<br />
[[file:Clusterization.png|thumb|center|800px|Рис. 1. Схема распределения векторов по кластерам. ''d'' &ndash; вычисление расстояния между векторами; ''m'' &ndash; вычисление минимума.]]<br />
<br />
<p><b>Вычисление расстояния между векторами</b></p><br />
Подробная схема вычисления расстояния между векторами <math>\mathbf{x}_i, \mathbf{\mu}_j</math> представлена на ''рисунке 2''. Как показано на графе, узел вычисления расстояния между векторами <i>"d"</i> состоит из шага взятия разности между векторами (узел "<math>-</math>") и взятия нормы получившегося вектора разности (узел "<math>||\cdot||^2</math>"). Более подробно, вычисление расстояния между векторами <math>\mathbf{x}_i = {x_{i1,}, ...,{x_{in}}}, \mathbf{\mu}_j = {\mu_{j1}, ...,\mu_{jn}}</math> может быть представлено как вычисление разности между каждой парой компонент <math>(x_{iz}, \mu_{jz}), \ z=1,...,d</math> (узел "<math>-</math>"), далее возведение в квадрат для каждого узла "<math>-</math>" (узел "<math>()^2</math>") и суммирования выходов всех узлов "<math>()^2</math>" (узел "<math>+</math>"). <br />
<br />
[[file:dist_calc.png|thumb|center|800px|Рис. 2. Схема вычисления расстояния между вектором и центром кластера.]]<br />
<br />
<p><b>Пересчет центров кластеров</b></p><br />
Информационный граф шага пересчета центров кластеров представлен на ''рисунке 3''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также им соответствующие метки кластера, <math>L_1, ..., L_n</math>, такие что <math>\forall x_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>, вычисленные на этапе распределения векторов по кластерам. Все векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math> подаются в узлы <math>+_1, ...+_k</math>, каждый узел <math>+_m, \ m = 1,...,k,</math> соответствует операции сложения векторов кластера с номером <math>m</math>. Метки кластера <math>L_1, ..., L_n</math> также совместно передаются на узлы <math>S_m, \ m=1,...,k</math>, на каждом из которых вычисляется количество векторов в соответствующем кластере (количество меток с соответствующим значением). Далее каждая пара выходов узлов <math>+_m</math> и <math>S_m</math> подается на узел "<math>/</math>", где производится деление суммы векторов кластера на количество элементов в нем. Значения, вычисленные на узлах "<math>/</math>", присваиваются новым центрам кластеров (выходные значения графа).<br />
<br />
[[file:Recluster.png|thumb|center|800px|Рис. 3. Схема пересчета центров кластеров]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
<p><br />
Работа алгоритма состоит из <math>i</math> итераций, в каждой из которых происходит <b>распределение d-мерных векторов по <math>k</math> кластерам</b>, а также <b>пересчет центров кластеров в d-мерном пространстве</b>. В шаге <b>распределения d-мерных векторов по <math>k</math> кластерам</b> расстояния между вектором и центрами кластеров вычисляются независимо (отсутствуют информационные зависимости). <b>Центры масс кластеров</b> также пересчитываются независимо друг от друга. Таким образом, имеет место [[глоссарий#Массовый параллелизм|''массовый параллелизм'']]. Вычислим параллельную сложность <math>\Psi_*</math> каждого из шагов, а также параллельную сложность всего алгоритма, <math>\Psi_{\rm k-means}</math>. Будем исходить из предположения, что может быть использовано любое необходимое число потоков.<br />
</p><br />
<br />
<p><b>Распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b></p> <br />
<p><br />
Поскольку на данном шаге для каждой пары векторов <math>\mathbf{x}_i, \ i=1,...,n</math> и <math>\mathbf{\mu}_j, \ j=1,...,k,</math> операции вычисления расстояния не зависят друг от друга, они могут выполняться параллельно. Тогда, разделив все вычисление расстояний на <math>n</math> потоков, получим, что в каждом потоке будет выполняться только одна операция вычисления расстояния между векторами размерности d. При этом каждому вычислительному потоку передаются координаты центров всех кластеров <math>\mathbf{\mu}_1, ..., \mathbf{\mu}_k</math>. Таким образом, параллельная сложность данного шага определяется <i> сложностью параллельной операции вычисления расстояния между d-мерными векторами</i>, <math>\Psi_{\rm distance}^d</math> и <i>сложностью определения наиболее близкого кластера</i> (паралельное взятие минимума по расстояниям), <math>\Psi_{\rm min}^k</math>. Для оценки <math>\Psi_{\rm distance}^d</math> воспользуемся [[Нахождение_частных_сумм_элементов_массива_сдваиванием | параллельной реализацией нахождения частичной суммы элементов массива путем сдваивания]]. Аналогично, <math>\Psi_{\rm min}^k = log(k)</math>. В результате, <math>\Psi_{\rm distance}^d = O(log(d))</math>. Таким образом: <br />
</p><br />
<p align="center"><math>\Psi_{\rm distribute}^{k, d} = \Psi_{\rm distance}^d + \Psi_{\rm min}^k = O(log(d))+O(log(k)) = O(log(kd))</math></p><br />
<br />
<p><b>Пересчет центров кластеров в d-мерном пространстве</b></p><br />
<p><br />
Поскольку на данном шаге для каждого из <math>k</math> кластеров центр масс может быть вычислен независимо, данные операции могут быть выполнены в отдельных потоках. Таким образом, параллельная сложность данного шага, <math>\Psi_{\rm recenter}^{k, d}</math>, будет определяться <i>параллельной сложностью вычисления одного центра масс кластера размера <math>m</math></i>, <math>\Psi_{\rm recenter}^{k, d}</math>, а так как <math>m \le n \Rightarrow \Psi_{\rm recenter}^{d, m} \le \Psi_{\rm recenter}^{d, n}</math>. Сложность вычисления центра масс кластера d-мерных векторов размера n аналогично предыдущим вычислениям равна <math>O(log(n))</math>. Тогда: <br />
</p><br />
<p align="center"><math>\Psi_{\rm recenter}^{k, d} \le \Psi_{\rm recenter}^{d, n} = O(log(n))</math></p><br />
<br />
<p><b>Общая параллельная сложность алгоритма</b></p><br />
<p><br />
На каждой итерации необходимо обновление центров кластеров, которые будут использованы на следующей итерации. Таким образом, итерационный процесс выполняется последовательно<ref>Zhao, Weizhong, Huifang Ma, and Qing He. "Parallel k-means clustering based on mapreduce." IEEE International Conference on Cloud Computing. Springer Berlin Heidelberg, 2009.</ref>. Тогда, поскольку сложность каждой итерации определяется <math>\Psi_{\rm distribute}^{k, d}</math> и <math>\Psi_{\rm recenter}</math>, сложность всего алгоритма, <math>\Psi_{\rm k-means}</math> в предположении, что было сделано <math>i</math> операций определяется выражением<br />
</p><br />
<p align="center"><math>\Psi_{\rm k-means} \approx i \cdot (\Psi_{\rm distribute}^{k, d} + \Psi_{\rm recenter}^{k, d}) \le i \cdot O(log(kdn))</math></p><br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
<p><br />
<b>Входные данные</b><br><br />
<ul><br />
<li>Матрица из <math>n \cdot d</math> элементов <math>x_{i, j} \in \mathbb{R}, \ i=1,...,n, \ j=1,...,d,</math> &ndash; координат векторов (наблюдений).</li><br />
<li>Целое положительное число <math>k, \ k \le n</math> &ndash; количество кластеров.</li><br />
</ul><br />
</p><br />
<p><br />
<b>Объем входных данных</b><br><br />
<math>1</math> целое число + <math>n \cdot d</math> вещественных чисел (при условии, что координаты &ndash; вещественные числа).<br />
</p><br />
<p><br />
<b>Выходные данные</b><br><br />
<math>n</math> целых положительных чисел <math>L_1, ..., L_n</math>&ndash; номера кластеров, соотвествующие каждому вектору (при условии, что нумерация кластеров начинается с <math>1</math>).<br />
</p><br />
<p><br />
<b>Объем выходных данных</b><br><br />
<math>n</math> целых положительных чисел.<br />
</p><br />
<br />
=== Свойства алгоритма ===<br />
<br />
<p><b>[[глоссарий#Вычислительная мощность|''Вычислительная мощность'']]</b></p><br />
Вычислительная мощность алгоритма <i>k means</i> равна <math>\frac{ikdn}{nd} = ki </math>, где <math>k</math> &ndash; число кластеров, <math>i</math> &ndash; число итераций алгоритма.<br />
<br />
<p><b>[[глоссарий#Детерминированность |''Детерминированность'']] и [[глоссарий#Устойчивость |''Устойчивость'']] </b></p><br />
Алгоритм <i>k means</i> является итерационным. Количество итераций алгоритма в общем случае не фиксируется и зависит от начального расположения объектов в пространстве, параметра <math>k</math>, а также от начального приближения центров кластеров, <math>\mu_1, ..., \mu_k</math>. В результате этого может варьироваться результат работы алгоритма. При неудачном выборе начальных параметров итерационный процесс может сойтись к локальному оптимуму<ref>Von Luxburg, Ulrike. Clustering Stability. Now Publishers Inc, 2010.</ref>. По этим причинам алгоритм не является ни <b>детермирированным</b>, ни <b>устойчивым</b>.<br />
<br />
<p><b>Соотношение последовательной и параллельной сложности алгоритма</b></p><br />
<br />
<math>\frac{\Theta_{\rm k-means}}{\Psi_{\rm k-means}} = \frac{O(ikdn)}{O(i \cdot log(kdn))}</math><br />
<br />
<b>Сильные стороны алгоритма</b>:<br />
<ul><br />
<li><i>Сравнительно высокая эффективность при простоте реализации</i></li><br />
<li><i>Высокое качество кластеризации</i></li><br />
<li><i>Возможность распараллеливания</i></li><br />
<li><i>Существование множества модификаций</i></li><br />
</ul><br />
<b>Недостатки алгоритма</b><ref>Ortega, Joaquín Pérez, Ma Del Rocío Boone Rojas, and María J. Somodevilla. "Research issues on K-means Algorithm: An Experimental Trial Using Matlab."</ref>:<br />
<ul><br />
<li><i>Количество кластеров является параметром алгоритма</i></li><br />
<li><br />
<p><i>Чувствительность к начальным условиям</i></p><br />
<p>Инициализация центров кластеров в значительной степени влияет на результат кластеризации.</p><br />
</li><br />
<li><br />
<p><i>Чувствительность к выбросам и шумам</i></p><br />
<p>Выбросы, далекие от центров настоящих кластеров, все равно учитываются при вычислении их центров.</p><br />
</li><br />
<li><br />
<p><i>Возможность сходимости к локальному оптимуму</i></p><br />
<p>Итеративный подход не дает гарантии сходимости к оптимальному решению.</p><br />
</li><br />
<li><br />
<p><i>Использование понятия "среднего"</i></p><br />
<p>Алгоритм неприменим к данным, для которых не определено понятие "среднего", например, категориальным данным.</p><br />
</li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализации алгоритма <math>k</math> средних согласно [[Scalability methodology|методике]] AlgoWiki.<br />
<br />
==== Реализация 1 ====<br />
<br />
Исследование масштабируемости параллельной реализации алгоритма k means проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Алгоритм реализован на языке C с использованием средств MPI.<br />
Для исследования масштабируемости проводилось множество запусков программы с разным значением параметра (количество векторов для кластеризации), а также с различным числом процессоров. Фиксировались результаты запусков &ndash; время работы <math>t</math> и количество произведенных итераций алгоритма <math>i</math>.<br />
<br />
Параметры запусков для экспериментальной оценки:<br />
<ul><br />
<li>Значения <b>количества векторов</b> <math>n</math>: 20'000, 30'000, 50'000, 100'000, 200'000, 300'000, 500'000, 700'000, 1'000'000, 1'500'000, 2'000'000.</li><br />
<li>Значения <b>количества процессоров</b> <math>p</math>: 1, 8, 16, 32, 64, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512.</li><br />
<li>Значение <b>количества кластеров</b> <math>k</math>: 100.</li><br />
<li>Значение <b>размерности векторов</b> <math>d</math>: 10.</li><br />
</ul><br />
<br />
Для проведения экспериментов были сгенерированы нормально распределенные псевдослучайные данные (с использованием Python библиотеки [http://scikit-learn.org/ scikit-learn]):<br />
<syntaxhighlight lang="python"><br />
from sklearn.datasets import make_classification<br />
X1, Y1 = make_classification(n_features=10, n_redundant=2, n_informative=8,<br />
n_clusters_per_class=1, n_classes=100,<br />
n_samples=2000000)<br />
</syntaxhighlight><br />
<br />
Для заданной конфигурации эксперимента (<math>n, d, p, k</math>) и полученных результатов (<math>t, i</math>) [[Глоссарий#Производительность|производительность]] и эффективность реализации расчитывались по формулам:<br />
<p><br />
<ul><li><math>{\rm Performance} = \frac{N_{\rm k-means}^{d, n}}{t}\ \ ({\rm FLOPS}),</math></li></ul><br />
где <math>N_{\rm k-means}^{d, n}</math> &ndash; точное число операций с плавающей точкой (операции с памятью, а также целочисленные операции не учитывались), вычисленное в соответствие с разделом [[#Последовательная сложность алгоритма| "Последовательная сложность алгоритма"]];<br />
</p><br />
<p><br />
<ul><li><math>{\rm Efficiency} = \frac{100 \cdot {\rm Performance}}{{\rm Performance}_{\rm Peak}^{p}}\ \ (\%),</math></li></ul><br />
где <math>{\rm Performance}_{\rm Peak}^{p}</math> &ndash; пиковая производительность суперкомпьютера при <math>p</math> процессорах, вычисленная согласно спецификациям Intel<sup>&reg;</sup> XEON<sup>&reg;</sup> X5670<ref>"http://ark.intel.com/ru/products/47920/Intel-Xeon-Processor-X5670-12M-Cache-2_93-GHz-6_40-GTs-Intel-QPI"</ref>.<br />
</p><br />
<br />
Графики зависимости производительности и эффективности параллельной реализации k means от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>) представлены на рисунках 4 и 5, соответственно.<br />
<br />
[[file:Flops.png|thumb|center|800px|Рис. 4. Параллельная реализация k means. График зависимости производительности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
[[file:Efficiency.png|thumb|center|800px|Рис. 5. Параллельная реализация k means. График зависимости эффективности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
<br />
<p><br />
<b>В результате</b> экспериментальной оценки были получены следующие оценки эффективности реализации:<br />
<ul><br />
<li><b>Минимальное</b> значение: 0.000409 % достигается при <math>n=20'000, p=480</math></li><br />
<li><b>Максимальное</b> значение: 0.741119 % достигается при <math>n=300'000, p=1</math></li><br />
</ul><br />
</p><br />
<br />
<p><br />
Оценки масштабируемости реализации алгоритма k means:<br />
<ul><br />
<li><b>По числу процессоров:</b> -0.002683 &ndash; эффективность убывает с ростом числа процессоров. Данный результат вызван ростом накладных расходов для обеспечения параллельного выполнения алгоритма.</li><br />
<li><b>По размеру задачи:</b> 0.002779 &ndash; эффективность растет с ростом числа векторов. Данный результат вызван тем, что при увеличении размера задачи, количество вычислений растет по сравнению с временем, затрачиваемым на пересылку данных.</li><br />
<li><b>Общая оценка:</b> -0.000058 &ndash; можно сделать вывод, что в целом эффективность реализации незначительно уменьшается с ростом размера задачи и числа процессоров.</li><br />
</ul><br />
</p><br />
<br />
[https://github.com/serban/kmeans Использованная параллельная реализация алгоритма k means]<br />
<br />
==== Реализация 2 ====<br />
<br />
Исследование также проводилось на суперкомпьютере "Ломоносов".<br />
<br />
<p>Набор данных для тестирования состоял из 946000 векторов размерности 2 (координаты на сфере)</p><br />
<p>Набор и границы значений изменяемых параметров запуска реализации алгоритма:</p><br />
<br />
* число процессов (виртуальных ядер) [8 : 512];<br />
* число кластеров [128 : 384].<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации <math>2,47%</math> достигается при делении исходных данных на 128 кластеров с использованием 512 процессов;<br />
* максимальная эффективность реализации <math>7,13%</math> достигается при делении исходных данных на 352 кластера с использованием 8 процессов.<br />
На рисунках 6 и 7, соответственно, представлены графики зависимости производительности и эффективности параллельной реализации k means от числа кластеров и числа процессов.<br />
<br />
[[file:kmeans_performance.jpg|thumb|center|720px|Рис. 6. График зависимости производительности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
По рис. 6 можно отметить практически полное отсутствие роста производительности с увеличением числа процессов от 256 до 512 при минимальном размере задачи. Это связано с быстрым ростом накладных расходов по отношению к крайне низкому объёму вычислений. При росте размерности задачи данный эффект пропадает, и при одновременном пропорциональном увеличении числа кластеров и числа процессов рост производительности становится близким к линейному.<br />
<br />
[[file:kmeans_efficiency.jpg|thumb|center|720px|Рис. 7. График зависимости эффективности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
Исследовалась [https://github.com/like-a-bauss/kmeans параллельная реализация алгоритма k means на MPI]. <br />
<p>Были получены следующие оценки масштабируемости реализации алгоритма k means:</p><br />
*<i>По числу процессов:</i> <math>-0.02209</math>. Следовательно, с ростом числа процессов эффективность уменьшается. На рис. 7 можно наблюдать плавное и равномерное снижение производительности по мере увеличения числа процессов при неизменном числе кластеров, что свидетельствует об относительно невысоком росте накладных расходов на передачу данных между процессами и преобладании объёма вычислений над объёмом пересылок данных по сети.<br />
*<i>По размеру задачи:</i> <math>0.01252</math>. Следовательно, с ростом размера задачи (числа кластеров) эффективность увеличивается. При этом объём пересылок данных по сети пропорционален <math>(n + k) \cdot p</math> (где <math>k</math> - число кластеров, <math>n</math> - число входных векторов, <math>p</math> - число процессов) таким образом, поскольку <math>k << n</math>, рост накладных расходов с ростом числа кластеров при неизменном числе процессов и входных векторов представляет собой незначительную величину.<br />
*<i>Общая оценка:</i> <math>-0.00081</math>. Таким образом, с ростом и размера задачи, и числа процессов эффективность уменьшается. Это связано с тем, что отношение объёма вычислений к объёму передаваемых данных изменяется пропорционально <math>{kn \over (n + k) \cdot p} \thicksim {k \over p}</math>, что представляет собой невысокий коэффициент, но при этом позволяет параллельной реализации не деградировать до нулевой эффективности при значительном увеличении числа процессов.<br />
<br />
==== Реализация 3 ====<br />
<br />
Исследование масштабируемости алгоритма k-means в зависимости от количества используемых процессов было проведено в статье Кумара<ref>Kumar, J., Mills, R. T., Hoffman, F. M., & Hargrove, W. W. (2011). Parallel k-means clustering for quantitative ecoregion delineation using large data sets. Procedia Computer Science, 4, 1602-1611.</ref>. Исследование происходило на суперкомпьютере Jaguar - Cray XT5<ref>https://www.top500.org/system/176029</ref>. На момент экспериментов данный суперкомпьютер имел следующую конфигурацию: 18,688 вычислительных узлов с двумя шестнадцатиядерными процессорами AMD Opteron 2435 (Istanbul) 2.6 GHz, 16 GB of DDR2-800 оперативной памяти, и SeaStar 2+ роутер. Всего он состоял из 224,256 вычислительных ядер, 300 TB памяти, и пиковой производительностью 2.3 petaflops.<br />
<br />
Реализация алгоритма была выполнена на языке программирования C с использованием MPI.<br />
<br />
Объем данных составлял 84 ГБ, количество объектов (d-мерных векторов) n равнялось 1,024,767,667, размерность векторов <math>d</math> равнялась 22, количество кластеров <math>k</math> равнялось 1000. <br />
<br />
На рис. 8 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров. Можно отметить, что время, затраченное на чтение данных и запись результатов кластеризации, практически не изменяется с увеличением количества задействованных процессоров. Время же работы самого алгоритма кластеризации уменьшается с увеличением количества процессоров.<br />
<br />
[[Файл:k-means-proc-scalability.png|thumb|center|700px|Рис. 8. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (из работы: Kumar etc. 2011).]]<br />
<br />
Также было произведено самостоятельное исследование масштабируемости алгоритма k-means. Исследование производилось на суперкомпьютере "Blue Gene/P"<ref>http://hpc.cmc.msu.ru/bgp</ref>.<br />
<br />
Набор и границы значений изменяемых параметров запуска реализации алгоритма:<br />
<br />
* число процессоров [1, 2, 4, 8, 16, 32, 64, 128, 256, 512];<br />
* количество объектов [5000, 10000, 25000, 50000].<br />
<br />
Был использован набор данных ''Dataset for Sensorless Drive Diagnosis Data Set''<ref>PASCHKE, Fabian ; BAYER, Christian ; BATOR, Martyna ; MÖNKS, Uwe ; DICKS, Alexander ; ENGE-ROSENBLATT, Olaf ; LOHWEG, Volker: Sensorlose Zustandsüberwachung an Synchronmotoren, Bd. 46. In: HOFFMANN, Frank; HÃœLLERMEIER, Eyke (Hrsg.): Proceedings 23. Workshop Computational Intelligence. Karlsruhe : KIT Scientific Publishing, 2013 (Schriftenreihe des Instituts für Angewandte Informatik - Automatisierungstechnik am Karlsruher Institut für Technologie, 46), S. 211-225</ref> из репозитория ''Machine learning repository''<ref>https://archive.ics.uci.edu/ml/datasets/Dataset+for+Sensorless+Drive+Diagnosis</ref>.<br />
<br />
Исследуемый набор данных содержит векторы, размерность которых равна 49. Компоненты векторов являются вещественными числами. Количество кластеров равно 11. Пропущенные значения отсутствуют.<br />
<br />
Для исследования масштабируемости алгоритма была использована реализация на языке C с использованием MPI<ref>http://users.eecs.northwestern.edu/~wkliao/Kmeans/index.html</ref>. Код можно найти здесь: https://github.com/serban/kmeans. Данная реализация предоставляет возможность распараллеливать решение задачи с помощью технологий MPI, OpenMP И CUDA. Для запуска MPI-версии программы использовалась цель "mpi_main" Makefile.<br />
<br />
На рис. 9 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (использовались логарифмические оси). Разными цветами помечены запуски, соответствующие разным количествам объектам, участвующих в кластеризации. Можно видеть близкое к линейному увеличение времени работы программы в зависимости от количества процессоров. Также можно видеть увеличение времени работы алгоритма при увеличении количества объектов.<br />
[[Файл:Plot_1.png|thumb|center|900px|Рис. 9. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
На рис. 10 показана эта же зависимость, только в трехмерном пространстве. По аналогии с рис. 9, были использованы логарифмические оси. Как и в случае двумерного рисунка, можно видеть близкое к линейному увеличение времени работы программы.<br />
[[Файл:Kmeans-3d.png|thumb|center|900px|Рис. 10. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
==== Реализация 4 ====<br />
<br />
Исследование масштабируемости данной параллельной реализации алгоритма k-средних также проводилось на суперкомпьютере "Ломоносов". Параллельная реализация была написана самостоятельно на языке C, [http://git.algowiki-project.org/iaegorov/Egorov-Bogomazov-k-means/tree/master ссылка на реализацию]. Так как на каждой итерации число действий на единицу данных не велико и данные должны быть собраны вместе при перерасчете центроидов, было решено для ускорения вычислений воспользоваться только OpenMP без использовании MPI.<br />
<br />
Код собирался под gcc c опцией -fopenmp. Код считался на одном процессоре, технология hyperthreading не использовалась.<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 16] с увеличением в 2 раза;<br />
* размер данных [100000 : 1600000] с увеличением в 2 раза.<br />
<br />
В результате проведённых экспериментов были получены следующие данные:<br />
<br />
* Максимальная эффективность в точке достигается при переходе от 1 потока на 4 при минимальном размере данных, она равна <math>87,5%</math>.<br />
* Усредненная максимальная эффективность достигается при переходе с одного потока на два. Среднее время вычислений на всех рассмотренных потока снижается с 16,33 до 11.87 секунд, поэтому формально эффективность <math>= 16.33 / 11.87 / 2 \approx 68,4\%</math><br />
* Минимальная эффективность в точке достигается при переходе от 1 потока на 16 при размере данных 800000, она равна <math>11,1\%</math>.<br />
* Усредненная минимальная эффективность наблюдается при переходе с одного на максимальное рассматриваемое в эксперименте число потоков, равное 16. Время вычисления изменяется с 16,33 до 7,6 секунд, поэтому формально эффективность <math> = 16.33 / 7.6 / 16 \approx 14,9\%</math><br />
<br />
Ниже приведены графики зависимости вычислительного времени алгоритма и его эффективности от изменяемых параметров запуска — размера данных и числа процессоров:<br />
<br />
[[file:Egorov_Bogomazov_Time.jpg|thumb|center|700px|Рис. 11. Параллельная реализация алгоритма k-средних. Изменение вычислительного времени алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь видно, что время выполнения операций алгоритма плавно убывает по каждому из параметров, причем скорость убывания по параметру числа процессоров выше, чем в зависимости от размерности задачи.<br />
<br />
[[file:Egorov_Bogomazov_Efficiency.jpg|thumb|center|700px|Рис. 12. Параллельная реализация алгоритма k-средних. Изменение эффективности алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь построена эффективность перехода от последовательной реализации к параллельной. Рассчитывается она по формуле ''Время вычисления на 1 потоке / Время вычисления на <math>T</math> потоках / <math>T</math>'', где <math>T</math> — это число потоков. При вычислении на 1 процессоре она равна 100 \% в силу используемой формулы, что и отражено на графике.<br />
<br />
Проведем оценки масштабируемости:<br />
<br />
'''По числу процессов''' — при увеличении числа процессов эффективность уменьшается на всей области рассматриваемых значений, причем темп убывания замедляется с ростом числа процессов.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений вначале кратковременно возрастает, но затем начинает относительно равномерно убывать на всей рассматриваемой области.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений в общем случае постепенно убывает. На малых данных она выходит на пик мощности, являющийся максимумом эффективности в исследуемых условиях, но затем возвращается к процессу убывания.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
В однопоточном режиме на наборах данных, представляющих практический интерес (порядка нескольких десятков тысяч векторов и выше), время работы алгоритма неприемлемо велико. Благодаря свойству массового параллелизма должно наблюдаться значительное ускорение алгоритма на многоядерных архитектурах (Intel Xeon), а также на графических процессорах, даже на мобильных вычислительных системах (ноутбуках), оснащенных видеокартой. Также алгоритм k means будет демонстрировать значительное ускорение на сверхмощных вычислительных комплексах (суперкомпьютерах, системах облачных вычислений<ref>"Issa. "Performance characterization and analysis for Hadoop K-means iteration". Journal of Cloud Computing, 2016"</ref>).<br />
<br />
На сегодняшний день существует множество реализаций алгоритма k means, в частности, направленных на оптимизацию параллельной работы на различных архитектурах<ref>"Raghavan R. A fast and scalable hardware architecture for K-means clustering for big data analysis : дис. – University of Colorado Colorado Springs. Kraemer Family Library, 2016."</ref><ref>"Yang, Luobin, et al. "High performance data clustering: a comparative analysis of performance for GPU, RASC, MPI, and OpenMP implementations." The Journal of supercomputing 70.1 (2014): 284-300."</ref><ref>"Li, You, et al. "Speeding up k-means algorithm by GPUs." Computer and Information Technology (CIT), 2010 IEEE 10th International Conference on. IEEE, 2010."</ref>. Предлагается множество адаптаций алгоритма под конкретные архитектуры. Например, авторы работы<ref>"Kanan, Gebali, Ibrahim. "Fast and Area-Efficient Hardware Implementation of the K-means<br />
Clustering Algorithm". WSEAS Transactions on circuits and systems. Vol. 15. 2016"</ref> производят перерасчет центров кластеров на этапе распределения векторов по кластерам.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
==== Открытое программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.icpsr.umich.edu/CrimeStat/ CrimeStat]</li> Программное обеспечение, созданное для операционных систем Windows, предоставляющее инструменты статистического и пространственного анализа для решения задачи картирования преступности.<br />
<li>[http://juliastats.github.io Julia]</li> Высокоуровневый высокопроизводительный свободный язык программирования с динамической типизацией, созданный для математических вычислений, содержит реализацию k-means.<br />
<li>[https://mahout.apache.org Mahout]</li> Apache Mahout - Java библиотека для работы с алгоритмами машинного обучения с использованием MapReduce. Содержит реализацию k-means.<br />
<li>[https://www.gnu.org/software/octave/ Octave]</li> Написанная на C++ свободная система для математических вычислений, использующая совместимый с MATLAB язык высокого уровня, содержит реализацию k-means.<br />
<li>[http://spark.apache.org/docs/latest/mllib-clustering.html Spark]</li> Распределенная реализация k-means содержится в библиотеке Mlib для работы с алгоритмами машинного обучения, взаимодействующая с Python библиотекой NumPy и библиотека R.<br />
<li>[http://torch.ch Torch]</li> MATLAB-подобная библиотека для языка программирования Lua с открытым исходным кодом, предоставляет большое количество алгоритмов для глубинного обучения и научных расчётов. Ядро написано на Си, прикладная часть выполняется на LuaJIT, поддерживается распараллеливание вычислений средствами CUDA и OpenMP. Существуют реализации k-means.<br />
<li>[http://www.cs.waikato.ac.nz/ml/weka/ Weka]</li> Cвободное программное обеспечение для анализа данных, написанное на Java. Содержит k-means и x-means.<br />
<li>[http://accord-framework.net/docs/html/T_Accord_MachineLearning_KMeans.htm Accord.NET]</li> C# реализация алгоритмов k-means, k-means++, k-modes.<br />
<li>[http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html OpenCV]</li> Написанная на С++ библиотека, направленная в основном на решение задач компьютерного зрения. Содержит реализацию k-means.<br />
<li>[http://mlpack.org/ MLPACK]</li> Масштабируемая С++ библиотека для работы с алгоритмами машинного обучения, содержит реализацию k-means.<br />
<li>[https://www.scipy.org/ SciPy]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[http://scikit-learn.org/ scikit-learn]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[https://www.r-bloggers.com/k-means-clustering-in-r/ R]</li> Язык программирования для статистической обработки данных и работы с графикой, а также свободная программная среда вычислений с открытым исходным кодом в рамках проекта GNU, содержит три реализации k-means.<br />
<li>[http://elki.dbs.ifi.lmu.de ELKI]</li> Java фреймворк, содержащий реализацию k-means, а также множество других алгоритмов кластеризации.<br />
</ol><br />
<br />
==== Проприетарное программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.ayasdi.com/blog/bigdata/topological-data-analysis-of-oil-and-gas-petrophysical-data/ Ayasdi]</li><br />
<li>[http://www.stata.com/features/cluster-analysis/ Stata]</li><br />
<li>[http://mathworld.wolfram.com/K-MeansClusteringAlgorithm.html Mathematica]</li><br />
<li>[http://www.mathworks.com/help/stats/kmeans.html?requestedDomain=www.mathworks.com MATLAB]</li><br />
<li>[https://support.sas.com/rnd/app/stat/procedures/fastclus.html SAS]</li><br />
<li>[http://docs.rapidminer.com/studio/operators/modeling/segmentation/k_means.html RapidMiner]</li><br />
<li>[https://blogs.sap.com/2013/03/28/sap-hana-pal-k-means-algorithm-or-how-to-do-customer-segmentation-for-the-telecommunications-industry/ SAP HANA]</li><br />
</ol><br />
<br />
== Литература ==<br />
<br />
<references /></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_k_%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D1%85_(k-means)&diff=25245Алгоритм k средних (k-means)2018-01-16T17:12:25Z<p>Konshin: </p>
<hr />
<div>Основные авторы статьи (разделы 1, 2.4.1, 2.6-2.7, 3):<br />
[https://algowiki-project.org/ru/Участник:Бротиковская_Данута<b>Д.Бротиковская</b>] и<br />
[https://algowiki-project.org/ru/Участник:DennZo1993<b>Д.Зобнин</b>]<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:IanaV<b>Я.А.Валуйская</b>] и<br />
[https://algowiki-project.org/ru/Участник:GlotovES<b>Е.С.Глотов</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Parkhomenko<b>П.А.Пархоменко</b>] и<br />
[https://algowiki-project.org/ru/Участник:Ivan.mashonskiy<b>И.Д.Машонский</b>] (раздел 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:Илья_Егоров<b>И.Егоров</b>] и<br />
[https://algowiki-project.org/ru/Участник:Богомазов_Евгений<b>Е.Богомазов</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Алгоритм <math>k</math> средних (<math>k</math>-means)<br />
| serial_complexity = <math>O(ikdn)</math><br />
| input_data = <math> dn </math><br />
| output_data = <math> n </math><br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Алгоритм <b><i>k средних</i></b> (англ. k-means) - один из алгоритмов машинного обучения, решающий задачу кластеризации.<br />
Этот алгоритм является неиерархическим<ref>"https://ru.wikipedia.org/wiki/Иерархическая_кластеризация"</ref>, итерационным методом кластеризации<ref>"https://ru.wikipedia.org/wiki/Кластерный_анализ"</ref>, он получил большую популярность благодаря своей простоте, наглядности реализации и достаточно высокому качеству работы. <br />
Был изобретен в 1950-х годах математиком <i>Гуго Штейнгаузом</i><ref>Steinhaus, Hugo. "Sur la division des corp materiels en parties." Bull. Acad. Polon. Sci 1.804 (1956): 801.</ref> и почти одновременно <i>Стюартом Ллойдом</i><ref>Lloyd, S. P. "Least square quantization in PCM. Bell Telephone Laboratories Paper. Published in journal much later: Lloyd, SP: Least squares quantization in PCM." IEEE Trans. Inform. Theor.(1957/1982).</ref>. Особую популярность приобрел после публикации работы <i>МакКуина</i><ref>MacQueen, James. "Some methods for classification and analysis of multivariate observations." Proceedings of the fifth Berkeley symposium on mathematical statistics and probability. Vol. 1. No. 14. 1967.</ref> в 1967.<br />
<br />
Алгоритм представляет собой версию EM-алгоритма<ref>"https://ru.wikipedia.org/wiki/EM-алгоритм"</ref>, применяемого также для разделения смеси гауссиан. Основная идея <i>k means</i> заключается в том, что данные произвольно разбиваются на кластеры, после чего итеративно перевычисляется центр масс для каждого кластера, полученного на предыдущем шаге, затем векторы разбиваются на кластеры вновь в соответствии с тем, какой из новых центров оказался ближе по выбранной метрике.<br />
<br />
Цель алгоритма заключается в разделении <math>n</math> наблюдений на <math>k</math> кластеров таким образом, чтобы каждое наблюдение принадлежало ровно одному кластеру, расположенному на наименьшем расстоянии от наблюдения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
<b>Дано:</b><br />
* набор из <math>n</math> наблюдений <math>X=\{\mathbf{x}_1, \mathbf{x}_2, ..., \mathbf{x}_n\}, \mathbf{x}_i \in \mathbb{R}^d, \ i=1,...,n</math>;<br />
* <math>k</math> - требуемое число кластеров, <math>k \in \mathbb{N}, k \leq n</math>.<br />
<br />
<b>Требуется:</b><br />
<br />
Разделить множество наблюдений <math>X</math> на <math>k</math> кластеров <math>S_1, S_2, ..., S_k</math>:<br />
* <math>S_i \cap S_j= \varnothing, \quad i \ne j</math><br />
<br />
* <math>\bigcup_{i=1}^{k} S_i = X</math><br />
<br />
<b>Действие алгоритма:</b><br />
<p>Алгоритм <i>k means</i> разбивает набор <math>X</math> на <math>k</math> наборов <math>S_1, S_2, ..., S_k,</math> таким образом, чтобы минимизировать сумму квадратов расстояний от каждой точки кластера до его центра (центр масс кластера). Введем обозначение, <math>S=\{S_1, S_2, ..., S_k\}</math>. Тогда действие алгоритма <i>k means</i> равносильно поиску:</p><br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math>\arg\min_{S} \sum\limits_{i=1}^k \sum\limits_{\mathbf{x} \in S_i} \rho(\mathbf{x}, \mathbf{\mu}_i )^2,</math></td><br />
<td align="right"><math>(1)</math></td><br />
</tr><br />
</table><br />
где <math>\mathbf{\mu}_i</math> &ndash; центры кластеров, <math>i=1,...,k, \quad \rho(\mathbf{x}, \mathbf{\mu}_i)</math> &ndash; функция расстояния между <math>\mathbf{x}</math> и <math>\mu_i</math><br />
<br />
<b>Шаги алгоритма:</b><br />
<ol><br />
<li><br />
<b>Начальный шаг: инициализация кластеров</b><br />
<p>Выбирается произвольное множество точек <math>\mu_i, \ i=1,...,k,</math> рассматриваемых как начальные центры кластеров: <math>\mu_i^{(0)} = \mu_i, \quad i=1,...,k</math></p><br />
</li><br />
<li><br />
<b>Распределение векторов по кластерам</b><br />
<p><b>Шаг</b> <math>t: \forall \mathbf{x}_i \in X, \ i=1,...,n: \mathbf{x}_i \in S_j \iff j=\arg\min_{k}\rho(\mathbf{x}_i,\mathbf{\mu}_k^{(t-1)})^2</math></p><br />
</li><br />
<li><br />
<b>Пересчет центров кластеров</b><br />
<p><b>Шаг </b> <math>t: \forall i=1,...,k: \mu_i^{(t)} = \cfrac{1}{|S_i|}\sum_{\mathbf{x}\in S_i}\mathbf{x}</math></p><br />
</li><br />
<li><br />
<b>Проверка условия останова:</b><br />
<p></p><br />
'''if''' <math>\exist i\in \overline{1,k}: \mu_i^{(t)} \ne \mu_i^{(t-1)}</math> '''then'''<br />
t = t + 1;<br />
goto 2;<br />
'''else'''<br />
'''stop'''<br />
</li><br />
</ol><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром являются шаги 2 и 3 приведенного выше алгоритма: <b><i>распределение векторов по кластерам</i></b> и <b><i>пересчет центров кластеров</i></b>. <br />
<br />
<p><br />
<b><i>Распределение векторов</i></b> по кластерам предполагает вычисление расстояний между каждым вектором <math>\mathbf{x}_i \in X, \ i= 1,...,n</math> и центрами кластера <math>\mathbf{\mu}_j, \ j= 1,...,k</math>. Таким образом, данный шаг предполагает <math>kn</math> вычислений расстояний между <math>d</math>-мерными векторами. <br />
</p><br />
<p><br />
<b><i>Пересчет центров кластеров</i></b> предполагает <math>k</math> вычислений центров масс <math>\mathbf{\mu}_i</math> множеств <math>S_i, \ i=1,...,k,</math> представленных выражением в шаге 3 представленного выше алгоритма.<br />
</p><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
<b>Инициализация центров масс <math>\mu_1, ..., \mu_k</math></b>. <br />
<br />
Наиболее распространенными являются следующие стратегии:<br />
<ul><br />
<li><br />
<b>Метод Forgy</b><br>В качестве начальных значений <math>\mu_1, ..., \mu_k</math> берутся случайно выбранные векторы.<br />
</li><br />
<li><br />
<b>Метод случайно разделения (Random Partitioning)</b><br>Для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> выбирается случайным образом кластер <math>S_1, ..., S_k</math>, после чего для каждого полученного кластера вычисляются значения <math>\mu_1, ..., \mu_k</math>.<br />
</li><br />
</ul><br />
<br />
<b>Распределение векторов по кластерам</b><br />
<br />
Для этого шага алгоритма между векторами <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> и центрами кластеров <math>\mu_1,...,\mu_k</math> вычисляются <b>расстояния</b> по формуле (как правило, используется Евлидово расстояние):<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mathbf{v}_1, \mathbf{v}_2 \in \mathbb{R}^d, \quad \rho(\mathbf{v}_1, \mathbf{v}_2) = \lVert \mathbf{v}_1- \mathbf{v}_2 \rVert= \sqrt{\sum_{i=1}^{d}(\mathbf{v}_{1,i} - \mathbf{v}_{2,i})^2}</math></td><br />
<td align="right"><math>(2)</math></td><br />
</tr><br />
</table><br />
<br />
<b>Пересчет центров кластеров</b><br />
<br />
Для этого шага алгоритма производится пересчет центров кластера по <b>формуле вычисления центра масс</b>:<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mu = \cfrac{1}{|S|}\sum_{\mathbf{x}\in S}\mathbf{x}</math></td><br />
<td align="right"><math>(3)</math></td><br />
</tr><br />
</table><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
'''1.''' Инициализировать центры кластеров <math>\mathbf{\mu}_i^{(1)}, \ i=1,...,k</math><br><br />
'''2.''' <math>t \leftarrow 1</math><br><br />
'''3.''' Распределение по кластерам<br><br />
<math>\quad S_i^{(t)}=\{\mathbf{x}_p: \lVert\mathbf{x}_p-\mathbf{\mu}_i^{(t)}\rVert^2 \leq \lVert\mathbf{x}_p-\mathbf{\mu}_j^{(t)}\rVert^2 \quad \forall j=1,...,k\},</math><br><br />
<math>\quad</math>где каждый вектор <math>\mathbf{x}_p</math> соотносится единственному кластеру <math>S^{(t)}</math><br><br />
'''4.''' Обновление центров кластеров<br><br />
<math>\quad \mathbf{\mu}_i^{(t+1)} = \frac{1}{|S^{(t)}_i|} \sum_{\mathbf{x}_j \in S^{(t)}_i} \mathbf{x}_j </math><br><br />
'''5.''' '''if''' <math>\exists i \in \overline{1,k}: \mathbf{\mu}_i^{(t+1)} \ne \mathbf{\mu}_i^{(t)}</math> '''then'''<br><br />
<math>\quad t = t + 1</math>;<br><br />
<math>\quad</math>goto '''3''';<br><br />
<math>~~~</math>'''else'''<br><br />
<math>\quad</math>'''stop'''<br><br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
<div style="padding-bottom: 20px"><br />
<p>Обозначим <math>\Theta_{\rm centroid}^{d, m}</math> временную сложность вычисления центорида кластера, число элементов которого равна <math>m</math>, в d-мерном пространстве.</p><br />
<p>Аналогично <math>\Theta_{\rm distance}^d</math> &ndash; временная сложность вычисления расстояния между двумя d-мерными векторами.</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<b>Сложность шага инициализации <math>k</math> кластеров мощности <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm init}^{k, d, m}</math></b><br />
<ul><br />
<li><i>Стратерия Forgy</i>: вычисления не требуются, <math>\Theta_{\rm init}^{k, d, m} = 0</math></li><br />
<li><i>Стратегия случайного разбиения</i>: вычисление центров <math>k</math> кластеров, <math>\Theta_{\rm init}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}, m \le n</math></li><br />
</ul><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Cложность шага распределения d мерных векторов по <math>k</math> кластерам &ndash; <math>\Theta_{\rm distribute}^{k, d}</math></b></p><br />
<p><br />
На этом шаге для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> вычисляется <math>k</math> расстояний до центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math><br />
</p><br />
<p align="center"><br />
<math>\Theta_{\rm distribute}^{k, d} = n \cdot k \cdot \Theta_{\rm distance}^d</math><br />
</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><br />
<b>Сложность шага пересчета центров <math>k</math> кластеров размера <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm recenter}^{k, d, m}</math></b><br />
</p><br />
<p>На этом шаге вычисляется <math>k</math> центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math></p><br />
<p align="center"><math>\Theta_{\rm recenter}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}</math> </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm centroid}^{d, m}</math> для кластера, число элементов которого равно <math>m</math></b></p><br />
<p align="center"><math>\Theta_{\rm centroid}^{d, m}</math> = <math>m \cdot d</math> сложений + <math>d</math> делений </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm distance}^d</math> в соответствие с формулой <math>(2)</math></b></p><br />
<p align="center"><math>\Theta_{\rm distance}^d</math> = <math>d</math> вычитаний + <math>d</math> умножений + <math>(d-1)</math> сложение</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Предположим, что алгоритм сошелся за <math>i</math> итераций, тогда временная сложность алгоритма <math>\Theta_{\rm k-means}^{d, n}</math></p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le \Theta_{\rm init}^{k, d, n} + i(\Theta_{\rm distribute}^{k, d} + \Theta_{\rm recenter}^{k, d, n})</math></b></p><br />
</div><br />
<div style="padding-bottom: 5px"><br />
<p>Операции сложения/вычитания:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le knd+ i(kn(2d-1) + knd) = knd+ i(kn(3d-1)) \thicksim O(ikdn)</math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Операции умножения/деления:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le kd + i(knd + kd) = kd + ikd(n+1) \thicksim O(ikdn) </math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Получаем, что <b>временная сложность</b> алгоритма <b>k means</b> кластеризации <math>n</math> <b>d-мерных</b> векторов на <math>k</math> кластеров за <math>i</math> итераций:</p><br />
<p align="center"><b><math> \Theta_{\rm k-means}^{d, n} \thicksim O(ikdn) </math></b></p><br />
</div><br />
<br />
=== Информационный граф ===<br />
<br />
Рассмотрим информационный граф алгоритма. Алгоритм <i>k means</i> начинается с этапа инициализации, после которого следуют итерации, на каждой из которых выполняется два последовательных шага (см. [[#Схема реализации последовательного алгоритма|"Схема реализации последовательного алгоритма"]]): <br />
* распределение векторов по кластерам<br />
* перерасчет центров кластеров<br />
<br />
Поскольку основная часть вычислений приходится на шаги итераций, распишем информационные графы данных шагов.<br />
<br />
<p><b>Распределение векторов по кластерам</b></p><br />
Информационный граф шага распределения векторов по кластерам представлен на ''рисунке 1''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также центры кластеров <math>\mathbf{\mu}_1, ... \mathbf{\mu}_k</math>, вычисленные ранее (на шаге инициализации, если рассматривается первая итерация алгоритма, или на шаге пересчета центров кластеров предыдущей итерации в противном случае). Каждая пара векторов данных <math>\mathbf{x}_i, \ i=1,...,n,</math> и центров кластера <math>\mathbf{\mu}_j, \ j=1,...,k</math> : (<math>\mathbf{x}_i</math>, <math>\mathbf{\mu}_j</math>) подаются на независимые узлы <i>"d"</i> вычисления расстояния между векторами (более подробная схема вычисления расстояния представлена далее, ''рисунок 2''). Далее узлы вычисления расстояния <i>"d"</i>, соответствующие одному и тому же исходному вектору <math>\mathbf{x}_i</math> передаются на один узел <i>"m"</i>, где далее происходит вычисление новой метки кластера для каждого вектора <math>\mathbf{x}_i</math> (берется кластер с минимальным результатом вычисления расстояния). На выходе графа выдаются метки кластеров , <math>L_1, ..., L_n</math>, такие что <math>\forall \mathbf{x}_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>. <br />
<br />
[[file:Clusterization.png|thumb|center|800px|Рис. 1. Схема распределения векторов по кластерам. ''d'' &ndash; вычисление расстояния между векторами; ''m'' &ndash; вычисление минимума.]]<br />
<br />
<p><b>Вычисление расстояния между векторами</b></p><br />
Подробная схема вычисления расстояния между векторами <math>\mathbf{x}_i, \mathbf{\mu}_j</math> представлена на ''рисунке 2''. Как показано на графе, узел вычисления расстояния между векторами <i>"d"</i> состоит из шага взятия разности между векторами (узел "<math>-</math>") и взятия нормы получившегося вектора разности (узел "<math>||\cdot||^2</math>"). Более подробно, вычисление расстояния между векторами <math>\mathbf{x}_i = {x_{i1,}, ...,{x_{in}}}, \mathbf{\mu}_j = {\mu_{j1}, ...,\mu_{jn}}</math> может быть представлено как вычисление разности между каждой парой компонент <math>(x_{iz}, \mu_{jz}), \ z=1,...,d</math> (узел "<math>-</math>"), далее возведение в квадрат для каждого узла "<math>-</math>" (узел "<math>()^2</math>") и суммирования выходов всех узлов "<math>()^2</math>" (узел "<math>+</math>"). <br />
<br />
[[file:dist_calc.png|thumb|center|800px|Рис. 2. Схема вычисления расстояния между вектором и центром кластера.]]<br />
<br />
<p><b>Пересчет центров кластеров</b></p><br />
Информационный граф шага пересчета центров кластеров представлен на ''рисунке 3''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также им соответствующие метки кластера, <math>L_1, ..., L_n</math>, такие что <math>\forall x_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>, вычисленные на этапе распределения векторов по кластерам. Все векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math> подаются в узлы <math>+_1, ...+_k</math>, каждый узел <math>+_m, \ m = 1,...,k,</math> соответствует операции сложения векторов кластера с номером <math>m</math>. Метки кластера <math>L_1, ..., L_n</math> также совместно передаются на узлы <math>S_m, \ m=1,...,k</math>, на каждом из которых вычисляется количество векторов в соответствующем кластере (количество меток с соответствующим значением). Далее каждая пара выходов узлов <math>+_m</math> и <math>S_m</math> подается на узел "<math>/</math>", где производится деление суммы векторов кластера на количество элементов в нем. Значения, вычисленные на узлах "<math>/</math>", присваиваются новым центрам кластеров (выходные значения графа).<br />
<br />
[[file:Recluster.png|thumb|center|800px|Рис. 3. Схема пересчета центров кластеров]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
<p><br />
Работа алгоритма состоит из <math>i</math> итераций, в каждой из которых происходит <b>распределение d-мерных векторов по <math>k</math> кластерам</b>, а также <b>пересчет центров кластеров в d-мерном пространстве</b>. В шаге <b>распределения d-мерных векторов по <math>k</math> кластерам</b> расстояния между вектором и центрами кластеров вычисляются независимо (отсутствуют информационные зависимости). <b>Центры масс кластеров</b> также пересчитываются независимо друг от друга. Таким образом, имеет место [[глоссарий#Массовый параллелизм|''массовый параллелизм'']]. Вычислим параллельную сложность <math>\Psi_*</math> каждого из шагов, а также параллельную сложность всего алгоритма, <math>\Psi_{\rm k-means}</math>. Будем исходить из предположения, что может быть использовано любое необходимое число потоков.<br />
</p><br />
<br />
<p><b>Распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b></p> <br />
<p><br />
Поскольку на данном шаге для каждой пары векторов <math>\mathbf{x}_i, \ i=1,...,n</math> и <math>\mathbf{\mu}_j, \ j=1,...,k,</math> операции вычисления расстояния не зависят друг от друга, они могут выполняться параллельно. Тогда, разделив все вычисление расстояний на <math>n</math> потоков, получим, что в каждом потоке будет выполняться только одна операция вычисления расстояния между векторами размерности d. При этом каждому вычислительному потоку передаются координаты центров всех кластеров <math>\mathbf{\mu}_1, ..., \mathbf{\mu}_k</math>. Таким образом, параллельная сложность данного шага определяется <i> сложностью параллельной операции вычисления расстояния между d-мерными векторами</i>, <math>\Psi_{\rm distance}^d</math> и <i>сложностью определения наиболее близкого кластера</i> (паралельное взятие минимума по расстояниям), <math>\Psi_{\rm min}^k</math>. Для оценки <math>\Psi_{\rm distance}^d</math> воспользуемся [[Нахождение_частных_сумм_элементов_массива_сдваиванием | параллельной реализацией нахождения частичной суммы элементов массива путем сдваивания]]. Аналогично, <math>\Psi_{\rm min}^k = log(k)</math>. В результате, <math>\Psi_{\rm distance}^d = O(log(d))</math>. Таким образом: <br />
</p><br />
<p align="center"><math>\Psi_{\rm distribute}^{k, d} = \Psi_{\rm distance}^d + \Psi_{\rm min}^k = O(log(d))+O(log(k)) = O(log(kd))</math></p><br />
<br />
<p><b>Пересчет центров кластеров в d-мерном пространстве</b></p><br />
<p><br />
Поскольку на данном шаге для каждого из <math>k</math> кластеров центр масс может быть вычислен независимо, данные операции могут быть выполнены в отдельных потоках. Таким образом, параллельная сложность данного шага, <math>\Psi_{\rm recenter}^{k, d}</math>, будет определяться <i>параллельной сложностью вычисления одного центра масс кластера размера <math>m</math></i>, <math>\Psi_{\rm recenter}^{k, d}</math>, а так как <math>m \le n \Rightarrow \Psi_{\rm recenter}^{d, m} \le \Psi_{\rm recenter}^{d, n}</math>. Сложность вычисления центра масс кластера d-мерных векторов размера n аналогично предыдущим вычислениям равна <math>O(log(n))</math>. Тогда: <br />
</p><br />
<p align="center"><math>\Psi_{\rm recenter}^{k, d} \le \Psi_{\rm recenter}^{d, n} = O(log(n))</math></p><br />
<br />
<p><b>Общая параллельная сложность алгоритма</b></p><br />
<p><br />
На каждой итерации необходимо обновление центров кластеров, которые будут использованы на следующей итерации. Таким образом, итерационный процесс выполняется последовательно<ref>Zhao, Weizhong, Huifang Ma, and Qing He. "Parallel k-means clustering based on mapreduce." IEEE International Conference on Cloud Computing. Springer Berlin Heidelberg, 2009.</ref>. Тогда, поскольку сложность каждой итерации определяется <math>\Psi_{\rm distribute}^{k, d}</math> и <math>\Psi_{\rm recenter}</math>, сложность всего алгоритма, <math>\Psi_{\rm k-means}</math> в предположении, что было сделано <math>i</math> операций определяется выражением<br />
</p><br />
<p align="center"><math>\Psi_{\rm k-means} \approx i \cdot (\Psi_{\rm distribute}^{k, d} + \Psi_{\rm recenter}^{k, d}) \le i \cdot O(log(kdn))</math></p><br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
<p><br />
<b>Входные данные</b><br><br />
<ul><br />
<li>Матрица из <math>n \cdot d</math> элементов <math>x_{i, j} \in \mathbb{R}, \ i=1,...,n, \ j=1,...,d,</math> &ndash; координат векторов (наблюдений).</li><br />
<li>Целое положительное число <math>k, \ k \le n</math> &ndash; количество кластеров.</li><br />
</ul><br />
</p><br />
<p><br />
<b>Объем входных данных</b><br><br />
<math>1</math> целое число + <math>n \cdot d</math> вещественных чисел (при условии, что координаты &ndash; вещественные числа).<br />
</p><br />
<p><br />
<b>Выходные данные</b><br><br />
<math>n</math> целых положительных чисел <math>L_1, ..., L_n</math>&ndash; номера кластеров, соотвествующие каждому вектору (при условии, что нумерация кластеров начинается с <math>1</math>).<br />
</p><br />
<p><br />
<b>Объем выходных данных</b><br><br />
<math>n</math> целых положительных чисел.<br />
</p><br />
<br />
=== Свойства алгоритма ===<br />
<br />
<p><b>[[глоссарий#Вычислительная мощность|''Вычислительная мощность'']]</b></p><br />
Вычислительная мощность алгоритма <i>k means</i> равна <math>\frac{ikdn}{nd} = ki </math>, где <math>k</math> &ndash; число кластеров, <math>i</math> &ndash; число итераций алгоритма.<br />
<br />
<p><b>[[глоссарий#Детерминированность |''Детерминированность'']] и [[глоссарий#Устойчивость |''Устойчивость'']] </b></p><br />
Алгоритм <i>k means</i> является итерационным. Количество итераций алгоритма в общем случае не фиксируется и зависит от начального расположения объектов в пространстве, параметра <math>k</math>, а также от начального приближения центров кластеров, <math>\mu_1, ..., \mu_k</math>. В результате этого может варьироваться результат работы алгоритма. При неудачном выборе начальных параметров итерационный процесс может сойтись к локальному оптимуму<ref>Von Luxburg, Ulrike. Clustering Stability. Now Publishers Inc, 2010.</ref>. По этим причинам алгоритм не является ни <b>детермирированным</b>, ни <b>устойчивым</b>.<br />
<br />
<p><b>Соотношение последовательной и параллельной сложности алгоритма</b></p><br />
<br />
<math>\frac{\Theta_{\rm k-means}}{\Psi_{\rm k-means}} = \frac{O(ikdn)}{O(i \cdot log(kdn))}</math><br />
<br />
<b>Сильные стороны алгоритма</b>:<br />
<ul><br />
<li><i>Сравнительно высокая эффективность при простоте реализации</i></li><br />
<li><i>Высокое качество кластеризации</i></li><br />
<li><i>Возможность распараллеливания</i></li><br />
<li><i>Существование множества модификаций</i></li><br />
</ul><br />
<b>Недостатки алгоритма</b><ref>Ortega, Joaquín Pérez, Ma Del Rocío Boone Rojas, and María J. Somodevilla. "Research issues on K-means Algorithm: An Experimental Trial Using Matlab."</ref>:<br />
<ul><br />
<li><i>Количество кластеров является параметром алгоритма</i></li><br />
<li><br />
<p><i>Чувствительность к начальным условиям</i></p><br />
<p>Инициализация центров кластеров в значительной степени влияет на результат кластеризации.</p><br />
</li><br />
<li><br />
<p><i>Чувствительность к выбросам и шумам</i></p><br />
<p>Выбросы, далекие от центров настоящих кластеров, все равно учитываются при вычислении их центров.</p><br />
</li><br />
<li><br />
<p><i>Возможность сходимости к локальному оптимуму</i></p><br />
<p>Итеративный подход не дает гарантии сходимости к оптимальному решению.</p><br />
</li><br />
<li><br />
<p><i>Использование понятия "среднего"</i></p><br />
<p>Алгоритм неприменим к данным, для которых не определено понятие "среднего", например, категориальным данным.</p><br />
</li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализации алгоритма <math>k</math> средних согласно [[Scalability methodology|методике]] AlgoWiki.<br />
<br />
==== Реализация 1 ====<br />
<br />
Исследование масштабируемости параллельной реализации алгоритма k means проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Алгоритм реализован на языке C с использованием средств MPI.<br />
Для исследования масштабируемости проводилось множество запусков программы с разным значением параметра (количество векторов для кластеризации), а также с различным числом процессоров. Фиксировались результаты запусков &ndash; время работы <math>t</math> и количество произведенных итераций алгоритма <math>i</math>.<br />
<br />
Параметры запусков для экспериментальной оценки:<br />
<ul><br />
<li>Значения <b>количества векторов</b> <math>n</math>: 20'000, 30'000, 50'000, 100'000, 200'000, 300'000, 500'000, 700'000, 1'000'000, 1'500'000, 2'000'000.</li><br />
<li>Значения <b>количества процессоров</b> <math>p</math>: 1, 8, 16, 32, 64, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512.</li><br />
<li>Значение <b>количества кластеров</b> <math>k</math>: 100.</li><br />
<li>Значение <b>размерности векторов</b> <math>d</math>: 10.</li><br />
</ul><br />
<br />
Для проведения экспериментов были сгенерированы нормально распределенные псевдослучайные данные (с использованием Python библиотеки [http://scikit-learn.org/ scikit-learn]):<br />
<syntaxhighlight lang="python"><br />
from sklearn.datasets import make_classification<br />
X1, Y1 = make_classification(n_features=10, n_redundant=2, n_informative=8,<br />
n_clusters_per_class=1, n_classes=100,<br />
n_samples=2000000)<br />
</syntaxhighlight><br />
<br />
Для заданной конфигурации эксперимента (<math>n, d, p, k</math>) и полученных результатов (<math>t, i</math>) [[Глоссарий#Производительность|производительность]] и эффективность реализации расчитывались по формулам:<br />
<p><br />
<ul><li><math>{\rm Performance} = \frac{N_{\rm k-means}^{d, n}}{t}\ \ ({\rm FLOPS}),</math></li></ul><br />
где <math>N_{\rm k-means}^{d, n}</math> &ndash; точное число операций с плавающей точкой (операции с памятью, а также целочисленные операции не учитывались), вычисленное в соответствие с разделом [[#Последовательная сложность алгоритма| "Последовательная сложность алгоритма"]];<br />
</p><br />
<p><br />
<ul><li><math>{\rm Efficiency} = \frac{100 \cdot {\rm Performance}}{{\rm Performance}_{\rm Peak}^{p}}\ \ (\%),</math></li></ul><br />
где <math>{\rm Performance}_{\rm Peak}^{p}</math> &ndash; пиковая производительность суперкомпьютера при <math>p</math> процессорах, вычисленная согласно спецификациям Intel<sup>&reg;</sup> XEON<sup>&reg;</sup> X5670<ref>"http://ark.intel.com/ru/products/47920/Intel-Xeon-Processor-X5670-12M-Cache-2_93-GHz-6_40-GTs-Intel-QPI"</ref>.<br />
</p><br />
<br />
Графики зависимости производительности и эффективности параллельной реализации k means от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>) представлены на рисунках 4 и 5, соответственно.<br />
<br />
[[file:Flops.png|thumb|center|800px|Рис. 4. Параллельная реализация k means. График зависимости производительности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
[[file:Efficiency.png|thumb|center|800px|Рис. 5. Параллельная реализация k means. График зависимости эффективности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
<br />
<p><br />
<b>В результате</b> экспериментальной оценки были получены следующие оценки эффективности реализации:<br />
<ul><br />
<li><b>Минимальное</b> значение: 0.000409 % достигается при <math>n=20'000, p=480</math></li><br />
<li><b>Максимальное</b> значение: 0.741119 % достигается при <math>n=300'000, p=1</math></li><br />
</ul><br />
</p><br />
<br />
<p><br />
Оценки масштабируемости реализации алгоритма k means:<br />
<ul><br />
<li><b>По числу процессоров:</b> -0.002683 &ndash; эффективность убывает с ростом числа процессоров. Данный результат вызван ростом накладных расходов для обеспечения параллельного выполнения алгоритма.</li><br />
<li><b>По размеру задачи:</b> 0.002779 &ndash; эффективность растет с ростом числа векторов. Данный результат вызван тем, что при увеличении размера задачи, количество вычислений растет по сравнению с временем, затрачиваемым на пересылку данных.</li><br />
<li><b>Общая оценка:</b> -0.000058 &ndash; можно сделать вывод, что в целом эффективность реализации незначительно уменьшается с ростом размера задачи и числа процессоров.</li><br />
</ul><br />
</p><br />
<br />
[https://github.com/serban/kmeans Использованная параллельная реализация алгоритма k means]<br />
<br />
==== Реализация 2 ====<br />
<br />
Исследование также проводилось на суперкомпьютере "Ломоносов".<br />
<br />
<p>Набор данных для тестирования состоял из 946000 векторов размерности 2 (координаты на сфере)</p><br />
<p>Набор и границы значений изменяемых параметров запуска реализации алгоритма:</p><br />
<br />
* число процессов (виртуальных ядер) [8 : 512];<br />
* число кластеров [128 : 384].<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации <math>2,47%</math> достигается при делении исходных данных на 128 кластеров с использованием 512 процессов;<br />
* максимальная эффективность реализации <math>7,13%</math> достигается при делении исходных данных на 352 кластера с использованием 8 процессов.<br />
На рисунках 6 и 7, соответственно, представлены графики зависимости производительности и эффективности параллельной реализации k means от числа кластеров и числа процессов.<br />
<br />
[[file:kmeans_performance.jpg|thumb|center|720px|Рис. 6. График зависимости производительности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
По рис. 6 можно отметить практически полное отсутствие роста производительности с увеличением числа процессов от 256 до 512 при минимальном размере задачи. Это связано с быстрым ростом накладных расходов по отношению к крайне низкому объёму вычислений. При росте размерности задачи данный эффект пропадает, и при одновременном пропорциональном увеличении числа кластеров и числа процессов рост производительности становится близким к линейному.<br />
<br />
[[file:kmeans_efficiency.jpg|thumb|center|720px|Рис. 7. График зависимости эффективности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
Исследовалась [https://github.com/like-a-bauss/kmeans параллельная реализация алгоритма k means на MPI]. <br />
<p>Были получены следующие оценки масштабируемости реализации алгоритма k means:</p><br />
*<i>По числу процессов:</i> <math>-0.02209</math>. Следовательно, с ростом числа процессов эффективность уменьшается. На рис. 7 можно наблюдать плавное и равномерное снижение производительности по мере увеличения числа процессов при неизменном числе кластеров, что свидетельствует об относительно невысоком росте накладных расходов на передачу данных между процессами и преобладании объёма вычислений над объёмом пересылок данных по сети.<br />
*<i>По размеру задачи:</i> <math>0.01252</math>. Следовательно, с ростом размера задачи (числа кластеров) эффективность увеличивается. При этом объём пересылок данных по сети пропорционален <math>(n + k) \cdot p</math> (где <math>k</math> - число кластеров, <math>n</math> - число входных векторов, <math>p</math> - число процессов) таким образом, поскольку <math>k << n</math>, рост накладных расходов с ростом числа кластеров при неизменном числе процессов и входных векторов представляет собой незначительную величину.<br />
*<i>Общая оценка:</i> <math>-0.00081</math>. Таким образом, с ростом и размера задачи, и числа процессов эффективность уменьшается. Это связано с тем, что отношение объёма вычислений к объёму передаваемых данных изменяется пропорционально <math>{kn \over (n + k) \cdot p} \thicksim {k \over p}</math>, что представляет собой невысокий коэффициент, но при этом позволяет параллельной реализации не деградировать до нулевой эффективности при значительном увеличении числа процессов.<br />
<br />
==== Реализация 3 ====<br />
<br />
Исследование масштабируемости алгоритма k-means в зависимости от количества используемых процессов было проведено в статье Кумара<ref>Kumar, J., Mills, R. T., Hoffman, F. M., & Hargrove, W. W. (2011). Parallel k-means clustering for quantitative ecoregion delineation using large data sets. Procedia Computer Science, 4, 1602-1611.</ref>. Исследование происходило на суперкомпьютере Jaguar - Cray XT5<ref>https://www.top500.org/system/176029</ref>. На момент экспериментов данный суперкомпьютер имел следующую конфигурацию: 18,688 вычислительных узлов с двумя шестнадцатиядерными процессорами AMD Opteron 2435 (Istanbul) 2.6 GHz, 16 GB of DDR2-800 оперативной памяти, и SeaStar 2+ роутер. Всего он состоял из 224,256 вычислительных ядер, 300 TB памяти, и пиковой производительностью 2.3 petaflops.<br />
<br />
Реализация алгоритма была выполнена на языке программирования C с использованием MPI.<br />
<br />
Объем данных составлял 84 ГБ, количество объектов (d-мерных векторов) n равнялось 1,024,767,667, размерность векторов <math>d</math> равнялась 22, количество кластеров <math>k</math> равнялось 1000. <br />
<br />
На рис. 8 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров. Можно отметить, что время, затраченное на чтение данных и запись результатов кластеризации, практически не изменяется с увеличением количества задействованных процессоров. Время же работы самого алгоритма кластеризации уменьшается с увеличением количества процессоров.<br />
<br />
[[Файл:k-means-proc-scalability.png|thumb|center|700px|Рис. 8. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (из работы: Kumar etc. 2011).]]<br />
<br />
Также было произведено самостоятельное исследование масштабируемости алгоритма k-means. Исследование производилось на суперкомпьютере "Blue Gene/P"<ref>http://hpc.cmc.msu.ru/bgp</ref>.<br />
<br />
Набор и границы значений изменяемых параметров запуска реализации алгоритма:<br />
<br />
* число процессоров [1, 2, 4, 8, 16, 32, 64, 128, 256, 512];<br />
* количество объектов [5000, 10000, 25000, 50000].<br />
<br />
Был использован набор данных ''Dataset for Sensorless Drive Diagnosis Data Set''<ref>PASCHKE, Fabian ; BAYER, Christian ; BATOR, Martyna ; MÖNKS, Uwe ; DICKS, Alexander ; ENGE-ROSENBLATT, Olaf ; LOHWEG, Volker: Sensorlose Zustandsüberwachung an Synchronmotoren, Bd. 46. In: HOFFMANN, Frank; HÃœLLERMEIER, Eyke (Hrsg.): Proceedings 23. Workshop Computational Intelligence. Karlsruhe : KIT Scientific Publishing, 2013 (Schriftenreihe des Instituts für Angewandte Informatik - Automatisierungstechnik am Karlsruher Institut für Technologie, 46), S. 211-225</ref> из репозитория ''Machine learning repository''<ref>https://archive.ics.uci.edu/ml/datasets/Dataset+for+Sensorless+Drive+Diagnosis</ref>.<br />
<br />
Исследуемый набор данных содержит векторы, размерность которых равна 49. Компоненты векторов являются вещественными числами. Количество кластеров равно 11. Пропущенные значения отсутствуют.<br />
<br />
Для исследования масштабируемости алгоритма была использована реализация на языке C с использованием MPI<ref>http://users.eecs.northwestern.edu/~wkliao/Kmeans/index.html</ref>. Код можно найти здесь: https://github.com/serban/kmeans. Данная реализация предоставляет возможность распараллеливать решение задачи с помощью технологий MPI, OpenMP И CUDA. Для запуска MPI-версии программы использовалась цель "mpi_main" Makefile.<br />
<br />
На рис. 9 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (использовались логарифмические оси). Разными цветами помечены запуски, соответствующие разным количествам объектам, участвующих в кластеризации. Можно видеть близкое к линейному увеличение времени работы программы в зависимости от количества процессоров. Также можно видеть увеличение времени работы алгоритма при увеличении количества объектов.<br />
[[Файл:Plot_1.png|thumb|center|900px|Рис. 9. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
На рис. 10 показана эта же зависимость, только в трехмерном пространстве. По аналогии с рис. 9, были использованы логарифмические оси. Как и в случае двумерного рисунка, можно видеть близкое к линейному увеличение времени работы программы.<br />
[[Файл:Kmeans-3d.png|thumb|center|900px|Рис. 10. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
==== Реализация 4 ====<br />
<br />
Исследование масштабируемости данной параллельной реализации алгоритма k-средних также проводилось на суперкомпьютере "Ломоносов". Параллельная реализация была написана самостоятельно на языке C, [http://git.algowiki-project.org/iaegorov/Egorov-Bogomazov-k-means/tree/master ссылка на реализацию]. Так как на каждой итерации число действий на единицу данных не велико и данные должны быть собраны вместе при перерасчете центроидов, было решено для ускорения вычислений воспользоваться только OpenMP без использовании MPI.<br />
<br />
Код собирался под gcc c опцией -fopenmp. Код считался на одном процессоре, технология hyperthreading не использовалась.<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 16] с увеличением в 2 раза;<br />
* размер данных [100000 : 1600000] с увеличением в 2 раза.<br />
<br />
В результате проведённых экспериментов были получены следующие данные:<br />
<br />
* Максимальная эффективность в точке достигается при переходе от 1 потока на 4 при минимальном размере данных, она равна <math>87,5%</math>.<br />
* Усредненная максимальная эффективность достигается при переходе с одного потока на два. Среднее время вычислений на всех рассмотренных потока снижается с 16,33 до 11.87 секунд, поэтому формально эффективность <math>= 16.33 / 11.87 / 2 \approx 68,4\%</math><br />
* Минимальная эффективность в точке достигается при переходе от 1 потока на 16 при размере данных 800000, она равна <math>11,1\%</math>.<br />
* Усредненная минимальная эффективность наблюдается при переходе с одного на максимальное рассматриваемое в эксперименте число потоков, равное 16. Время вычисления изменяется с 16,33 до 7,6 секунд, поэтому формально эффективность <math> = 16.33 / 7.6 / 16 \approx 14,9\%</math><br />
<br />
Ниже приведены графики зависимости вычислительного времени алгоритма и его эффективности от изменяемых параметров запуска — размера данных и числа процессоров:<br />
<br />
[[file:Egorov_Bogomazov_Time.jpg|thumb|center|700px|Рис. 11. Параллельная реализация алгоритма k-средних. Изменение вычислительного времени алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь видно, что время выполнения операций алгоритма плавно убывает по каждому из параметров, причем скорость убывания по параметру числа процессоров выше, чем в зависимости от размерности задачи.<br />
<br />
[[file:Egorov_Bogomazov_Efficiency.jpg|thumb|center|700px|Рис. 12. Параллельная реализация алгоритма k-средних. Изменение эффективности алгоритма в зависимости от числа процессоров и размера исходных данных.]]<br />
<br />
Здесь построена эффективность перехода от последовательной реализации к параллельной. Рассчитывается она по формуле ''Время вычисления на 1 потоке / Время вычисления на <math>T</math> потоках / <math>T</math>'', где <math>T</math> — это число потоков. При вычислении на 1 процессоре она равна 100 \% в силу используемой формулы, что и отражено на графике.<br />
<br />
Проведем оценки масштабируемости:<br />
<br />
'''По числу процессов''' — при увеличении числа процессов эффективность уменьшается на всей области рассматриваемых значений, причем темп убывания замедляется с ростом числа процессов.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений вначале кратковременно возрастает, но затем начинает относительно равномерно убывать на всей рассматриваемой области.<br />
<br />
'''По размеру задачи''' — при увеличении размера задачи эффективность вычислений в общем случае постепенно убывает. На малых данных она выходит на пик мощности, являющийся максимумом эффективности в исследуемых условиях, но затем возвращается к процессу убывания.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
В однопоточном режиме на наборах данных, представляющих практический интерес (порядка нескольких десятков тысяч векторов и выше), время работы алгоритма неприемлемо велико. Благодаря свойству массового параллелизма должно наблюдаться значительное ускорение алгоритма на многоядерных архитектурах (Intel Xeon), а также на графических процессорах, даже на мобильных вычислительных системах (ноутбуках), оснащенных видеокартой. Также алгоритм k means будет демонстрировать значительное ускорение на сверхмощных вычислительных комплексах (суперкомпьютерах, системах облачных вычислений<ref>"Issa. "Performance characterization and analysis for Hadoop K-means iteration". Journal of Cloud Computing, 2016"</ref>).<br />
<br />
На сегодняшний день существует множество реализаций алгоритма k means, в частности, направленных на оптимизацию параллельной работы на различных архитектурах<ref>"Raghavan R. A fast and scalable hardware architecture for K-means clustering for big data analysis : дис. – University of Colorado Colorado Springs. Kraemer Family Library, 2016."</ref><ref>"Yang, Luobin, et al. "High performance data clustering: a comparative analysis of performance for GPU, RASC, MPI, and OpenMP implementations." The Journal of supercomputing 70.1 (2014): 284-300."</ref><ref>"Li, You, et al. "Speeding up k-means algorithm by GPUs." Computer and Information Technology (CIT), 2010 IEEE 10th International Conference on. IEEE, 2010."</ref>. Предлагается множество адаптаций алгоритма под конкретные архитектуры. Например, авторы работы<ref>"Kanan, Gebali, Ibrahim. "Fast and Area-Efficient Hardware Implementation of the K-means<br />
Clustering Algorithm". WSEAS Transactions on circuits and systems. Vol. 15. 2016"</ref> производят перерасчет центров кластеров на этапе распределения векторов по кластерам.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
==== Открытое программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.icpsr.umich.edu/CrimeStat/ CrimeStat]</li> Программное обеспечение, созданное для операционных систем Windows, предоставляющее инструменты статистического и пространственного анализа для решения задачи картирования преступности.<br />
<li>[http://juliastats.github.io Julia]</li> Высокоуровневый высокопроизводительный свободный язык программирования с динамической типизацией, созданный для математических вычислений, содержит реализацию k-means.<br />
<li>[https://mahout.apache.org Mahout]</li> Apache Mahout - Java библиотека для работы с алгоритмами машинного обучения с использованием MapReduce. Содержит реализацию k-means.<br />
<li>[https://www.gnu.org/software/octave/ Octave]</li> Написанная на C++ свободная система для математических вычислений, использующая совместимый с MATLAB язык высокого уровня, содержит реализацию k-means.<br />
<li>[http://spark.apache.org/docs/latest/mllib-clustering.html Spark]</li> Распределенная реализация k-means содержится в библиотеке Mlib для работы с алгоритмами машинного обучения, взаимодействующая с Python библиотекой NumPy и библиотека R.<br />
<li>[http://torch.ch Torch]</li> MATLAB-подобная библиотека для языка программирования Lua с открытым исходным кодом, предоставляет большое количество алгоритмов для глубинного обучения и научных расчётов. Ядро написано на Си, прикладная часть выполняется на LuaJIT, поддерживается распараллеливание вычислений средствами CUDA и OpenMP. Существуют реализации k-means.<br />
<li>[http://www.cs.waikato.ac.nz/ml/weka/ Weka]</li> Cвободное программное обеспечение для анализа данных, написанное на Java. Содержит k-means и x-means.<br />
<li>[http://accord-framework.net/docs/html/T_Accord_MachineLearning_KMeans.htm Accord.NET]</li> C# реализация алгоритмов k-means, k-means++, k-modes.<br />
<li>[http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html OpenCV]</li> Написанная на С++ библиотека, направленная в основном на решение задач компьютерного зрения. Содержит реализацию k-means.<br />
<li>[http://mlpack.org/ MLPACK]</li> Масштабируемая С++ библиотека для работы с алгоритмами машинного обучения, содержит реализацию k-means.<br />
<li>[https://www.scipy.org/ SciPy]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[http://scikit-learn.org/ scikit-learn]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[https://www.r-bloggers.com/k-means-clustering-in-r/ R]</li> Язык программирования для статистической обработки данных и работы с графикой, а также свободная программная среда вычислений с открытым исходным кодом в рамках проекта GNU, содержит три реализации k-means.<br />
<li>[http://elki.dbs.ifi.lmu.de ELKI]</li> Java фреймворк, содержащий реализацию k-means, а также множество других алгоритмов кластеризации.<br />
</ol><br />
<br />
==== Проприетарное программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.ayasdi.com/blog/bigdata/topological-data-analysis-of-oil-and-gas-petrophysical-data/ Ayasdi]</li><br />
<li>[http://www.stata.com/features/cluster-analysis/ Stata]</li><br />
<li>[http://mathworld.wolfram.com/K-MeansClusteringAlgorithm.html Mathematica]</li><br />
<li>[http://www.mathworks.com/help/stats/kmeans.html?requestedDomain=www.mathworks.com MATLAB]</li><br />
<li>[https://support.sas.com/rnd/app/stat/procedures/fastclus.html SAS]</li><br />
<li>[http://docs.rapidminer.com/studio/operators/modeling/segmentation/k_means.html RapidMiner]</li><br />
<li>[https://blogs.sap.com/2013/03/28/sap-hana-pal-k-means-algorithm-or-how-to-do-customer-segmentation-for-the-telecommunications-industry/ SAP HANA]</li><br />
</ol><br />
<br />
== Литература ==<br />
<br />
<references /></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_k_%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D1%85_(k-means)&diff=25244Алгоритм k средних (k-means)2018-01-16T17:02:09Z<p>Konshin: /* Реализация 3 */</p>
<hr />
<div>Основные авторы статьи (разделы 1, 2.4.1, 2.6-2.7, 3):<br />
[https://algowiki-project.org/ru/Участник:Бротиковская_Данута<b>Д.Бротиковская</b>] и<br />
[https://algowiki-project.org/ru/Участник:DennZo1993<b>Д.Зобнин</b>]<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:IanaV<b>Я.А.Валуйская</b>] и<br />
[https://algowiki-project.org/ru/Участник:GlotovES<b>Е.С.Глотов</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Parkhomenko<b>П.А.Пархоменко</b>] и<br />
[https://algowiki-project.org/ru/Участник:Ivan.mashonskiy<b>И.Д.Машонский</b>] (раздел 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:ХХ<b>ХХ</b>] и<br />
[https://algowiki-project.org/ru/Участник:ХХ<b>ХХ</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Алгоритм <math>k</math> средних (<math>k</math>-means)<br />
| serial_complexity = <math>O(ikdn)</math><br />
| input_data = <math> dn </math><br />
| output_data = <math> n </math><br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Алгоритм <b><i>k средних</i></b> (англ. k-means) - один из алгоритмов машинного обучения, решающий задачу кластеризации.<br />
Этот алгоритм является неиерархическим<ref>"https://ru.wikipedia.org/wiki/Иерархическая_кластеризация"</ref>, итерационным методом кластеризации<ref>"https://ru.wikipedia.org/wiki/Кластерный_анализ"</ref>, он получил большую популярность благодаря своей простоте, наглядности реализации и достаточно высокому качеству работы. <br />
Был изобретен в 1950-х годах математиком <i>Гуго Штейнгаузом</i><ref>Steinhaus, Hugo. "Sur la division des corp materiels en parties." Bull. Acad. Polon. Sci 1.804 (1956): 801.</ref> и почти одновременно <i>Стюартом Ллойдом</i><ref>Lloyd, S. P. "Least square quantization in PCM. Bell Telephone Laboratories Paper. Published in journal much later: Lloyd, SP: Least squares quantization in PCM." IEEE Trans. Inform. Theor.(1957/1982).</ref>. Особую популярность приобрел после публикации работы <i>МакКуина</i><ref>MacQueen, James. "Some methods for classification and analysis of multivariate observations." Proceedings of the fifth Berkeley symposium on mathematical statistics and probability. Vol. 1. No. 14. 1967.</ref> в 1967.<br />
<br />
Алгоритм представляет собой версию EM-алгоритма<ref>"https://ru.wikipedia.org/wiki/EM-алгоритм"</ref>, применяемого также для разделения смеси гауссиан. Основная идея <i>k means</i> заключается в том, что данные произвольно разбиваются на кластеры, после чего итеративно перевычисляется центр масс для каждого кластера, полученного на предыдущем шаге, затем векторы разбиваются на кластеры вновь в соответствии с тем, какой из новых центров оказался ближе по выбранной метрике.<br />
<br />
Цель алгоритма заключается в разделении <math>n</math> наблюдений на <math>k</math> кластеров таким образом, чтобы каждое наблюдение принадлежало ровно одному кластеру, расположенному на наименьшем расстоянии от наблюдения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
<b>Дано:</b><br />
* набор из <math>n</math> наблюдений <math>X=\{\mathbf{x}_1, \mathbf{x}_2, ..., \mathbf{x}_n\}, \mathbf{x}_i \in \mathbb{R}^d, \ i=1,...,n</math>;<br />
* <math>k</math> - требуемое число кластеров, <math>k \in \mathbb{N}, k \leq n</math>.<br />
<br />
<b>Требуется:</b><br />
<br />
Разделить множество наблюдений <math>X</math> на <math>k</math> кластеров <math>S_1, S_2, ..., S_k</math>:<br />
* <math>S_i \cap S_j= \varnothing, \quad i \ne j</math><br />
<br />
* <math>\bigcup_{i=1}^{k} S_i = X</math><br />
<br />
<b>Действие алгоритма:</b><br />
<p>Алгоритм <i>k means</i> разбивает набор <math>X</math> на <math>k</math> наборов <math>S_1, S_2, ..., S_k,</math> таким образом, чтобы минимизировать сумму квадратов расстояний от каждой точки кластера до его центра (центр масс кластера). Введем обозначение, <math>S=\{S_1, S_2, ..., S_k\}</math>. Тогда действие алгоритма <i>k means</i> равносильно поиску:</p><br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math>\arg\min_{S} \sum\limits_{i=1}^k \sum\limits_{\mathbf{x} \in S_i} \rho(\mathbf{x}, \mathbf{\mu}_i )^2,</math></td><br />
<td align="right"><math>(1)</math></td><br />
</tr><br />
</table><br />
где <math>\mathbf{\mu}_i</math> &ndash; центры кластеров, <math>i=1,...,k, \quad \rho(\mathbf{x}, \mathbf{\mu}_i)</math> &ndash; функция расстояния между <math>\mathbf{x}</math> и <math>\mu_i</math><br />
<br />
<b>Шаги алгоритма:</b><br />
<ol><br />
<li><br />
<b>Начальный шаг: инициализация кластеров</b><br />
<p>Выбирается произвольное множество точек <math>\mu_i, \ i=1,...,k,</math> рассматриваемых как начальные центры кластеров: <math>\mu_i^{(0)} = \mu_i, \quad i=1,...,k</math></p><br />
</li><br />
<li><br />
<b>Распределение векторов по кластерам</b><br />
<p><b>Шаг</b> <math>t: \forall \mathbf{x}_i \in X, \ i=1,...,n: \mathbf{x}_i \in S_j \iff j=\arg\min_{k}\rho(\mathbf{x}_i,\mathbf{\mu}_k^{(t-1)})^2</math></p><br />
</li><br />
<li><br />
<b>Пересчет центров кластеров</b><br />
<p><b>Шаг </b> <math>t: \forall i=1,...,k: \mu_i^{(t)} = \cfrac{1}{|S_i|}\sum_{\mathbf{x}\in S_i}\mathbf{x}</math></p><br />
</li><br />
<li><br />
<b>Проверка условия останова:</b><br />
<p></p><br />
'''if''' <math>\exist i\in \overline{1,k}: \mu_i^{(t)} \ne \mu_i^{(t-1)}</math> '''then'''<br />
t = t + 1;<br />
goto 2;<br />
'''else'''<br />
'''stop'''<br />
</li><br />
</ol><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром являются шаги 2 и 3 приведенного выше алгоритма: <b><i>распределение векторов по кластерам</i></b> и <b><i>пересчет центров кластеров</i></b>. <br />
<br />
<p><br />
<b><i>Распределение векторов</i></b> по кластерам предполагает вычисление расстояний между каждым вектором <math>\mathbf{x}_i \in X, \ i= 1,...,n</math> и центрами кластера <math>\mathbf{\mu}_j, \ j= 1,...,k</math>. Таким образом, данный шаг предполагает <math>kn</math> вычислений расстояний между <math>d</math>-мерными векторами. <br />
</p><br />
<p><br />
<b><i>Пересчет центров кластеров</i></b> предполагает <math>k</math> вычислений центров масс <math>\mathbf{\mu}_i</math> множеств <math>S_i, \ i=1,...,k,</math> представленных выражением в шаге 3 представленного выше алгоритма.<br />
</p><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
<b>Инициализация центров масс <math>\mu_1, ..., \mu_k</math></b>. <br />
<br />
Наиболее распространенными являются следующие стратегии:<br />
<ul><br />
<li><br />
<b>Метод Forgy</b><br>В качестве начальных значений <math>\mu_1, ..., \mu_k</math> берутся случайно выбранные векторы.<br />
</li><br />
<li><br />
<b>Метод случайно разделения (Random Partitioning)</b><br>Для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> выбирается случайным образом кластер <math>S_1, ..., S_k</math>, после чего для каждого полученного кластера вычисляются значения <math>\mu_1, ..., \mu_k</math>.<br />
</li><br />
</ul><br />
<br />
<b>Распределение векторов по кластерам</b><br />
<br />
Для этого шага алгоритма между векторами <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> и центрами кластеров <math>\mu_1,...,\mu_k</math> вычисляются <b>расстояния</b> по формуле (как правило, используется Евлидово расстояние):<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mathbf{v}_1, \mathbf{v}_2 \in \mathbb{R}^d, \quad \rho(\mathbf{v}_1, \mathbf{v}_2) = \lVert \mathbf{v}_1- \mathbf{v}_2 \rVert= \sqrt{\sum_{i=1}^{d}(\mathbf{v}_{1,i} - \mathbf{v}_{2,i})^2}</math></td><br />
<td align="right"><math>(2)</math></td><br />
</tr><br />
</table><br />
<br />
<b>Пересчет центров кластеров</b><br />
<br />
Для этого шага алгоритма производится пересчет центров кластера по <b>формуле вычисления центра масс</b>:<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mu = \cfrac{1}{|S|}\sum_{\mathbf{x}\in S}\mathbf{x}</math></td><br />
<td align="right"><math>(3)</math></td><br />
</tr><br />
</table><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
'''1.''' Инициализировать центры кластеров <math>\mathbf{\mu}_i^{(1)}, \ i=1,...,k</math><br><br />
'''2.''' <math>t \leftarrow 1</math><br><br />
'''3.''' Распределение по кластерам<br><br />
<math>\quad S_i^{(t)}=\{\mathbf{x}_p: \lVert\mathbf{x}_p-\mathbf{\mu}_i^{(t)}\rVert^2 \leq \lVert\mathbf{x}_p-\mathbf{\mu}_j^{(t)}\rVert^2 \quad \forall j=1,...,k\},</math><br><br />
<math>\quad</math>где каждый вектор <math>\mathbf{x}_p</math> соотносится единственному кластеру <math>S^{(t)}</math><br><br />
'''4.''' Обновление центров кластеров<br><br />
<math>\quad \mathbf{\mu}_i^{(t+1)} = \frac{1}{|S^{(t)}_i|} \sum_{\mathbf{x}_j \in S^{(t)}_i} \mathbf{x}_j </math><br><br />
'''5.''' '''if''' <math>\exists i \in \overline{1,k}: \mathbf{\mu}_i^{(t+1)} \ne \mathbf{\mu}_i^{(t)}</math> '''then'''<br><br />
<math>\quad t = t + 1</math>;<br><br />
<math>\quad</math>goto '''3''';<br><br />
<math>~~~</math>'''else'''<br><br />
<math>\quad</math>'''stop'''<br><br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
<div style="padding-bottom: 20px"><br />
<p>Обозначим <math>\Theta_{\rm centroid}^{d, m}</math> временную сложность вычисления центорида кластера, число элементов которого равна <math>m</math>, в d-мерном пространстве.</p><br />
<p>Аналогично <math>\Theta_{\rm distance}^d</math> &ndash; временная сложность вычисления расстояния между двумя d-мерными векторами.</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<b>Сложность шага инициализации <math>k</math> кластеров мощности <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm init}^{k, d, m}</math></b><br />
<ul><br />
<li><i>Стратерия Forgy</i>: вычисления не требуются, <math>\Theta_{\rm init}^{k, d, m} = 0</math></li><br />
<li><i>Стратегия случайного разбиения</i>: вычисление центров <math>k</math> кластеров, <math>\Theta_{\rm init}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}, m \le n</math></li><br />
</ul><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Cложность шага распределения d мерных векторов по <math>k</math> кластерам &ndash; <math>\Theta_{\rm distribute}^{k, d}</math></b></p><br />
<p><br />
На этом шаге для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> вычисляется <math>k</math> расстояний до центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math><br />
</p><br />
<p align="center"><br />
<math>\Theta_{\rm distribute}^{k, d} = n \cdot k \cdot \Theta_{\rm distance}^d</math><br />
</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><br />
<b>Сложность шага пересчета центров <math>k</math> кластеров размера <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm recenter}^{k, d, m}</math></b><br />
</p><br />
<p>На этом шаге вычисляется <math>k</math> центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math></p><br />
<p align="center"><math>\Theta_{\rm recenter}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}</math> </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm centroid}^{d, m}</math> для кластера, число элементов которого равно <math>m</math></b></p><br />
<p align="center"><math>\Theta_{\rm centroid}^{d, m}</math> = <math>m \cdot d</math> сложений + <math>d</math> делений </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm distance}^d</math> в соответствие с формулой <math>(2)</math></b></p><br />
<p align="center"><math>\Theta_{\rm distance}^d</math> = <math>d</math> вычитаний + <math>d</math> умножений + <math>(d-1)</math> сложение</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Предположим, что алгоритм сошелся за <math>i</math> итераций, тогда временная сложность алгоритма <math>\Theta_{\rm k-means}^{d, n}</math></p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le \Theta_{\rm init}^{k, d, n} + i(\Theta_{\rm distribute}^{k, d} + \Theta_{\rm recenter}^{k, d, n})</math></b></p><br />
</div><br />
<div style="padding-bottom: 5px"><br />
<p>Операции сложения/вычитания:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le knd+ i(kn(2d-1) + knd) = knd+ i(kn(3d-1)) \thicksim O(ikdn)</math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Операции умножения/деления:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le kd + i(knd + kd) = kd + ikd(n+1) \thicksim O(ikdn) </math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Получаем, что <b>временная сложность</b> алгоритма <b>k means</b> кластеризации <math>n</math> <b>d-мерных</b> векторов на <math>k</math> кластеров за <math>i</math> итераций:</p><br />
<p align="center"><b><math> \Theta_{\rm k-means}^{d, n} \thicksim O(ikdn) </math></b></p><br />
</div><br />
<br />
=== Информационный граф ===<br />
<br />
Рассмотрим информационный граф алгоритма. Алгоритм <i>k means</i> начинается с этапа инициализации, после которого следуют итерации, на каждой из которых выполняется два последовательных шага (см. [[#Схема реализации последовательного алгоритма|"Схема реализации последовательного алгоритма"]]): <br />
* распределение векторов по кластерам<br />
* перерасчет центров кластеров<br />
<br />
Поскольку основная часть вычислений приходится на шаги итераций, распишем информационные графы данных шагов.<br />
<br />
<p><b>Распределение векторов по кластерам</b></p><br />
Информационный граф шага распределения векторов по кластерам представлен на ''рисунке 1''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также центры кластеров <math>\mathbf{\mu}_1, ... \mathbf{\mu}_k</math>, вычисленные ранее (на шаге инициализации, если рассматривается первая итерация алгоритма, или на шаге пересчета центров кластеров предыдущей итерации в противном случае). Каждая пара векторов данных <math>\mathbf{x}_i, \ i=1,...,n,</math> и центров кластера <math>\mathbf{\mu}_j, \ j=1,...,k</math> : (<math>\mathbf{x}_i</math>, <math>\mathbf{\mu}_j</math>) подаются на независимые узлы <i>"d"</i> вычисления расстояния между векторами (более подробная схема вычисления расстояния представлена далее, ''рисунок 2''). Далее узлы вычисления расстояния <i>"d"</i>, соответствующие одному и тому же исходному вектору <math>\mathbf{x}_i</math> передаются на один узел <i>"m"</i>, где далее происходит вычисление новой метки кластера для каждого вектора <math>\mathbf{x}_i</math> (берется кластер с минимальным результатом вычисления расстояния). На выходе графа выдаются метки кластеров , <math>L_1, ..., L_n</math>, такие что <math>\forall \mathbf{x}_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>. <br />
<br />
[[file:Clusterization.png|thumb|center|800px|Рис. 1. Схема распределения векторов по кластерам. ''d'' &ndash; вычисление расстояния между векторами; ''m'' &ndash; вычисление минимума.]]<br />
<br />
<p><b>Вычисление расстояния между векторами</b></p><br />
Подробная схема вычисления расстояния между векторами <math>\mathbf{x}_i, \mathbf{\mu}_j</math> представлена на ''рисунке 2''. Как показано на графе, узел вычисления расстояния между векторами <i>"d"</i> состоит из шага взятия разности между векторами (узел "<math>-</math>") и взятия нормы получившегося вектора разности (узел "<math>||\cdot||^2</math>"). Более подробно, вычисление расстояния между векторами <math>\mathbf{x}_i = {x_{i1,}, ...,{x_{in}}}, \mathbf{\mu}_j = {\mu_{j1}, ...,\mu_{jn}}</math> может быть представлено как вычисление разности между каждой парой компонент <math>(x_{iz}, \mu_{jz}), \ z=1,...,d</math> (узел "<math>-</math>"), далее возведение в квадрат для каждого узла "<math>-</math>" (узел "<math>()^2</math>") и суммирования выходов всех узлов "<math>()^2</math>" (узел "<math>+</math>"). <br />
<br />
[[file:dist_calc.png|thumb|center|800px|Рис. 2. Схема вычисления расстояния между вектором и центром кластера.]]<br />
<br />
<p><b>Пересчет центров кластеров</b></p><br />
Информационный граф шага пересчета центров кластеров представлен на ''рисунке 3''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также им соответствующие метки кластера, <math>L_1, ..., L_n</math>, такие что <math>\forall x_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>, вычисленные на этапе распределения векторов по кластерам. Все векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math> подаются в узлы <math>+_1, ...+_k</math>, каждый узел <math>+_m, \ m = 1,...,k,</math> соответствует операции сложения векторов кластера с номером <math>m</math>. Метки кластера <math>L_1, ..., L_n</math> также совместно передаются на узлы <math>S_m, \ m=1,...,k</math>, на каждом из которых вычисляется количество векторов в соответствующем кластере (количество меток с соответствующим значением). Далее каждая пара выходов узлов <math>+_m</math> и <math>S_m</math> подается на узел "<math>/</math>", где производится деление суммы векторов кластера на количество элементов в нем. Значения, вычисленные на узлах "<math>/</math>", присваиваются новым центрам кластеров (выходные значения графа).<br />
<br />
[[file:Recluster.png|thumb|center|800px|Рис. 3. Схема пересчета центров кластеров]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
<p><br />
Работа алгоритма состоит из <math>i</math> итераций, в каждой из которых происходит <b>распределение d-мерных векторов по <math>k</math> кластерам</b>, а также <b>пересчет центров кластеров в d-мерном пространстве</b>. В шаге <b>распределения d-мерных векторов по <math>k</math> кластерам</b> расстояния между вектором и центрами кластеров вычисляются независимо (отсутствуют информационные зависимости). <b>Центры масс кластеров</b> также пересчитываются независимо друг от друга. Таким образом, имеет место [[глоссарий#Массовый параллелизм|''массовый параллелизм'']]. Вычислим параллельную сложность <math>\Psi_*</math> каждого из шагов, а также параллельную сложность всего алгоритма, <math>\Psi_{\rm k-means}</math>. Будем исходить из предположения, что может быть использовано любое необходимое число потоков.<br />
</p><br />
<br />
<p><b>Распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b></p> <br />
<p><br />
Поскольку на данном шаге для каждой пары векторов <math>\mathbf{x}_i, \ i=1,...,n</math> и <math>\mathbf{\mu}_j, \ j=1,...,k,</math> операции вычисления расстояния не зависят друг от друга, они могут выполняться параллельно. Тогда, разделив все вычисление расстояний на <math>n</math> потоков, получим, что в каждом потоке будет выполняться только одна операция вычисления расстояния между векторами размерности d. При этом каждому вычислительному потоку передаются координаты центров всех кластеров <math>\mathbf{\mu}_1, ..., \mathbf{\mu}_k</math>. Таким образом, параллельная сложность данного шага определяется <i> сложностью параллельной операции вычисления расстояния между d-мерными векторами</i>, <math>\Psi_{\rm distance}^d</math> и <i>сложностью определения наиболее близкого кластера</i> (паралельное взятие минимума по расстояниям), <math>\Psi_{\rm min}^k</math>. Для оценки <math>\Psi_{\rm distance}^d</math> воспользуемся [[Нахождение_частных_сумм_элементов_массива_сдваиванием | параллельной реализацией нахождения частичной суммы элементов массива путем сдваивания]]. Аналогично, <math>\Psi_{\rm min}^k = log(k)</math>. В результате, <math>\Psi_{\rm distance}^d = O(log(d))</math>. Таким образом: <br />
</p><br />
<p align="center"><math>\Psi_{\rm distribute}^{k, d} = \Psi_{\rm distance}^d + \Psi_{\rm min}^k = O(log(d))+O(log(k)) = O(log(kd))</math></p><br />
<br />
<p><b>Пересчет центров кластеров в d-мерном пространстве</b></p><br />
<p><br />
Поскольку на данном шаге для каждого из <math>k</math> кластеров центр масс может быть вычислен независимо, данные операции могут быть выполнены в отдельных потоках. Таким образом, параллельная сложность данного шага, <math>\Psi_{\rm recenter}^{k, d}</math>, будет определяться <i>параллельной сложностью вычисления одного центра масс кластера размера <math>m</math></i>, <math>\Psi_{\rm recenter}^{k, d}</math>, а так как <math>m \le n \Rightarrow \Psi_{\rm recenter}^{d, m} \le \Psi_{\rm recenter}^{d, n}</math>. Сложность вычисления центра масс кластера d-мерных векторов размера n аналогично предыдущим вычислениям равна <math>O(log(n))</math>. Тогда: <br />
</p><br />
<p align="center"><math>\Psi_{\rm recenter}^{k, d} \le \Psi_{\rm recenter}^{d, n} = O(log(n))</math></p><br />
<br />
<p><b>Общая параллельная сложность алгоритма</b></p><br />
<p><br />
На каждой итерации необходимо обновление центров кластеров, которые будут использованы на следующей итерации. Таким образом, итерационный процесс выполняется последовательно<ref>Zhao, Weizhong, Huifang Ma, and Qing He. "Parallel k-means clustering based on mapreduce." IEEE International Conference on Cloud Computing. Springer Berlin Heidelberg, 2009.</ref>. Тогда, поскольку сложность каждой итерации определяется <math>\Psi_{\rm distribute}^{k, d}</math> и <math>\Psi_{\rm recenter}</math>, сложность всего алгоритма, <math>\Psi_{\rm k-means}</math> в предположении, что было сделано <math>i</math> операций определяется выражением<br />
</p><br />
<p align="center"><math>\Psi_{\rm k-means} \approx i \cdot (\Psi_{\rm distribute}^{k, d} + \Psi_{\rm recenter}^{k, d}) \le i \cdot O(log(kdn))</math></p><br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
<p><br />
<b>Входные данные</b><br><br />
<ul><br />
<li>Матрица из <math>n \cdot d</math> элементов <math>x_{i, j} \in \mathbb{R}, \ i=1,...,n, \ j=1,...,d,</math> &ndash; координат векторов (наблюдений).</li><br />
<li>Целое положительное число <math>k, \ k \le n</math> &ndash; количество кластеров.</li><br />
</ul><br />
</p><br />
<p><br />
<b>Объем входных данных</b><br><br />
<math>1</math> целое число + <math>n \cdot d</math> вещественных чисел (при условии, что координаты &ndash; вещественные числа).<br />
</p><br />
<p><br />
<b>Выходные данные</b><br><br />
<math>n</math> целых положительных чисел <math>L_1, ..., L_n</math>&ndash; номера кластеров, соотвествующие каждому вектору (при условии, что нумерация кластеров начинается с <math>1</math>).<br />
</p><br />
<p><br />
<b>Объем выходных данных</b><br><br />
<math>n</math> целых положительных чисел.<br />
</p><br />
<br />
=== Свойства алгоритма ===<br />
<br />
<p><b>[[глоссарий#Вычислительная мощность|''Вычислительная мощность'']]</b></p><br />
Вычислительная мощность алгоритма <i>k means</i> равна <math>\frac{ikdn}{nd} = ki </math>, где <math>k</math> &ndash; число кластеров, <math>i</math> &ndash; число итераций алгоритма.<br />
<br />
<p><b>[[глоссарий#Детерминированность |''Детерминированность'']] и [[глоссарий#Устойчивость |''Устойчивость'']] </b></p><br />
Алгоритм <i>k means</i> является итерационным. Количество итераций алгоритма в общем случае не фиксируется и зависит от начального расположения объектов в пространстве, параметра <math>k</math>, а также от начального приближения центров кластеров, <math>\mu_1, ..., \mu_k</math>. В результате этого может варьироваться результат работы алгоритма. При неудачном выборе начальных параметров итерационный процесс может сойтись к локальному оптимуму<ref>Von Luxburg, Ulrike. Clustering Stability. Now Publishers Inc, 2010.</ref>. По этим причинам алгоритм не является ни <b>детермирированным</b>, ни <b>устойчивым</b>.<br />
<br />
<p><b>Соотношение последовательной и параллельной сложности алгоритма</b></p><br />
<br />
<math>\frac{\Theta_{\rm k-means}}{\Psi_{\rm k-means}} = \frac{O(ikdn)}{O(i \cdot log(kdn))}</math><br />
<br />
<b>Сильные стороны алгоритма</b>:<br />
<ul><br />
<li><i>Сравнительно высокая эффективность при простоте реализации</i></li><br />
<li><i>Высокое качество кластеризации</i></li><br />
<li><i>Возможность распараллеливания</i></li><br />
<li><i>Существование множества модификаций</i></li><br />
</ul><br />
<b>Недостатки алгоритма</b><ref>Ortega, Joaquín Pérez, Ma Del Rocío Boone Rojas, and María J. Somodevilla. "Research issues on K-means Algorithm: An Experimental Trial Using Matlab."</ref>:<br />
<ul><br />
<li><i>Количество кластеров является параметром алгоритма</i></li><br />
<li><br />
<p><i>Чувствительность к начальным условиям</i></p><br />
<p>Инициализация центров кластеров в значительной степени влияет на результат кластеризации.</p><br />
</li><br />
<li><br />
<p><i>Чувствительность к выбросам и шумам</i></p><br />
<p>Выбросы, далекие от центров настоящих кластеров, все равно учитываются при вычислении их центров.</p><br />
</li><br />
<li><br />
<p><i>Возможность сходимости к локальному оптимуму</i></p><br />
<p>Итеративный подход не дает гарантии сходимости к оптимальному решению.</p><br />
</li><br />
<li><br />
<p><i>Использование понятия "среднего"</i></p><br />
<p>Алгоритм неприменим к данным, для которых не определено понятие "среднего", например, категориальным данным.</p><br />
</li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализации алгоритма <math>k</math> средних согласно [[Scalability methodology|методике]] AlgoWiki.<br />
<br />
==== Реализация 1 ====<br />
<br />
Исследование масштабируемости параллельной реализации алгоритма k means проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Алгоритм реализован на языке C с использованием средств MPI.<br />
Для исследования масштабируемости проводилось множество запусков программы с разным значением параметра (количество векторов для кластеризации), а также с различным числом процессоров. Фиксировались результаты запусков &ndash; время работы <math>t</math> и количество произведенных итераций алгоритма <math>i</math>.<br />
<br />
Параметры запусков для экспериментальной оценки:<br />
<ul><br />
<li>Значения <b>количества векторов</b> <math>n</math>: 20'000, 30'000, 50'000, 100'000, 200'000, 300'000, 500'000, 700'000, 1'000'000, 1'500'000, 2'000'000.</li><br />
<li>Значения <b>количества процессоров</b> <math>p</math>: 1, 8, 16, 32, 64, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512.</li><br />
<li>Значение <b>количества кластеров</b> <math>k</math>: 100.</li><br />
<li>Значение <b>размерности векторов</b> <math>d</math>: 10.</li><br />
</ul><br />
<br />
Для проведения экспериментов были сгенерированы нормально распределенные псевдослучайные данные (с использованием Python библиотеки [http://scikit-learn.org/ scikit-learn]):<br />
<syntaxhighlight lang="python"><br />
from sklearn.datasets import make_classification<br />
X1, Y1 = make_classification(n_features=10, n_redundant=2, n_informative=8,<br />
n_clusters_per_class=1, n_classes=100,<br />
n_samples=2000000)<br />
</syntaxhighlight><br />
<br />
Для заданной конфигурации эксперимента (<math>n, d, p, k</math>) и полученных результатов (<math>t, i</math>) [[Глоссарий#Производительность|производительность]] и эффективность реализации расчитывались по формулам:<br />
<p><br />
<ul><li><math>{\rm Performance} = \frac{N_{\rm k-means}^{d, n}}{t}\ \ ({\rm FLOPS}),</math></li></ul><br />
где <math>N_{\rm k-means}^{d, n}</math> &ndash; точное число операций с плавающей точкой (операции с памятью, а также целочисленные операции не учитывались), вычисленное в соответствие с разделом [[#Последовательная сложность алгоритма| "Последовательная сложность алгоритма"]];<br />
</p><br />
<p><br />
<ul><li><math>{\rm Efficiency} = \frac{100 \cdot {\rm Performance}}{{\rm Performance}_{\rm Peak}^{p}}\ \ (\%),</math></li></ul><br />
где <math>{\rm Performance}_{\rm Peak}^{p}</math> &ndash; пиковая производительность суперкомпьютера при <math>p</math> процессорах, вычисленная согласно спецификациям Intel<sup>&reg;</sup> XEON<sup>&reg;</sup> X5670<ref>"http://ark.intel.com/ru/products/47920/Intel-Xeon-Processor-X5670-12M-Cache-2_93-GHz-6_40-GTs-Intel-QPI"</ref>.<br />
</p><br />
<br />
Графики зависимости производительности и эффективности параллельной реализации k means от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>) представлены на рисунках 4 и 5, соответственно.<br />
<br />
[[file:Flops.png|thumb|center|800px|Рис. 4. Параллельная реализация k means. График зависимости производительности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
[[file:Efficiency.png|thumb|center|800px|Рис. 5. Параллельная реализация k means. График зависимости эффективности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
<br />
<p><br />
<b>В результате</b> экспериментальной оценки были получены следующие оценки эффективности реализации:<br />
<ul><br />
<li><b>Минимальное</b> значение: 0.000409 % достигается при <math>n=20'000, p=480</math></li><br />
<li><b>Максимальное</b> значение: 0.741119 % достигается при <math>n=300'000, p=1</math></li><br />
</ul><br />
</p><br />
<br />
<p><br />
Оценки масштабируемости реализации алгоритма k means:<br />
<ul><br />
<li><b>По числу процессоров:</b> -0.002683 &ndash; эффективность убывает с ростом числа процессоров. Данный результат вызван ростом накладных расходов для обеспечения параллельного выполнения алгоритма.</li><br />
<li><b>По размеру задачи:</b> 0.002779 &ndash; эффективность растет с ростом числа векторов. Данный результат вызван тем, что при увеличении размера задачи, количество вычислений растет по сравнению с временем, затрачиваемым на пересылку данных.</li><br />
<li><b>Общая оценка:</b> -0.000058 &ndash; можно сделать вывод, что в целом эффективность реализации незначительно уменьшается с ростом размера задачи и числа процессоров.</li><br />
</ul><br />
</p><br />
<br />
[https://github.com/serban/kmeans Использованная параллельная реализация алгоритма k means]<br />
<br />
==== Реализация 2 ====<br />
<br />
Исследование также проводилось на суперкомпьютере "Ломоносов".<br />
<br />
<p>Набор данных для тестирования состоял из 946000 векторов размерности 2 (координаты на сфере)</p><br />
<p>Набор и границы значений изменяемых параметров запуска реализации алгоритма:</p><br />
<br />
* число процессов (виртуальных ядер) [8 : 512];<br />
* число кластеров [128 : 384].<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации <math>2,47%</math> достигается при делении исходных данных на 128 кластеров с использованием 512 процессов;<br />
* максимальная эффективность реализации <math>7,13%</math> достигается при делении исходных данных на 352 кластера с использованием 8 процессов.<br />
На рисунках 6 и 7, соответственно, представлены графики зависимости производительности и эффективности параллельной реализации k means от числа кластеров и числа процессов.<br />
<br />
[[file:kmeans_performance.jpg|thumb|center|720px|Рис. 6. График зависимости производительности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
По рис. 6 можно отметить практически полное отсутствие роста производительности с увеличением числа процессов от 256 до 512 при минимальном размере задачи. Это связано с быстрым ростом накладных расходов по отношению к крайне низкому объёму вычислений. При росте размерности задачи данный эффект пропадает, и при одновременном пропорциональном увеличении числа кластеров и числа процессов рост производительности становится близким к линейному.<br />
<br />
[[file:kmeans_efficiency.jpg|thumb|center|720px|Рис. 7. График зависимости эффективности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
Исследовалась [https://github.com/like-a-bauss/kmeans параллельная реализация алгоритма k means на MPI]. <br />
<p>Были получены следующие оценки масштабируемости реализации алгоритма k means:</p><br />
*<i>По числу процессов:</i> <math>-0.02209</math>. Следовательно, с ростом числа процессов эффективность уменьшается. На рис. 7 можно наблюдать плавное и равномерное снижение производительности по мере увеличения числа процессов при неизменном числе кластеров, что свидетельствует об относительно невысоком росте накладных расходов на передачу данных между процессами и преобладании объёма вычислений над объёмом пересылок данных по сети.<br />
*<i>По размеру задачи:</i> <math>0.01252</math>. Следовательно, с ростом размера задачи (числа кластеров) эффективность увеличивается. При этом объём пересылок данных по сети пропорционален <math>(n + k) \cdot p</math> (где <math>k</math> - число кластеров, <math>n</math> - число входных векторов, <math>p</math> - число процессов) таким образом, поскольку <math>k << n</math>, рост накладных расходов с ростом числа кластеров при неизменном числе процессов и входных векторов представляет собой незначительную величину.<br />
*<i>Общая оценка:</i> <math>-0.00081</math>. Таким образом, с ростом и размера задачи, и числа процессов эффективность уменьшается. Это связано с тем, что отношение объёма вычислений к объёму передаваемых данных изменяется пропорционально <math>{kn \over (n + k) \cdot p} \thicksim {k \over p}</math>, что представляет собой невысокий коэффициент, но при этом позволяет параллельной реализации не деградировать до нулевой эффективности при значительном увеличении числа процессов.<br />
<br />
==== Реализация 3 ====<br />
<br />
Исследование масштабируемости алгоритма k-means в зависимости от количества используемых процессов было проведено в статье Кумара<ref>Kumar, J., Mills, R. T., Hoffman, F. M., & Hargrove, W. W. (2011). Parallel k-means clustering for quantitative ecoregion delineation using large data sets. Procedia Computer Science, 4, 1602-1611.</ref>. Исследование происходило на суперкомпьютере Jaguar - Cray XT5<ref>https://www.top500.org/system/176029</ref>. На момент экспериментов данный суперкомпьютер имел следующую конфигурацию: 18,688 вычислительных узлов с двумя шестнадцатиядерными процессорами AMD Opteron 2435 (Istanbul) 2.6 GHz, 16 GB of DDR2-800 оперативной памяти, и SeaStar 2+ роутер. Всего он состоял из 224,256 вычислительных ядер, 300 TB памяти, и пиковой производительностью 2.3 petaflops.<br />
<br />
Реализация алгоритма была выполнена на языке программирования C с использованием MPI.<br />
<br />
Объем данных составлял 84 ГБ, количество объектов (d-мерных векторов) n равнялось 1,024,767,667, размерность векторов <math>d</math> равнялась 22, количество кластеров <math>k</math> равнялось 1000. <br />
<br />
На рис. 8 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров. Можно отметить, что время, затраченное на чтение данных и запись результатов кластеризации, практически не изменяется с увеличением количества задействованных процессоров. Время же работы самого алгоритма кластеризации уменьшается с увеличением количества процессоров.<br />
<br />
[[Файл:k-means-proc-scalability.png|thumb|center|700px|Рис. 8. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (из работы: Kumar etc. 2011).]]<br />
<br />
Также было произведено самостоятельное исследование масштабируемости алгоритма k-means. Исследование производилось на суперкомпьютере "Blue Gene/P"<ref>http://hpc.cmc.msu.ru/bgp</ref>.<br />
<br />
Набор и границы значений изменяемых параметров запуска реализации алгоритма:<br />
<br />
* число процессоров [1, 2, 4, 8, 16, 32, 64, 128, 256, 512];<br />
* количество объектов [5000, 10000, 25000, 50000].<br />
<br />
Был использован набор данных ''Dataset for Sensorless Drive Diagnosis Data Set''<ref>PASCHKE, Fabian ; BAYER, Christian ; BATOR, Martyna ; MÖNKS, Uwe ; DICKS, Alexander ; ENGE-ROSENBLATT, Olaf ; LOHWEG, Volker: Sensorlose Zustandsüberwachung an Synchronmotoren, Bd. 46. In: HOFFMANN, Frank; HÃœLLERMEIER, Eyke (Hrsg.): Proceedings 23. Workshop Computational Intelligence. Karlsruhe : KIT Scientific Publishing, 2013 (Schriftenreihe des Instituts für Angewandte Informatik - Automatisierungstechnik am Karlsruher Institut für Technologie, 46), S. 211-225</ref> из репозитория ''Machine learning repository''<ref>https://archive.ics.uci.edu/ml/datasets/Dataset+for+Sensorless+Drive+Diagnosis</ref>.<br />
<br />
Исследуемый набор данных содержит векторы, размерность которых равна 49. Компоненты векторов являются вещественными числами. Количество кластеров равно 11. Пропущенные значения отсутствуют.<br />
<br />
Для исследования масштабируемости алгоритма была использована реализация на языке C с использованием MPI<ref>http://users.eecs.northwestern.edu/~wkliao/Kmeans/index.html</ref>. Код можно найти здесь: https://github.com/serban/kmeans. Данная реализация предоставляет возможность распараллеливать решение задачи с помощью технологий MPI, OpenMP И CUDA. Для запуска MPI-версии программы использовалась цель "mpi_main" Makefile.<br />
<br />
На рис. 9 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (использовались логарифмические оси). Разными цветами помечены запуски, соответствующие разным количествам объектам, участвующих в кластеризации. Можно видеть близкое к линейному увеличение времени работы программы в зависимости от количества процессоров. Также можно видеть увеличение времени работы алгоритма при увеличении количества объектов.<br />
[[Файл:Plot_1.png|thumb|center|900px|Рис. 9. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
На рис. 10 показана эта же зависимость, только в трехмерном пространстве. По аналогии с рис. 9, были использованы логарифмические оси. Как и в случае двумерного рисунка, можно видеть близкое к линейному увеличение времени работы программы.<br />
[[Файл:Kmeans-3d.png|thumb|center|900px|Рис. 10. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
==== Реализация 4 ====<br />
<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
В однопоточном режиме на наборах данных, представляющих практический интерес (порядка нескольких десятков тысяч векторов и выше), время работы алгоритма неприемлемо велико. Благодаря свойству массового параллелизма должно наблюдаться значительное ускорение алгоритма на многоядерных архитектурах (Intel Xeon), а также на графических процессорах, даже на мобильных вычислительных системах (ноутбуках), оснащенных видеокартой. Также алгоритм k means будет демонстрировать значительное ускорение на сверхмощных вычислительных комплексах (суперкомпьютерах, системах облачных вычислений<ref>"Issa. "Performance characterization and analysis for Hadoop K-means iteration". Journal of Cloud Computing, 2016"</ref>).<br />
<br />
На сегодняшний день существует множество реализаций алгоритма k means, в частности, направленных на оптимизацию параллельной работы на различных архитектурах<ref>"Raghavan R. A fast and scalable hardware architecture for K-means clustering for big data analysis : дис. – University of Colorado Colorado Springs. Kraemer Family Library, 2016."</ref><ref>"Yang, Luobin, et al. "High performance data clustering: a comparative analysis of performance for GPU, RASC, MPI, and OpenMP implementations." The Journal of supercomputing 70.1 (2014): 284-300."</ref><ref>"Li, You, et al. "Speeding up k-means algorithm by GPUs." Computer and Information Technology (CIT), 2010 IEEE 10th International Conference on. IEEE, 2010."</ref>. Предлагается множество адаптаций алгоритма под конкретные архитектуры. Например, авторы работы<ref>"Kanan, Gebali, Ibrahim. "Fast and Area-Efficient Hardware Implementation of the K-means<br />
Clustering Algorithm". WSEAS Transactions on circuits and systems. Vol. 15. 2016"</ref> производят перерасчет центров кластеров на этапе распределения векторов по кластерам.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
==== Открытое программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.icpsr.umich.edu/CrimeStat/ CrimeStat]</li> Программное обеспечение, созданное для операционных систем Windows, предоставляющее инструменты статистического и пространственного анализа для решения задачи картирования преступности.<br />
<li>[http://juliastats.github.io Julia]</li> Высокоуровневый высокопроизводительный свободный язык программирования с динамической типизацией, созданный для математических вычислений, содержит реализацию k-means.<br />
<li>[https://mahout.apache.org Mahout]</li> Apache Mahout - Java библиотека для работы с алгоритмами машинного обучения с использованием MapReduce. Содержит реализацию k-means.<br />
<li>[https://www.gnu.org/software/octave/ Octave]</li> Написанная на C++ свободная система для математических вычислений, использующая совместимый с MATLAB язык высокого уровня, содержит реализацию k-means.<br />
<li>[http://spark.apache.org/docs/latest/mllib-clustering.html Spark]</li> Распределенная реализация k-means содержится в библиотеке Mlib для работы с алгоритмами машинного обучения, взаимодействующая с Python библиотекой NumPy и библиотека R.<br />
<li>[http://torch.ch Torch]</li> MATLAB-подобная библиотека для языка программирования Lua с открытым исходным кодом, предоставляет большое количество алгоритмов для глубинного обучения и научных расчётов. Ядро написано на Си, прикладная часть выполняется на LuaJIT, поддерживается распараллеливание вычислений средствами CUDA и OpenMP. Существуют реализации k-means.<br />
<li>[http://www.cs.waikato.ac.nz/ml/weka/ Weka]</li> Cвободное программное обеспечение для анализа данных, написанное на Java. Содержит k-means и x-means.<br />
<li>[http://accord-framework.net/docs/html/T_Accord_MachineLearning_KMeans.htm Accord.NET]</li> C# реализация алгоритмов k-means, k-means++, k-modes.<br />
<li>[http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html OpenCV]</li> Написанная на С++ библиотека, направленная в основном на решение задач компьютерного зрения. Содержит реализацию k-means.<br />
<li>[http://mlpack.org/ MLPACK]</li> Масштабируемая С++ библиотека для работы с алгоритмами машинного обучения, содержит реализацию k-means.<br />
<li>[https://www.scipy.org/ SciPy]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[http://scikit-learn.org/ scikit-learn]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[https://www.r-bloggers.com/k-means-clustering-in-r/ R]</li> Язык программирования для статистической обработки данных и работы с графикой, а также свободная программная среда вычислений с открытым исходным кодом в рамках проекта GNU, содержит три реализации k-means.<br />
<li>[http://elki.dbs.ifi.lmu.de ELKI]</li> Java фреймворк, содержащий реализацию k-means, а также множество других алгоритмов кластеризации.<br />
</ol><br />
<br />
==== Проприетарное программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.ayasdi.com/blog/bigdata/topological-data-analysis-of-oil-and-gas-petrophysical-data/ Ayasdi]</li><br />
<li>[http://www.stata.com/features/cluster-analysis/ Stata]</li><br />
<li>[http://mathworld.wolfram.com/K-MeansClusteringAlgorithm.html Mathematica]</li><br />
<li>[http://www.mathworks.com/help/stats/kmeans.html?requestedDomain=www.mathworks.com MATLAB]</li><br />
<li>[https://support.sas.com/rnd/app/stat/procedures/fastclus.html SAS]</li><br />
<li>[http://docs.rapidminer.com/studio/operators/modeling/segmentation/k_means.html RapidMiner]</li><br />
<li>[https://blogs.sap.com/2013/03/28/sap-hana-pal-k-means-algorithm-or-how-to-do-customer-segmentation-for-the-telecommunications-industry/ SAP HANA]</li><br />
</ol><br />
<br />
== Литература ==<br />
<br />
<references /></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_k_%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D1%85_(k-means)&diff=25243Алгоритм k средних (k-means)2018-01-16T16:57:43Z<p>Konshin: </p>
<hr />
<div>Основные авторы статьи (разделы 1, 2.4.1, 2.6-2.7, 3):<br />
[https://algowiki-project.org/ru/Участник:Бротиковская_Данута<b>Д.Бротиковская</b>] и<br />
[https://algowiki-project.org/ru/Участник:DennZo1993<b>Д.Зобнин</b>]<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:IanaV<b>Я.А.Валуйская</b>] и<br />
[https://algowiki-project.org/ru/Участник:GlotovES<b>Е.С.Глотов</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:Parkhomenko<b>П.А.Пархоменко</b>] и<br />
[https://algowiki-project.org/ru/Участник:Ivan.mashonskiy<b>И.Д.Машонский</b>] (раздел 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:ХХ<b>ХХ</b>] и<br />
[https://algowiki-project.org/ru/Участник:ХХ<b>ХХ</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Алгоритм <math>k</math> средних (<math>k</math>-means)<br />
| serial_complexity = <math>O(ikdn)</math><br />
| input_data = <math> dn </math><br />
| output_data = <math> n </math><br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Алгоритм <b><i>k средних</i></b> (англ. k-means) - один из алгоритмов машинного обучения, решающий задачу кластеризации.<br />
Этот алгоритм является неиерархическим<ref>"https://ru.wikipedia.org/wiki/Иерархическая_кластеризация"</ref>, итерационным методом кластеризации<ref>"https://ru.wikipedia.org/wiki/Кластерный_анализ"</ref>, он получил большую популярность благодаря своей простоте, наглядности реализации и достаточно высокому качеству работы. <br />
Был изобретен в 1950-х годах математиком <i>Гуго Штейнгаузом</i><ref>Steinhaus, Hugo. "Sur la division des corp materiels en parties." Bull. Acad. Polon. Sci 1.804 (1956): 801.</ref> и почти одновременно <i>Стюартом Ллойдом</i><ref>Lloyd, S. P. "Least square quantization in PCM. Bell Telephone Laboratories Paper. Published in journal much later: Lloyd, SP: Least squares quantization in PCM." IEEE Trans. Inform. Theor.(1957/1982).</ref>. Особую популярность приобрел после публикации работы <i>МакКуина</i><ref>MacQueen, James. "Some methods for classification and analysis of multivariate observations." Proceedings of the fifth Berkeley symposium on mathematical statistics and probability. Vol. 1. No. 14. 1967.</ref> в 1967.<br />
<br />
Алгоритм представляет собой версию EM-алгоритма<ref>"https://ru.wikipedia.org/wiki/EM-алгоритм"</ref>, применяемого также для разделения смеси гауссиан. Основная идея <i>k means</i> заключается в том, что данные произвольно разбиваются на кластеры, после чего итеративно перевычисляется центр масс для каждого кластера, полученного на предыдущем шаге, затем векторы разбиваются на кластеры вновь в соответствии с тем, какой из новых центров оказался ближе по выбранной метрике.<br />
<br />
Цель алгоритма заключается в разделении <math>n</math> наблюдений на <math>k</math> кластеров таким образом, чтобы каждое наблюдение принадлежало ровно одному кластеру, расположенному на наименьшем расстоянии от наблюдения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
<b>Дано:</b><br />
* набор из <math>n</math> наблюдений <math>X=\{\mathbf{x}_1, \mathbf{x}_2, ..., \mathbf{x}_n\}, \mathbf{x}_i \in \mathbb{R}^d, \ i=1,...,n</math>;<br />
* <math>k</math> - требуемое число кластеров, <math>k \in \mathbb{N}, k \leq n</math>.<br />
<br />
<b>Требуется:</b><br />
<br />
Разделить множество наблюдений <math>X</math> на <math>k</math> кластеров <math>S_1, S_2, ..., S_k</math>:<br />
* <math>S_i \cap S_j= \varnothing, \quad i \ne j</math><br />
<br />
* <math>\bigcup_{i=1}^{k} S_i = X</math><br />
<br />
<b>Действие алгоритма:</b><br />
<p>Алгоритм <i>k means</i> разбивает набор <math>X</math> на <math>k</math> наборов <math>S_1, S_2, ..., S_k,</math> таким образом, чтобы минимизировать сумму квадратов расстояний от каждой точки кластера до его центра (центр масс кластера). Введем обозначение, <math>S=\{S_1, S_2, ..., S_k\}</math>. Тогда действие алгоритма <i>k means</i> равносильно поиску:</p><br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math>\arg\min_{S} \sum\limits_{i=1}^k \sum\limits_{\mathbf{x} \in S_i} \rho(\mathbf{x}, \mathbf{\mu}_i )^2,</math></td><br />
<td align="right"><math>(1)</math></td><br />
</tr><br />
</table><br />
где <math>\mathbf{\mu}_i</math> &ndash; центры кластеров, <math>i=1,...,k, \quad \rho(\mathbf{x}, \mathbf{\mu}_i)</math> &ndash; функция расстояния между <math>\mathbf{x}</math> и <math>\mu_i</math><br />
<br />
<b>Шаги алгоритма:</b><br />
<ol><br />
<li><br />
<b>Начальный шаг: инициализация кластеров</b><br />
<p>Выбирается произвольное множество точек <math>\mu_i, \ i=1,...,k,</math> рассматриваемых как начальные центры кластеров: <math>\mu_i^{(0)} = \mu_i, \quad i=1,...,k</math></p><br />
</li><br />
<li><br />
<b>Распределение векторов по кластерам</b><br />
<p><b>Шаг</b> <math>t: \forall \mathbf{x}_i \in X, \ i=1,...,n: \mathbf{x}_i \in S_j \iff j=\arg\min_{k}\rho(\mathbf{x}_i,\mathbf{\mu}_k^{(t-1)})^2</math></p><br />
</li><br />
<li><br />
<b>Пересчет центров кластеров</b><br />
<p><b>Шаг </b> <math>t: \forall i=1,...,k: \mu_i^{(t)} = \cfrac{1}{|S_i|}\sum_{\mathbf{x}\in S_i}\mathbf{x}</math></p><br />
</li><br />
<li><br />
<b>Проверка условия останова:</b><br />
<p></p><br />
'''if''' <math>\exist i\in \overline{1,k}: \mu_i^{(t)} \ne \mu_i^{(t-1)}</math> '''then'''<br />
t = t + 1;<br />
goto 2;<br />
'''else'''<br />
'''stop'''<br />
</li><br />
</ol><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром являются шаги 2 и 3 приведенного выше алгоритма: <b><i>распределение векторов по кластерам</i></b> и <b><i>пересчет центров кластеров</i></b>. <br />
<br />
<p><br />
<b><i>Распределение векторов</i></b> по кластерам предполагает вычисление расстояний между каждым вектором <math>\mathbf{x}_i \in X, \ i= 1,...,n</math> и центрами кластера <math>\mathbf{\mu}_j, \ j= 1,...,k</math>. Таким образом, данный шаг предполагает <math>kn</math> вычислений расстояний между <math>d</math>-мерными векторами. <br />
</p><br />
<p><br />
<b><i>Пересчет центров кластеров</i></b> предполагает <math>k</math> вычислений центров масс <math>\mathbf{\mu}_i</math> множеств <math>S_i, \ i=1,...,k,</math> представленных выражением в шаге 3 представленного выше алгоритма.<br />
</p><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
<b>Инициализация центров масс <math>\mu_1, ..., \mu_k</math></b>. <br />
<br />
Наиболее распространенными являются следующие стратегии:<br />
<ul><br />
<li><br />
<b>Метод Forgy</b><br>В качестве начальных значений <math>\mu_1, ..., \mu_k</math> берутся случайно выбранные векторы.<br />
</li><br />
<li><br />
<b>Метод случайно разделения (Random Partitioning)</b><br>Для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> выбирается случайным образом кластер <math>S_1, ..., S_k</math>, после чего для каждого полученного кластера вычисляются значения <math>\mu_1, ..., \mu_k</math>.<br />
</li><br />
</ul><br />
<br />
<b>Распределение векторов по кластерам</b><br />
<br />
Для этого шага алгоритма между векторами <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> и центрами кластеров <math>\mu_1,...,\mu_k</math> вычисляются <b>расстояния</b> по формуле (как правило, используется Евлидово расстояние):<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mathbf{v}_1, \mathbf{v}_2 \in \mathbb{R}^d, \quad \rho(\mathbf{v}_1, \mathbf{v}_2) = \lVert \mathbf{v}_1- \mathbf{v}_2 \rVert= \sqrt{\sum_{i=1}^{d}(\mathbf{v}_{1,i} - \mathbf{v}_{2,i})^2}</math></td><br />
<td align="right"><math>(2)</math></td><br />
</tr><br />
</table><br />
<br />
<b>Пересчет центров кластеров</b><br />
<br />
Для этого шага алгоритма производится пересчет центров кластера по <b>формуле вычисления центра масс</b>:<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mu = \cfrac{1}{|S|}\sum_{\mathbf{x}\in S}\mathbf{x}</math></td><br />
<td align="right"><math>(3)</math></td><br />
</tr><br />
</table><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
'''1.''' Инициализировать центры кластеров <math>\mathbf{\mu}_i^{(1)}, \ i=1,...,k</math><br><br />
'''2.''' <math>t \leftarrow 1</math><br><br />
'''3.''' Распределение по кластерам<br><br />
<math>\quad S_i^{(t)}=\{\mathbf{x}_p: \lVert\mathbf{x}_p-\mathbf{\mu}_i^{(t)}\rVert^2 \leq \lVert\mathbf{x}_p-\mathbf{\mu}_j^{(t)}\rVert^2 \quad \forall j=1,...,k\},</math><br><br />
<math>\quad</math>где каждый вектор <math>\mathbf{x}_p</math> соотносится единственному кластеру <math>S^{(t)}</math><br><br />
'''4.''' Обновление центров кластеров<br><br />
<math>\quad \mathbf{\mu}_i^{(t+1)} = \frac{1}{|S^{(t)}_i|} \sum_{\mathbf{x}_j \in S^{(t)}_i} \mathbf{x}_j </math><br><br />
'''5.''' '''if''' <math>\exists i \in \overline{1,k}: \mathbf{\mu}_i^{(t+1)} \ne \mathbf{\mu}_i^{(t)}</math> '''then'''<br><br />
<math>\quad t = t + 1</math>;<br><br />
<math>\quad</math>goto '''3''';<br><br />
<math>~~~</math>'''else'''<br><br />
<math>\quad</math>'''stop'''<br><br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
<div style="padding-bottom: 20px"><br />
<p>Обозначим <math>\Theta_{\rm centroid}^{d, m}</math> временную сложность вычисления центорида кластера, число элементов которого равна <math>m</math>, в d-мерном пространстве.</p><br />
<p>Аналогично <math>\Theta_{\rm distance}^d</math> &ndash; временная сложность вычисления расстояния между двумя d-мерными векторами.</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<b>Сложность шага инициализации <math>k</math> кластеров мощности <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm init}^{k, d, m}</math></b><br />
<ul><br />
<li><i>Стратерия Forgy</i>: вычисления не требуются, <math>\Theta_{\rm init}^{k, d, m} = 0</math></li><br />
<li><i>Стратегия случайного разбиения</i>: вычисление центров <math>k</math> кластеров, <math>\Theta_{\rm init}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}, m \le n</math></li><br />
</ul><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Cложность шага распределения d мерных векторов по <math>k</math> кластерам &ndash; <math>\Theta_{\rm distribute}^{k, d}</math></b></p><br />
<p><br />
На этом шаге для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> вычисляется <math>k</math> расстояний до центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math><br />
</p><br />
<p align="center"><br />
<math>\Theta_{\rm distribute}^{k, d} = n \cdot k \cdot \Theta_{\rm distance}^d</math><br />
</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><br />
<b>Сложность шага пересчета центров <math>k</math> кластеров размера <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm recenter}^{k, d, m}</math></b><br />
</p><br />
<p>На этом шаге вычисляется <math>k</math> центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math></p><br />
<p align="center"><math>\Theta_{\rm recenter}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}</math> </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm centroid}^{d, m}</math> для кластера, число элементов которого равно <math>m</math></b></p><br />
<p align="center"><math>\Theta_{\rm centroid}^{d, m}</math> = <math>m \cdot d</math> сложений + <math>d</math> делений </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm distance}^d</math> в соответствие с формулой <math>(2)</math></b></p><br />
<p align="center"><math>\Theta_{\rm distance}^d</math> = <math>d</math> вычитаний + <math>d</math> умножений + <math>(d-1)</math> сложение</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Предположим, что алгоритм сошелся за <math>i</math> итераций, тогда временная сложность алгоритма <math>\Theta_{\rm k-means}^{d, n}</math></p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le \Theta_{\rm init}^{k, d, n} + i(\Theta_{\rm distribute}^{k, d} + \Theta_{\rm recenter}^{k, d, n})</math></b></p><br />
</div><br />
<div style="padding-bottom: 5px"><br />
<p>Операции сложения/вычитания:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le knd+ i(kn(2d-1) + knd) = knd+ i(kn(3d-1)) \thicksim O(ikdn)</math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Операции умножения/деления:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le kd + i(knd + kd) = kd + ikd(n+1) \thicksim O(ikdn) </math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Получаем, что <b>временная сложность</b> алгоритма <b>k means</b> кластеризации <math>n</math> <b>d-мерных</b> векторов на <math>k</math> кластеров за <math>i</math> итераций:</p><br />
<p align="center"><b><math> \Theta_{\rm k-means}^{d, n} \thicksim O(ikdn) </math></b></p><br />
</div><br />
<br />
=== Информационный граф ===<br />
<br />
Рассмотрим информационный граф алгоритма. Алгоритм <i>k means</i> начинается с этапа инициализации, после которого следуют итерации, на каждой из которых выполняется два последовательных шага (см. [[#Схема реализации последовательного алгоритма|"Схема реализации последовательного алгоритма"]]): <br />
* распределение векторов по кластерам<br />
* перерасчет центров кластеров<br />
<br />
Поскольку основная часть вычислений приходится на шаги итераций, распишем информационные графы данных шагов.<br />
<br />
<p><b>Распределение векторов по кластерам</b></p><br />
Информационный граф шага распределения векторов по кластерам представлен на ''рисунке 1''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также центры кластеров <math>\mathbf{\mu}_1, ... \mathbf{\mu}_k</math>, вычисленные ранее (на шаге инициализации, если рассматривается первая итерация алгоритма, или на шаге пересчета центров кластеров предыдущей итерации в противном случае). Каждая пара векторов данных <math>\mathbf{x}_i, \ i=1,...,n,</math> и центров кластера <math>\mathbf{\mu}_j, \ j=1,...,k</math> : (<math>\mathbf{x}_i</math>, <math>\mathbf{\mu}_j</math>) подаются на независимые узлы <i>"d"</i> вычисления расстояния между векторами (более подробная схема вычисления расстояния представлена далее, ''рисунок 2''). Далее узлы вычисления расстояния <i>"d"</i>, соответствующие одному и тому же исходному вектору <math>\mathbf{x}_i</math> передаются на один узел <i>"m"</i>, где далее происходит вычисление новой метки кластера для каждого вектора <math>\mathbf{x}_i</math> (берется кластер с минимальным результатом вычисления расстояния). На выходе графа выдаются метки кластеров , <math>L_1, ..., L_n</math>, такие что <math>\forall \mathbf{x}_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>. <br />
<br />
[[file:Clusterization.png|thumb|center|800px|Рис. 1. Схема распределения векторов по кластерам. ''d'' &ndash; вычисление расстояния между векторами; ''m'' &ndash; вычисление минимума.]]<br />
<br />
<p><b>Вычисление расстояния между векторами</b></p><br />
Подробная схема вычисления расстояния между векторами <math>\mathbf{x}_i, \mathbf{\mu}_j</math> представлена на ''рисунке 2''. Как показано на графе, узел вычисления расстояния между векторами <i>"d"</i> состоит из шага взятия разности между векторами (узел "<math>-</math>") и взятия нормы получившегося вектора разности (узел "<math>||\cdot||^2</math>"). Более подробно, вычисление расстояния между векторами <math>\mathbf{x}_i = {x_{i1,}, ...,{x_{in}}}, \mathbf{\mu}_j = {\mu_{j1}, ...,\mu_{jn}}</math> может быть представлено как вычисление разности между каждой парой компонент <math>(x_{iz}, \mu_{jz}), \ z=1,...,d</math> (узел "<math>-</math>"), далее возведение в квадрат для каждого узла "<math>-</math>" (узел "<math>()^2</math>") и суммирования выходов всех узлов "<math>()^2</math>" (узел "<math>+</math>"). <br />
<br />
[[file:dist_calc.png|thumb|center|800px|Рис. 2. Схема вычисления расстояния между вектором и центром кластера.]]<br />
<br />
<p><b>Пересчет центров кластеров</b></p><br />
Информационный граф шага пересчета центров кластеров представлен на ''рисунке 3''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также им соответствующие метки кластера, <math>L_1, ..., L_n</math>, такие что <math>\forall x_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>, вычисленные на этапе распределения векторов по кластерам. Все векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math> подаются в узлы <math>+_1, ...+_k</math>, каждый узел <math>+_m, \ m = 1,...,k,</math> соответствует операции сложения векторов кластера с номером <math>m</math>. Метки кластера <math>L_1, ..., L_n</math> также совместно передаются на узлы <math>S_m, \ m=1,...,k</math>, на каждом из которых вычисляется количество векторов в соответствующем кластере (количество меток с соответствующим значением). Далее каждая пара выходов узлов <math>+_m</math> и <math>S_m</math> подается на узел "<math>/</math>", где производится деление суммы векторов кластера на количество элементов в нем. Значения, вычисленные на узлах "<math>/</math>", присваиваются новым центрам кластеров (выходные значения графа).<br />
<br />
[[file:Recluster.png|thumb|center|800px|Рис. 3. Схема пересчета центров кластеров]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
<p><br />
Работа алгоритма состоит из <math>i</math> итераций, в каждой из которых происходит <b>распределение d-мерных векторов по <math>k</math> кластерам</b>, а также <b>пересчет центров кластеров в d-мерном пространстве</b>. В шаге <b>распределения d-мерных векторов по <math>k</math> кластерам</b> расстояния между вектором и центрами кластеров вычисляются независимо (отсутствуют информационные зависимости). <b>Центры масс кластеров</b> также пересчитываются независимо друг от друга. Таким образом, имеет место [[глоссарий#Массовый параллелизм|''массовый параллелизм'']]. Вычислим параллельную сложность <math>\Psi_*</math> каждого из шагов, а также параллельную сложность всего алгоритма, <math>\Psi_{\rm k-means}</math>. Будем исходить из предположения, что может быть использовано любое необходимое число потоков.<br />
</p><br />
<br />
<p><b>Распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b></p> <br />
<p><br />
Поскольку на данном шаге для каждой пары векторов <math>\mathbf{x}_i, \ i=1,...,n</math> и <math>\mathbf{\mu}_j, \ j=1,...,k,</math> операции вычисления расстояния не зависят друг от друга, они могут выполняться параллельно. Тогда, разделив все вычисление расстояний на <math>n</math> потоков, получим, что в каждом потоке будет выполняться только одна операция вычисления расстояния между векторами размерности d. При этом каждому вычислительному потоку передаются координаты центров всех кластеров <math>\mathbf{\mu}_1, ..., \mathbf{\mu}_k</math>. Таким образом, параллельная сложность данного шага определяется <i> сложностью параллельной операции вычисления расстояния между d-мерными векторами</i>, <math>\Psi_{\rm distance}^d</math> и <i>сложностью определения наиболее близкого кластера</i> (паралельное взятие минимума по расстояниям), <math>\Psi_{\rm min}^k</math>. Для оценки <math>\Psi_{\rm distance}^d</math> воспользуемся [[Нахождение_частных_сумм_элементов_массива_сдваиванием | параллельной реализацией нахождения частичной суммы элементов массива путем сдваивания]]. Аналогично, <math>\Psi_{\rm min}^k = log(k)</math>. В результате, <math>\Psi_{\rm distance}^d = O(log(d))</math>. Таким образом: <br />
</p><br />
<p align="center"><math>\Psi_{\rm distribute}^{k, d} = \Psi_{\rm distance}^d + \Psi_{\rm min}^k = O(log(d))+O(log(k)) = O(log(kd))</math></p><br />
<br />
<p><b>Пересчет центров кластеров в d-мерном пространстве</b></p><br />
<p><br />
Поскольку на данном шаге для каждого из <math>k</math> кластеров центр масс может быть вычислен независимо, данные операции могут быть выполнены в отдельных потоках. Таким образом, параллельная сложность данного шага, <math>\Psi_{\rm recenter}^{k, d}</math>, будет определяться <i>параллельной сложностью вычисления одного центра масс кластера размера <math>m</math></i>, <math>\Psi_{\rm recenter}^{k, d}</math>, а так как <math>m \le n \Rightarrow \Psi_{\rm recenter}^{d, m} \le \Psi_{\rm recenter}^{d, n}</math>. Сложность вычисления центра масс кластера d-мерных векторов размера n аналогично предыдущим вычислениям равна <math>O(log(n))</math>. Тогда: <br />
</p><br />
<p align="center"><math>\Psi_{\rm recenter}^{k, d} \le \Psi_{\rm recenter}^{d, n} = O(log(n))</math></p><br />
<br />
<p><b>Общая параллельная сложность алгоритма</b></p><br />
<p><br />
На каждой итерации необходимо обновление центров кластеров, которые будут использованы на следующей итерации. Таким образом, итерационный процесс выполняется последовательно<ref>Zhao, Weizhong, Huifang Ma, and Qing He. "Parallel k-means clustering based on mapreduce." IEEE International Conference on Cloud Computing. Springer Berlin Heidelberg, 2009.</ref>. Тогда, поскольку сложность каждой итерации определяется <math>\Psi_{\rm distribute}^{k, d}</math> и <math>\Psi_{\rm recenter}</math>, сложность всего алгоритма, <math>\Psi_{\rm k-means}</math> в предположении, что было сделано <math>i</math> операций определяется выражением<br />
</p><br />
<p align="center"><math>\Psi_{\rm k-means} \approx i \cdot (\Psi_{\rm distribute}^{k, d} + \Psi_{\rm recenter}^{k, d}) \le i \cdot O(log(kdn))</math></p><br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
<p><br />
<b>Входные данные</b><br><br />
<ul><br />
<li>Матрица из <math>n \cdot d</math> элементов <math>x_{i, j} \in \mathbb{R}, \ i=1,...,n, \ j=1,...,d,</math> &ndash; координат векторов (наблюдений).</li><br />
<li>Целое положительное число <math>k, \ k \le n</math> &ndash; количество кластеров.</li><br />
</ul><br />
</p><br />
<p><br />
<b>Объем входных данных</b><br><br />
<math>1</math> целое число + <math>n \cdot d</math> вещественных чисел (при условии, что координаты &ndash; вещественные числа).<br />
</p><br />
<p><br />
<b>Выходные данные</b><br><br />
<math>n</math> целых положительных чисел <math>L_1, ..., L_n</math>&ndash; номера кластеров, соотвествующие каждому вектору (при условии, что нумерация кластеров начинается с <math>1</math>).<br />
</p><br />
<p><br />
<b>Объем выходных данных</b><br><br />
<math>n</math> целых положительных чисел.<br />
</p><br />
<br />
=== Свойства алгоритма ===<br />
<br />
<p><b>[[глоссарий#Вычислительная мощность|''Вычислительная мощность'']]</b></p><br />
Вычислительная мощность алгоритма <i>k means</i> равна <math>\frac{ikdn}{nd} = ki </math>, где <math>k</math> &ndash; число кластеров, <math>i</math> &ndash; число итераций алгоритма.<br />
<br />
<p><b>[[глоссарий#Детерминированность |''Детерминированность'']] и [[глоссарий#Устойчивость |''Устойчивость'']] </b></p><br />
Алгоритм <i>k means</i> является итерационным. Количество итераций алгоритма в общем случае не фиксируется и зависит от начального расположения объектов в пространстве, параметра <math>k</math>, а также от начального приближения центров кластеров, <math>\mu_1, ..., \mu_k</math>. В результате этого может варьироваться результат работы алгоритма. При неудачном выборе начальных параметров итерационный процесс может сойтись к локальному оптимуму<ref>Von Luxburg, Ulrike. Clustering Stability. Now Publishers Inc, 2010.</ref>. По этим причинам алгоритм не является ни <b>детермирированным</b>, ни <b>устойчивым</b>.<br />
<br />
<p><b>Соотношение последовательной и параллельной сложности алгоритма</b></p><br />
<br />
<math>\frac{\Theta_{\rm k-means}}{\Psi_{\rm k-means}} = \frac{O(ikdn)}{O(i \cdot log(kdn))}</math><br />
<br />
<b>Сильные стороны алгоритма</b>:<br />
<ul><br />
<li><i>Сравнительно высокая эффективность при простоте реализации</i></li><br />
<li><i>Высокое качество кластеризации</i></li><br />
<li><i>Возможность распараллеливания</i></li><br />
<li><i>Существование множества модификаций</i></li><br />
</ul><br />
<b>Недостатки алгоритма</b><ref>Ortega, Joaquín Pérez, Ma Del Rocío Boone Rojas, and María J. Somodevilla. "Research issues on K-means Algorithm: An Experimental Trial Using Matlab."</ref>:<br />
<ul><br />
<li><i>Количество кластеров является параметром алгоритма</i></li><br />
<li><br />
<p><i>Чувствительность к начальным условиям</i></p><br />
<p>Инициализация центров кластеров в значительной степени влияет на результат кластеризации.</p><br />
</li><br />
<li><br />
<p><i>Чувствительность к выбросам и шумам</i></p><br />
<p>Выбросы, далекие от центров настоящих кластеров, все равно учитываются при вычислении их центров.</p><br />
</li><br />
<li><br />
<p><i>Возможность сходимости к локальному оптимуму</i></p><br />
<p>Итеративный подход не дает гарантии сходимости к оптимальному решению.</p><br />
</li><br />
<li><br />
<p><i>Использование понятия "среднего"</i></p><br />
<p>Алгоритм неприменим к данным, для которых не определено понятие "среднего", например, категориальным данным.</p><br />
</li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализации алгоритма <math>k</math> средних согласно [[Scalability methodology|методике]] AlgoWiki.<br />
<br />
==== Реализация 1 ====<br />
<br />
Исследование масштабируемости параллельной реализации алгоритма k means проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Алгоритм реализован на языке C с использованием средств MPI.<br />
Для исследования масштабируемости проводилось множество запусков программы с разным значением параметра (количество векторов для кластеризации), а также с различным числом процессоров. Фиксировались результаты запусков &ndash; время работы <math>t</math> и количество произведенных итераций алгоритма <math>i</math>.<br />
<br />
Параметры запусков для экспериментальной оценки:<br />
<ul><br />
<li>Значения <b>количества векторов</b> <math>n</math>: 20'000, 30'000, 50'000, 100'000, 200'000, 300'000, 500'000, 700'000, 1'000'000, 1'500'000, 2'000'000.</li><br />
<li>Значения <b>количества процессоров</b> <math>p</math>: 1, 8, 16, 32, 64, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512.</li><br />
<li>Значение <b>количества кластеров</b> <math>k</math>: 100.</li><br />
<li>Значение <b>размерности векторов</b> <math>d</math>: 10.</li><br />
</ul><br />
<br />
Для проведения экспериментов были сгенерированы нормально распределенные псевдослучайные данные (с использованием Python библиотеки [http://scikit-learn.org/ scikit-learn]):<br />
<syntaxhighlight lang="python"><br />
from sklearn.datasets import make_classification<br />
X1, Y1 = make_classification(n_features=10, n_redundant=2, n_informative=8,<br />
n_clusters_per_class=1, n_classes=100,<br />
n_samples=2000000)<br />
</syntaxhighlight><br />
<br />
Для заданной конфигурации эксперимента (<math>n, d, p, k</math>) и полученных результатов (<math>t, i</math>) [[Глоссарий#Производительность|производительность]] и эффективность реализации расчитывались по формулам:<br />
<p><br />
<ul><li><math>{\rm Performance} = \frac{N_{\rm k-means}^{d, n}}{t}\ \ ({\rm FLOPS}),</math></li></ul><br />
где <math>N_{\rm k-means}^{d, n}</math> &ndash; точное число операций с плавающей точкой (операции с памятью, а также целочисленные операции не учитывались), вычисленное в соответствие с разделом [[#Последовательная сложность алгоритма| "Последовательная сложность алгоритма"]];<br />
</p><br />
<p><br />
<ul><li><math>{\rm Efficiency} = \frac{100 \cdot {\rm Performance}}{{\rm Performance}_{\rm Peak}^{p}}\ \ (\%),</math></li></ul><br />
где <math>{\rm Performance}_{\rm Peak}^{p}</math> &ndash; пиковая производительность суперкомпьютера при <math>p</math> процессорах, вычисленная согласно спецификациям Intel<sup>&reg;</sup> XEON<sup>&reg;</sup> X5670<ref>"http://ark.intel.com/ru/products/47920/Intel-Xeon-Processor-X5670-12M-Cache-2_93-GHz-6_40-GTs-Intel-QPI"</ref>.<br />
</p><br />
<br />
Графики зависимости производительности и эффективности параллельной реализации k means от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>) представлены на рисунках 4 и 5, соответственно.<br />
<br />
[[file:Flops.png|thumb|center|800px|Рис. 4. Параллельная реализация k means. График зависимости производительности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
[[file:Efficiency.png|thumb|center|800px|Рис. 5. Параллельная реализация k means. График зависимости эффективности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
<br />
<p><br />
<b>В результате</b> экспериментальной оценки были получены следующие оценки эффективности реализации:<br />
<ul><br />
<li><b>Минимальное</b> значение: 0.000409 % достигается при <math>n=20'000, p=480</math></li><br />
<li><b>Максимальное</b> значение: 0.741119 % достигается при <math>n=300'000, p=1</math></li><br />
</ul><br />
</p><br />
<br />
<p><br />
Оценки масштабируемости реализации алгоритма k means:<br />
<ul><br />
<li><b>По числу процессоров:</b> -0.002683 &ndash; эффективность убывает с ростом числа процессоров. Данный результат вызван ростом накладных расходов для обеспечения параллельного выполнения алгоритма.</li><br />
<li><b>По размеру задачи:</b> 0.002779 &ndash; эффективность растет с ростом числа векторов. Данный результат вызван тем, что при увеличении размера задачи, количество вычислений растет по сравнению с временем, затрачиваемым на пересылку данных.</li><br />
<li><b>Общая оценка:</b> -0.000058 &ndash; можно сделать вывод, что в целом эффективность реализации незначительно уменьшается с ростом размера задачи и числа процессоров.</li><br />
</ul><br />
</p><br />
<br />
[https://github.com/serban/kmeans Использованная параллельная реализация алгоритма k means]<br />
<br />
==== Реализация 2 ====<br />
<br />
Исследование также проводилось на суперкомпьютере "Ломоносов".<br />
<br />
<p>Набор данных для тестирования состоял из 946000 векторов размерности 2 (координаты на сфере)</p><br />
<p>Набор и границы значений изменяемых параметров запуска реализации алгоритма:</p><br />
<br />
* число процессов (виртуальных ядер) [8 : 512];<br />
* число кластеров [128 : 384].<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации <math>2,47%</math> достигается при делении исходных данных на 128 кластеров с использованием 512 процессов;<br />
* максимальная эффективность реализации <math>7,13%</math> достигается при делении исходных данных на 352 кластера с использованием 8 процессов.<br />
На рисунках 6 и 7, соответственно, представлены графики зависимости производительности и эффективности параллельной реализации k means от числа кластеров и числа процессов.<br />
<br />
[[file:kmeans_performance.jpg|thumb|center|720px|Рис. 6. График зависимости производительности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
По рис. 6 можно отметить практически полное отсутствие роста производительности с увеличением числа процессов от 256 до 512 при минимальном размере задачи. Это связано с быстрым ростом накладных расходов по отношению к крайне низкому объёму вычислений. При росте размерности задачи данный эффект пропадает, и при одновременном пропорциональном увеличении числа кластеров и числа процессов рост производительности становится близким к линейному.<br />
<br />
[[file:kmeans_efficiency.jpg|thumb|center|720px|Рис. 7. График зависимости эффективности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
Исследовалась [https://github.com/like-a-bauss/kmeans параллельная реализация алгоритма k means на MPI]. <br />
<p>Были получены следующие оценки масштабируемости реализации алгоритма k means:</p><br />
*<i>По числу процессов:</i> <math>-0.02209</math>. Следовательно, с ростом числа процессов эффективность уменьшается. На рис. 7 можно наблюдать плавное и равномерное снижение производительности по мере увеличения числа процессов при неизменном числе кластеров, что свидетельствует об относительно невысоком росте накладных расходов на передачу данных между процессами и преобладании объёма вычислений над объёмом пересылок данных по сети.<br />
*<i>По размеру задачи:</i> <math>0.01252</math>. Следовательно, с ростом размера задачи (числа кластеров) эффективность увеличивается. При этом объём пересылок данных по сети пропорционален <math>(n + k) \cdot p</math> (где <math>k</math> - число кластеров, <math>n</math> - число входных векторов, <math>p</math> - число процессов) таким образом, поскольку <math>k << n</math>, рост накладных расходов с ростом числа кластеров при неизменном числе процессов и входных векторов представляет собой незначительную величину.<br />
*<i>Общая оценка:</i> <math>-0.00081</math>. Таким образом, с ростом и размера задачи, и числа процессов эффективность уменьшается. Это связано с тем, что отношение объёма вычислений к объёму передаваемых данных изменяется пропорционально <math>{kn \over (n + k) \cdot p} \thicksim {k \over p}</math>, что представляет собой невысокий коэффициент, но при этом позволяет параллельной реализации не деградировать до нулевой эффективности при значительном увеличении числа процессов.<br />
<br />
==== Реализация 3 ====<br />
<br />
Исследование масштабируемости алгоритма k-means в зависимости от количества используемых процессов было проведено в статье Кумара<ref>Kumar, J., Mills, R. T., Hoffman, F. M., & Hargrove, W. W. (2011). Parallel k-means clustering for quantitative ecoregion delineation using large data sets. Procedia Computer Science, 4, 1602-1611.</ref>. Исследование происходило на суперкомпьютере Jaguar - Cray XT5<ref>https://www.top500.org/system/176029</ref>. На момент экспериментов данный суперкомпьютер имел следующую конфигурацию: 18,688 вычислительных узлов с двумя шестнадцатиядерными процессорами AMD Opteron 2435 (Istanbul) 2.6 GHz, 16 GB of DDR2-800 оперативной памяти, и SeaStar 2+ роутер. Всего он состоял из 224,256 вычислительных ядер, 300 TB памяти, и пиковой производительностью 2.3 petaflops.<br />
<br />
Реализация алгоритма была выполнена на языке программирования C с использованием MPI.<br />
<br />
Объем данных составлял 84 ГБ, количество объектов (d-мерных векторов) n равнялось 1,024,767,667, размерность векторов <math>d</math> равнялась 22, количество кластеров <math>k</math> равнялось 1000. <br />
<br />
На рис. 8 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров. Можно отметить, что время, затраченное на чтение данных и запись результатов кластеризации, практически не изменяется с увеличением количества задействованных процессоров. Время же работы самого алгоритма кластеризации уменьшается с увеличением количества процессоров.<br />
<br />
[[Файл:k-means-proc-scalability.png|thumb|center|700px|Рис. 8. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
Также было произведено самостоятельное исследование масштабируемости алгоритма. Исследование производилось на суперкомпьютере "Blue Gene/P"<ref>http://hpc.cmc.msu.ru/bgp</ref>.<br />
<br />
Набор и границы значений изменяемых параметров запуска реализации алгоритма:<br />
<br />
* число процессоров [1, 2, 4, 8, 16, 32, 64, 128, 256, 512];<br />
* количество объектов [5000, 10000, 25000, 50000].<br />
<br />
Был использован набор данных ''Dataset for Sensorless Drive Diagnosis Data Set''<ref>PASCHKE, Fabian ; BAYER, Christian ; BATOR, Martyna ; MÖNKS, Uwe ; DICKS, Alexander ; ENGE-ROSENBLATT, Olaf ; LOHWEG, Volker: Sensorlose Zustandsüberwachung an Synchronmotoren, Bd. 46. In: HOFFMANN, Frank; HÃœLLERMEIER, Eyke (Hrsg.): Proceedings 23. Workshop Computational Intelligence. Karlsruhe : KIT Scientific Publishing, 2013 (Schriftenreihe des Instituts für Angewandte Informatik - Automatisierungstechnik am Karlsruher Institut für Technologie, 46), S. 211-225</ref> из репозитория ''Machine learning repository''<ref>https://archive.ics.uci.edu/ml/datasets/Dataset+for+Sensorless+Drive+Diagnosis</ref>.<br />
<br />
Исследуемый набор данных содержит векторы, размерность которых равна 49. Компоненты векторов являются вещественными числами. Количество кластеров равно 11. Пропущенные значения отсутствуют.<br />
<br />
Для исследования масштабируемости алгоритма была использована реализация на языке C с использованием MPI<ref>http://users.eecs.northwestern.edu/~wkliao/Kmeans/index.html</ref>. Код можно найти здесь: https://github.com/serban/kmeans. Данная реализация предоставляет возможность распараллеливать решение задачи с помощью технологий MPI, OpenMP И CUDA. Для запуска MPI-версии программы использовалась цель "mpi_main" Makefile.<br />
<br />
На рис. 9 показана зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров (использовались логарифмические оси). Разными цветами помечены запуски, соответствующие разным количествам объектам, участвующих в кластеризации. Можно видеть близкое к линейному увеличение времени работы программы в зависимости от количества процессоров. Также можно видеть увеличение времени работы алгоритма при увеличении количества объектов.<br />
[[Файл:Plot_1.png|thumb|center|900px|Рис. 9. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
На рис. 10 показана эта же зависимость, только в трехмерном пространстве. По аналогии с рис. 9, были использованы логарифмические оси. Как и в случае двумерного рисунка, можно видеть близкое к линейному увеличение времени работы программы.<br />
[[Файл:Kmeans-3d.png|thumb|center|900px|Рис. 10. Зависимости времени работы алгоритма кластеризации k-means в зависимости от количества используемых процессоров.]]<br />
<br />
==== Реализация 4 ====<br />
<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
В однопоточном режиме на наборах данных, представляющих практический интерес (порядка нескольких десятков тысяч векторов и выше), время работы алгоритма неприемлемо велико. Благодаря свойству массового параллелизма должно наблюдаться значительное ускорение алгоритма на многоядерных архитектурах (Intel Xeon), а также на графических процессорах, даже на мобильных вычислительных системах (ноутбуках), оснащенных видеокартой. Также алгоритм k means будет демонстрировать значительное ускорение на сверхмощных вычислительных комплексах (суперкомпьютерах, системах облачных вычислений<ref>"Issa. "Performance characterization and analysis for Hadoop K-means iteration". Journal of Cloud Computing, 2016"</ref>).<br />
<br />
На сегодняшний день существует множество реализаций алгоритма k means, в частности, направленных на оптимизацию параллельной работы на различных архитектурах<ref>"Raghavan R. A fast and scalable hardware architecture for K-means clustering for big data analysis : дис. – University of Colorado Colorado Springs. Kraemer Family Library, 2016."</ref><ref>"Yang, Luobin, et al. "High performance data clustering: a comparative analysis of performance for GPU, RASC, MPI, and OpenMP implementations." The Journal of supercomputing 70.1 (2014): 284-300."</ref><ref>"Li, You, et al. "Speeding up k-means algorithm by GPUs." Computer and Information Technology (CIT), 2010 IEEE 10th International Conference on. IEEE, 2010."</ref>. Предлагается множество адаптаций алгоритма под конкретные архитектуры. Например, авторы работы<ref>"Kanan, Gebali, Ibrahim. "Fast and Area-Efficient Hardware Implementation of the K-means<br />
Clustering Algorithm". WSEAS Transactions on circuits and systems. Vol. 15. 2016"</ref> производят перерасчет центров кластеров на этапе распределения векторов по кластерам.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
==== Открытое программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.icpsr.umich.edu/CrimeStat/ CrimeStat]</li> Программное обеспечение, созданное для операционных систем Windows, предоставляющее инструменты статистического и пространственного анализа для решения задачи картирования преступности.<br />
<li>[http://juliastats.github.io Julia]</li> Высокоуровневый высокопроизводительный свободный язык программирования с динамической типизацией, созданный для математических вычислений, содержит реализацию k-means.<br />
<li>[https://mahout.apache.org Mahout]</li> Apache Mahout - Java библиотека для работы с алгоритмами машинного обучения с использованием MapReduce. Содержит реализацию k-means.<br />
<li>[https://www.gnu.org/software/octave/ Octave]</li> Написанная на C++ свободная система для математических вычислений, использующая совместимый с MATLAB язык высокого уровня, содержит реализацию k-means.<br />
<li>[http://spark.apache.org/docs/latest/mllib-clustering.html Spark]</li> Распределенная реализация k-means содержится в библиотеке Mlib для работы с алгоритмами машинного обучения, взаимодействующая с Python библиотекой NumPy и библиотека R.<br />
<li>[http://torch.ch Torch]</li> MATLAB-подобная библиотека для языка программирования Lua с открытым исходным кодом, предоставляет большое количество алгоритмов для глубинного обучения и научных расчётов. Ядро написано на Си, прикладная часть выполняется на LuaJIT, поддерживается распараллеливание вычислений средствами CUDA и OpenMP. Существуют реализации k-means.<br />
<li>[http://www.cs.waikato.ac.nz/ml/weka/ Weka]</li> Cвободное программное обеспечение для анализа данных, написанное на Java. Содержит k-means и x-means.<br />
<li>[http://accord-framework.net/docs/html/T_Accord_MachineLearning_KMeans.htm Accord.NET]</li> C# реализация алгоритмов k-means, k-means++, k-modes.<br />
<li>[http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html OpenCV]</li> Написанная на С++ библиотека, направленная в основном на решение задач компьютерного зрения. Содержит реализацию k-means.<br />
<li>[http://mlpack.org/ MLPACK]</li> Масштабируемая С++ библиотека для работы с алгоритмами машинного обучения, содержит реализацию k-means.<br />
<li>[https://www.scipy.org/ SciPy]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[http://scikit-learn.org/ scikit-learn]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[https://www.r-bloggers.com/k-means-clustering-in-r/ R]</li> Язык программирования для статистической обработки данных и работы с графикой, а также свободная программная среда вычислений с открытым исходным кодом в рамках проекта GNU, содержит три реализации k-means.<br />
<li>[http://elki.dbs.ifi.lmu.de ELKI]</li> Java фреймворк, содержащий реализацию k-means, а также множество других алгоритмов кластеризации.<br />
</ol><br />
<br />
==== Проприетарное программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.ayasdi.com/blog/bigdata/topological-data-analysis-of-oil-and-gas-petrophysical-data/ Ayasdi]</li><br />
<li>[http://www.stata.com/features/cluster-analysis/ Stata]</li><br />
<li>[http://mathworld.wolfram.com/K-MeansClusteringAlgorithm.html Mathematica]</li><br />
<li>[http://www.mathworks.com/help/stats/kmeans.html?requestedDomain=www.mathworks.com MATLAB]</li><br />
<li>[https://support.sas.com/rnd/app/stat/procedures/fastclus.html SAS]</li><br />
<li>[http://docs.rapidminer.com/studio/operators/modeling/segmentation/k_means.html RapidMiner]</li><br />
<li>[https://blogs.sap.com/2013/03/28/sap-hana-pal-k-means-algorithm-or-how-to-do-customer-segmentation-for-the-telecommunications-industry/ SAP HANA]</li><br />
</ol><br />
<br />
== Литература ==<br />
<br />
<references /></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_k_%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D1%85_(k-means)&diff=25242Алгоритм k средних (k-means)2018-01-16T16:43:29Z<p>Konshin: </p>
<hr />
<div>Основные авторы статьи (разделы 1, 2.4.1, 2.6-2.7, 3):<br />
[https://algowiki-project.org/ru/Участник:Бротиковская_Данута<b>Д.Бротиковская</b>] и<br />
[https://algowiki-project.org/ru/Участник:ХХ<b>ХХ</b>]<br/><br />
Авторы отдельных разделов:<br />
[https://algowiki-project.org/ru/Участник:IanaV<b>Я.А.Валуйская</b>] и<br />
[https://algowiki-project.org/ru/Участник:GlotovES<b>Е.С. Глотов</b>] (раздел 2.4.2),<br />
[https://algowiki-project.org/ru/Участник:ХХ<b>ХХ</b>] и<br />
[https://algowiki-project.org/ru/Участник:ХХ<b>ХХ</b>] (раздел 2.4.3),<br />
[https://algowiki-project.org/ru/Участник:ХХ<b>ХХ</b>] и<br />
[https://algowiki-project.org/ru/Участник:ХХ<b>ХХ</b>] (раздел 2.4.4)<br />
<br />
{{algorithm<br />
| name = Алгоритм <math>k</math> средних (<math>k</math>-means)<br />
| serial_complexity = <math>O(ikdn)</math><br />
| input_data = <math> dn </math><br />
| output_data = <math> n </math><br />
}}<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Алгоритм <b><i>k средних</i></b> (англ. k-means) - один из алгоритмов машинного обучения, решающий задачу кластеризации.<br />
Этот алгоритм является неиерархическим<ref>"https://ru.wikipedia.org/wiki/Иерархическая_кластеризация"</ref>, итерационным методом кластеризации<ref>"https://ru.wikipedia.org/wiki/Кластерный_анализ"</ref>, он получил большую популярность благодаря своей простоте, наглядности реализации и достаточно высокому качеству работы. <br />
Был изобретен в 1950-х годах математиком <i>Гуго Штейнгаузом</i><ref>Steinhaus, Hugo. "Sur la division des corp materiels en parties." Bull. Acad. Polon. Sci 1.804 (1956): 801.</ref> и почти одновременно <i>Стюартом Ллойдом</i><ref>Lloyd, S. P. "Least square quantization in PCM. Bell Telephone Laboratories Paper. Published in journal much later: Lloyd, SP: Least squares quantization in PCM." IEEE Trans. Inform. Theor.(1957/1982).</ref>. Особую популярность приобрел после публикации работы <i>МакКуина</i><ref>MacQueen, James. "Some methods for classification and analysis of multivariate observations." Proceedings of the fifth Berkeley symposium on mathematical statistics and probability. Vol. 1. No. 14. 1967.</ref> в 1967.<br />
<br />
Алгоритм представляет собой версию EM-алгоритма<ref>"https://ru.wikipedia.org/wiki/EM-алгоритм"</ref>, применяемого также для разделения смеси гауссиан. Основная идея <i>k means</i> заключается в том, что данные произвольно разбиваются на кластеры, после чего итеративно перевычисляется центр масс для каждого кластера, полученного на предыдущем шаге, затем векторы разбиваются на кластеры вновь в соответствии с тем, какой из новых центров оказался ближе по выбранной метрике.<br />
<br />
Цель алгоритма заключается в разделении <math>n</math> наблюдений на <math>k</math> кластеров таким образом, чтобы каждое наблюдение принадлежало ровно одному кластеру, расположенному на наименьшем расстоянии от наблюдения.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
<b>Дано:</b><br />
* набор из <math>n</math> наблюдений <math>X=\{\mathbf{x}_1, \mathbf{x}_2, ..., \mathbf{x}_n\}, \mathbf{x}_i \in \mathbb{R}^d, \ i=1,...,n</math>;<br />
* <math>k</math> - требуемое число кластеров, <math>k \in \mathbb{N}, k \leq n</math>.<br />
<br />
<b>Требуется:</b><br />
<br />
Разделить множество наблюдений <math>X</math> на <math>k</math> кластеров <math>S_1, S_2, ..., S_k</math>:<br />
* <math>S_i \cap S_j= \varnothing, \quad i \ne j</math><br />
<br />
* <math>\bigcup_{i=1}^{k} S_i = X</math><br />
<br />
<b>Действие алгоритма:</b><br />
<p>Алгоритм <i>k means</i> разбивает набор <math>X</math> на <math>k</math> наборов <math>S_1, S_2, ..., S_k,</math> таким образом, чтобы минимизировать сумму квадратов расстояний от каждой точки кластера до его центра (центр масс кластера). Введем обозначение, <math>S=\{S_1, S_2, ..., S_k\}</math>. Тогда действие алгоритма <i>k means</i> равносильно поиску:</p><br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math>\arg\min_{S} \sum\limits_{i=1}^k \sum\limits_{\mathbf{x} \in S_i} \rho(\mathbf{x}, \mathbf{\mu}_i )^2,</math></td><br />
<td align="right"><math>(1)</math></td><br />
</tr><br />
</table><br />
где <math>\mathbf{\mu}_i</math> &ndash; центры кластеров, <math>i=1,...,k, \quad \rho(\mathbf{x}, \mathbf{\mu}_i)</math> &ndash; функция расстояния между <math>\mathbf{x}</math> и <math>\mu_i</math><br />
<br />
<b>Шаги алгоритма:</b><br />
<ol><br />
<li><br />
<b>Начальный шаг: инициализация кластеров</b><br />
<p>Выбирается произвольное множество точек <math>\mu_i, \ i=1,...,k,</math> рассматриваемых как начальные центры кластеров: <math>\mu_i^{(0)} = \mu_i, \quad i=1,...,k</math></p><br />
</li><br />
<li><br />
<b>Распределение векторов по кластерам</b><br />
<p><b>Шаг</b> <math>t: \forall \mathbf{x}_i \in X, \ i=1,...,n: \mathbf{x}_i \in S_j \iff j=\arg\min_{k}\rho(\mathbf{x}_i,\mathbf{\mu}_k^{(t-1)})^2</math></p><br />
</li><br />
<li><br />
<b>Пересчет центров кластеров</b><br />
<p><b>Шаг </b> <math>t: \forall i=1,...,k: \mu_i^{(t)} = \cfrac{1}{|S_i|}\sum_{\mathbf{x}\in S_i}\mathbf{x}</math></p><br />
</li><br />
<li><br />
<b>Проверка условия останова:</b><br />
<p></p><br />
'''if''' <math>\exist i\in \overline{1,k}: \mu_i^{(t)} \ne \mu_i^{(t-1)}</math> '''then'''<br />
t = t + 1;<br />
goto 2;<br />
'''else'''<br />
'''stop'''<br />
</li><br />
</ol><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром являются шаги 2 и 3 приведенного выше алгоритма: <b><i>распределение векторов по кластерам</i></b> и <b><i>пересчет центров кластеров</i></b>. <br />
<br />
<p><br />
<b><i>Распределение векторов</i></b> по кластерам предполагает вычисление расстояний между каждым вектором <math>\mathbf{x}_i \in X, \ i= 1,...,n</math> и центрами кластера <math>\mathbf{\mu}_j, \ j= 1,...,k</math>. Таким образом, данный шаг предполагает <math>kn</math> вычислений расстояний между <math>d</math>-мерными векторами. <br />
</p><br />
<p><br />
<b><i>Пересчет центров кластеров</i></b> предполагает <math>k</math> вычислений центров масс <math>\mathbf{\mu}_i</math> множеств <math>S_i, \ i=1,...,k,</math> представленных выражением в шаге 3 представленного выше алгоритма.<br />
</p><br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
<b>Инициализация центров масс <math>\mu_1, ..., \mu_k</math></b>. <br />
<br />
Наиболее распространенными являются следующие стратегии:<br />
<ul><br />
<li><br />
<b>Метод Forgy</b><br>В качестве начальных значений <math>\mu_1, ..., \mu_k</math> берутся случайно выбранные векторы.<br />
</li><br />
<li><br />
<b>Метод случайно разделения (Random Partitioning)</b><br>Для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> выбирается случайным образом кластер <math>S_1, ..., S_k</math>, после чего для каждого полученного кластера вычисляются значения <math>\mu_1, ..., \mu_k</math>.<br />
</li><br />
</ul><br />
<br />
<b>Распределение векторов по кластерам</b><br />
<br />
Для этого шага алгоритма между векторами <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> и центрами кластеров <math>\mu_1,...,\mu_k</math> вычисляются <b>расстояния</b> по формуле (как правило, используется Евлидово расстояние):<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mathbf{v}_1, \mathbf{v}_2 \in \mathbb{R}^d, \quad \rho(\mathbf{v}_1, \mathbf{v}_2) = \lVert \mathbf{v}_1- \mathbf{v}_2 \rVert= \sqrt{\sum_{i=1}^{d}(\mathbf{v}_{1,i} - \mathbf{v}_{2,i})^2}</math></td><br />
<td align="right"><math>(2)</math></td><br />
</tr><br />
</table><br />
<br />
<b>Пересчет центров кластеров</b><br />
<br />
Для этого шага алгоритма производится пересчет центров кластера по <b>формуле вычисления центра масс</b>:<br />
<table style="width:100%"><br />
<tr><br />
<td align="center" width="90%"><math> \mu = \cfrac{1}{|S|}\sum_{\mathbf{x}\in S}\mathbf{x}</math></td><br />
<td align="right"><math>(3)</math></td><br />
</tr><br />
</table><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
'''1.''' Инициализировать центры кластеров <math>\mathbf{\mu}_i^{(1)}, \ i=1,...,k</math><br><br />
'''2.''' <math>t \leftarrow 1</math><br><br />
'''3.''' Распределение по кластерам<br><br />
<math>\quad S_i^{(t)}=\{\mathbf{x}_p: \lVert\mathbf{x}_p-\mathbf{\mu}_i^{(t)}\rVert^2 \leq \lVert\mathbf{x}_p-\mathbf{\mu}_j^{(t)}\rVert^2 \quad \forall j=1,...,k\},</math><br><br />
<math>\quad</math>где каждый вектор <math>\mathbf{x}_p</math> соотносится единственному кластеру <math>S^{(t)}</math><br><br />
'''4.''' Обновление центров кластеров<br><br />
<math>\quad \mathbf{\mu}_i^{(t+1)} = \frac{1}{|S^{(t)}_i|} \sum_{\mathbf{x}_j \in S^{(t)}_i} \mathbf{x}_j </math><br><br />
'''5.''' '''if''' <math>\exists i \in \overline{1,k}: \mathbf{\mu}_i^{(t+1)} \ne \mathbf{\mu}_i^{(t)}</math> '''then'''<br><br />
<math>\quad t = t + 1</math>;<br><br />
<math>\quad</math>goto '''3''';<br><br />
<math>~~~</math>'''else'''<br><br />
<math>\quad</math>'''stop'''<br><br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
<div style="padding-bottom: 20px"><br />
<p>Обозначим <math>\Theta_{\rm centroid}^{d, m}</math> временную сложность вычисления центорида кластера, число элементов которого равна <math>m</math>, в d-мерном пространстве.</p><br />
<p>Аналогично <math>\Theta_{\rm distance}^d</math> &ndash; временная сложность вычисления расстояния между двумя d-мерными векторами.</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<b>Сложность шага инициализации <math>k</math> кластеров мощности <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm init}^{k, d, m}</math></b><br />
<ul><br />
<li><i>Стратерия Forgy</i>: вычисления не требуются, <math>\Theta_{\rm init}^{k, d, m} = 0</math></li><br />
<li><i>Стратегия случайного разбиения</i>: вычисление центров <math>k</math> кластеров, <math>\Theta_{\rm init}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}, m \le n</math></li><br />
</ul><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Cложность шага распределения d мерных векторов по <math>k</math> кластерам &ndash; <math>\Theta_{\rm distribute}^{k, d}</math></b></p><br />
<p><br />
На этом шаге для каждого вектора <math>\mathbf{x}_i \in X, \ i=1,...,n,</math> вычисляется <math>k</math> расстояний до центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math><br />
</p><br />
<p align="center"><br />
<math>\Theta_{\rm distribute}^{k, d} = n \cdot k \cdot \Theta_{\rm distance}^d</math><br />
</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><br />
<b>Сложность шага пересчета центров <math>k</math> кластеров размера <math>m</math> в d-мерном пространстве &ndash; <math>\Theta_{\rm recenter}^{k, d, m}</math></b><br />
</p><br />
<p>На этом шаге вычисляется <math>k</math> центров кластеров <math>\mathbf{\mu}_1, ...\mathbf{\mu}_k</math></p><br />
<p align="center"><math>\Theta_{\rm recenter}^{k, d, m} = k \cdot \Theta_{\rm centroid}^{d, m}</math> </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm centroid}^{d, m}</math> для кластера, число элементов которого равно <math>m</math></b></p><br />
<p align="center"><math>\Theta_{\rm centroid}^{d, m}</math> = <math>m \cdot d</math> сложений + <math>d</math> делений </p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p><b>Рассчитаем <math>\Theta_{\rm distance}^d</math> в соответствие с формулой <math>(2)</math></b></p><br />
<p align="center"><math>\Theta_{\rm distance}^d</math> = <math>d</math> вычитаний + <math>d</math> умножений + <math>(d-1)</math> сложение</p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Предположим, что алгоритм сошелся за <math>i</math> итераций, тогда временная сложность алгоритма <math>\Theta_{\rm k-means}^{d, n}</math></p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le \Theta_{\rm init}^{k, d, n} + i(\Theta_{\rm distribute}^{k, d} + \Theta_{\rm recenter}^{k, d, n})</math></b></p><br />
</div><br />
<div style="padding-bottom: 5px"><br />
<p>Операции сложения/вычитания:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le knd+ i(kn(2d-1) + knd) = knd+ i(kn(3d-1)) \thicksim O(ikdn)</math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Операции умножения/деления:</p><br />
<p align="center"><b><math>\Theta_{\rm k-means}^{d, n} \le kd + i(knd + kd) = kd + ikd(n+1) \thicksim O(ikdn) </math></b></p><br />
</div><br />
<div style="padding-bottom: 20px"><br />
<p>Получаем, что <b>временная сложность</b> алгоритма <b>k means</b> кластеризации <math>n</math> <b>d-мерных</b> векторов на <math>k</math> кластеров за <math>i</math> итераций:</p><br />
<p align="center"><b><math> \Theta_{\rm k-means}^{d, n} \thicksim O(ikdn) </math></b></p><br />
</div><br />
<br />
=== Информационный граф ===<br />
<br />
Рассмотрим информационный граф алгоритма. Алгоритм <i>k means</i> начинается с этапа инициализации, после которого следуют итерации, на каждой из которых выполняется два последовательных шага (см. [[#Схема реализации последовательного алгоритма|"Схема реализации последовательного алгоритма"]]): <br />
* распределение векторов по кластерам<br />
* перерасчет центров кластеров<br />
<br />
Поскольку основная часть вычислений приходится на шаги итераций, распишем информационные графы данных шагов.<br />
<br />
<p><b>Распределение векторов по кластерам</b></p><br />
Информационный граф шага распределения векторов по кластерам представлен на ''рисунке 1''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также центры кластеров <math>\mathbf{\mu}_1, ... \mathbf{\mu}_k</math>, вычисленные ранее (на шаге инициализации, если рассматривается первая итерация алгоритма, или на шаге пересчета центров кластеров предыдущей итерации в противном случае). Каждая пара векторов данных <math>\mathbf{x}_i, \ i=1,...,n,</math> и центров кластера <math>\mathbf{\mu}_j, \ j=1,...,k</math> : (<math>\mathbf{x}_i</math>, <math>\mathbf{\mu}_j</math>) подаются на независимые узлы <i>"d"</i> вычисления расстояния между векторами (более подробная схема вычисления расстояния представлена далее, ''рисунок 2''). Далее узлы вычисления расстояния <i>"d"</i>, соответствующие одному и тому же исходному вектору <math>\mathbf{x}_i</math> передаются на один узел <i>"m"</i>, где далее происходит вычисление новой метки кластера для каждого вектора <math>\mathbf{x}_i</math> (берется кластер с минимальным результатом вычисления расстояния). На выходе графа выдаются метки кластеров , <math>L_1, ..., L_n</math>, такие что <math>\forall \mathbf{x}_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>. <br />
<br />
[[file:Clusterization.png|thumb|center|800px|Рис. 1. Схема распределения векторов по кластерам. ''d'' &ndash; вычисление расстояния между векторами; ''m'' &ndash; вычисление минимума.]]<br />
<br />
<p><b>Вычисление расстояния между векторами</b></p><br />
Подробная схема вычисления расстояния между векторами <math>\mathbf{x}_i, \mathbf{\mu}_j</math> представлена на ''рисунке 2''. Как показано на графе, узел вычисления расстояния между векторами <i>"d"</i> состоит из шага взятия разности между векторами (узел "<math>-</math>") и взятия нормы получившегося вектора разности (узел "<math>||\cdot||^2</math>"). Более подробно, вычисление расстояния между векторами <math>\mathbf{x}_i = {x_{i1,}, ...,{x_{in}}}, \mathbf{\mu}_j = {\mu_{j1}, ...,\mu_{jn}}</math> может быть представлено как вычисление разности между каждой парой компонент <math>(x_{iz}, \mu_{jz}), \ z=1,...,d</math> (узел "<math>-</math>"), далее возведение в квадрат для каждого узла "<math>-</math>" (узел "<math>()^2</math>") и суммирования выходов всех узлов "<math>()^2</math>" (узел "<math>+</math>"). <br />
<br />
[[file:dist_calc.png|thumb|center|800px|Рис. 2. Схема вычисления расстояния между вектором и центром кластера.]]<br />
<br />
<p><b>Пересчет центров кластеров</b></p><br />
Информационный граф шага пересчета центров кластеров представлен на ''рисунке 3''. Исходами данного графа является исходные векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math>, а также им соответствующие метки кластера, <math>L_1, ..., L_n</math>, такие что <math>\forall x_i, \ i=1,...,n, \ \mathbf{x}_i \in S_j \Leftrightarrow L_i = j</math>, вычисленные на этапе распределения векторов по кластерам. Все векторы <math>\mathbf{x}_1, ..., \mathbf{x}_n</math> подаются в узлы <math>+_1, ...+_k</math>, каждый узел <math>+_m, \ m = 1,...,k,</math> соответствует операции сложения векторов кластера с номером <math>m</math>. Метки кластера <math>L_1, ..., L_n</math> также совместно передаются на узлы <math>S_m, \ m=1,...,k</math>, на каждом из которых вычисляется количество векторов в соответствующем кластере (количество меток с соответствующим значением). Далее каждая пара выходов узлов <math>+_m</math> и <math>S_m</math> подается на узел "<math>/</math>", где производится деление суммы векторов кластера на количество элементов в нем. Значения, вычисленные на узлах "<math>/</math>", присваиваются новым центрам кластеров (выходные значения графа).<br />
<br />
[[file:Recluster.png|thumb|center|800px|Рис. 3. Схема пересчета центров кластеров]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
<p><br />
Работа алгоритма состоит из <math>i</math> итераций, в каждой из которых происходит <b>распределение d-мерных векторов по <math>k</math> кластерам</b>, а также <b>пересчет центров кластеров в d-мерном пространстве</b>. В шаге <b>распределения d-мерных векторов по <math>k</math> кластерам</b> расстояния между вектором и центрами кластеров вычисляются независимо (отсутствуют информационные зависимости). <b>Центры масс кластеров</b> также пересчитываются независимо друг от друга. Таким образом, имеет место [[глоссарий#Массовый параллелизм|''массовый параллелизм'']]. Вычислим параллельную сложность <math>\Psi_*</math> каждого из шагов, а также параллельную сложность всего алгоритма, <math>\Psi_{\rm k-means}</math>. Будем исходить из предположения, что может быть использовано любое необходимое число потоков.<br />
</p><br />
<br />
<p><b>Распределение <math>d</math>-мерных векторов по <math>k</math> кластерам</b></p> <br />
<p><br />
Поскольку на данном шаге для каждой пары векторов <math>\mathbf{x}_i, \ i=1,...,n</math> и <math>\mathbf{\mu}_j, \ j=1,...,k,</math> операции вычисления расстояния не зависят друг от друга, они могут выполняться параллельно. Тогда, разделив все вычисление расстояний на <math>n</math> потоков, получим, что в каждом потоке будет выполняться только одна операция вычисления расстояния между векторами размерности d. При этом каждому вычислительному потоку передаются координаты центров всех кластеров <math>\mathbf{\mu}_1, ..., \mathbf{\mu}_k</math>. Таким образом, параллельная сложность данного шага определяется <i> сложностью параллельной операции вычисления расстояния между d-мерными векторами</i>, <math>\Psi_{\rm distance}^d</math> и <i>сложностью определения наиболее близкого кластера</i> (паралельное взятие минимума по расстояниям), <math>\Psi_{\rm min}^k</math>. Для оценки <math>\Psi_{\rm distance}^d</math> воспользуемся [[Нахождение_частных_сумм_элементов_массива_сдваиванием | параллельной реализацией нахождения частичной суммы элементов массива путем сдваивания]]. Аналогично, <math>\Psi_{\rm min}^k = log(k)</math>. В результате, <math>\Psi_{\rm distance}^d = O(log(d))</math>. Таким образом: <br />
</p><br />
<p align="center"><math>\Psi_{\rm distribute}^{k, d} = \Psi_{\rm distance}^d + \Psi_{\rm min}^k = O(log(d))+O(log(k)) = O(log(kd))</math></p><br />
<br />
<p><b>Пересчет центров кластеров в d-мерном пространстве</b></p><br />
<p><br />
Поскольку на данном шаге для каждого из <math>k</math> кластеров центр масс может быть вычислен независимо, данные операции могут быть выполнены в отдельных потоках. Таким образом, параллельная сложность данного шага, <math>\Psi_{\rm recenter}^{k, d}</math>, будет определяться <i>параллельной сложностью вычисления одного центра масс кластера размера <math>m</math></i>, <math>\Psi_{\rm recenter}^{k, d}</math>, а так как <math>m \le n \Rightarrow \Psi_{\rm recenter}^{d, m} \le \Psi_{\rm recenter}^{d, n}</math>. Сложность вычисления центра масс кластера d-мерных векторов размера n аналогично предыдущим вычислениям равна <math>O(log(n))</math>. Тогда: <br />
</p><br />
<p align="center"><math>\Psi_{\rm recenter}^{k, d} \le \Psi_{\rm recenter}^{d, n} = O(log(n))</math></p><br />
<br />
<p><b>Общая параллельная сложность алгоритма</b></p><br />
<p><br />
На каждой итерации необходимо обновление центров кластеров, которые будут использованы на следующей итерации. Таким образом, итерационный процесс выполняется последовательно<ref>Zhao, Weizhong, Huifang Ma, and Qing He. "Parallel k-means clustering based on mapreduce." IEEE International Conference on Cloud Computing. Springer Berlin Heidelberg, 2009.</ref>. Тогда, поскольку сложность каждой итерации определяется <math>\Psi_{\rm distribute}^{k, d}</math> и <math>\Psi_{\rm recenter}</math>, сложность всего алгоритма, <math>\Psi_{\rm k-means}</math> в предположении, что было сделано <math>i</math> операций определяется выражением<br />
</p><br />
<p align="center"><math>\Psi_{\rm k-means} \approx i \cdot (\Psi_{\rm distribute}^{k, d} + \Psi_{\rm recenter}^{k, d}) \le i \cdot O(log(kdn))</math></p><br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
<p><br />
<b>Входные данные</b><br><br />
<ul><br />
<li>Матрица из <math>n \cdot d</math> элементов <math>x_{i, j} \in \mathbb{R}, \ i=1,...,n, \ j=1,...,d,</math> &ndash; координат векторов (наблюдений).</li><br />
<li>Целое положительное число <math>k, \ k \le n</math> &ndash; количество кластеров.</li><br />
</ul><br />
</p><br />
<p><br />
<b>Объем входных данных</b><br><br />
<math>1</math> целое число + <math>n \cdot d</math> вещественных чисел (при условии, что координаты &ndash; вещественные числа).<br />
</p><br />
<p><br />
<b>Выходные данные</b><br><br />
<math>n</math> целых положительных чисел <math>L_1, ..., L_n</math>&ndash; номера кластеров, соотвествующие каждому вектору (при условии, что нумерация кластеров начинается с <math>1</math>).<br />
</p><br />
<p><br />
<b>Объем выходных данных</b><br><br />
<math>n</math> целых положительных чисел.<br />
</p><br />
<br />
=== Свойства алгоритма ===<br />
<br />
<p><b>[[глоссарий#Вычислительная мощность|''Вычислительная мощность'']]</b></p><br />
Вычислительная мощность алгоритма <i>k means</i> равна <math>\frac{ikdn}{nd} = ki </math>, где <math>k</math> &ndash; число кластеров, <math>i</math> &ndash; число итераций алгоритма.<br />
<br />
<p><b>[[глоссарий#Детерминированность |''Детерминированность'']] и [[глоссарий#Устойчивость |''Устойчивость'']] </b></p><br />
Алгоритм <i>k means</i> является итерационным. Количество итераций алгоритма в общем случае не фиксируется и зависит от начального расположения объектов в пространстве, параметра <math>k</math>, а также от начального приближения центров кластеров, <math>\mu_1, ..., \mu_k</math>. В результате этого может варьироваться результат работы алгоритма. При неудачном выборе начальных параметров итерационный процесс может сойтись к локальному оптимуму<ref>Von Luxburg, Ulrike. Clustering Stability. Now Publishers Inc, 2010.</ref>. По этим причинам алгоритм не является ни <b>детермирированным</b>, ни <b>устойчивым</b>.<br />
<br />
<p><b>Соотношение последовательной и параллельной сложности алгоритма</b></p><br />
<br />
<math>\frac{\Theta_{\rm k-means}}{\Psi_{\rm k-means}} = \frac{O(ikdn)}{O(i \cdot log(kdn))}</math><br />
<br />
<b>Сильные стороны алгоритма</b>:<br />
<ul><br />
<li><i>Сравнительно высокая эффективность при простоте реализации</i></li><br />
<li><i>Высокое качество кластеризации</i></li><br />
<li><i>Возможность распараллеливания</i></li><br />
<li><i>Существование множества модификаций</i></li><br />
</ul><br />
<b>Недостатки алгоритма</b><ref>Ortega, Joaquín Pérez, Ma Del Rocío Boone Rojas, and María J. Somodevilla. "Research issues on K-means Algorithm: An Experimental Trial Using Matlab."</ref>:<br />
<ul><br />
<li><i>Количество кластеров является параметром алгоритма</i></li><br />
<li><br />
<p><i>Чувствительность к начальным условиям</i></p><br />
<p>Инициализация центров кластеров в значительной степени влияет на результат кластеризации.</p><br />
</li><br />
<li><br />
<p><i>Чувствительность к выбросам и шумам</i></p><br />
<p>Выбросы, далекие от центров настоящих кластеров, все равно учитываются при вычислении их центров.</p><br />
</li><br />
<li><br />
<p><i>Возможность сходимости к локальному оптимуму</i></p><br />
<p>Итеративный подход не дает гарантии сходимости к оптимальному решению.</p><br />
</li><br />
<li><br />
<p><i>Использование понятия "среднего"</i></p><br />
<p>Алгоритм неприменим к данным, для которых не определено понятие "среднего", например, категориальным данным.</p><br />
</li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализации алгоритма <math>k</math> средних согласно [[Scalability methodology|методике]] AlgoWiki.<br />
<br />
==== Реализация 1 ====<br />
<br />
Исследование масштабируемости параллельной реализации алгоритма k means проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Алгоритм реализован на языке C с использованием средств MPI.<br />
Для исследования масштабируемости проводилось множество запусков программы с разным значением параметра (количество векторов для кластеризации), а также с различным числом процессоров. Фиксировались результаты запусков &ndash; время работы <math>t</math> и количество произведенных итераций алгоритма <math>i</math>.<br />
<br />
Параметры запусков для экспериментальной оценки:<br />
<ul><br />
<li>Значения <b>количества векторов</b> <math>n</math>: 20'000, 30'000, 50'000, 100'000, 200'000, 300'000, 500'000, 700'000, 1'000'000, 1'500'000, 2'000'000.</li><br />
<li>Значения <b>количества процессоров</b> <math>p</math>: 1, 8, 16, 32, 64, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512.</li><br />
<li>Значение <b>количества кластеров</b> <math>k</math>: 100.</li><br />
<li>Значение <b>размерности векторов</b> <math>d</math>: 10.</li><br />
</ul><br />
<br />
Для проведения экспериментов были сгенерированы нормально распределенные псевдослучайные данные (с использованием Python библиотеки [http://scikit-learn.org/ scikit-learn]):<br />
<syntaxhighlight lang="python"><br />
from sklearn.datasets import make_classification<br />
X1, Y1 = make_classification(n_features=10, n_redundant=2, n_informative=8,<br />
n_clusters_per_class=1, n_classes=100,<br />
n_samples=2000000)<br />
</syntaxhighlight><br />
<br />
Для заданной конфигурации эксперимента (<math>n, d, p, k</math>) и полученных результатов (<math>t, i</math>) [[Глоссарий#Производительность|производительность]] и эффективность реализации расчитывались по формулам:<br />
<p><br />
<ul><li><math>{\rm Performance} = \frac{N_{\rm k-means}^{d, n}}{t}\ \ ({\rm FLOPS}),</math></li></ul><br />
где <math>N_{\rm k-means}^{d, n}</math> &ndash; точное число операций с плавающей точкой (операции с памятью, а также целочисленные операции не учитывались), вычисленное в соответствие с разделом [[#Последовательная сложность алгоритма| "Последовательная сложность алгоритма"]];<br />
</p><br />
<p><br />
<ul><li><math>{\rm Efficiency} = \frac{100 \cdot {\rm Performance}}{{\rm Performance}_{\rm Peak}^{p}}\ \ (\%),</math></li></ul><br />
где <math>{\rm Performance}_{\rm Peak}^{p}</math> &ndash; пиковая производительность суперкомпьютера при <math>p</math> процессорах, вычисленная согласно спецификациям Intel<sup>&reg;</sup> XEON<sup>&reg;</sup> X5670<ref>"http://ark.intel.com/ru/products/47920/Intel-Xeon-Processor-X5670-12M-Cache-2_93-GHz-6_40-GTs-Intel-QPI"</ref>.<br />
</p><br />
<br />
Графики зависимости производительности и эффективности параллельной реализации k means от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>) представлены на рисунках 4 и 5, соответственно.<br />
<br />
[[file:Flops.png|thumb|center|800px|Рис. 4. Параллельная реализация k means. График зависимости производительности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
[[file:Efficiency.png|thumb|center|800px|Рис. 5. Параллельная реализация k means. График зависимости эффективности реализации алгоритма от числа векторов для кластеризации (<math>n</math>) и числа процессоров (<math>p</math>).]]<br />
<br />
<p><br />
<b>В результате</b> экспериментальной оценки были получены следующие оценки эффективности реализации:<br />
<ul><br />
<li><b>Минимальное</b> значение: 0.000409 % достигается при <math>n=20'000, p=480</math></li><br />
<li><b>Максимальное</b> значение: 0.741119 % достигается при <math>n=300'000, p=1</math></li><br />
</ul><br />
</p><br />
<br />
<p><br />
Оценки масштабируемости реализации алгоритма k means:<br />
<ul><br />
<li><b>По числу процессоров:</b> -0.002683 &ndash; эффективность убывает с ростом числа процессоров. Данный результат вызван ростом накладных расходов для обеспечения параллельного выполнения алгоритма.</li><br />
<li><b>По размеру задачи:</b> 0.002779 &ndash; эффективность растет с ростом числа векторов. Данный результат вызван тем, что при увеличении размера задачи, количество вычислений растет по сравнению с временем, затрачиваемым на пересылку данных.</li><br />
<li><b>Общая оценка:</b> -0.000058 &ndash; можно сделать вывод, что в целом эффективность реализации незначительно уменьшается с ростом размера задачи и числа процессоров.</li><br />
</ul><br />
</p><br />
<br />
[https://github.com/serban/kmeans Использованная параллельная реализация алгоритма k means]<br />
<br />
==== Реализация 2 ====<br />
<br />
Исследование также проводилось на суперкомпьютере "Ломоносов".<br />
<br />
<p>Набор данных для тестирования состоял из 946000 векторов размерности 2 (координаты на сфере)</p><br />
<p>Набор и границы значений изменяемых параметров запуска реализации алгоритма:</p><br />
<br />
* число процессов (виртуальных ядер) [8 : 512];<br />
* число кластеров [128 : 384].<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации <math>2,47%</math> достигается при делении исходных данных на 128 кластеров с использованием 512 процессов;<br />
* максимальная эффективность реализации <math>7,13%</math> достигается при делении исходных данных на 352 кластера с использованием 8 процессов.<br />
На рисунках 6 и 7, соответственно, представлены графики зависимости производительности и эффективности параллельной реализации k means от числа кластеров и числа процессов.<br />
<br />
[[file:kmeans_performance.jpg|thumb|center|720px|Рис. 6. График зависимости производительности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
По рис. 6 можно отметить практически полное отсутствие роста производительности с увеличением числа процессов от 256 до 512 при минимальном размере задачи. Это связано с быстрым ростом накладных расходов по отношению к крайне низкому объёму вычислений. При росте размерности задачи данный эффект пропадает, и при одновременном пропорциональном увеличении числа кластеров и числа процессов рост производительности становится близким к линейному.<br />
<br />
[[file:kmeans_efficiency.jpg|thumb|center|720px|Рис. 7. График зависимости эффективности параллельной реализации алгоритма от числа кластеров и числа процессов.]]<br />
<br />
Исследовалась [https://github.com/like-a-bauss/kmeans параллельная реализация алгоритма k means на MPI]. <br />
<p>Были получены следующие оценки масштабируемости реализации алгоритма k means:</p><br />
*<i>По числу процессов:</i> <math>-0.02209</math>. Следовательно, с ростом числа процессов эффективность уменьшается. На рис. 7 можно наблюдать плавное и равномерное снижение производительности по мере увеличения числа процессов при неизменном числе кластеров, что свидетельствует об относительно невысоком росте накладных расходов на передачу данных между процессами и преобладании объёма вычислений над объёмом пересылок данных по сети.<br />
*<i>По размеру задачи:</i> <math>0.01252</math>. Следовательно, с ростом размера задачи (числа кластеров) эффективность увеличивается. При этом объём пересылок данных по сети пропорционален <math>(n + k) \cdot p</math> (где <math>k</math> - число кластеров, <math>n</math> - число входных векторов, <math>p</math> - число процессов) таким образом, поскольку <math>k << n</math>, рост накладных расходов с ростом числа кластеров при неизменном числе процессов и входных векторов представляет собой незначительную величину.<br />
*<i>Общая оценка:</i> <math>-0.00081</math>. Таким образом, с ростом и размера задачи, и числа процессов эффективность уменьшается. Это связано с тем, что отношение объёма вычислений к объёму передаваемых данных изменяется пропорционально <math>{kn \over (n + k) \cdot p} \thicksim {k \over p}</math>, что представляет собой невысокий коэффициент, но при этом позволяет параллельной реализации не деградировать до нулевой эффективности при значительном увеличении числа процессов.<br />
<br />
==== Реализация 3 ====<br />
<br />
<br />
==== Реализация 4 ====<br />
<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
В однопоточном режиме на наборах данных, представляющих практический интерес (порядка нескольких десятков тысяч векторов и выше), время работы алгоритма неприемлемо велико. Благодаря свойству массового параллелизма должно наблюдаться значительное ускорение алгоритма на многоядерных архитектурах (Intel Xeon), а также на графических процессорах, даже на мобильных вычислительных системах (ноутбуках), оснащенных видеокартой. Также алгоритм k means будет демонстрировать значительное ускорение на сверхмощных вычислительных комплексах (суперкомпьютерах, системах облачных вычислений<ref>"Issa. "Performance characterization and analysis for Hadoop K-means iteration". Journal of Cloud Computing, 2016"</ref>).<br />
<br />
На сегодняшний день существует множество реализаций алгоритма k means, в частности, направленных на оптимизацию параллельной работы на различных архитектурах<ref>"Raghavan R. A fast and scalable hardware architecture for K-means clustering for big data analysis : дис. – University of Colorado Colorado Springs. Kraemer Family Library, 2016."</ref><ref>"Yang, Luobin, et al. "High performance data clustering: a comparative analysis of performance for GPU, RASC, MPI, and OpenMP implementations." The Journal of supercomputing 70.1 (2014): 284-300."</ref><ref>"Li, You, et al. "Speeding up k-means algorithm by GPUs." Computer and Information Technology (CIT), 2010 IEEE 10th International Conference on. IEEE, 2010."</ref>. Предлагается множество адаптаций алгоритма под конкретные архитектуры. Например, авторы работы<ref>"Kanan, Gebali, Ibrahim. "Fast and Area-Efficient Hardware Implementation of the K-means<br />
Clustering Algorithm". WSEAS Transactions on circuits and systems. Vol. 15. 2016"</ref> производят перерасчет центров кластеров на этапе распределения векторов по кластерам.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
==== Открытое программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.icpsr.umich.edu/CrimeStat/ CrimeStat]</li> Программное обеспечение, созданное для операционных систем Windows, предоставляющее инструменты статистического и пространственного анализа для решения задачи картирования преступности.<br />
<li>[http://juliastats.github.io Julia]</li> Высокоуровневый высокопроизводительный свободный язык программирования с динамической типизацией, созданный для математических вычислений, содержит реализацию k-means.<br />
<li>[https://mahout.apache.org Mahout]</li> Apache Mahout - Java библиотека для работы с алгоритмами машинного обучения с использованием MapReduce. Содержит реализацию k-means.<br />
<li>[https://www.gnu.org/software/octave/ Octave]</li> Написанная на C++ свободная система для математических вычислений, использующая совместимый с MATLAB язык высокого уровня, содержит реализацию k-means.<br />
<li>[http://spark.apache.org/docs/latest/mllib-clustering.html Spark]</li> Распределенная реализация k-means содержится в библиотеке Mlib для работы с алгоритмами машинного обучения, взаимодействующая с Python библиотекой NumPy и библиотека R.<br />
<li>[http://torch.ch Torch]</li> MATLAB-подобная библиотека для языка программирования Lua с открытым исходным кодом, предоставляет большое количество алгоритмов для глубинного обучения и научных расчётов. Ядро написано на Си, прикладная часть выполняется на LuaJIT, поддерживается распараллеливание вычислений средствами CUDA и OpenMP. Существуют реализации k-means.<br />
<li>[http://www.cs.waikato.ac.nz/ml/weka/ Weka]</li> Cвободное программное обеспечение для анализа данных, написанное на Java. Содержит k-means и x-means.<br />
<li>[http://accord-framework.net/docs/html/T_Accord_MachineLearning_KMeans.htm Accord.NET]</li> C# реализация алгоритмов k-means, k-means++, k-modes.<br />
<li>[http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_ml/py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html OpenCV]</li> Написанная на С++ библиотека, направленная в основном на решение задач компьютерного зрения. Содержит реализацию k-means.<br />
<li>[http://mlpack.org/ MLPACK]</li> Масштабируемая С++ библиотека для работы с алгоритмами машинного обучения, содержит реализацию k-means.<br />
<li>[https://www.scipy.org/ SciPy]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[http://scikit-learn.org/ scikit-learn]</li> Библиотека Python, содержит множество реализаций k-means.<br />
<li>[https://www.r-bloggers.com/k-means-clustering-in-r/ R]</li> Язык программирования для статистической обработки данных и работы с графикой, а также свободная программная среда вычислений с открытым исходным кодом в рамках проекта GNU, содержит три реализации k-means.<br />
<li>[http://elki.dbs.ifi.lmu.de ELKI]</li> Java фреймворк, содержащий реализацию k-means, а также множество других алгоритмов кластеризации.<br />
</ol><br />
<br />
==== Проприетарное программное обеспечение ====<br />
<br />
<ol><br />
<li>[https://www.ayasdi.com/blog/bigdata/topological-data-analysis-of-oil-and-gas-petrophysical-data/ Ayasdi]</li><br />
<li>[http://www.stata.com/features/cluster-analysis/ Stata]</li><br />
<li>[http://mathworld.wolfram.com/K-MeansClusteringAlgorithm.html Mathematica]</li><br />
<li>[http://www.mathworks.com/help/stats/kmeans.html?requestedDomain=www.mathworks.com MATLAB]</li><br />
<li>[https://support.sas.com/rnd/app/stat/procedures/fastclus.html SAS]</li><br />
<li>[http://docs.rapidminer.com/studio/operators/modeling/segmentation/k_means.html RapidMiner]</li><br />
<li>[https://blogs.sap.com/2013/03/28/sap-hana-pal-k-means-algorithm-or-how-to-do-customer-segmentation-for-the-telecommunications-industry/ SAP HANA]</li><br />
</ol><br />
<br />
== Литература ==<br />
<br />
<references /></div>Konshinhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9B%D0%B0%D0%BD%D1%86%D0%BE%D1%88%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%82%D0%BE%D1%87%D0%BD%D0%BE%D0%B9_%D0%B0%D1%80%D0%B8%D1%84%D0%BC%D0%B5%D1%82%D0%B8%D0%BA%D0%B8_(%D0%B1%D0%B5%D0%B7_%D0%BF%D0%B5%D1%80%D0%B5%D0%BE%D1%80%D1%82%D0%BE%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8)&diff=25241Алгоритм Ланцоша для точной арифметики (без переортогонализации)2018-01-16T16:27:16Z<p>Konshin: /* Масштабируемость алгоритма и его реализации */</p>
<hr />
<div><br />
Основные авторы статьи (разделы 1, 2.4, 2.7):<br />
[https://algowiki-project.org/ru/Участник:Bashleon<b>Башлыков Л.А.</b>] и<br />
[https://algowiki-project.org/ru/Участник:VolkovNikita94<b>Волков Н.И.</b>]<br/><br />
Авторы отдельных разделов (2.4, 2.7):<br />
[https://algowiki-project.org/ru/Участник:M.grigoriev<b>Григорьев М.А.</b>],<br />
[https://algowiki-project.org/ru/Участник:KAKTUZ<b>Заспа А.Ю.</b>],<br />
[https://algowiki-project.org/ru/Участник:AleksLevin<b>Левин А.Д.</b>],<br />
[https://algowiki-project.org/ru/Участник:Danyanya<b>Слюсарь Д.Р.</b>],<br />
[https://algowiki-project.org/ru/Участник:A.Freeman<b>Фролов А.А.</b>] и<br />
[https://algowiki-project.org/ru/Участник:Shostix<b>Шостик А.В.</b>]<br/><br />
В работе также принимали участие:<br />
[https://algowiki-project.org/ru/Участник:ASA<b>Антонов А.С.</b>] и<br />
[https://algowiki-project.org/ru/Участник:Konshin<b>Коньшин И.Н.</b>]<br/><br />
<br />
{{algorithm<br />
| name = Алгоритм Ланцоша для точной арифметики (без переортогонализации)<br />
| serial_complexity = <math>O(n^2) + O(k^3)</math> - на одну итерацию и <math>O(kn^2)</math> - суммарно для <math>k</math> итераций<br />
| pf_height = <math>O(n)</math><br />
| pf_width = <math>O(n^2)</math><br />
| input_data = <math>n^2 + n + 1</math>, при экономии памяти затраты сокращаются до <math>\frac{n(n - 1)}{2} + n + 1</math><br />
| output_data = <math>k</math>, а если нужны собственные векторы, то <math>k + kn</math><br />
}}<br />
<br />
==Свойства и структура алгоритма==<br />
===Общее описание алгоритма===<br />
<br />
'''Алгоритм Ланцоша''' был опубликован венгерским математиком и физиком Корнелием Ланцошем<ref>https://ru.wikipedia.org/wiki/Ланцош,_Корнелий</ref> в 1950 году<ref>Lanczos, C. "An iteration method for the solution of the eigenvalue problem of linear differential and integral operators", J. Res. Nat’l Bur. Std. 45, 255-282 (1950).</ref>. Этот метод является частным случаем алгоритма Арнольда в случае, если исходная матрица <math>A</math> - симметрична, и был представлен как итерационный метод вычисления собственных значений симметричной матрицы. Этот метод позволяет за <math>k</math> итераций вычислять <math>k</math> приближений собственных значений и собственных векторов исходной матрицы. Хотя алгоритм и был эффективным в вычислительном смысле, но он на некоторое время был предан забвению из-за численной неустойчивости. Только в 1970 Ojalvo и Newman модифицировали алгоритм для использования в арифметике с плавающей точкой<ref>Ojalvo, I.U. and Newman, M., "Vibration modes of large structures by an automatic matrix-reduction method", AIAA J., 8 (7), 1234–1239 (1970).</ref>. Новый метод получил название [[Алгоритм Ланцоша с полной переортогонализацией | алгоритма Ланцоша с полной переортогонализацией]]. <br />
<br />
В данной статье рассматривается исходная версия алгоритма<ref>Деммель Дж. Вычислительная линейная алгебра. Теория и приложения - М.: Мир, 2001. ([http://www.bookvoed.ru/book?id=5252274 см.])</ref>.<br />
<br />
===Математическое описание алгоритма===<br />
<br />
Математическое описание алгоритма Ланцоша для вычисления собственных значений симметричной матрицы А<br />
требует 1) математического описания метода Ланцоща для построения крыловского подпространства и 2)<br />
математического описания т.н. процедуры Рэлея-Ритца.<br />
<br />
==== Метод Ланцоша для построения крыловского подпространства ====<br />
<br />
Пусть дано уравнение <math>Ax = b</math>, причём вектор <math>b</math> известен и имеется<br />
способ вычисления <math>Ax</math>.<br/><br />
<br />
Вычислим <math>y_{1} = b</math>, <math>y_{2} = Ab</math>, <math>y_{3} = A^{2}b</math>,...,<math>y_{n} = A^{(n - 1)}b</math>, где <math>n</math> - <br />
порядок матрицы <math>A</math>. Определим матрицу <math>K = [y_{1},...,y_{n}]</math>.<br/><br />
<br />
Тогда <math>AK = [Ay_{1},...,Ay_{n - 1}, Ay_{n}] = [y_{2},...,y_{n},A^n{y_{1}}]</math>. <br />
В предположении, что матрица <math>K</math> - невырождена, можно вычислить вектор <math>c = -K^{-1}A^{ny_{1}}</math>.<br />
Поскольку первые <math>n - 1</math> столбцов в <math>AK</math> совпадают с последними <math>n - 1</math> столбцами в<br />
<math>K</math>, то справедливо: <math>AK = K[e_{2},e_{3},...,e_{n},-c] = KC</math>, то есть <math>K^{-1}AK = C</math>, где <math>C</math> - верхняя хессенбергова матрица.<br/><br />
<br />
Заменим <math>K</math> ортогональной матрицей <math>Q</math> так, что при любом <math>k</math><br />
линейные оболочки первых <math>k</math> столбцов в <math>K</math> и <math>Q</math> - одно и то же<br />
подпространство. Это подпространство называется крыловским и точно определяется как<br />
<math>\kappa_{k}(A, b)</math> - линейная оболочка векторов <math>b</math>,<math>Ab</math>,...,<math>A^{(k - 1)}b</math>.<br />
Суть алгоритма Ланцоша заключается в вычислении стольких первых столбцов в <math>Q</math>,<br />
сколько необходимо для получения требуемого приближения к решению <math>Ax = b </math> (<math>Ax = {\lambda}x</math>).<br />
<br />
Используем QR-разложение матрицы <math>K = QQ</math><br/><br />
Тогда <math>K^{-1}AK = (R^{-1}Q^{T})A(QR) = C</math>, откуда <math>Q^{T}AQ = RCR^{-1} = H</math><br/><br />
<br />
Матрица <math>H</math> является верхней хессенберговой в силу того, что верхней хессенберговой<br />
является матрица <math>C</math>, а матрицы <math>R</math> и <math>R^{-1}</math> - верхние треугольные.<br />
<br />
Однако алгоритм Ланцоша подразумевает симметричность матрицы <math>A</math>.<br />
Положим <math>Q = [Q_{k}, Q_{u}]</math>, где <math>Q_{k} = [q_{1},...,q_{k}]</math> и <br />
<math>Q_{u} = [q_{k+1},...,q_{n}]</math>. В итоге получается, что <math>H = Q^{T}AQ = [Q_{k},Q_{u}]^{T}A[Q_{k},Q_{u}]</math><br />
Из симметричности <math>A</math>, таким образом, следует симметричность и трехдиагональность матрицы <math>H = T</math>.<br />
Теперь можно вывести две основные формулы, используемые в методе Ланцоша:<br />
<br />
*<math>AQ_{j} = \beta_{j - 1}q_{j - 1} + \alpha_{j}q_{j} + \beta_{j}q_{j + 1}</math> - получается приравниванием столбцов <math>j</math> в обеих частях равенства <math>AQ = QT</math>.<br />
*<math>q_{j}^{T}Aq_{j} = \alpha_{j}</math> - получается домножением обеих частей предыдущего соотношения на <math>q_{j}^{T}</math> и учетом ортогональности <math>Q</math>.<br />
<br />
==== Процедура Рэлея-Ритца ====<br />
<br />
Пусть <math>Q = [Q_{k}, Q_{u}]</math> - произвольная ортогональная матрица порядка <math>n</math>, причём<br />
<math>Q_{k}</math> и <math>Q_{u}</math> имеют размеры <math>nk</math> и <math>n(n - k)</math>.<br />
<br />
Пусть <math>T = Q^{T}AQ = [Q_{k},Q_{u}]^{T}A[Q_{k},Q_{u}]</math>. Обратим внимание, что в случае <math>k = 1</math> выражение <math>T_{k} = Q_{k}^{T}AQ_{k}</math> -<br />
отношение Рэлея <math>\rho(Q_{1},A)</math>, то есть число <math>(Q_{1}^{T}AQ_{1}) / (Q_{1}^{T}Q_{1})</math>. Определим теперь суть<br />
процедуры Рэлея-Ритца:<br/><br />
<br />
*Процедура Рэлея-Ритца - интерпретация собственных значений матрицы <math>T = Q^{T}AQ</math> как приближений к собственным значениям матрицы <math>A</math>. Эти приближения называются числами Ритца. Пусть <math>T_{k} = V{\lambda}V^{T}</math> - спектральное разложение матрицы <math>T_{k}</math>. Столбцы матрицы <math>Q_{k}V</math> рассматриваются как приближения к соответствующим собственным векторам и называются векторами Ритца.<br />
<br />
Существует несколько важных фактов о процедуре Рэлея-Ритца, характеризующих получаемые приближения собственных значений матрицы <math>A</math>.<br />
<br />
*Минимум величины <math>||AQ_{k} - Q_{k}R||_{2}</math> по всем симметричным <math>k^2</math> матрицам <math>R</math> достигается при <math>R = T_{k}</math>. При этом <math>||AQ_{k} - Q_{k}R||_{2} = ||T_{ku}||_{2}</math>. Пусть <math>T_{k} = V{\lambda}V^{T}</math> - спектральное разложение матрицы <math>T_{k}</math>. Минимум величины <math>||AP_{k} - P_{k}D||_{2}</math>, когда <math>P_{k}</math> пробегает множество <math>nk </math> матрицы с ортонормированными столбцами, таких, что <math>span(P_{k}) = span(Q_{k})</math>, а <math>D</math> пробегает множество диагональных <math>k^2</math> матриц также равен <math>||T_{ku}||_{2}</math> и достигается для <math>P_{k} = Q_{k}V</math> и <math>D = \lambda</math>.<br />
<br />
Пусть <math>T_{k}</math>, <math>T_{ku}</math>, <math>Q_{k}</math> - те же самые матрицы. Пусть <math>T_{k} = V{\lambda}V^{T}</math> - всё то же спектральное разложение,<br />
причём <math>V = [v_{1},...,v_{k}]</math> - ортогональная матрица, а <math>\lambda = diag(\theta_{1},...,\theta_{k})</math>. Тогда:<br />
<br />
* Найдутся <math>k</math> необязательно наибольших собственных значений <math>\alpha_{1},...,\alpha_{k}</math> матрицы <math>A</math>, такие, что <math>|\theta_{i} - \alpha_{i}| <= ||T_{ku}||_{2}</math> для <math>i = 1,...,k</math>. Если <math>Q_{k}</math> вычислена методом Ланцоша,то правая часть этого выражения равняется <math>\beta_{k}</math>, то есть единственному элементу блока <math>T_{ku}</math>, который может быть отличен от нуля. Указанный эдемент находится в правом верхнем углу блока.<br />
* <math>||A(Q_{k}v_{i}) - (Q_{k}v_{i})\theta_{i}||_{2} = ||T_{ku}v_{i}||_{2}</math>. Таким образом, разность между числом Ритца <math>\theta_{j}</math> и некоторым собственным значением <math>\alpha</math> матрицы <math>A</math> не превышает величины <math>||T_{ku}v_{i}||_{2}</math>, которая может быть много меньше, чем <math>||T_{ku}||_{2}</math>. Если матрица <math>Q_{k}</math> вычислена алгоритмом Ланцоша, то эта величина равна <math>\beta_{k}|v_{i}(k)|</math>, где <math>v_{i}(k)</math> - k-ая компонента вектора <math>v_{i}</math>. Таким образом, можно дещево вычислить норму невязки - левой части выражения, не выполняя ни одного умножения вектора на матрицы <math>Q_{k}</math> или <math>A</math>.<br />
* Не имея информации о спектре матрицы <math>T_{u}</math>, нельзя дать какую-либо содержательную оценку для погрешности в векторе Ритца <math>Q_{k}v_{i}</math>. Если известно, что <math>\theta_{j}</math> отделено расстоянием, не меньшим <math>g</math>, от прочих собственных значений матриц <math>T_{k}</math>, <math>T_{u}</math> и матрица <math>Q_{k}</math> вычислена алгоритмом Ланцоша, то угол <math>\theta</math> между <math>Q_{k}v_{i}</math> и точным собственным вектором <math>A</math> можно оценить формулой <math>\frac{1}{2}\sin{2\theta} <= \frac{b_{k}}{g}</math><br />
<br />
В алгоритме Ланцоша из ортонормированных векторов строится матрица <math>Q_{k} = [q_{1},...,q_{k}]</math> и в качестве приближенных собственных значений <math>A</math> принимаются числа Ритца - собственные значения симметричной трёхдиагональной матрицы <math>T_{k} = Q_{k}^{T}AQ_{k}</math>.<br />
<br />
===Вычислительное ядро алгоритма===<br />
Вычислительное ядро алгоритма Ланцоща состоит в получении на каждой итерации очередного промежуточного вектора <math>z</math>, получаемого путём умножения исходной матрицы <math>A</math> на вектор Ланцоша <math>q_{j}</math>, полученный на предыдущей итерации.<br />
<br />
Также при значениях <math>k</math>, сопоставимых с <math>n</math>, процедура вычисления собственных значений и собственных векторов симметричной трёхдиагональной матрицы по некоторому выбранному алгоритму подразумевает значительный объём расчётов и может считаться вычислительным ядром. Однако на практике метод используется прежде всего при малых <math>k</math>.<br />
<br />
===Макроструктура алгоритма===<br />
<br />
Метод Ланцоша соединяет метод Ланцоша для построения крыловского подпространства в процедурой Рэлея-Ритца, подразумевающей вычисление собственных значений симметричной трёхдиагональной матрицы. Первая часть алгоритма представляет собой строго последовательные итерации, на каждой из которых вначале строится очередной столбец матрицы <math>Q_{j}</math> из первых <math>j</math> ортонормированных векторов Ланцоша и матрица <math>T_{j} = Q^T_{j}AQ_{j}</math> порядка <math>j</math>. Итерационный процесс завершается, когда <math>j = k</math>. Вторая часть алгоритма представляет собой поиск собственных значений и собственных векторов симметричной трёхдиагональной матрицы <math>T_{j}</math>, что реализуется отдельным алгоритмом, на практике используется QR-алгоритм.<br/><br />
<br />
Особенность методов крыловского подпространства состоит в предположении, что матрица <math>A</math> доступна в виде "черного ящика", а именно подпрограммы, вычисляющей по заданному вектору <math>z</math> произведение <math>y = Az</math> (а также, возможно, произведение <math>y = A^{T}z</math>, если матрица <math>A</math> несимметрична). Другими словами, прямой доступ к элементам матрицы и их изменение не используются, т.к. <br />
*Умножение матрицы на вектор - наиболее дешевая нетривиальная операция, которую можно проделать с разреженной матрицей: в случае разреженной <math>A</math>, содержащей <math>m</math> ненулевых элементов, для матрично-векторного умножения нужны <math>m</math> скалярных умножений и не более <math>m</math> сложений.<br />
*<math>A</math> может быть не представлена в виде матрицы явно, а доступна именно через подпрограмму для вычисления произведений <math>Ax</math>.<br />
Таким образом, как QR-алгоритм поиска собственных значений, собственных векторов матрицы <math>T_{j}</math> и оценки погрешности в них результирующей трехдиагональной матрицы, так и умножение матрицы на вектор внутри каждой итерации могут быть рассмотрены в качестве отдельных макроопераций. Проблемой является то, что именно эти манипуляции порождают основную вычислительную сложность алгоритма. Поскольку матрица, к которой применяется QR-алгоритм, имеет порядок <math>k</math> и на практике бывает мала, сделаем допущение о плотности матрицы <math>A</math>. Тогда умножение этой матрицы на вектор внутри каждой итерации нужно будет реализовывать в рамках самого алгоритма. Еще одним важным замечанием является момент вычисления собственных значений матрицы <math>T_{j}</math>. Её собственные значения, полученные на итерации <math>p</math>, никак не используются на итерации <math>p + 1</math>, поэтому целесообразно вынести эту процедуру за основной цикл. Однако на практике в силу малых размеров матрицы <math>T_{j}</math> вычисление соответствующих величин производится непосредственно в теле основного цикла, что позволяет сразу оценить достигнутую точность вычислений.<br />
<br />
===Схема последовательной реализации алгоритма===<br />
<br />
Алгоритм итерационный, на каждой итерации выполняется вычисление очередного столбца матрицы <math>Q</math>, а также вычисление элементов очередной пары (диагонального и околодиагонального) элементов матрицы <math>T</math> с использованием значений столбца матрицы <math>Q</math>, полученного на предыдущей итерации и значения околодиагонального элемента матрицы <math>T</math> с предыдущей итерации. Вектор <math>z</math> носит вспомогательный характер. Коэффициенты <math>\alpha_{j}</math> соответствуют диагональным элементам матрицы <math>T_{j}</math>, коэффициенты <math>\beta_{j}</math> – наддиагональным и поддиагональным элементам той же матрицы. Далее приводится пример псевдокода. По окончании итераций вычислеяются собственные значения и собственные векторы матрицы <math>T_{j}</math>, что в псевдокоде не отражается (т.к. это отдельный алгоритм).<br />
<br />
Вычислить столбец q1 матрицы Qk q1 = b / ||b||<br />
q0 - нулевой вектор<br />
beta_0 = 0<br />
foreach ( j = 1 .. k ) <br />
{<br />
z = A * qj<br />
alpha_j = qj * z <br />
z = z – alpha_j * qj – beta_(j-1) * q(j – 1)<br />
beta_j = ||z||<br />
if ( beta_j == 0 )<br />
{<br />
break // Все ненулевые собственные значения найдены. Выход может быть досрочным.<br />
}<br />
else <br />
{<br />
q(j + 1) = z/beta_j<br />
}<br />
}<br />
<br />
Вычислить собственные значения и собственные векторы симметричной трехдиагональной матрицы Tj наиболее подходящим методом.<br />
Полученный вектор Lj есть искомые собственные значения.<br />
<br />
===Последовательная сложность алгоритма===<br />
Все дальнейшие выкладки верны для наиболее быстрого последовательного варианта выполнения указываемых операций. <br />
* Умножение квадратной матрицы порядка <math>n</math> на вектор длины <math>n</math> требует <math>n^2</math> умножений и сложений.<br />
* Перемножение векторов длины <math>n</math> требует по <math>n</math> умножений и сложений.<br/><br />
* Поэлементное сложение векторов длины <math>n</math> требует <math>n</math> сложений.<br />
* Умножение вектора длины <math>n</math> на число требует <math>n</math> умножений.<br />
* Нахождение квадратичной нормы вектора длины <math>n</math> требует по <math>n</math> умножений и сложений, а также одну операцию извлечения квадратного корня.<br />
* Вычисление собственных значений матрицы порядка <math>k</math> QR-алгоритмом требует <math>O(k^2)</math> операций, вычисление также и собственных векторов требует примерно <math>6k^3</math>, то есть <math>O(k^3)</math> операций; при использовании метода «Разделяй-и-властвуй» для вычисления собственных значений и векторов аналогичной матрицы в среднем затрачивается <math>O(k^{2})</math> операций.<br />
Таким образом, для выполнения одной итерации метода Ланцоша требуется 1 операция вычисления квадратного корня, <math>n</math> умножений, а также по <math>n ^ 2 + n + 2n + n</math> сложений и умножений, а также суммарно <math>O(k ^ 3)</math> операций, требуемых для поиска собственных значений и собственных векторов матрицы <math>T_{j}</math>. Таким образом последовательная сложность алгоритма Ланцоша составляет <math>O(n ^ 2) + O(k ^ 3)</math>. Это, очевидно, меньше чем <math>O(n^3)</math> операций, требуемых в алгоритмах вычисления всех собственных значений произвольных симметричных матриц, в чём и заключается эффективность применения метода Ланцоша.<br />
<br />
===Информационный граф===<br />
<br />
Информационный граф алгоритма, определяемый в <ref>Параллельные вычисления (Воеводин В.В., Воеводин Вл.В.) - Спб, изд-во "БХВ-Петербург", 2002 ([https://parallel.ru/news/bhv_parallelcomputing.html см.])</ref> требует следующего замечания: подразумевается, что, например, умножение квадратной матрицы на вектор распараллеливается на <math>n</math> потоков, где <math>n</math> - порядок матрицы, а не на <math>n\log{n}</math> потоков, чего можно было бы достичь, используя суммирование сдваиванием. Это допущение аналогично допущению из статьи [https://algowiki-project.org/ru/%D0%A3%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D0%BB%D0%BE%D1%82%D0%BD%D0%BE%D0%B9_%D0%BD%D0%B5%D0%BE%D1%81%D0%BE%D0%B1%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BD%D0%B0_%D0%B2%D0%B5%D0%BA%D1%82%D0%BE%D1%80_(%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82) Умножение матрицы на вектор] и Это же допущение будет использовано и при оценке ресурса параллелизма алгоритма. Приводится граф без отображения входных и выходных данных.<br />
<br />
[[File:Volkov Lanzcos 1.png|600px|thumb|center|Рис. 1: Граф алгоритма для матрицы А порядка 5 и k = 3. Op - одна итерация алгоритма, Eigen - процедура вычисления собственных значений]]<br />
[[File:Volkov Lanzcos 2.png|600px|thumb|center|Рис. 2: Внутренний граф итерации алгоритма для матрицы A порядка 5. Vector sum - нахождение суммы элементов вектора-<b>поэлементного произведения</b> векторов z и qj. Op - вычитания из z векторов qj и q(j - 1), домноженных на коэффициенты. Norm - вычисление нормы вектора z.]]<br />
<br />
===Ресурс параллелизма алгоритма===<br />
Каждая итерация алгоритма Ланцоша выполняется строго последовательно. Однако внутри итерации возможно распараллеливание алгоритма.<br />
<br />
*Умножение квадратной матрицы порядка <math>n</math> на вектор длины <math>n</math> требует последовательного выполнения <math>n</math> ярусов умножений и сложений.<br />
*Все остальные операции в рамках одной итерации, ведущие к вычислению матрицы <math>T_{j}</math> выполняются строго последовательно. При этом вычисление значений векторов может быть выполнено за 1 ярус умножений / сложений, а вычисление чисел - за <math>\log{n}</math> таких операций.<br />
*Ресурс параллелизма алгоритма вычисления собственных значений зависит от используемого алгоритма и рассматривается в соответствующей статье.<br />
<br />
Таким образом, при классификации по ширине ЯПФ алгоритм обладает <i>линейной</i> сложностью. В случае классификации по высоте ЯПФ алгоритм также имеет <i>линейную</i> сложность.<br />
<br />
===Входные и выходные данные алгоритма===<br />
<b>Входные данные: </b> квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math>, число <math>k</math><br/><br />
<b>Объём входных данных:</b> <math>n^2 + n + 1</math><br/><br />
<b>Выходные данные:</b> вектор <math>L</math>, матрица <math>E</math> (элементы <math>e_{pq}</math>)<br/><br />
<b>Объём выходных данных:</b> <math>k + kn</math><br/><br />
<br />
===Свойства алгоритма===<br />
В случае неограниченности ресурсов соотношение последовательной и параллельной сложности алгоритма Ланцоща по сумме всех итераций представляет собой <math>k ^ 3 + kn ^ 2</math> к <math>X + kn</math>, где <math>X</math> - параллельная сложность алгоритма, используемого для вычисления собственных значений. В случае использования метода вычисления собственных значений с линейной сложностью при классисифкации по высоте ЯПФ это соотношение будет равно <math>(k ^ 2 + n ^ 2) / (k + n)</math>. <br />
<br />
Алгоритм Ланцоша не является полностью детерминированным в том смысле, что возможно выполнение числа итераций алгоритма, меньшего, чем заданное (из-за того, что вычислены все ненулевые собственные значения матрицы <math>A</math>). <br />
<br />
Важное свойство метода Ланцоша состоит в том, что первыми в матрице <math>T_{j}</math> появляются собственные значения с максимальной величиной по модулю. Таким образом, метод особенно хорошо подходит для вычисления собственных значений матрицы <math>A</math>, находящихся на краях её спектра.<br />
<br />
==Программная реализация алгоритма==<br />
===Особенности реализации последователього алгоритма===<br />
===Локальность данных и вычислений===<br />
===Возможные способы и особенности параллельной реализации алгоритма===<br />
===Масштабируемость алгоритма и его реализации===<br />
<br />
В настоящем разделе проведено исследование масштабируемости различных параллельных реализаций алгоритма Ланцоша без переортогонализации согласно [[Scalability methodology|методике]] AlgoWiki. Исследование проводилось на суперкомпьютере "Ломоносов" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
==== Реализация 1 ====<br />
<br />
Для проверки масштабируемости алгоритма из реализации была исключена непосредственно задача поиска собственных значений матрицы <math>T_{j}</math> на каждой итерации, так как это является предметом отдельных статей ([https://algowiki-project.org/ru/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:F-morozov/%D0%9D%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D1%87%D0%B8%D1%81%D0%B5%D0%BB_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_QR_%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_(4) QR-алгоритм], метод [https://algowiki-project.org/ru/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%C2%AB%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9_%D0%B8_%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9%C2%BB_%D0%B2%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B9_%D0%B8_%D0%B2%D0%B5%D0%BA%D1%82%D0%BE%D1%80%D0%BE%D0%B2_%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D0%BE%D0%B9_%D1%82%D1%80%D0%B5%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B «Разделяй-и-властвуй»], [https://en.wikipedia.org/wiki/Arnoldi_iteration итерации Арнольди] и.т.д.). Таким образом, проверяемая на масштабируемость задача сводится к итерационному вычислению коэффициентов матрицы <math>T_{j}</math>. Матрица <math>A</math> в рассматриваемой реализации хранится построчно и построчно же разделяется между процессорами. Также в целях исключения влияния недетерминированности алгоритма этот процесс изменен так, что при вычислении всех ненулевых собственных значений матрицы <math>A</math> работа алгоритма продолжается над фиктивными данными, пока не будет завершено заранее заданное число итераций. При этом в реализации используется OpenMP для распараллеливания в пределах одного узла. <br />
<br />
[[File:Ланцош_масштабы.png|1000px|thumb|center|Рис. 3: Производительность алгоритма для указанных значений размера задачи и числа MPI процессов (по 4 OpenMP нити каждый). Потенциальная максимальная производительность, которая может быть достигнута - 13.6 GFLOPS на 1 MPI процесс. Тогда, например, эффективность вычислений на 100 процессах при размере задачи 4000<math>{\cdot}</math>4000 составляет приблизительно 14.33%]]<br />
<br />
Набор изменяемых параметров запуска реализации алгоритма и границы значений параметров алгоритма:<br />
<br />
*Число MPI процессов [1 : 200]<br />
*Число OpenMP нитей [1 : 4] (Запуск с 1 OpenMP нитью производился только для 1 MPI процесса, ускорение замерялось относительно этого запуска).<br />
*Размерность матрицы [400 : 4000]<br />
<br />
Эффективность выполнения реализации алгоритма:<br />
<br />
*Минимальная эффективность - <1% (неактуально, так как в исследовании достигнут предел масштабируемости алгоритма).<br />
*Максимальная эффективность - ~30% (получается на 2-8 процессов при ~640000 ячеек матрицы на процесс; в случае 8 процессов это входная матрица 1600<math>{\cdot}</math>1600). <br />
<br />
Оценка масштабируемости:<br />
<br />
*По числу процессов - при увеличении числа процессов эффективность уменьшается на всей области рассмотренных значений, но не слишком интенсивно. Это связано с небольшим объёмом пересылок данных, реализованных через достаточно эффективные операции редукции.<br />
*По размеру задачи - при увеличении размера задачи эффективность вычислений вначале кратковременно возрастает, но затем начинает относительно равномерно убывать на всей рассматриваемой области.<br />
*По двум направлениям - при рассмотрении увеличения, как вычислительной сложности, так и числа процессов по всей рассмотренной области значений наибольшая эффективность наблюдается при соответствии числа процессов и размера задачи. По этой "диагонали" эффективность вначале кратковременно возрастает, затем начинает убывать.<br />
<br />
Интересно, что для разного числа процессоров положительные скачки производительности (зачастую - довольно заметные) имеют место на разных объёмах входных данных.<br />
<br />
Далее приводятся графики ускорения программы для разных объёмов входных данных. Ускорение считалось относительно варианта с 1 MPI процессом без дальнейшнего распараллеливания посредством OpenMP. На оси абсцисс отмечается число MPI процессов, число OpenMP нитей во всех случаях равно четырём.<br />
<br />
[[File:Speedup_2000.png|600px|thumb|center|Рис. 4: Ускорение для размера задачи 2000<math>{\cdot}</math>2000]]<br />
[[File:Speedup_3000.png|600px|thumb|center|Рис. 5: Ускорение для размера задачи 3000<math>{\cdot}</math>3000]]<br />
[[File:Speedup_4000.png|600px|thumb|center|Рис. 6: Ускорение для размера задачи 4000<math>{\cdot}</math>4000]]<br />
[[File:Speedup total.png|600px|thumb|center|Рис. 6: Сравнение ускорения для различных размеров задачи]]<br />
<br />
[https://github.com/VolkovNikita94/Lanczos Используемая реализация алгоритма]<br />
Реализация алгоритма гибридная, использует как MPI версии 2.2 (использование MPI_IN_PLACE), так и OpenMP. Указанный код компилировался с помощью компилятора IBM (mpixlcxx_r) с опцией -qsmp=omp (она включает в себя оптимизацию -O2). Реализация алгоритма полностью собственная и не использует встроенные библиотеки. Каждая "quadcore node" - это 1 узел системы BG/P, содержащий 4 микропроцессорных ядра IBM PowerPC 450 c суммарной пиковой производительностью 13.6 GFLOPS на узел и 2 GB общей памяти с пропускной способностью 13.6 GB/sec.<br />
<br />
==== Реализация 2 ====<br />
<br />
Для исследования масштабируемости рассматриваемого алгоритма Ланцоша без переортогонализации автором была реализована программа на языке С++ с использованием технологии MPI. <ref>https://goo.gl/pNOFmW или альтернативная ссылка с прикреплённым кодом: https://goo.gl/9AapM6 , по запросу автор реализации готов предоставить желающим доступ к проекту с этим кодом на ресурсе Bitbucket</ref> Дальнейшее исследование было проведено на суперкомпьютере "Ломоносов" Суперкомпьютерного комплекса МГУ.<ref>https://parallel.ru/cluster/lomonosov.html</ref> <br> Важно отметить, что реализованная программа выполняет только первую часть алгоритма - формирование трёхдиагональной матрицы <math >T_j</math>. Вторая часть алгоритма (задача поиска собственных значений матрицы <math >T_j</math>) может быть решена несколькими методами, каждый из которых обладает своей эффективностью распараллеливания и характерными свойствами. Подробно этот вопрос уже обсуждался в статье ранее, поэтому не будем ещё раз заострять на этом внимание и останавливаться.<br />
<br><br><br />
Сборка осуществлялась со следующими параметрами:<br />
* Компилятор intel/15.0.090 <br />
* Openmpi/1.8.4-icc<br />
В серии проведённых расчётов рассматривались следующие числовые значения параметров:<br />
* Размер <math style="vertical-align:0%;>n</math> задачи и, соответственно, матрицы <math style="vertical-align:0%;>A</math>, задействованной в операции умножения на вектор: [2000 : 30000] с шагом 1000<br />
* Число <math style="vertical-align:-20%;>p</math> процессоров: 1, 2, 4, 16, 32, 48, 64, 96, 128, 192, 256<br />
* Число итераций в алгоритме: <math style="vertical-align:0%;>k=350</math><br />
Число <math style="vertical-align:0%;>k</math> взято таким вопреки информации в начале статьи, где говорится, что значение <math style="vertical-align:0%;>k</math> редко превышает <math style="vertical-align:-10%;>10^2</math> (хоть мы и имеем право брать его любым по нашему усмотрению). Это сделано исключительно для лучшей визуализации пиков на графиках в данном разделе и наглядности получаемых результатов, так как при меньших на порядок значениях параметра <math style="vertical-align:0%;>k</math> времена выполнения программ были слишком малы даже при минимальных числах процессоров и максимальных размерах <math style="vertical-align:0%;>n</math> задачи.<br />
<br><br />
На рис. 2 изображён график зависимости времени выполнения программы от числа процессоров и размера <math style="vertical-align:0%;>n</math> задачи. <br><br />
{|align="center"<br />
|-valign="top"<br />
|[[file:Levin_task1_execTIME.png|620px|thumb|center|Рис. 2 График зависимости времени выполнения программы.]]<br />
|[[file:Levin604 task1 result EFFICIENCY.png|630px|thumb|center|Рис. 3 График зависимости эффективности распараллеливания программы.]]<br />
|}<br />
Многочисленные тесты показали при малых количествах ядер уменьшение времени выполнения программы почти в 2 раза при увеличении числа ядер в 2 раза на всём диапазоне размеров матриц, что и отражает сильный излом на первом графике. Изменение значений времени можно легко оценить по предоставленной цветовой шкале.<br />
Был построен график эффективности распараллеливания (parallel efficiency)<ref>https://goo.gl/WCNEjR</ref> в зависимости от числа процессоров и размера задачи, изображённый на рис. 3.<br />
Получены следующие результаты численных экспериментов:<br />
* Средняя эффективность распараллеливания программы составила: 42.5576% ;<br />
* Минимальная эффективность реализации: 0.674% (число процессоров 256 и минимальный размер матрицы 2000);<br />
* Максимальная эффективность реализации: 77.773% (число процессоров 2, размер матрицы 20000).<br />
Нужно понимать, что это лишь экстремумы, соответствующие двум экспериментам, которые сами по себе не дают полной картины и не позволяют однозначно судить о параллельных качествах алгоритма и реализующей его программы при столь большом числе параметров и проводимых расчётов. <br>На основании рис. 3 можно отметить следующие значимые и важные факты:<br />
*Имеется ярко выраженный спад эффективности при минимальном рассматриваемом размере матрицы <math style="vertical-align:0%;>n=2000</math> вдоль всей оси OY, отвечающей за число процессоров;<br />
*Присутствуют два пика эффективности (жёлтого цвета на рис. 3): при числе <math style="vertical-align:-20%;>p</math> процессоров 2 и 128. Затем при дальнейшем увеличении числа процессоров с 128 до 256 наблюдается постепенное уменьшение эффективности реализации. Это связано с увеличением накладных расходов и затрат времени на обмены сообщениями между процессами.<br />
<br />
==== Реализация 3 ====<br />
<br />
Для исследования масшабируемости алгоритма была написана реализация[https://github.com/danyanya/ss16-task1] на языке C++ с использованием MPI. Реализация была протестирована на суперкомпьютере Ломоносов[http://parallel.ru/cluster/].<br />
<br />
Были исследованы:<br />
* время выполнения программы в зависимости от размера входных данных (матрицы);<br />
* время выполнения в зависимости от размера параллельных нод.<br />
<br />
Дополнительно было измерено время работы исходя из оптимизационных флагов компилятора GCC [https://gcc.gnu.org]. <br />
<br />
Параметры запуска алгоритма:<br />
* размер матрицы от 20000 до 175000 с шагом 2500;<br />
* количество процессоров от 8 до 128 с шагом 8.<br />
<br />
Программа запускалась на суперкомьютере "Ломоносов" со следующими характеристиками:<br />
* Компилятор GCC 5.2.1;<br />
* Версия MPI 1.8.4;<br />
* Сборка проводилась командой: <pre>mpic++ -std=c++0x -O2 <CPP_FILE> -lm -static-libstdc++</pre><br />
<br />
Полученная эффективность реализации варьируется в пределах от 2% (на маленьких входных данных) до 45% (на больших входных данных и максимальном числе нодов).<br />
<br />
На рисунке 4 представлена эффективность программы при отсутствии флага оптимизации O2. <br />
<br />
[[Файл:Grigorev_lanczos_eff_no_02.png|thumb|center|600px|Рисунок 4. Эффективность параллельной реализации алгоритма Ланцоща на MPI.]]<br />
<br />
На рисунке 5 представлена эффективность алгоритма с оптимизацией компилятора GCC (-O2).<br />
<br />
[[Файл:Grigorev_lanczos_eff_o2.png|thumb|center|600px|Рисунок 5. Эффективность параллельной реализации алгоритма Ланцоща на MPI с использованием оптимизации компилятора -O2.]]<br />
<br />
Как видно из графиков выше, средняя эффективность минимальна при малых входных данных (на размере матрицы в 10000). <br />
В среднем данный показатель держится между 9% и 14%.<br />
<br />
При этом, оптимизация компилятора MPIC++ позволяет получить выигрыш в эффективности более чем в 3 раза (45,5% против 14,7%). Однако, как можно заметить из графиков, при оптимизации алгоритм ведет себя более нестабильно, скорее всего из-за значительной траты времени на работу с памятью (более плавные участки свидетельствует о том, что необходимые данные нашлись в кеше).<br />
<br />
==== Реализация 4 ====<br />
<br />
Для исследования масшабируемости алгоритма была написана реализация[https://github.com/xrstalker/lanczos] на языке C с использованием MPI. Реализация была протестирована на суперкомпьютере Ломоносов[http://parallel.ru/cluster/].<br />
<br />
Сборка осуществлялась со следующими параметрами:<br />
<br />
* gcc-5.2.0<br />
* openmpi-1.8.4<br />
* аргументы компилятора: -std=c11 -Ofast<br />
<br />
Набор и границы значений изменяемых параметров реализации алгоритма: <br />
<br />
* число процессоров [32 : 256] с шагом 16;<br />
* размер матрицы [5000 : 200000] с шагом 5000.<br />
<br />
В результате проведённых экспериментов был получен следующий диапазон эффективности реализации алгоритма:<br />
<br />
* минимальная эффективность реализации 1.92%;<br />
* максимальная эффективность реализации 59.47%.<br />
<br />
График полученного распределения эффективности:<br />
<br />
[[file:Lanczos Without Orthogonalization Eff2Dist.png|thumb|center|600px|Рисунок 4. Параллельная реализация алгоритма Ланцоша. Распределение производительности.]]<br />
<br />
На следующих рисунках приведены графики производительности и эффективности данной реализации в зависимости от изменяемых параметров запуска.<br />
{|align="center"<br />
|-valign="top"<br />
|[[file:Lanczos Without Orthogonalization Perf2.png|thumb|center|600px|Рисунок 5. Параллельная реализация алгоритма Ланцоша. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
|[[file:Lanczos Without Orthogonalization Eff2.png|thumb|center|600px|Рисунок 6. Параллельная реализация алгоритма Ланцоша. Изменение эффективности в зависимости от числа процессоров и размера матрицы.]]<br />
|}<br />
<br />
Средняя эффективность для матриц с размером больше 25000 составила 52.7%.<br />
<br />
==== Реализация 5 ====<br />
<br />
Для изучения свойств масштабируемости алгоритма Ланцоша без переортогонализации былы исследованы результаты выполненных на с/к Ломоносов вычислений.<br />
<br />
В рамках алгоритма не решается проблема поиска собственных значений, т.к. эта задача является самостоятельной задачей.<br />
<br />
Для компиляции программы использовались компиляторы intel/15.0.090 и Openmpi/1.8.4-icc.<br />
<br />
Для проведения расчетов и получения полноценной картины поведения алгоритма в зависимости от входных данных и числа процессоров, программа была запущена на следующих параметрах:<br />
<br />
* в качестве размера входной матрицы подавались значения в диапазоне [2000:30000] c шагом 2000.<br />
* Число процессоров варьировалось от 1 до 256. <br />
* В качестве числа, отвечающего за количество выполняемых методом итераций бралось значение 100.<br />
<br />
Ниже на рисунке изображен трехмерный график, показывающий зависимость времени выполнения программы от входных данных/<br />
<br />
[[Файл:Lanczos_time_matrix.png|1000px|thumb|center| График зависимости времени выполнения программы от входных данных]]<br />
<br />
Также была исследована эффективность распараллеливания:<br />
* Средняя эффективность выполнения алгоритма составила порядка 12%<br />
* Наивысший показатель равен 43%<br />
* Минимальное значение составило менее 1%<br />
<br />
==== Реализация 6 ====<br />
<br />
Исследование проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster/].<br />
<br />
Исследовалась реализация<ref>http://pastebin.com/3W4uUVGj</ref> на языке C++ с использованием MPI. Компилятор GCC 5.2.1; версия MPI 1.8.4.<br />
<br />
Для компиляции и запуска использовались следующие команды:<br />
<pre>$ mpic++ -std=c++0x -O2 <CPP_FILE> -lm -static-libstdc++ -o <EXE_FILE><br />
$ mpirun -np <N_PROCESSORS> <EXE_FILE><br />
</pre><br />
<br />
* Число процессоров: 2, 4, 8, 16, 32, 64, 128.<br />
* Размерность матрицы <math>N</math> от 5 000 до 50 000, шаг 5 000.<br />
* Количество итераций алгоритма Ланцоша для всех экспериментов 200.<br />
<br />
Результаты:<br />
<br />
{| class="wikitable"<br />
|+ Время выполнения алгоритма<br />
|-<br />
!<br />
! 2<br />
! 4<br />
! 8<br />
! 16 <br />
! 32<br />
! 48<br />
! 64 <br />
! 96<br />
! 128<br />
|-<br />
! 5000<br />
| 1,5243<br />
| 1,0951<br />
| 0,9334<br />
| 0,6631<br />
| 0,7456<br />
| 0,7038<br />
| 0,7989<br />
| 0,9985<br />
| 0,985<br />
|-<br />
! 10 000<br />
| 5,667<br />
| 4,2625<br />
| 2,7502<br />
| 3,6762<br />
| 3,4435<br />
| 3,6192<br />
| 3,5194<br />
| 3,8486<br />
| 3,2956<br />
|-<br />
! 15 000<br />
| 12,5207<br />
| 6,5995<br />
| 7,2897<br />
| 8,5086<br />
| 8,539<br />
| 9,0018<br />
| 8,9427<br />
| 15,0132<br />
| 7,7088<br />
|-<br />
! 20 000<br />
| 23,8143<br />
| 15,851<br />
| 10,6313<br />
| 13,7241<br />
| 12,8051<br />
| 14,3183<br />
| 13,2946<br />
| 14,0864<br />
| 13,7499<br />
|-<br />
! 25 000<br />
| 37,5419<br />
| 17,9436<br />
| 15,5451<br />
| 18,3944<br />
| 19,6076<br />
| 18,5444<br />
| 19,1347<br />
| 21,8366<br />
| 21,4454<br />
|-<br />
! 30 000<br />
| 55,014<br />
| 26,2702<br />
| 22,3943<br />
| 26,4935<br />
| 25,5618<br />
| 29,6784<br />
| 28,0853<br />
| 27,8872<br />
| 31,2415<br />
|-<br />
! 35 000<br />
| 71,3969<br />
| 37,4811<br />
| 30,2274<br />
| 34,0041<br />
| 38,1028<br />
| 38,4912<br />
| 38,0174<br />
| 37,1407<br />
| 40,4463<br />
|-<br />
! 40 000<br />
| 100,326<br />
| 47,4248<br />
| 40,2295<br />
| 44,0332<br />
| 50,0015<br />
| 52,9428<br />
| 46,2961<br />
| 51,4372<br />
| 49,7504<br />
|-<br />
! 45 000<br />
| 142,93<br />
| 66,4829<br />
| 52,1382<br />
| 55,4683<br />
| 62,3042<br />
| 65,0885<br />
| 60,0512<br />
| 57,8891<br />
| 64,3764<br />
|}<br />
<br />
Ниже приведен график зависимости времени выполнения программы от числа процессоров и размера исходной матрицы.<br />
<br />
[[File:Lanzcos.png|600px|thumb|center|Рисунок 2. Время работы программы в зависимости от количества процессоров и размера матрицы.<br/><br />
<br />
<math>Procs</math> — количество процессоров, <br/><br />
<math>Size</math> — размерность матрицы <math>N</math>, <br/><br />
<math>Time</math> — время работы алгоритма в сек. <br/>]]<br />
<br />
===Динамические характеристики и эффективность реализации алгоритма===<br />
===Выводы для классов архитектур===<br />
===Существующие реализации алгоритма===<br />
Подробный анализ алгоритмов нахождения собственных значений, включая алгоритм Ланцоша, был проведен в <ref>V. Hernandez, J. E. Roman, A. Tomas, V. Vidal. A Survey of Software for Sparse Eigenvalue Problems ([http://slepc.upv.es/documentation/reports/str6.pdf см.])</ref>. Существует несколько как последовательных, так и параллельных реализаций алгоритма Ланцоша, включенных в программные библиотеки для поиска собственных значений матриц. Свойства этих реализаций можно увидеть в таблицах ниже. Первая таблица — относительно недавние реализации, вторая - «музейные экспонаты». Следует уделить особенное внимание столбцам «тип метода» и «параллелизация». В первом из них значение только N соответствует описанному варианту без реортогонализации, F – полной реортогонализации, P – частичной реортогонализации, S – выборочной реортогонализации. Во втором значние «none» соответствует отсутствию параллельной реализации, M – параллельной реализации посредством MPI, O – параллельной реализации посредством OpenMP. Их приведение целесообразно, так как на практике алгоритм Ланцоша без переортогонализации неустойчив. Также указываются библиотеки, в которых реализованы более глубокие модификации метода Ланцоша, с указанием изменений в графе «тип метода».<br />
<br />
{| class="wikitable"<br />
|+<b>Таблица 1 - «современные» реализации.</b><br />
!Название библиотеки<br />
!Язык программирования<br />
!Дата появления, версия библиотки<br />
!Тип метода<br />
!Параллелизация<br />
|-<br />
|BLKLAN<br />
|C/Matlab<br />
|2003<br />
|P<br />
|none<br />
|-<br />
|BLZPACK<br />
|F77<br />
|2000, 04/00<br />
|P + S<br />
|M<br />
|-<br />
|IETL<br />
|C++<br />
|2006, 2.2<br />
|N<br />
|none<br />
|-<br />
|SLEPc<br />
|C/F77<br />
|2009, 3.0.0<br />
|All<br />
|M<br />
|-<br />
|TRLAN<br />
|F90<br />
|2006<br />
|Dynamic thick-restart<br />
|M<br />
|-<br />
|PROPACK<br />
|F77/Matlab<br />
|2005, 2.1 / 1.1<br />
|P, finds SVD<br />
|O<br />
|-<br />
|IRBLEIGS<br />
|Matlab<br />
|2000, 1.0<br />
|Indefinitie symmetric<br />
|none<br />
|}<br />
<br />
{| class="wikitable"<br />
|+<b>Таблица 2 - «исторические» реализации.</b><br />
!Название библиотеки<br />
!Язык программирования<br />
!Дата появления, версия библиотки<br />
!Тип метода<br />
!Параллелизация<br />
|-<br />
|ARPACK<br />
|F77<br />
|1995, 2.1<br />
|Arnoldi iterations, impliicit restart<br />
|M<br />
|-<br />
|ARPACK++<br />
|C++<br />
|1998, 1.1<br />
|Arnoldi iterations, impliicit restart<br />
|none<br />
|-<br />
|LANCZOS<br />
|F77<br />
|1992<br />
|N<br />
|none<br />
|-<br />
|LANZ<br />
|F77<br />
|1991, 1.0<br />
|P<br />
|none<br />
|-<br />
|LASO<br />
|F77<br />
|1983, 2<br />
|S<br />
|none<br />
|-<br />
|NAPACK<br />
|F77<br />
|1987<br />
|N<br />
|none<br />
|-<br />
|QMRPACK<br />
|F77<br />
|1996<br />
|Designed for nonsymmetrical matrices (lookahead)<br />
|none<br />
|-<br />
|SVDPACK<br />
|C/F77<br />
|1992<br />
|P, finds SVD<br />
|none<br />
|-<br />
|Underwood<br />
|F77<br />
|1975<br />
|F, block version<br />
|none<br />
|}<br />
<br />
В настоящее время алгоритм Ланцоша поиска собственных значений квадратной симметричной матрицы включён в несколько библиотек и реализован на различных языках, среди которых такие наиболее распространенные как '''C''', '''C++''', '''FORTRAN77/90''', '''MathLab'''. Ссылки на наиболее проверенные и часто используемые реализации алгоритма:<br />
<br />
* Так, например, в языке '''MathLab''' во встроенном пакете '''ARPACK''' найти собственные значения методом Ланцоша можно при помощи вызова функции ''eigs()''. <br />
<br />
* На языке '''С''' существует библиотека '''[http://www.cs.wm.edu/~andreas/software/ PRIMME]''', название которой расшифровывается как PReconditioned Iterative MultiMethod Eigensolver (итерационные методы поиска собственных значений с предусловием).<br />
<br />
* Библиотека '''[http://www.nag.co.uk NAG] Numerical Library''' также содержит обширный набор функций, позволяющих проводить математический анализ, среди которых есть и реализация алгоритма Ланцоша. Библиотека доступна в трех пакетах: NAG C Library, NAG Fortran Library и NAG Library for .NET, совместна со многими языками и с основными операционными системами (Windows, Linux и OS X, а также Solaris, AIX и HP-UX).<br />
<br />
Заслуживают внимания также реализации в проектах IETL<ref>http://www.comp-phys.org/software/ietl/lanczos.html</ref>, ARPACK <ref>http://www.caam.rice.edu/software/ARPACK/</ref> и SLEPc<ref>Hernandez V., Roman J. E., Vidal V. SLEPc: A scalable and flexible toolkit for the solution of eigenvalue problems //ACM Transactions on Mathematical Software (TOMS).– Т. 31. – №. 3. – С. 351-362.</ref>.<br />
<br />
==Литература==<br />
<br />
<references \></div>Konshin