<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Bagnikita</id>
	<title>Алговики - Вклад [ru]</title>
	<link rel="self" type="application/atom+xml" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Bagnikita"/>
	<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/ru/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/Bagnikita"/>
	<updated>2026-10-22T23:00:06Z</updated>
	<subtitle>Вклад</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15729</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15729"/>
		<updated>2016-11-02T08:53:19Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment}}&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились системы&amp;lt;ref&amp;gt;Taigman Y. et al. Deepface: Closing the gap to human-level performance in face verification //Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. – 2014. – С. 1701-1708.&amp;lt;/ref&amp;gt;, точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки, например, NVIDIA cuDNN&amp;lt;ref&amp;gt;https://developer.nvidia.com/cudnn&amp;lt;/ref&amp;gt;) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как JointBayesian&amp;lt;ref&amp;gt;Chen D. et al. Bayesian face revisited: A joint formulation //European Conference on Computer Vision. – Springer Berlin Heidelberg, 2012. – С. 566-579.&amp;lt;/ref&amp;gt;. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-graph.png|thumb|center|700px|Рисунок 0. Граф параллельного алгоритма]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц Tevian FaceSDK&amp;lt;ref&amp;gt;http://tevian.ru/product/facesdk&amp;lt;/ref&amp;gt;. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15728</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15728"/>
		<updated>2016-11-02T08:52:27Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились системы&amp;lt;ref&amp;gt;Taigman Y. et al. Deepface: Closing the gap to human-level performance in face verification //Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. – 2014. – С. 1701-1708.&amp;lt;/ref&amp;gt;, точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки, например, NVIDIA cuDNN&amp;lt;ref&amp;gt;https://developer.nvidia.com/cudnn&amp;lt;/ref&amp;gt;) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как JointBayesian&amp;lt;ref&amp;gt;Chen D. et al. Bayesian face revisited: A joint formulation //European Conference on Computer Vision. – Springer Berlin Heidelberg, 2012. – С. 566-579.&amp;lt;/ref&amp;gt;. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-graph.png|thumb|center|700px|Рисунок 0. Граф параллельного алгоритма]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц Tevian FaceSDK&amp;lt;ref&amp;gt;http://tevian.ru/product/facesdk&amp;lt;/ref&amp;gt;. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15727</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15727"/>
		<updated>2016-11-02T08:52:18Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились системы&amp;lt;ref&amp;gt;Taigman Y. et al. Deepface: Closing the gap to human-level performance in face verification //Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. – 2014. – С. 1701-1708.&amp;lt;/ref&amp;gt;, точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки, например, NVIDIA cuDNN&amp;lt;ref&amp;gt;https://developer.nvidia.com/cudnn&amp;lt;/ref&amp;gt;) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как JointBayesian&amp;lt;ref&amp;gt;Chen D. et al. Bayesian face revisited: A joint formulation //European Conference on Computer Vision. – Springer Berlin Heidelberg, 2012. – С. 566-579.&amp;lt;/ref&amp;gt;. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-1raph.png|thumb|center|700px|Рисунок 0. Граф параллельного алгоритма]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц Tevian FaceSDK&amp;lt;ref&amp;gt;http://tevian.ru/product/facesdk&amp;lt;/ref&amp;gt;. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15726</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15726"/>
		<updated>2016-11-02T08:50:41Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились системы&amp;lt;ref&amp;gt;Taigman Y. et al. Deepface: Closing the gap to human-level performance in face verification //Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. – 2014. – С. 1701-1708.&amp;lt;/ref&amp;gt;, точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки, например, NVIDIA cuDNN&amp;lt;ref&amp;gt;https://developer.nvidia.com/cudnn&amp;lt;/ref&amp;gt;) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как JointBayesian&amp;lt;ref&amp;gt;Chen D. et al. Bayesian face revisited: A joint formulation //European Conference on Computer Vision. – Springer Berlin Heidelberg, 2012. – С. 566-579.&amp;lt;/ref&amp;gt;. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-graph.png|thumb|center|700px|Рисунок 0. Граф параллельного алгоритма]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц Tevian FaceSDK&amp;lt;ref&amp;gt;http://tevian.ru/product/facesdk&amp;lt;/ref&amp;gt;. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15725</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15725"/>
		<updated>2016-11-02T08:40:31Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились системы&amp;lt;ref&amp;gt;Taigman Y. et al. Deepface: Closing the gap to human-level performance in face verification //Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. – 2014. – С. 1701-1708.&amp;lt;/ref&amp;gt;, точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки, например, NVIDIA cuDNN&amp;lt;ref&amp;gt;https://developer.nvidia.com/cudnn&amp;lt;/ref&amp;gt;) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как JointBayesian&amp;lt;ref&amp;gt;Chen D. et al. Bayesian face revisited: A joint formulation //European Conference on Computer Vision. – Springer Berlin Heidelberg, 2012. – С. 566-579.&amp;lt;/ref&amp;gt;. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
TODO: сгенерировать картинку в graphviz&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц Tevian FaceSDK&amp;lt;ref&amp;gt;http://tevian.ru/product/facesdk&amp;lt;/ref&amp;gt;. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15724</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15724"/>
		<updated>2016-11-02T08:39:58Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Программная реализация алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились системы&amp;lt;ref&amp;gt;Taigman Y. et al. Deepface: Closing the gap to human-level performance in face verification //Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. – 2014. – С. 1701-1708.&amp;lt;/ref&amp;gt;, точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки, например, NVIDIA cuDNN&amp;lt;ref&amp;gt;https://developer.nvidia.com/cudnn&amp;lt;/ref&amp;gt;) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как JointBayesian&amp;lt;ref&amp;gt;Chen D. et al. Bayesian face revisited: A joint formulation //European Conference on Computer Vision. – Springer Berlin Heidelberg, 2012. – С. 566-579.&amp;lt;/ref&amp;gt;. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
