Этот текст написан для тех, кто интересуется глубоким обучением, кто хочет использовать разные методы библиотек pytorch и tensorflow для минимизации функции многих переменных, кому интересно научиться превращать последовательно выполняющуюся программу в выполняемые с помощью numpy векторизованные матричные вычисления. А ещё можно научиться делать мультфильм из данных, визуализированных с помощью PovRay и vapory.   В одной из предыдущих публикаций нашего блога, а именно в пункте "Грабли №6", рассказывалось о технологии DSSM, которая позволяет преобразовать текст в векторы. Чтобы найти такое отображение, нужно найти коэффициенты линейных преобразований, минимизирующих довольно сложную функцию потерь. Понять, что происходит при обучении такой модели, тяжело из-за большой размерности векторов. Чтобы выработать интуицию, позволяющую правильно выбрать метод оптимизации и задать параметры этого метода, можно рассмотреть упрощённый вариант этой задачи. Пусть теперь вектора находятся в нашем обычном трёхмерном пространстве, тогда их легко нарисовать. А функция потерь будет потенциалом отталкивания точек друг от друга и притяжения к единичной сфере. В этом случае решением задачи будет равномерное распределение точек по сфере. Качество текущего решения легко оценить на глаз. Итак, будем искать равномерное распределение по сфере заданного количества  точек. Такое распределение бывает нужно акустику для того, чтобы понять, в каком направлении запустить волну в кристалле. Связисту — чтобы узнать, как расположить на орбите спутники для достижения наилучшего качества связи. Метеорологу — для размещения станций слежения за погодой. Для некоторых  задача решается легко. Например, если , мы можем взять куб и его вершины будут являться ответом к задаче. Также нам повезёт, если  будет равно количеству вершин икосаэдра, додекаэдра или другого платонова тела. В противном случае задача не столь проста. Для достаточно большого количества точек есть формула с эмпирически подобранными коэффициентами, ещё несколько вариантов — здесь, здесь, здесь, и здесь. Но есть и более универсальное, хотя и более сложное решение, которому посвящена данная статья. Решим задачу, очень похожую на задачу Томсона (wiki). Разбросаем  точек случайно, а потом заставим их притягиваться к какой-то поверхности, например к сфере, и отталкиваться друг от друга. Притяжение и отталкивание определяются функцией — потенциалом. При минимальном значении потенциальной функции точки будут лежать на сфере наиболее равномерно.  Эта задача очень похожа на процесс обучения моделей machine learning (ML), в ходе которого минимизируется функция потерь. Но дата-сайентист обычно смотрит, как уменьшается одно единственное число, а мы сможем наблюдать за изменением картинки. Если у нас всё получится, то мы увидим, как точки, случайно размещённые внутри куба со стороной 1, разойдутся по поверхности сферы:  Схематически алгоритм решения задачи можно представить следущим образом. Каждой точке, расположенной в трёхмерном пространстве, соответствует строчка матрицы . По этой матрице рассчитывается функция потерь , минимальному значению которой соответствует равномерное распределение точек по сфере. Минимальное значение находится с помощью библиотек pytorch и tensorflow, позволяющих вычислять градиент функции потерь по матрице  и спускаться в минимум, используя один из реализованных в библиотеке методов: SGD, ADAM, ADAGRAD и т.д.  Благодаря своей простоте, выразительности и способности служить интерфейсом к библиотекам, написанным на других языках, Python широко используется для решения задач машинного обучения. Поэтому примеры кода в этой статье написаны на Python. Для проведения быстрых матричных вычислений будем использовать библиотеку numpy. Для минимизации функции многих переменных — pytorch и tensorflow. Случайно разбрасываем точки в кубе со стороной 1. Пусть у нас будет 500 точек, а упругое взаимодействие в 1000 раз более значимо, чем электростатическое: Этот код сгенерировал матрицу  размером , заполненную случайными числами от 0 до 1:  Будем считать, что каждая строка этой матрицы соответствует одной точке. А в трёх столбцах записаны координаты  всех наших точек. Потенциал упругого взаимодействия точки с повехностью единичной сферы . Потенциал электростатического взаимодействия точек  и  — . Полный потенциал складывается из электростатического взаимодействия всех пар точек и упругого взаимодействия каждой точки с поверхностью сферы:   В принципе, можно посчитать значение потенциала по этой формуле: Но есть небольшая беда. Для жалких 2000 точек эта программа будет работать 2 секунды. Гораздо быстрее будет, если мы посчитаем эту величину, используя векторизованные матричные вычисления. Ускорение достигается как за счёт реализации матричных операций с помощью "быстрых" языков fortran и C, так и за счёт использования векторизированных операций процессора, позволяющих за один такт выполнить действие над большим количеством входных данных. Посмотрим на матрицу . Она обладает многими интересными свойствами и часто встречается в выкладках, связанных с теорией линейных классификаторов в ML. Так, если считать что в строчке матрицы  с индексами  и  записаны вектора трёхмерного пространства , то матрица  будет состоять из скалярных произведений этих векторов. Таких векторов  штук, значит размерность матрицы  равна .  На диагонали матрицы  стоят квадраты длин векторов . Зная это, давайте считать полный потенциал взаимодействия. Начнём с расчёта двух вспомогательных матриц. В одной диагональ матрицы  будет повторяться в строках, в другой — в столбцах.  Посмотрим теперь на значение выражения p_roll + q_roll - 2 * S  Элемент с индексами  матрицы sq_dist равен . То есть, у нас получилась матрица квадратов расстояний между точками.  dist = np.sqrt(sq_dist) — матрица расстояний между точками. Нам нужно посчитать потенциал, учитывающий отталкивание точек между собой и притяжение к сфере. Поставим на диагональ единицы и заменим каждый элемент на обратный ему (только не подумайте, что мы при этом обратили матрицу!): rec_dist_one = 1 / (dist + np.eye(n)). Получилась матрица, на диагонали которой стоят единицы, другие элементы — потенциалы электростатического взаимодействия между точками. Добавим теперь квадратичный потенциал притяжения к поверхности единичной сферы. Расстояние от поверхности сферы . Возводим его в квадрат и умножаем на , который задаёт соотношение между ролью электростатического отталкивания частиц и притяжения сферы. Итого k = 1000, all_interactions = rec_dist_one - torch.eye(n) + (d.sqrt() - torch.ones(n))**2. Долгожданная функция потерь, которую мы будем минимизировать: t = all_interactions.sum() Программа, рассчитывающая потенциал с помощью библиотеки numpy: Здесь дела обстоят чуть лучше — 200 мс на 2000 точек. Используем pytorch: И, наконец, tensorflow: Сравниваем производительность этих трёх подходов: Векторизованные вычисления дают выигрыш более чем на полтора десятичных порядка относительно кода на чистом python. Виден "boiler plate" у pytorch: вычисления малого объёма занимают заметное время, но оно почти не изменяется при увеличении объёма вычислений. В наше время данные можно визуализировать средствами огромного количества пакетов, таких как Matlab, Wolfram Mathematica, Maple, Matplotlib и т. д. и т. п. В этих пакетах очень много сложных функций, делающих сложные вещи. К сожалению, если перед тобой стоит простая, но нестандартная задача, ты оказываешься безоружен. Моё любимое решение в такой ситуации — povray. Это очень мощная программа, которую обычно применяют для создания фотореалистичных изображений, но её можно использовать как "ассемблер визуализации". Обычно, сколь бы сложной ни была поверхность, которую хочется отобразить, достаточно попросить povray нарисовать сферы с центрами, лежащими на этой поверхности. С помощью библиотеки vapory можно создать povray-сцену прямо в python, отрендерить её и посмотреть на результат. Сейчас он выглядит так:  Картинка получена так: Из кучи файлов анимированный gif собирается с помощью ImageMagic: На github можно увидеть рабочий код, фрагменты которого приведены здесь. Во второй статье я расскажу о том, как запустить минимизацию функционала, чтобы точки вышли из куба и равномерно расползлись по сфере. программист, аналитик