TODO: сгенерировать картинку в graphviz&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц Tevian FaceSDK&amp;lt;ref&amp;gt;http://tevian.ru/product/facesdk&amp;lt;/ref&amp;gt;. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15723</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15723"/>
		<updated>2016-11-02T08:39:45Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Программная реализация алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились системы&amp;lt;ref&amp;gt;Taigman Y. et al. Deepface: Closing the gap to human-level performance in face verification //Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. – 2014. – С. 1701-1708.&amp;lt;/ref&amp;gt;, точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки, например, NVIDIA cuDNN&amp;lt;ref&amp;gt;https://developer.nvidia.com/cudnn&amp;lt;/ref&amp;gt;) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как JointBayesian&amp;lt;ref&amp;gt;Chen D. et al. Bayesian face revisited: A joint formulation //European Conference on Computer Vision. – Springer Berlin Heidelberg, 2012. – С. 566-579.&amp;lt;/ref&amp;gt;. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
TODO: сгенерировать картинку в graphviz&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK&amp;lt;ref&amp;gt;http://tevian.ru/product/facesdk&amp;lt;/ref&amp;gt;. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15722</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15722"/>
		<updated>2016-11-02T08:39:02Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Сравнение биометрических шаблонов */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились системы&amp;lt;ref&amp;gt;Taigman Y. et al. Deepface: Closing the gap to human-level performance in face verification //Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. – 2014. – С. 1701-1708.&amp;lt;/ref&amp;gt;, точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки, например, NVIDIA cuDNN&amp;lt;ref&amp;gt;https://developer.nvidia.com/cudnn&amp;lt;/ref&amp;gt;) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как JointBayesian&amp;lt;ref&amp;gt;Chen D. et al. Bayesian face revisited: A joint formulation //European Conference on Computer Vision. – Springer Berlin Heidelberg, 2012. – С. 566-579.&amp;lt;/ref&amp;gt;. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
TODO: сгенерировать картинку в graphviz&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15721</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15721"/>
		<updated>2016-11-02T08:38:13Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Обучение сверточной нейронной сети */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились системы&amp;lt;ref&amp;gt;Taigman Y. et al. Deepface: Closing the gap to human-level performance in face verification //Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. – 2014. – С. 1701-1708.&amp;lt;/ref&amp;gt;, точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки, например, NVIDIA cuDNN&amp;lt;ref&amp;gt;https://developer.nvidia.com/cudnn&amp;lt;/ref&amp;gt;) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
TODO: сгенерировать картинку в graphviz&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15720</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=15720"/>
		<updated>2016-11-02T08:37:07Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Общее описание алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились системы&amp;lt;ref&amp;gt;Taigman Y. et al. Deepface: Closing the gap to human-level performance in face verification //Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. – 2014. – С. 1701-1708.&amp;lt;/ref&amp;gt;, точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
TODO: сгенерировать картинку в graphviz&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8466</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8466"/>
		<updated>2016-10-14T23:19:09Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment}}&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
TODO: сгенерировать картинку в graphviz&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8465</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8465"/>
		<updated>2016-10-14T23:18:49Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Исследование влияния HyperThreading на работу алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment}}&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
TODO: сгенерировать картинку в graphviz&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в зависимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8323</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8323"/>
		<updated>2016-10-14T21:46:23Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment}}&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
TODO: сгенерировать картинку в graphviz&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8322</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8322"/>
		<updated>2016-10-14T21:46:02Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment}}&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге необходимо построить биометрический шаблон - поскольку внути алгоритма используется нейросеть, то эти вычисления обычно выполняются на видеокартах. В этом разделе подробно не рассматривается работа нейросети, поэтому можно считать, что ему соответствует одна вершина в графе.&lt;br /&gt;
&lt;br /&gt;
На втором шаге уже есть посчитанный биометрический шаблон (дескриптор) - его необходимо сопоставить в базой (пусть в ней N дескрипторов). Предположим, что доступно Q исполнителей (процессов или потоков). Тогда возможна паралельная обработка Q диапазонов базы размером N/Q. Для каждого такого вычисления длина последовательной части &amp;lt;math&amp;gt;N/Q*d&amp;lt;/math&amp;gt;, где d - размерность дескриптора.&lt;br /&gt;
&lt;br /&gt;
На третьем шаге производится агрегация полученных результатов сопоставнения длины k с один общий список, отсортированный по убыванию меры сходства изображений. Здесь аналогично можно рассматривать параллельную сортировку на Q вычислителях.&lt;br /&gt;
&lt;br /&gt;
Видно, что у алгоритма большой ресурс по возможности распараллеливания за счет возможности параллельной реализации практически любой его части.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8299</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8299"/>
		<updated>2016-10-14T21:34:29Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Cортировка по убыванию метрики сходства двух изображений */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment}}&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором (человеком).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8291</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8291"/>
		<updated>2016-10-14T21:33:37Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Assignment}}&lt;br /&gt;
{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8276</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8276"/>
		<updated>2016-10-14T21:30:11Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Макроструктура алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма связана с вычислением скалярных произведений и удалением повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8275</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8275"/>
		<updated>2016-10-14T21:29:54Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Макроструктура алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
Основная вычислительная сложность алгоритма с вычислении скалярных произведений и удалении повторных сопоставнений людей с выбором максимума.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8249</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8249"/>
		<updated>2016-10-14T21:25:02Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Выводы для классов архитектур */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов. Также при необходимость его можно использовать на мобильных рабочих станциях (ноутбуках) и специализированных серверах (4x xeon E7-8XXX), которые смогут обрабатывать в реальном времени большие объемы данных (например, биометрические для всех людей в стране).&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8242</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8242"/>
		<updated>2016-10-14T21:21:56Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Программная реализация алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|thumb|center|700px|Рисунок 1. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|thumb|center|700px|Рисунок 2. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_overall.png|thumb|center|700px|Рисунок 3. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|thumb|center|700px|Рисунок 4. Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|thumb|center|700px|Рисунок 5. Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|thumb|center|700px|Рисунок 6. Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков)]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный метод должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов.&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8212</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8212"/>
		<updated>2016-10-14T21:17:20Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Исследование влияния HyperThreading на работу алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading повышает производительность системы. Для reduce шага это ускорение значительно.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный метод должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов.&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8209</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8209"/>
		<updated>2016-10-14T21:15:21Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading не ухудшает работу системы. Для reduce эта технология позволяет значительно ускорить работу системы.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Эксперименты проводились на сервере с 2x E5-2683v3 128GB RAM. &lt;br /&gt;
&lt;br /&gt;
Для семейства процессоров на ядре haswell возможно выполнение 32 операций умножения+сложения за такт.&lt;br /&gt;
&lt;br /&gt;
Поэтому пиковая производительность &amp;lt;math&amp;gt;Rpeak=2*14*32*2.0*10^9&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* 2 - число процессоров&lt;br /&gt;
* 14 - количество ядер на каждом из процессоров&lt;br /&gt;
* 32 - количество операций за такт&lt;br /&gt;
* &amp;lt;math&amp;gt;2.0*10^9&amp;lt;/math&amp;gt; - частота ядра без turbo boost&lt;br /&gt;
&lt;br /&gt;
FLOPS алгоритма &amp;lt;math&amp;gt;k=100*10^6*128*2&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;100*10^6&amp;lt;/math&amp;gt; - число операций в секунду&lt;br /&gt;
* 128*2 - количество действий, необходимых для проведения одного сравнения&lt;br /&gt;
&lt;br /&gt;
Эффективность для map = k / peak = 0.014 (1.42%)&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный метод должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов.&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8184</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8184"/>
		<updated>2016-10-14T21:05:19Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Существующие реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading не ухудшает работу системы. Для reduce эта технология позволяет значительно ускорить работу системы.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный метод должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов.&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Системы распознавания лиц редко бывают открытыми (или они низкого качества), поэтому известных распределенных систем распознавания лиц мало. Такие системы часто создаются под отдельный проект и их нельзя купить как &amp;quot;коробочное&amp;quot; решение.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8176</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=8176"/>
		<updated>2016-10-14T21:03:14Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Выводы для классов архитектур */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading не ухудшает работу системы. Для reduce эта технология позволяет значительно ускорить работу системы.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
Данный метод должен хорошо работать как на суперкомпьютерах со специализированной сетевой частью, так и на кластерах серверов с 1/10G Ethernet за счет низкой частоты обменов.&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7729</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7729"/>
		<updated>2016-10-14T15:58:02Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Исследование влияния HyperThreading на работу алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат c HyperThreading, красная - HyperThreading выключен.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading не ухудшает работу системы. Для reduce эта технология позволяет значительно ускорить работу системы.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7724</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7724"/>
		<updated>2016-10-14T15:57:06Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Значительное ускорение при размере базы в 100M (значение близко к 64GB) возникает предположительно из-за использование памяти второго процессора (суммарно 4 канала памяти, а не 2).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат без HyperThreading, красная - HyperThreading включен.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading не ухудшает работу системы. Для reduce эта технология позволяет значительно ускорить работу системы.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7715</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7715"/>
		<updated>2016-10-14T15:52:35Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Масштабируемость алгоритма и его реализации */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Каждый тест проводится 20 раз, результаты усредняются.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат без HyperThreading, красная - HyperThreading включен.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading не ухудшает работу системы. Для reduce эта технология позволяет значительно ускорить работу системы.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7709</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7709"/>
		<updated>2016-10-14T15:51:38Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Исследование влияния HyperThreading на работу алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат без HyperThreading, красная - HyperThreading включен.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Видно, что включение HyperThreading не ухудшает работу системы. Для reduce эта технология позволяет значительно ускорить работу системы.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7705</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7705"/>
		<updated>2016-10-14T15:50:01Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Программная реализация алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. Вместо OpenMP можно использовать любой метод многопоточной обработки данных, например, pthread. Сложность модификации кода минимальна, поскольку map/reduce подразумевает отсутствие зависимостей между различными блоками.&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
&lt;br /&gt;
В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
==== Исследование влияния HyperThreading на работу алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Поскольку могут возникать проблемы из-за низкой пропускной способности памяти, можно предположить, что HyperThreading позволит ускорить работу программы. Во время ожидания данных из памяти может быть задействован второй вычислитель в ядре.&lt;br /&gt;
&lt;br /&gt;
Синяя точка на графике - лучший результат без HyperThreading, красная - HyperThreading включен.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp (10-56 потоков):&lt;br /&gt;
[[Файл:Facerecognition-ht-overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7695</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7695"/>
		<updated>2016-10-14T15:42:29Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Возможные способы и особенности параллельной реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
CUDA можно применять на этапе построения биометрического шаблона, что позволит значительно ускорить построение дескрипторов (например, во время добавления в базу большого числа изображений новых людей).&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7692</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7692"/>
		<updated>2016-10-14T15:40:32Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Возможные способы и особенности параллельной реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
При реализации распределенной системы можно размещать разные chunks на вычислительных узлах и агрегировать результаты. Из-за небольшого размера сообщений такую систему можно легко построить в ЦОД без использования специальных коммуникаций на обычных серверах.&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7688</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7688"/>
		<updated>2016-10-14T15:38:48Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Возможные способы и особенности параллельной реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7678</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7678"/>
		<updated>2016-10-14T15:35:38Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Практическая реализация системы */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В текущей версии алгоритма применяется векторизация с использованием FMA3 инструкций, что позволяет значительно ускорить работу программы. При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов и агрегации по людям используются функции из stl - вполне возможно, что можно реализовать более эффективно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм производит вычисления скалярных произведений, поэтому на этом этапе все обращения к памяти обладают свойством локальности. Биометрический шаблон полностью помещается в кэш память любого современного процессора, данные под базу изобрежний выделяются в виде больших непрерывных блоков памяти. &lt;br /&gt;
&lt;br /&gt;
Сортировка результатов также обладает свойством локальности, поскольку массив хранится непрерывно в памяти. При небольшом размере базы (1-10M изображений) он может полностью поместиться в кэш памяти.&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм построен по модели map/reduce. В map происходит вычисление скалярных произведений дескриптора-запроса и элементов базы. В reduce происходит сортировка и удаление повторов.&lt;br /&gt;
&lt;br /&gt;
Исходная база разбирается на блоки (chunks) по 1M биометрических шаблонов, каждый такой блок обрабатывается независимо в своем потоке (или процессе). Важно то, что в результате работы алгоритма на каждом блоке в результате получается небольшой объем данных - &amp;lt;math&amp;gt;k*(sizeof(float)+sizeof(int32_t))&amp;lt;/math&amp;gt;. Такие данные можно легко передать по медленному каналу с высокой задержкой (не требуется использование InfiniBand). Также данный алгоритм можно запускать как на MPI кластерах с передачей сообщений, так и на hadoop кластерах.&lt;br /&gt;
&lt;br /&gt;
Для сортировки результатов можно применять параллельную сортировку (которая ассимптотически) лучше, но на практике это не нужно из-за разумного ограчения на число результатов. Часто их просматривает вручную оператор системы, поэтому больше нескольких тысяч ставить бессмысленно.&lt;br /&gt;
&lt;br /&gt;
В текущей версии системы реализован параллелизм с использованием OpenMP. В дальнейшем во всех тестах будет использоваться сервер с 2x E5-2683v3 128GB RAM. MOPS - миллионы операций (для map -скалярное произведение, reduce - сортировка) в секунду.&lt;br /&gt;
&lt;br /&gt;
Результаты работы map функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Т.к скорость чтения из DDR4 памяти 17GB/s (на практике немного меньше), то с 4 каналов памяти (по 2 на каждый процессор) нельзя считывать быстрее, чем 68GB/s. База 200M дескрипторов занимает 98GB ram, поэтому время 2300ms для вычисления всех скалярных произведений близко к нижней границе. Дальнейшее увеличение числа потоков на одном сервере не будет приводить к существенному ускорению работы программы.&lt;br /&gt;
&lt;br /&gt;
Результаты работы reduce функции в завсимости от размера базы и числа потоков в openmp:&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Reduce этап лучше распараллеливается, поскольку в нем намного меньше число обращений к памяти. Его также можно ускорять, например подбирая оптимальный размер блока (chunk size). Для базы в 200M лиц лучшая скорость 1400ms (достигается на 20 потоках, дальше ускорения нет).&lt;br /&gt;
&lt;br /&gt;
Суммарная скорость работы системы:&lt;br /&gt;
[[Файл:Facerecognition_overall.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7596</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7596"/>
		<updated>2016-10-14T14:21:14Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Быстрое сравнение биометрических шаблонов */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
Ниже приводится пример кода для вычисления дескриптора:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
pp = descriptor;&lt;br /&gt;
pg = gallery_ptr;&lt;br /&gt;
__m256 sum = _mm256_setzero_ps();&lt;br /&gt;
for(int j = 0; j &amp;lt; 16; ++j, pp += 8, pg += 8) {&lt;br /&gt;
    sum = _mm256_fmadd_ps(_mm256_load_ps(pp), _mm256_load_ps(pg), sum);&lt;br /&gt;
}&lt;br /&gt;
const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(sum, 1), _mm256_castps256_ps128(sum));&lt;br /&gt;
const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));&lt;br /&gt;
const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));&lt;br /&gt;
score = _mm_cvtss_f32(x32);&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. Параллельная реализация в данном случае будет неэффективна.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7595</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7595"/>
		<updated>2016-10-14T14:19:03Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Быстрое формирование результата */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. Параллельная реализация в данном случае будет неэффективна.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7594</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7594"/>
		<updated>2016-10-14T14:17:38Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Быстрое сравнение биометрических шаблонов */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7593</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7593"/>
		<updated>2016-10-14T14:17:06Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Быстрое сравнение биометрических шаблонов */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px|thumb]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7591</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7591"/>
		<updated>2016-10-14T14:15:31Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Быстрое формирование результата */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition_reduce.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7587</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7587"/>
		<updated>2016-10-14T14:14:56Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Быстрое формирование результата */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Файл:faceredognition_reduce.png]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7585</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7585"/>
		<updated>2016-10-14T14:13:19Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Свойства алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
Из преимуществ данного алгоритма можно назвать низкую нагрузку на сеть при обмене данными, возможность работы на множестве систем (MPI, hadoop и тд), посколько объем данных, передаваемых между узлами небольшой. Для практической реализации не требуется использования специальных коммуникаций с низкой задержкой (обмены происходят достаточно редко, время доставки сообщений не критично).&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7579</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7579"/>
		<updated>2016-10-14T14:06:14Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Свойства алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основная проблема при реализации параллельной версии данного алгоритма - необходимость сортировки результатов. Исходя из размеров базы важно правильно выбрать количество вычислителей, поскольку в ряде случаев сортировка будет работать очень быстро (число элементов будет значительно меньше объема базы) и не потребуется реализация параллельной сортировки. &lt;br /&gt;
&lt;br /&gt;
Другая проблема - низкая вычислительная сложность самого алгоритма и большое число обращений к памяти. При реализации важно выделять память большими блоками для успешного чтения из кэша процессора.&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7566</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7566"/>
		<updated>2016-10-14T13:56:03Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Входные и выходные данные алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Входные данные:&lt;br /&gt;
* изображение &amp;lt;math&amp;gt;P\in{R}^{w*h*c}&amp;lt;/math&amp;gt;, где w,h,c - ширина, высота, число каналов&lt;br /&gt;
* база изображений лиц &amp;lt;math&amp;gt;G={g_1,g_2,g_3,...,g_n}, g_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из биометрического шаблона и порядкового номера человека&lt;br /&gt;
&lt;br /&gt;
Выходные данные*&lt;br /&gt;
* список &amp;lt;math&amp;gt;L={l_1,l_2,...,l_k}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;l_i\in{T}&amp;lt;/math&amp;gt;&lt;br /&gt;
* T - объект, состоящий из числа (мера сходства двух изображений) и id (порядкового номера) человека в базе&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7559</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7559"/>
		<updated>2016-10-14T13:51:12Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(k*logk+q*(log(k*q))^2+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*(k*log(q)+q*(log(q*k))^2))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Данная оценка получена с применением [http://darwin.bth.rwth-aachen.de/opus3/volltexte/2010/3502/pdf/3502.pdf параллельной сортировки].&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма === &lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7530</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7530"/>
		<updated>2016-10-14T13:38:18Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(q*k*logk+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*q*k*log(q*k))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
Видно, что с увеличением числа процессоров&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма === &lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7524</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7524"/>
		<updated>2016-10-14T13:35:27Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Сложность параллельного алгоритма, в предположении доступности q узлов: &amp;lt;math&amp;gt;O(q*k*logk+\frac{n}{q}*(d+log(\frac{n}{q}))+\lambda*q*k*log(q*k))&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* n - размер базы изображений лиц&lt;br /&gt;
* d - размерность биометрического шаблона&lt;br /&gt;
* k - количество результатов в выдаче (k &amp;lt;&amp;lt; n)&lt;br /&gt;
* &amp;lt;math&amp;gt;\lambda&amp;lt;/math&amp;gt; - флаг (0 или 1), нужно ли производить агрегацию результатов по людям&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма === &lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7507</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7507"/>
		<updated>2016-10-14T13:20:43Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Общее описание алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма === &lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7505</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7505"/>
		<updated>2016-10-14T13:20:21Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Последовательная сложность алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* Обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число элементов в базе&lt;br /&gt;
*&amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; - размерность биометрического шаблона&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма === &lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7493</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7493"/>
		<updated>2016-10-14T13:10:46Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Макроструктура алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* Обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK]. Предложенная параллельная реализация может работать с любой системой распознавания лиц, для которой выполнены следующие условия:&lt;br /&gt;
* известна метрика для сравнения&lt;br /&gt;
* можно получить дескрипторы (биометрические шаблоны) в явном виде&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма === &lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7491</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7491"/>
		<updated>2016-10-14T13:09:18Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* Обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK].&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма === &lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7488</id>
		<title>Участник:Bagnikita/Face Recognition</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Bagnikita/Face_Recognition&amp;diff=7488"/>
		<updated>2016-10-14T13:09:00Z</updated>

		<summary type="html">&lt;p&gt;Bagnikita: /* Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Распознавание лиц&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;w*h*c&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(k)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Bagnikita|Н.Ю.Багров]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Распознавание лиц при помощи компьютера было впервые предложено в 1966 г. Woody Bledsoe и др. В первой системе использовался графический планшет, на котором было необходимо вручную отметить ключевые особенности на лице (брови, губы и тд). Система в дальнешейм использовала расстояния между этими точками для сравнения разных изображений. Поскольку требовалось много ручной работы, то оператор мог обработать около 40 изображений в час. В 1997 была создана система, которая непосредственно работала с видеоданными и могла применяться на практике. В дальшейшем с увеличением производительности вычислительных систем появились более качественные алгоритмы, но они заметно уступали человеку на данной задаче. С появлением высокороизводительных видеокарт в 2014 году появились [https://research.facebook.com/publications/deepface-closing-the-gap-to-human-level-performance-in-face-verification/ системы], точность которых уже превышала точность оператора (человека). Основное преимущество таких систем в том, что они могут обрабатывать большие объемы данных - производительность человека намного ниже (требуется несколько секунд на сравнение пары изображений). &lt;br /&gt;
&lt;br /&gt;
Задачу распознавания лиц можно разбить на несколько этапов:&lt;br /&gt;
&lt;br /&gt;
* Обучение нейронной сети&lt;br /&gt;
* построение биометрического шаблона&lt;br /&gt;
* сравнение биометрических шаблонов&lt;br /&gt;
* поиск в базе и формирование результата&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из построения биометрических шаблонов (дескрипторов) по изображению и поиску заданного шаблона в базе уже вычисленных дескрипторов. &lt;br /&gt;
&lt;br /&gt;
В дальшейшем будем рассматривать только сверточные нейронные сети. Классические архитектуры нейронных сетей с полносвязными слоями неприменимы на практике для анализа изображений, поскольку количество параметров такой сети экспоненциально возрастает с размером входных данных и количеством слоев. В таких сетях каждый нейрон не связан со всеми нейронами предыдущего слоя, а домножается на некоторый коэффициент (ядро свертки) по всему изображению. Таким образом, каждый такой слой применяет операцию свертки ко входам со своими коэффициентами, что заметно уменьшает число весов в нейронной сети. Это позволяет уменьшить потребление памяти и эффект переобучения сети, который проявляется в том, что сеть хорошо работает на классификации элементов обучающей выборки и хуже на валидационной.&lt;br /&gt;
&lt;br /&gt;
==== Обучение сверточной нейронной сети ====&lt;br /&gt;
&lt;br /&gt;
Для обучения нейронной сети используется стохастический градиентный спуск. Начальные веса сети устанавливаются случайными. Заметим, что существенное время занимают сверточные и полносвязные слои. Их можно свести к операции перемножения матриц следуюим образом:&lt;br /&gt;
* пусть размерность фильтра свертки k*k&lt;br /&gt;
* скопируем блоки исходного изображения размером k*k в столбцы новой матрицы&lt;br /&gt;
* умножим эту матрицу на коэффициеты фильтра свертки&lt;br /&gt;
* полученная матрица будет результатом применения свертки к входному изображению&lt;br /&gt;
&lt;br /&gt;
Сложность задачи вычисления свертки зависит квадратично от размера фильтра и линейно от количества выходных данных.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;FeatureMap = A^{n, w * h} * B^{w * h, c * k^2}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;FeatureMap&amp;lt;/math&amp;gt; - результат применения свертки, а &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;B&amp;lt;/math&amp;gt; - входное изображение и матрица весов слоя нейронной сети&lt;br /&gt;
&lt;br /&gt;
Существуют библиотеки ([https://developer.nvidia.com/cudnn NVIDIA cuDNN]) которые решают данную задачу более эффективно. Например, свертки размером 3x3, 5x5 могут быть реализованы с использованием FFT (что работает на видеокарте быстрее, чем умножение матриц)&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Будем называть биометрическим шаблоном вектор &amp;lt;math&amp;gt;{R}^d&amp;lt;/math&amp;gt;, который является представлением лица человека. Этот вектор получается после применения нейронной сети к изображению - в качестве такого вектора используют отклики с одного последних слоев нейронной сети. Для обеспечения возможности сравнивать между собой эти шаблоны вектор нормализуют.&lt;br /&gt;
&lt;br /&gt;
==== Сравнение биометрических шаблонов ====&lt;br /&gt;
&lt;br /&gt;
На предыдущем этапе был получен вектор значений &amp;lt;math&amp;gt;D={A}^d&amp;lt;/math&amp;gt;, где d - размерность дескриптора. Можно рассматривать достаточно много различных оценивающих функций (метриками их называть некорректно, т.к для них не всегда выполняется неравенство треугольника). Например, в качестве такой функции можно рассматривать cosine distance - косинус угла между векторами-признаками в d-мерном пространстве. Также достаточно часто используют L2 метрику, либо более сложные контрукции, такие как [https://www.microsoft.com/en-us/research/publication/bayesian-face-revisited-a-joint-formulation/ Joint Bayesian]. У всех таких &amp;quot;метрик&amp;quot; есть преимущества и недостатки, например в L2 можно легко проводить кластеризацию, тогда как в JointBayes и cosine metric нет. JointBayes можно дообучить на целевой выборке и повысить точность работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе и формирование результата ====&lt;br /&gt;
&lt;br /&gt;
Предположим, что для каждого человека в базе есть несколько фотографий. Тогда после выполнения операции сравнения биометрических шаблона изображения-запроса &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и всех изображений в базе &amp;lt;math&amp;gt;G={g1,g2,...,gn}&amp;lt;/math&amp;gt; необходимо выбрать результат с максимальным значением оценивающей функции для данного человека. Т.е необходимо оставить в результате только уникальные по людям совпадения, выбрав при этом максимальное значение при удалении дубликатов по людям.&lt;br /&gt;
&lt;br /&gt;
После выполнения поиска в базе необходимо произвести нормализацию полученных значений оценивающей функции. В системах распознавания лиц обычно используют диапазон [0.0 - 1.0], где 0.0 означает минимальное сходство двух изображений лиц, а 1.0 максимальное.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основные операции составляют построение биометрического шаблона, и поиск в базе&lt;br /&gt;
&lt;br /&gt;
==== Построение биометрического шаблона ====&lt;br /&gt;
&lt;br /&gt;
Обозначим за &amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; сложность операции вычисления биометричсекого шаблона по входному изображению лица человека. Поскольку сверточной нейронной сети с полносвязными слоями должно подаваться на вход изображения фиксированного размера, то необходимо изменить разрешение исходного изображения. В данной реализации алгоритма распознавания лиц используется билинейная интерполяция. Нейронная сеть получает на вход одноканальные изображения размером &amp;lt;math&amp;gt;100*100&amp;lt;/math&amp;gt;. Поэтому число необходимых операций (по операциям умножения) можно оценить как &amp;lt;math&amp;gt;O(w*h*c) + O(100 * 100 * 6) = O(w*h*c) + Const&amp;lt;/math&amp;gt;. На практике эта константа существенна, поскольку эти операции неоднородны по памяти.&lt;br /&gt;
&lt;br /&gt;
Непосредственно время работы нейронной сети существенно зависит от предложенной архитектуры (глубины сети, типов слоев и числа выходов каждого слоя).&lt;br /&gt;
&lt;br /&gt;
В реализации, предлолженной в практической части, используются более 10 сверточных слоев. Количестве операций можно оценить исходя из количества весов связей в нейронной сети - 80M и размера сверток &amp;lt;math&amp;gt;3*3&amp;lt;/math&amp;gt;. Оценка числа операций получилась ~2 GFlops на одну операцию применения нейросети (forward pass, 180ms). Она согласуется с реальным временем работы системы и производительностью системы на тесте linpack (130GFlops 4 cores) на ноутбуке.&lt;br /&gt;
&lt;br /&gt;
==== Поиск в базе ====&lt;br /&gt;
&lt;br /&gt;
Поиск в базе состоит из следующих частей:&lt;br /&gt;
* сравнение между собой двух дескрипторов изображений и вычисление метрики по базе&lt;br /&gt;
* агрегация результатов по людям&lt;br /&gt;
* сортировка по убыванию метрики сходства двух изображений&lt;br /&gt;
&lt;br /&gt;
===== Сравнение между собой двух дескрипторов изображений и вычисление метрики по базе =====&lt;br /&gt;
&lt;br /&gt;
В практической реализации алгоритма используется косинусовая метрика &amp;lt;math&amp;gt;M(x,y)=\frac{(x,y)}{\lVert x \lVert * \lVert y \lVert}&amp;lt;/math&amp;gt;. Заметим, что вектора &amp;lt;math&amp;gt;x, y&amp;lt;/math&amp;gt; уже нормированы на предыдущем этапе. Поэтому число операций для вычисления метрики составляет &amp;lt;math&amp;gt;d&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;d - 1&amp;lt;/math&amp;gt; сложений, где d - размерность дескриптора. В практической реализации размерность дескриптора 128. Если размер базы &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, то число операций &amp;lt;math&amp;gt;O(n*d+n*(d-1))=O(n*d)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Агрегация результатов по людям =====&lt;br /&gt;
&lt;br /&gt;
Для выполнения операции unique (без использования дополнительной памяти) необходимо отсортировать элементы (person_id, match_score) массива по ключу person_id, а при совпадении по score. После этого можно за линейное время удалить дубликаты по person_id, оставив первый элемент (который будет максимальным по match_score). Сложность таких операций &amp;lt;math&amp;gt;O(n*logn+n)=O(n*logn)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===== Cортировка по убыванию метрики сходства двух изображений =====&lt;br /&gt;
&lt;br /&gt;
Финальная сортировка по match_score будет выполнена за O(n*logk), где k - количество результатов в выдаче. Обычно на практике число k не более 1000, поскольку обработка результата чаще всего производится вручную оператором.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм содержит вычисление биометричиского шаблона, который на этой странице не приводится. Используется готовая бибилотека распознавания лиц [http://tevian.ru Tevian FaceSDK].&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм можно представить в виде следующего фрагмента псевдокода (он менее эффективный, чем предложенный выше. Эффективная реализация на c++):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&#039;python&#039;&amp;gt;&lt;br /&gt;
desc = enroll(Image) # Image - изображение лица человека&lt;br /&gt;
matches = {}&lt;br /&gt;
for gallery_entry in G:&lt;br /&gt;
    if matches.get(gallery_entry.person_id, None) is None:&lt;br /&gt;
        matches[gallery_entry.person_id] = 0.0&lt;br /&gt;
    matches[gallery_entry.person_id] = max(matches[gallery_entry.person_id], match(desc, gallery_entry.descriptor))&lt;br /&gt;
matches = sorted(key = lambda x: x[1], matches.items())&lt;br /&gt;
return [x[0], applyMapping(x[1]) for x in matches]&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Если рассматривать всю последовательность действий (построение шаблона, поиск в базе), то сложность алгоритма &amp;lt;math&amp;gt;O(E(w,h,c)+n*d+n*logn+n*logk+CNNConst)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
*&amp;lt;math&amp;gt;E(w,h,c)&amp;lt;/math&amp;gt; - сложность обработки изображения размером (width*height*channels) до необходимого размера для нейросети&lt;br /&gt;
*&amp;lt;math&amp;gt;n*d&amp;lt;/math&amp;gt; - число операций для вычисления метрики между запросом и базой&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logn&amp;lt;/math&amp;gt; - сортировка результата для удаления дубликатов&lt;br /&gt;
*&amp;lt;math&amp;gt;n*logk&amp;lt;/math&amp;gt; - сортировка результата по значению оценивающей функции&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма === &lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
TODO&lt;br /&gt;
&lt;br /&gt;
== Практическая реализация системы ==&lt;br /&gt;
&lt;br /&gt;
Построение биометрических шаблонов выполнено при помощи библиотеки распознавания лиц FaceSDK. В этом разделе приводится эффективность параллельной реализации сравнения изображени лица с некоторой базой лиц. &lt;br /&gt;
&lt;br /&gt;
=== Быстрое сравнение биометрических шаблонов ===&lt;br /&gt;
&lt;br /&gt;
При выполнении оценки производительности системы использовалась простая реализация cosine метрики (цикл для вычисления скалярного произведения) и с использованием FMA инстукций. Набор инструкций FMA позволяет выполнять операцию &amp;lt;math&amp;gt;y = ax + b&amp;lt;/math&amp;gt; без дополнительных операций.&lt;br /&gt;
&lt;br /&gt;
Псевдокод операции mm256_fmadd (intel)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
FOR j := 0 to 7&lt;br /&gt;
    i := j*32&lt;br /&gt;
    dst[i+31:i] := (a[i+31:i] * b[i+31:i]) + c[i+31:i]&lt;br /&gt;
ENDFOR&lt;br /&gt;
dst[MAX:256] := 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате тестирования (процессор 6300HQ, ноутбук, база 30М биометрических шаблонов) однопоточной программы было установлено, что FMA3 версия в 5 раз быстрее по сравнению с обычным циклом (880ms vs 4600ms). Все дальнейшие экспериенты будут проводиться с FMA3 реализацией (если не указано иначе). Компиляция производится gcc 5.4 с флагами &amp;lt;code&amp;gt;-O3 -static-libgcc -static-libstdc++ -mavx -mavx2 -mfma -fopenmp&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Применение openmp позволило ускорить версию с обычным циклом до 1300ms, что медленнее FMA3. Вычисление в несколько потоков не ускорило реализацию на FMA3. Вероятно, причина в пропускной способности оперативной памяти - для DDR4 15GBps на канал. Объем тестовой базы 15.5GB, поэтому не менее 500ms должно было занять чтение из памяти.&lt;br /&gt;
&lt;br /&gt;
На сервере (2x E5-2683v3 256GB RAM, база 150М биометрических шаблонов) получилось следующее ускорение от использования FMA3 + OpenMP (MOPS - количество операций сравнения в секунду):&lt;br /&gt;
&lt;br /&gt;
[[Файл:Facerecognition-matcher.png|1280px]]&lt;br /&gt;
&lt;br /&gt;
Также в процессе тестирования было замечено, что при превышении размера базы лиц объема оперативной памяти, напрямую доступной одному из процессоров, скороость работы снижается.&lt;br /&gt;
&lt;br /&gt;
Количество операций с плавающей точкой (умножения) для сравнения биометрических шаблонов: &amp;lt;math&amp;gt;n*128&amp;lt;/math&amp;gt; FLOP.&lt;br /&gt;
&lt;br /&gt;
=== Быстрое формирование результата ===&lt;br /&gt;
&lt;br /&gt;
Если на каждой операции поиска изображения в базе необходимо выбирать максимальную фотографию для каждого челокека в базе, то задача сводится к классической map/reduce:&lt;br /&gt;
* map - вычисление скалярных произведений&lt;br /&gt;
* reduce - группировка результатов по людям и выдача N максимально похожих&lt;br /&gt;
&lt;br /&gt;
В reduce применение openmp позволило увеличить скорость с 3000ms до 1000ms на 4 ядрах процессора (ноутбук с 6300HQ, 30M записей в базе)&lt;br /&gt;
&lt;br /&gt;
Из дальшейнего фрагмента кода очевидно, что при таком распараллеливании нет зависимостей по переменным. Эксперимент показал, что применять openmp для inplace_merge неэффективно для текущих ограничений на размер базы (200-300M).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;c++&amp;quot;&amp;gt;&lt;br /&gt;
vector&amp;lt;MatchResult&amp;gt; OMP_Blob::reduce(size_t top_k, bool merge_person)&lt;br /&gt;
{&lt;br /&gt;
    const int num_chunks = static_cast&amp;lt;int&amp;gt;(_chunks.size());&lt;br /&gt;
&lt;br /&gt;
    vector&amp;lt;MatchResult&amp;gt; result;&lt;br /&gt;
    vector&amp;lt;vector&amp;lt;MatchResult&amp;gt;&amp;gt; results(num_chunks);&lt;br /&gt;
    #pragma omp parallel for num_threads(_num_threads)&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        results[i] = _chunks[i].reduce(top_k, merge_person, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
        result.insert(result.end(), results[i].begin(), results[i].end());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if(merge_person) {&lt;br /&gt;
        size_t csum = 0;&lt;br /&gt;
        for(int i = 0; i &amp;lt; num_chunks; ++i) {&lt;br /&gt;
            inplace_merge(result.begin(), result.begin() + csum, result.begin() + csum + results[i].size(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
                return a.person_id &amp;lt; b.person_id || (a.person_id == b.person_id &amp;amp;&amp;amp; a.score &amp;gt; b.score);&lt;br /&gt;
            });&lt;br /&gt;
            csum += results[i].size();&lt;br /&gt;
        }&lt;br /&gt;
        result.resize(unique(result.begin(), result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
            return a.person_id == b.person_id;&lt;br /&gt;
        }) - result.begin());&lt;br /&gt;
    }&lt;br /&gt;
    size_t result_size = min(top_k, result.size());&lt;br /&gt;
    partial_sort(result.begin(), result.begin() + result_size, result.end(), [](const MatchResult &amp;amp;a, const MatchResult &amp;amp;b) {&lt;br /&gt;
        return a.score &amp;gt; b.score;&lt;br /&gt;
    });&lt;br /&gt;
    result.resize(result_size);&lt;br /&gt;
    result.shrink_to_fit();&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Финальную сортировку результатов по score ускорять не требуется, поскольку top_k на практике не более 1000. &lt;br /&gt;
&lt;br /&gt;
[[Категория:Нейронные сети]]&lt;/div&gt;</summary>
		<author><name>Bagnikita</name></author>
	</entry>
</feed>