Русский
Русский
English
Статистика
Реклама

Алгоритмы

Перевод Python 18 задач на вывод символов по заданному шаблону

11.04.2021 16:04:19 | Автор: admin
Подготовка к техническому собеседованию по Python нелёгкая задача. На таком собеседовании вам, вполне возможно, встретятся задачи на вывод символов по заданным шаблонам. Если вы хотите научиться решать такие задачи вам может пригодиться подборка способов их решения, приведённая в этом материале.



Здесь продемонстрировано 18 примеров кода. Начинающие программисты вполне могут проработать всё по порядку, а опытные могут разобраться именно с тем, что им нужно. Главное понять, как устроен тот или иной пример. Отсутствие чёткого понимания того, что происходит в программах, способно сыграть злую шутку с тем, кто, например, заучив фрагмент кода и воспроизведя его на собеседовании, попытается объяснить то, как именно этот код работает. А тех, кто проводит собеседование, часто интересуют именно такие вот разъяснения.

1. Простой числовой треугольник


Желаемый результат:

12 23 3 34 4 4 45 5 5 5 5

Код:

rows = 6for num in range(rows):for i in range(num):print(num, end=" ") # вывод числа# вывод пустой строки после каждой строки с числами для правильного отображения шаблонаprint(" ")

2. Обратный числовой треугольник


Желаемый результат:

1 1 1 1 1 2 2 2 2 3 3 3 4 4 5

Код:

rows = 5b = 0for i in range(rows, 0, -1):b += 1for j in range(1, i + 1):print(b, end=' ')print('\r')

3. Полупирамида из чисел


Желаемый результат:

11 21 2 31 2 3 41 2 3 4 5

Код:

rows = 5for row in range(1, rows+1):for column in range(1, row + 1):print(column, end=' ')print("")

4. Обратная пирамида из уменьшающихся чисел


Желаемый результат:

5 5 5 5 5 4 4 4 4 3 3 3 2 2 1

Код:

rows = 5for i in range(rows, 0, -1):num = ifor j in range(0, i):print(num, end=' ')print("\r")

5. Обратная пирамида, все элементы которой представлены одним и тем же числом


Желаемый результат:

5 5 5 5 5 5 5 5 5 5 5 5 5 5 5

Код:

rows = 5num = rowsfor i in range(rows, 0, -1):for j in range(0, i):print(num, end=' ')print('\r')

6. Пирамида из чисел, расположенных в обратном порядке


Желаемый результат:

12 13 2 14 3 2 15 4 3 2 1

Код:

rows = 6for row in range(1, rows):for column in range(row, 0, -1):print(column, end=' ')print("")

7. Обратная полупирамида из чисел


Желаемый результат:

0 1 2 3 4 5 0 1 2 3 4 0 1 2 3 0 1 2 0 1

Код:

rows = 5for i in range(rows, 0, -1):for j in range(0, i + 1):print(j, end=' ')print('\r')

8. Пирамида из натуральных чисел меньше 10


Желаемый результат:

12 3 45 6 7 8 9

Код:

currentNumber = 1stop = 2rows = 3 # Количество строк, из которых состоит пирамидаfor i in range(rows):for column in range(1, stop):print(currentNumber, end=' ')currentNumber += 1print("")stop += 2

9. Пирамида из чисел от 10, расположенных в обратном порядке


Желаемый результат:

13 26 5 410 9 8 7

Код:

start = 1stop = 2currentNumber = stopfor row in range(2, 6):for col in range(start, stop):currentNumber -= 1print(currentNumber, end=' ')print("")start = stopstop += rowcurrentNumber = stop

10. Пирамида из определённых наборов цифр


Желаемый результат:

11 2 11 2 3 2 11 2 3 4 3 2 11 2 3 4 5 4 3 2 1

Код:

rows = 6for i in range(1, rows + 1):for j in range(1, i - 1):print(j, end=" ")for j in range(i - 1, 0, -1):print(j, end=" ")print()

11. Обратная пирамида из связанных чисел


Желаемый результат:

5 4 3 2 1 1 2 3 4 55 4 3 2 2 3 4 55 4 3 3 4 55 4 4 55 5

Код:

rows = 6for i in range(0, rows):for j in range(rows - 1, i, -1):print(j, '', end='')for l in range(i):print('', end='')for k in range(i + 1, rows):print(k, '', end='')print('\n')

12. Пирамида из чётных чисел


Желаемый результат:

10 10 8 10 8 6 10 8 6 4 10 8 6 4 2

Код:

rows = 5LastEvenNumber = 2 * rowsevenNumber = LastEvenNumberfor i in range(1, rows+1):evenNumber = LastEvenNumberfor j in range(i):print(evenNumber, end=' ')evenNumber -= 2print("\r")

13. Пирамида из наборов чисел


Желаемый результат:

00 10 2 40 3 6 90 4 8 12 160 5 10 15 20 250 6 12 18 24 30 36

Код:

rows = 7for i in range(0, rows):for j in range(0, i + 1):print(i * j, end=' ')print()

14. Пирамида, в каждой строке которой выводятся разные числа


Желаемый результат:

13 35 5 57 7 7 79 9 9 9 9

Код:

rows = 5i = 1while i <= rows:j = 1while j <= i:print((i * 2 - 1), end=" ")j = j + 1i = i + 1print()

15. Зеркально отражённая пирамида из чисел (прямоугольный числовой треугольник)


Желаемый результат:

11 21 2 31 2 3 41 2 3 4 5

Код:

rows = 6for row in range(1, rows):num = 1for j in range(rows, 0, -1):if j > row:print(" ", end=' ')else:print(num, end=' ')num += 1print("")

16. Равносторонний треугольник из символов *


Желаемый результат:

** ** * ** * * ** * * * ** * * * * ** * * * * * *

Код:

size = 7m = (2 * size) - 2for i in range(0, size):for j in range(0, m):print(end=" ")m = m - 1 # уменьшение m после каждого прохода циклаfor j in range(0, i + 1):# вывод пирамиды из звёздочекprint("*", end=' ')print(" ")

17. Перевёрнутый треугольник из символов *


Желаемый результат:

* * * * * ** * * * ** * * ** * ** **

Код:

rows = 5k = 2 * rows - 2for i in range(rows, -1, -1):for j in range(k, 0, -1):print(end=" ")k = k + 1for j in range(0, i + 1):print("*", end=" ")print("")

18. Пирамида из символов *


Желаемый результат:

** ** * ** * * ** * * * *

Код:

rows = 5for i in range(0, rows):for j in range(0, i + 1):print("*", end=' ')print("\r")

Какие задачи вы посоветовали бы прорешать тем, кто готовится к собеседованию по Python?

Подробнее..

Перевод Go распространенные антипаттерны

13.04.2021 12:15:42 | Автор: admin
Программирование это искусство. Мастера своего дела, создающие потрясающие работы, могут ими гордиться. То же самое относится и к программистам, которые пишут код. Чтобы достичь вершин мастерства, творцы постоянно ищут новые подходы к работе и новые инструменты.

Так поступают и разработчики, которые, не прекращая профессионально развиваться, постоянно стремятся найти ответ на самый важный свой вопрос: Как писать хороший код?. Вот что говорит об этом Фредерик Брукс в книге Мифический человеко-месяц, или Как создаются программные системы:

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


Как писать хороший код (источник)

В этом материале сделана попытка найти ответ на большой вопрос из вышеприведенного комикса. Самый простой способ писать хороший код заключается в том, чтобы не употреблять в своих программах так называемые антипаттерны.

Что такое антипаттерны


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

В качестве простого примера проникновения в код антипаттерна можно привести ситуацию, когда при создании API не учитывается то, как именно потребитель этого API будет им пользоваться. Этому посвящён наш первый рассказ об антипаттерне.

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

Рассмотрим некоторые распространённые антипаттерны, встречающиеся в коде, написанном на Go.

1. Возврат значения неэкспортируемого типа из экспортируемой функции


В Go, для экспорта любого поля или любой переменной, нужно, чтобы имя поля или переменной начиналось бы с большой буквы. Сущности экспортируют из пакетов для того чтобы они были бы видны другим пакетам. Например, если в программе нужно воспользоваться константой Pi из пакета math, то обращаться к ней надо с помощью конструкции math.Pi. Использование конструкции math.pi приведет к возникновению ошибки.

Имена (это относится к полям структур, к функциям, к переменным), которые начинаются с маленькой буквы, являются неэкспортируемыми, они видны только в пакете, в котором они объявлены.

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

// Не рекомендованоtype unexportedType stringfunc ExportedFunc() unexportedType {return unexportedType("some string")}// Рекомендованоtype ExportedType stringfunc ExportedFunc() ExportedType {return ExportedType("some string")}

2. Неоправданное использование пустых идентификаторов


В целом ряде ситуаций присвоение значений пустому идентификатору нецелесообразно. Вот, например, что сказано в спецификации Go об использовании пустого идентификатора в циклах for:

Если последней итерационной переменной является пустой идентификатор, то выражение range эквивалентно такому же выражению без этого идентификатора.

// Не рекомендованоfor _ = range sequence{run()}x, _ := someMap[key]_ = <-ch// Рекомендованоfor range something{run()}x := someMap[key]<-ch

3. Использование циклов или нескольких вызовов append для объединения срезов


Если нужно объединить пару срезов нет нужды перебирать один из них в цикле и присоединять элементы к другому срезу по одному. Вместо этого гораздо лучше и эффективнее будет сделать это в одном вызове функции append.

В следующем примере объединение срезов выполняется путём перебора элементов sliceTwo и присоединения этих элементов к sliceOne по одному:

for _, v := range sliceTwo {sliceOne = append(sliceOne, v)}

Но известно, что append это вариативная функция, а это значит, что её можно вызывать с разным количеством аргументов. В результате предыдущий пример можно значительно упростить и переписать с использованием функции append:

sliceOne = append(sliceOne, sliceTwo)

4. Избыточные аргументы в вызовах make


В Go имеется особая встроенная функция make, которая используется для создания и инициализации объектов типов map (ассоциативный массив), slice (срез), chan (канал). Для инициализации среза с использованием make нужно предоставить этой функции, в виде аргументов, тип среза, его длину и емкость. При инициализации ассоциативного массива с помощью make нужно передать функции размер этого массива.

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

  • В случае с каналами емкость буфера устанавливается в 0 (речь идёт о небуферизованном канале).
  • В случае с ассоциативными массивами размер по умолчанию устанавливается в небольшое начальное значение.
  • В случае со срезами емкость по умолчанию устанавливается в значение, равное указанной длине среза.

Вот неудачный пример использования make:

ch = make(chan int, 0)sl = make([]int, 1, 1)

Этот код можно переписать так:

ch = make(chan int)sl = make([]int, 1)

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

const c = 0ch = make(chan int, c) // Это  не антипаттерн

5. Ненужное выражение return в функциях


Не рекомендуется ставить в конец функции выражение return в том случае, если функция ничего не возвращает.

// Бесполезное выражение return, не рекомендованоfunc alwaysPrintFoofoo() {fmt.Println("foofoo")return}// Рекомендованоfunc alwaysPrintFoo() {fmt.Println("foofoo")}

При этом надо отметить, что возврат с помощью return именованных возвращаемых значений не стоит путать с бесполезным использованием return. Например, в следующем фрагменте кода return возвращает именованное значение:

func printAndReturnFoofoo() (foofoo string) {foofoo := "foofoo"fmt.Println(foofoo)return}

6. Ненужные команды break в выражениях switch


В Go выражения switch устроены так, что при выполнении одного из вариантов кода, описываемого в блоке case, код блоков case, которые следуют за ним, выполняться не будет. В других языках, наподобие C, выполнение кода должно быть явным образом прервано с помощью команды break. В противном случае, если, например, в switch нет ни одного break, после выполнения кода одного блока case выполняется и код следующих за ним блоков. Известно, что эта возможность в выражениях switch используется очень редко и обычно вызывает ошибки. В результате многие современные языки программирования, вроде Go, отказались от такой схемы выполнения выражений switch.

В результате в конце блоков case нет необходимости пользоваться командами break. Это значит, что оба нижеприведенных примера дают один и тот же результат.

// Не рекомендованоswitch s {case 1:fmt.Println("case one")breakcase 2:fmt.Println("case two")}// Рекомендованоswitch s {case 1:fmt.Println("case one")case 2:fmt.Println("case two")}

Но, если нужно, в switch можно реализовать переход к последовательному выполнению кода блоков case. Для этого используется команда fallthrough. Например, следующий код выведет 23:

switch 2 {case 1:fmt.Print("1")fallthroughcase 2:fmt.Print("2")fallthroughcase 3:fmt.Print("3")}

7. Отказ от использования стандартных вспомогательных функций для решения распространённых задач


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

Например, в Go, для организации ожидания завершения выполнения нескольких горутин, можно использовать счетчик sync.WaitGroup. При работе с ним могут применяться вспомогательные функции. В частности функция wg.Add() (переменная wg в наших примерах имеет тип sync.WaitGroup), позволяющая добавить нужное количество горутин в группу. Когда горутина из группы завершает выполнение, счетчик уменьшают, вызывая функцию wg.Add() с передачей ей -1:

wg.Add(1)// ...какой-то кодwg.Add(-1)

Если говорить о конструкции wg.Add(-1), то, вместо того, чтобы использовать её для ручного декрементирования счетчика, можно воспользоваться функцией wg.Done(), которая тоже декрементирует счетчик, уменьшая его значение на 1, но при этом выглядит лучше и понятнее, чем wg.Add(-1):

wg.Add(1)// ... какой-то кодwg.Done()

8. Избыточные проверки на nil при работе со срезами


Длина нулевого (nil) среза приводится к 0. Это значит, что не нужно проверять срез на nil перед проверкой его длины.

Например, в следующем фрагменте кода проверка на nil избыточна:

if x != nil && len(x) != 0 {// выполняем какие-то действия}

Этот код можно переписать, убрав из него проверку на nil:

if len(x) != 0 {// выполняем какие-то действия}

9. Ненужные функциональные литералы


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

fn := func(x int, y int) int { return add(x, y) }

Этот код можно улучшить, вынеся add из функционального литерала:

fn := add

10. Использование единственного блока case в выражениях select


Выражения select используются при работе с каналами. Обычно они включают в себя несколько блоков case. Но в том случае, если речь идёт об обработке единственной операции, представленной единственным блоком case, использование выражения select оказывается избыточным. В подобной ситуации можно просто воспользоваться операциями отправки данных в канал или их получения из канала:

// Не рекомендованоselect {case x := <-ch:fmt.Println(x)}// Рекомендованоx := <-chfmt.Println(x)

В выражении select может применяться блок default, код которого выполняется в том случае, если системе не удаётся подобрать подходящий блок case. Использование default позволяет создавать неблокирующие выражения select:

select {case x := <-ch:fmt.Println(x)default:fmt.Println("default")}

11. Параметр типа context.Context, который не является первым параметром функции, в которой используется этот параметр


Если функция имеет параметр типа context.Context, то ему обычно дают имя ctx, а при объявлении функции его следует ставить первым в списке параметров. Такой аргумент используется в Go-функциях достаточно часто, а подобные аргументы, с логической точки зрения, лучше размещать в начале или в конце списка аргументов. Почему?

Это помогает разработчикам не забывать об этих аргументах благодаря единообразному подходу к их использованию в различных функциях. Вариативные функции в Go объявляют с использованием конструкции вида elems ...Type, которая должна располагаться в конце списка их параметров. В результате рекомендуется делать параметр типа context.Context первым параметром функции. Подобные соглашения имеются и в других проектах, например, в среде Node.js первым параметром, который передают коллбэкам, принято делать объект ошибки.

// Не рекомендованоfunc badPatternFunc(k favContextKey, ctx context.Context) {// выполняем какие-то действия}// Рекомендованоfunc goodPatternFunc(ctx context.Context, k favContextKey) {// выполняем какие-то действия}

На что стоит обратить внимание тем, кто хочет писать хороший код на Go?

Подробнее..

Перевод Архитектор современных алгоритмов

05.04.2021 16:10:13 | Автор: admin

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

Барбара Лисков изобрела архитектуру, лежащую в основе современных программ. Создание чего-либо достаточно мощного это искусство.

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

Но к концу 1960-х годов достижения в области вычислительной мощности превзошли возможности программистов. Многие специалисты по информатике создавали программы, не задумываясь о дизайне. Они писали длинные, бессвязные алгоритмы, пронизанные операторами goto машинными инструкциями для перехода к другой части программы, если выполняется определенное условие. Первые программисты полагались на эти операторы, чтобы исправлять непредвиденные последствия своего кода, но они делали программы трудными для чтения, непредсказуемыми и даже опасными. Плохое программное обеспечение в конечном итоге стоило людям жизни, например, когда радиационная установка Therac-25, управляемая компьютером, стала причиной серьезных передозировок радиацией у больных онкологией.

В те времена, когда Барбара Лисков получала докторскую степень по информатике в Стэнфордском университете в 1968 году, она завидовала инженерам-электрикам, потому что они работали с аппаратным обеспечением, соединенным проводами. Эта архитектура, естественно, позволяла им дробить задачи и разделять их на модули подход, который давал им больше контроля, поскольку позволял им независимо рассуждать о дискретных компонентах.

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

Там предо мной были серьезные задачи. Все, что мне нужно было сделать, это без промедления начать решать их.

Барбара Лисков

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

Когда она была еще молодым профессором Массачусетского технологического института, она возглавляла команду, создавшую первый язык программирования, который не полагался на операторы goto. Язык CLU (сокращение от cluster) основывался на изобретенном ею подходе абстракции данных который организовывал код в модули. Все используемые сегодня важные языки программирования, включая Java, C++ и C#, являются потомками CLU.

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

Журнал Quanta встретился с Лисков в ее доме после Гейдельбергского форума лауреатов закрытого собрания (исключительно по приглашениям) ученых-информатиков и математиков, получивших самые престижные награды в своих областях. Лисков была приглашен в Гейдельберг, но ей пришлось отменить свое участие за несколько недель до форума по личным причинам. Интервью было сокращено и отредактировано в целях лучшего понимания.

Вы достигли профессионального совершеннолетия во время разработки искусственного интеллекта. Как изменилось представление об искусственном интеллекте и машинном обучении за время вашей карьеры?

Я защитила докторскую диссертацию с Джоном Маккарти в области искусственного интеллекта. Я написала программу для разыгрывания шахматных эндшпилей. Эту тему предложил Джон, потому что я не играла в шахматы. Я прочитала [шахматные] учебники и перевела эти алгоритмы в поле информатики. В то время, вершиной научной мысли было сделать так, чтобы программа вела себя так, как поступал бы человек. Сейчас уже дела обстоят иначе.

Сегодня программы на основе машинного обучения в большинстве случаев неплохо справляются со своими задачами, но все же не всегда. Люди не понимают, почему они работают или не работают. Если я работаю над проблемой и мне нужно точно понять, почему работает алгоритм, я не буду применять машинное обучение. С другой стороны, один из моих коллег анализирует маммограммы с помощью машинного обучения и находит доказательства того, что рак можно обнаружить намного раньше.

Искусственный интеллект это скорее прикладная, нежели обособленная дисциплина. Его всегда использовали для чего-то конкретного.

Вас он больше интересовал как отдельная дисциплина?

Честно говоря, в то время искусственный интеллект мало на что был способен. Меня интересовали основополагающие задачи. Вопрос как организовать программное обеспечение? был для меня действительно интересен. В процессе проектирования вы сталкиваетесь с необходимостью выяснить, как вам следует реализовать приложение. Вам нужно организовать код, разбив его на части. В этом помогает абстракция данных. Это очень похоже на доказательство теоремы. Невозможно доказать теорему одним махом. Вместо этого вы придумываете несколько лемм и дробите задачу.

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

Первые компьютеры использовали перфокарты для ввода как программ, так и данных.

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

Если бы у вас была волшебная палочка, и вы могли бы направить развитие информатики вперед, как бы это выглядело?

Я очень переживаю за интернет. У нас целая куча проблем, включая фейковые новости и проблемы с безопасностью. Меня беспокоит разведенная пара, в которой муж публикует клевету на жену, в том числе информацию о том, где она живет. Происходят ужасные вещи. Частично это выросло из настроений 80-х. В то время у нас было 15 университетов и пара государственных лабораторий, соединенных интернетом. Мы все были друзьями. Позиция заключалась в том, что сайты не должны нести ответственности за контент. Это задушит их развитие. Вы можете наблюдать, что такое отношение пока сохраняется.

Было ли такое мышление следствием академической свободы?

Нет, это был чистый прагматизм, без понимания того, чем мы закончим. Если бы они взяли на себя модерирование, им пришлось бы решать сложные задачи. Они пошли на это без добавления защитных мер. Для решения наших текущих проблем нужны не только технологии. Нам нужны законы, регулирующие ненадлежащее поведение людей. Нам нужно решить вопросы о конфиденциальности и безопасности. Некоторые из них технические. Например, у Facebook есть алгоритм распространения информации. Они могут распространять информацию медленнее или распознавать, какая информация не должна распространяться. Обществу всегда трудно иметь дело с чем-то новым. Мы можем лишь надеяться, что повзрослеем. Но если бы у меня была волшебная палочка, я бы решила именно эти проблемы.

Видео: Барбара Лисков говорит о вызовах, с которыми сталкиваются компьютерные науки.

Расскажите мне о своем личном опыте в качестве женщины в информатике.

Меня поощряли хорошо учиться в школе. Не знаю, подбадривала ли меня мама, но она точно не мешала мне и не говорила: О нет, это плохая идея. Я прошла все курсы математики и естествознания, к чему девочек не поощряли. В Беркли я была одной из двух женщин (или даже единственной) в классе из 100 человек. Никто никогда не говорил: Вот это да, дела у тебя идут хорошо, почему бы тебе не поработать со мной? Я не знал, что такое вообще может происходить. Я поступила в аспирантуру Стэнфорда. Когда я закончила учебу, со мной никто не разговаривал о работе. Я заметил, что коллег-мужчин, таких как Радж Редди, который был моим другом, брали на академические должности. Мне такую работу никто не предлагал.

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

Да, но мне никто ничего не предлагал. В 90-х я вернулась в Стэнфорд на день факультета. Группа старых профессоров, не осознавая, что они делают, рассуждали о подобном кумовстве (old boy network). Они говорили: О, мой друг сказал мне, что у меня есть хороший молодой парень, которого ты должен нанять. Так обстояли дела. Они не понимали, что тут не так. Они говорили о молодой женщине, которая так преуспела, потому что вышла замуж за профессора! Разве это правильно? У другого коллеги в офисе была пинап-фотография кинозвезды. Я спросил его: Что это за пинап-фотография в твоем офисе? Никакого вразумительного ответа.

Я подала заявление в Массачусетский технологический институт, но меня даже не рассматривали на должность преподавателя. Когда такое происходит, вы думаете: Я недостаточно хороша. Вы ничего не можете с этим поделать. Но также я думала: Информатика - открытая наука. Моя промышленная работа в Mitre была хорошей исследовательской работой. Там я работала над методологией программирования и проводила исследования, которые принесли мне мою первую премию. Затем в 1971 году я выступила с докладом, после которого Корби [Фернандо Корбато] пригласил меня в Массачусетский технологический институт. Меня также пригласили в Беркли. Ситуация менялась.

Тем не менее, правда ли, что когда вы начали работать в Массачусетском технологическом институте, было около 1000 преподавателей, из которых только 10 были женщинами?

Насколько я помню.

Так что прогресс был, но

Раздел IX еще не был законом, но давление нарастало. Президент Массачусетского технологического института Джерри Визнер был источником давления. Давление должно идти сверху. Оно не пузырится снизу. В Массачусетском технологическом институте было несколько выдающихся женщин, которых не было в составе факультета. Примерно тогда нескольких из них неожиданно пригласили войти в состав факультета. Конечно, в математике их никогда не было. В математике с этим дела обстоят очень плохо.

В 2018 году Лисков была награждена премией Computer Pioneer Award в знак признания ее достижений.

Мне кажется, что ни одна научная область не признала какой-либо фундаментальный вклад женщин.

За 10 лет до того, как я возглавила факультет информатики в Массачусетском технологическом институте, департамент нашел только одну женщину достойной предложения работы. Когда я была руководителем [с 2001 по 2004 год], я наняла семь женщин. По сусекам скрести не приходилось. Все три молодые женщины, которых я наняла выдающиеся. Долгое время женщины вообще не рассматривались.

После того, как вы выиграли премию Тьюринга, в сети появился комментарий: Почему она получила эту награду? Она не сделала ничего, о чем мы еще не знали. Мог ли этот пренебрежительный комментарий иметь отношение к тому факту, что вы женщина.

Бьюсь об заклад, что да! Был еще один комментарий о котором я никогда не рассказываю в котором говорилось: О, это не она проделал эту работу. [Коллега-мужчина] сделал это вместо нее. Это полная чушь. Я не смотрела комментарии. Мой муж смотрел. Это была пара, о которых он мне поведал. Иногда я выступаю с докладами, на которых мне задают враждебные вопросы (и к этому нужно быть готовым), будь то из-за того, что я женщина, или потому, что люди пытаются сконфузить меня, понимаете

Сконфузить лауреата премии Тьюринга?

Ага! Тогда я не понимала, что некоторые люди в моем отделе поддерживают меня. И к тому времени, когда я начала путешествовать, я была уже хорошо известна. Но вот в чем загадка: почему некоторые женщины способны стойко, упорно продолжать двигаться вперед?

Есть ли у вас какие-нибудь советы для начинающих женщин-ученых? Есть ли какой-нибудь тефлон, который женщины могут применять для предотвращения прилипания дискриминации или домогательств?

Было бы неплохо узнать, как применять этот тефлон. Только проработав достаточное количество времени в Массачусетском технологическом институте, я разрушила в себе блок задавать вопросы публично. Потребовалось много времени, чтобы развить эту уверенность в себе

Это достаточно деликатно. В вашей истории обнаруживается скрытый подтекст: не высовывайся, пока не сможешь по-настоящему стоять прямо, а затем всецело отдайся этому.

Да, возможно, это была моя стратегия. Это, вместе с отсутствием потребности угождать другим людям. Женщины социализированы, чтобы угождать.

Это уже конкретный совет: откажитесь от потребности угождать.

Вы знаете, сейчас дела обстоят не лучше, чем тогда. Может мне повезло. Если бы я вышла замуж сразу после колледжа, я бы, наверное, попала в совершенно другое место.

Вы действительно так думаете? Ваш вклад изменил компьютерные технологии и общество.

Вы знаете, вы просто идете по этому извилистому пути, и кто знает, что будет?


Узнать подробнее о курсе Архитектура и шаблоны проектирования.

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

Подробнее..

Где я и где конечный автомат? Доклад Вадима Пацева оматематике во фронтенде

10.04.2021 12:12:31 | Автор: admin
Некоторые фронтенд-разработчики полушутливо называют себя форма-клепатель. Это не так. Руководитель фронтенда Яндекс.Маршрутизации Вадим Пацев поставил себе задачу на примере развития и уточнения одной простой задачи взаимодействия с пользователем показать: не стоит бояться лезть в такие вещи, как конечный автомат, цепи Маркова и так далее. Во фронтенде тоже есть место взрослым архитектурным паттернам и алгоритмам. Ссылка на видео в конце текста.



Меня зовут Вадим Пацев, я долгое время работаю в Яндексе, участвовал в таких проектах, как Я.ру, Музыка, Почта, Диск, а также в проектах вне Яндекса, это Roborace, система управления гонками беспилотных автомобилей, и ExperimentX комплекс по анализу крови. Вообще, я люблю программировать, занимаюсь этим достаточно давно, и это одна из причин, почему я тут и хочу вам рассказать про всякие интересные вещи.

Intro


Сразу дисклеймер: я буду рассказывать о проекте не про Яндекс. Как вы заметили, оформление не яндексовое, и тут не будет ничего связанного с Яндексом. И еще один дисклеймер: все детали проекта вымышленные, любые совпадения с реальными проектами случайные, потому что так сложилось.

Я довольно много собеседую, и часто фронтенд-разработчики говорят мне в полушутку: мы форма-клепатели, делаем кнопочки, формы, простые вещи. И когда мы читаем или слышим про всякие конечные автоматы, математику, вероятности и так далее, встает вопрос: где я, а где все это? У нас простая сфера, мы делаем несложные вещи, и вообще, все это больше про дизайн, нежели про программирование.

Сегодня я хочу рассказать как раз не про формы и кнопки. Забавный момент: параллельно со мной идет секция Виталия Фридмана как раз про то, как правильно делать формы и кнопки в интернете. Я сегодня буду говорить не про это, а расскажу об одной нашей задаче, о том, как она развивалась и к чему мы пришли.

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

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

Контекст


Что это за задача, в каком контексте все происходило?

Мы работали с людьми искусства. Допустим, это были люди, которые делают кино. И они хотели сделать публичное мероприятие на сотни человек. Несколько площадок, здания, открытое пространство. В зданиях целые этажи, переходы между зданиями и так далее. Это несколько стоящих зданий, там можно получить интересный опыт.



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

Наша задача состояла в том, чтобы сделать софт для такого навигатора. Я беру устройство, мне говорят: Иди вперед, увидишь темную комнату. Посмотри направо, налево, там могут быть какие-то актеры. Дальше начинается опыт, взаимодействие и так далее.

Раз мы тут на фронтенд-конференции, то допустим, что решение было написано на React Native, но это не так важно.

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

Также устроители и режиссеры этого мероприятия не хотели, чтобы материалы с этого мероприятия уходили в сеть. То есть речь о персональном устройстве, у которого не всегда есть сеть и точно нет интернета.

Линейные сценарии


Итак, нам надо сделать навигацию, пошаговую инструкцию.


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

В терминах Redux это будет некий action, который запускается и что-то показывает на экране. Опционально у него может быть другой action, то есть тот, который надо делать дальше. А также может быть фидбэк. То есть если мы показываем пользователю, где кнопки, то он выбирает одну из них и дальше либо идет по сценарию, по которому шел до этого, либо перекидывается на другой сценарий. Это обычный декларативный сценарий, который можно написать в терминах Redux.


Схематично это выглядит так. Есть некий сценарий, массив. В нем есть несколько шагов. Каждый шаг описан. Дальше может быть несколько сценариев: из одного мы идем в другой и так далее. Также они могут ветвиться, то есть в конце каждого сценария мы можем либо рандомно выбирать переход на другой сценарий, либо показывать кнопки, и пользователь сам выберет, куда идти дальше.

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

Наверное, для вас не секрет, что философия работы Redux связана с конечным автоматом. В машине состояний системы store определяет состояние, action вызывает работу reducers. Reducers изменяют состояние системы, система переводит в новое состояние store, то есть возникает новое состояние автомата.


Такое решение работает, и оно довольно простое. Мы берем обычный state management, предустановленный набор сценариев. Запускаем машинку. Она сама переключает шаги.

Так как есть изначально сформулированный сценарий, то сразу есть и некие рельсы, по которым идут пользователи. Это может работать офлайн, онлайн, неважно.

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


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

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

Стало понятно, что при таких масштабах, когда есть всего сто участников и пять-десять сценариев, нет необходимости в распределении ресурсов. Площадки довольно большие, комнат и актеров довольно много, на таких масштабах все работает весьма неплохо. На этом этапе задачу выполнили.

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


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

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

С одной стороны, это увеличило драму. С другой, это позволяло распределять нагрузку между ресурсами, которых не хватало. То есть при таком объеме, условно, темных комнат и актеров могло не хватить. Надо было руками разводить людей так, чтобы они не сталкивались головами в самых узких местах.

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

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


Схематично это выглядит так же. У нас есть сценарии. Мы умеем по шагам их проходить и переводить на другие сценарии. Их стало больше, вариативность увеличилась. Также появились красные переходы. Когда поступает внешний сигнал, мы переключаем систему сразу в нужное состояние. Дальше работа идет оттуда.

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

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

Кроме того, сразу стало понятно, что физические ресурсы это бутылочное горлышко. На таком масштабе темных комнат не хватает, актеров не хватает, ничего не хватает.

Также непонятно, когда это бутылочное горлышко начнет мешать. Мы не можем прогнозировать, когда людям понадобится 100500 комнат: для этого надо синхронизировать работу всех операторов и всегда держать в уме, что сейчас люди могут пойти вот туда, а если мы нажмем на это, то здесь они столкнутся лбами. Сразу начинается хаос, с которым не понятно, что делать.

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


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

Дальше нужно сделать набор сценариев, запустить их. Машинки должны виртуально работать, перещелкивать сценарии и так далее. Люди куда-то идут, куда-то доходят. Мы сразу видим, насколько работают придуманные сценарии.

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

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

Но если вы разрабатывали чат-ботов или пользовались ими, то вам сразу видны эти рельсы, сразу видно: если происходит шаг вправо, шаг влево от сценария, то бот перестает тебя понимать. Теряется магия того, что ты общаешься с умным существом.

У нас примерно та же история: есть рельсы и любой шаг вправо-влево ошибка. Это сразу ломает весь user experience. Требовалось другое решение.

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

Есть некоторая предметная область, с помощью которой можно строить сценарии так, что они становятся не очень похожи на рельсы. Нам было нужно именно это, мы применили их предметную область у себя. На самом деле это был качественный переход, потому что нам надо было уйти от простой state-машины к более сложному решению.

Конечный автомат


Из чего состоит более сложный конечный автомат?


У нас были декларативные сценарии и сигналы. Мы делаем три новых сущности, в которые конвертируем уже существующие сценарии. Это можно сделать автоматически. Новые сущности это Activity (активность), Trigger и Interaction (взаимодействие).



Activity регистрирует то, что увидел пользователь, и его действия в ответ на это. Если ему просто что-то показали на экране, это просто регистрация того, что показали. Если ему показали кнопки, то мы записываем то, на какую кнопку он нажал.

Технически это тоже выглядит как декларативная структура. Interaction взаимодействие, после которого это Activity произошло. Trigger то, что вызвало это взаимодействие. Некий фидбек это то, на что нажал пользователь, и так далее. Metadata контекст, в котором это было сделано: сколько времени пользователь потратил на данную активность, какой был заряд батареи, какие биконы были вокруг.


Trigger запускает взаимодействие. Это тоже декларативная структура, она состоит из Interaction, того взаимодействия, которое мы запускаем; и из условия, при которых этот Trigger срабатывает. В нашем случае там мог быть либо определенный набор Activity, определенный контекст, который уже есть у пользователя, либо сигнал то, что приходит извне.

Другими словами если извне поступает сигнал, срабатывают те Triggers, в условиях которых написано, что надо срабатывать по этому сигналу. Также есть вес данного Trigger относительно других, о нем я расскажу чуть позже.


Последнее Interaction, более знакомая нам структура. Это стандартный Redux action, который меняет локальное состояние приложения, показывает на экране какие-то вещи, выполняет взаимодействие, то есть переводит локальную state-машину, которую мы делали до этого, в некое новое состояние.

Здесь важно, что в результате срабатывания этого взаимодействия регистрируется новая Activity, которая, в свою очередь, триггерит Trigger, и запускает новое взаимодействие.


Еще раз: Activity однозначно определяет состояние нового конечного автомата. У пользователя сначала нет никаких Activities, пустое состояние. Потом они начинают накапливаться.

Каждый раз, когда появляется новая Activity, появляется и новое состояние системы. Набор однозначен, так что система находится в однозначном состоянии.

У Trigger есть некоторые условия, которые завязаны на Activity, на эти состояния. И мы, имея одно состояние, выбираем набор Triggers, которые могут сейчас привести систему в новое состояние.

И есть Interaction, который переводит систему в новое состояние.

Схематично это выглядит так.

У нас имеется унекоторое множество состояний, в которых может быть система. Это возможный набор Activities, которые пользователь может собрать за свой путь.



Существуют Triggers, которые определяют, как система переходит из одного состояния в другое. Они описаны в conditions. Там могут быть описаны сигналы, которые просто из любой точки приводят систему в любую другую точку. Это нужно для ручного управления. А Interactions осуществляют регистрацию нового Activity и переход в новое состояние.



Набор сущностей Trigger и Interaction, то есть два набора возможных переходов и нулевое пустое состояние определяют работу системы. Мы закидываем набор Trigger/Interaction, при нулевом состоянии срабатывает первый Trigger.

Регистрируется первая Activity. Срабатывает еще один Trigger, который запускает какой-то Interaction, регистрируется новая Activity. Система переходит в новое состояние, и так далее. Это такая автономная система, которая примерно так же, как до этого по сценариям, но уже чуть более сложным, начинает работать сама.

То, как мы регистрируем Activity, большой источник аналитических данных. Мы не только регистрируем некий path, путь по всем возможным состояниям, но и регистрируем там метаданные: сколько времени понадобилось, какое было окружение и так далее. Из этого уже можно сделать некие выводы.

Также есть сигналы, которые позволяют безусловно перевести систему в новое состояние и влиять на нее извне.

Цепи Маркова


Дальше речь пойдет про цепи Маркова. К конечному автомату мы добавляем вероятности.

Еще одно лирическое отступление. Что такое вообще марковские процессы, цепи Маркова? Последовательность случайных событий с конечным или счетным числом исходов, где вероятность наступления каждого события зависит от состояния, достигнутого в предыдущем событии.

Грубо говоря, марковское свойство системы это когда вероятность любого следующего перехода зависит только от текущего состояния, и не зависит от того, как пользователь в него попал.


Схематично выглядит так. У нас есть некоторое количество возможных состояний. Для каждого состояния есть вычисленная вероятность перехода из состояния например, из S1 в S3.

Вероятность, что дальше произойдет переход S3-S4 или S3-S5, никак не зависит от того, как мы попали в S3. Мы могли как-то по-другому попасть в это состояние, но с точки зрения этой картины ничего бы не изменилось. Это и есть, вкратце, марковское свойство системы.


В нашей системе, в конечном автомате, который у нас получился, соблюдается марковское свойство. У нас детерминированная система. Каждый раз регистрируется новое Activity. Мы попадаем в это состояние, и неважно, как мы туда дошли. Да, мы можем построить путь, по которому шел пользователь, но это неважно.

Дальше срабатывают какие-то Triggers, их может быть несколько. Тогда мы рандомно выбираем. Если Trigger один, тогда вероятность перехода в следующее состояние равняется единице.

Марковский процесс, которым мы усложнили наш конечный автомат, позволяет посчитать вероятность срабатывания Trigger через n шагов. Здесь написаны формулы, я могу их озвучить. Вероятность перехода из состояния j в состояние i за m шагов равна сумме вероятности перехода из i в k, умноженной на вероятность перехода из j в k при m минус первом шаге. Это простая формула, описывающая довольно сложные вычисления.

И мы можем грубо по этой формуле посчитать вероятности нахождения системы на определенном шаге. Также мы можем уточнить ее с помощью статистики, которую мы накопили по Trigger и Activity.

Итак, еще раз. Есть конечный автомат множество состояний, в котором может находиться система. Есть Triggers, возможности перехода из одного состояния в другое.



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


Все это дает нам следующее. Мы можем с какой-то долей вероятности посчитать, в каком состоянии будет система через N шагов. Это дает нам довольно интересный инсайт. Например, через сколько шагов и какому количеству людей нужно будет воспользоваться каким-то ресурсом.

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

Также этот математический аппарат позволяет нам тестировать наборы Trigger/Interaction. Допустим, мы сделали набор Triggers, запускаем эту машинку. Проверяем ее на тысяче разных пользователей, которые рандомно нажимают на кнопки, рандомно делают выбор. Мы вычисляем вероятности, с которыми пользователи были в определенных участках, то есть в определенных состояниях нашей системы. И дальше понимаем, как вообще распределяются потоки, нагрузка и так далее.

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


В итоге мы получили модель поведения пользователей. Она может быть реализована на клиенте и на сервере, она синхронизируется с помощью Activity, Trigger и Interaction. То есть мы, с одной стороны, с клиента, посылаем Activity в сторону сервера и тем самым уточняем модель, меняем состояние на сервере. И наоборот, можно одновременно изменять наборы Trigger и Interaction на борту телефонов и на сервере.

Это позволяет делать забавные вещи. Если есть модель и в реальности что-то происходит на площадке, то мы можем параллельно с помощью нейронных сетей на сервере прогонять эту модель на других наборах Trigger/Interaction и понимать, как мы можем изменять Trigger так, чтобы наша модель работала, потоки расходились и так далее.

Потом, когда мы поняли, что данное сочетание Trigger и Interaction работает лучше, мы посылаем его на клиента и жизнь на площадке начинает жить по этим новым правилам. Это можно делать несколько раз, если есть сеть.

Если сети нет, то система работает так же, как и работала, по старым правилам. Когда придут Activities, мы обновим модели на сервере, начнем заново обучать нашу систему и так далее.

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

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

Еще один забавный момент, к которому мы и стремились: мы предлагаем рандомные Interaction. То есть когда в системе всплывает несколько Triggers с одинаковым весом и одинаковой вероятностью перехода, система сама выбирает какой-то рандомный Trigger и у пользователя на экране появляются рандомные предложения либо рандомные истории.

Это по-настоящему персональный опыт: из-за того, что такое рандомное срабатывание у каждого свое, получается некая магия. То, что ты видел на экране своего телефона, немного отличается от того, что видят другие посетители.

Outro


Цепи Маркова, конечный автомат всё, о чем я сейчас рассказал, это всё в контексте фронтенда. У нас был React Native плюс Redux. Технически все это было сделано на фронтовых технологиях и находилось в поле ответственности фронтенд-разработчика.


Но нельзя сказать, что есть какой-то ряд задач, которые надо решать так. Основная мысль в другом. Мы делаем решение, потом сталкиваемся с новой проблемой. И тут мы можем сказать: окей, есть привычные для нас фронтовые решения, Redux и так далее. Но также мы можем оглянуться вокруг, посмотреть, какие, например, есть другие абстракции, заглянуть в теорию массового обслуживания, в построение чат-ботов. И вообще в те абстракции, которые уже есть в теории Computer Science и математики, статистики. И найти там какое-то вдохновение для более интересного и гибкого решения.

Полезно знать, что вокруг, какая есть теория, до чего вообще додумались люди за годы Computer Science в математике, чем это может быть полезно конкретно для меня в решении задачи прямо сейчас.

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

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

Здесь я собрал несколько ссылок на материалы:

  • Хорошая статья на Smashing Magazine про state-машины, там хорошо описано, как они работают поверх Redux.
  • Цепи Маркова, краткое объяснение довольно простым языком на Brilliant.
  • И очень хорошая видеолекция от профессора из MIT, в которой очень хорошо рассказывается математический аппарат цепей Маркова.

Спасибо за внимание.



Смотреть видео доклада
Подробнее..

Recovery mode Недополученная прибыль на бирже из-за отключенного робота и лени

04.04.2021 14:14:58 | Автор: admin

Видео-версия:

Всем привет.В прошлый раз я рассказывал про маржин-колл, что является неоспоримым фейлом в торговле на бирже, и с тех пор ситуация более-менее выровнялась. Как вы могли догадаться, внизу рынка меня разгрузили далеко не на весь депозит, и что важно, брокер не выкупил резко подорожавшие из-за взлета волатильности короткие опционы. Сейчас некоторые из них серьезно подешевели, и я начинаю выкупать их сам, фиксируя кое-какую прибыль, и одну из таких сделок сегодня хотелось бы рассмотреть, в контексте использования торговых роботов. Хотелось бы пояснить, под роботом я никогда не воспринимал высокочастотную торговлю, потому что соревноваться в этом с техникой и линиями игроков с миллиардными капиталами бесполезно. Робот в моем понимании - это автоматизация элементов своей торговой системы, для которой не хватило встроенного функционала терминала брокера, а необходимо это потому, что у вас никогда не будет времени на постоянный анализ изменяющейся обстановки. Тем более, как я уже говорил, трейдинг не является моей основной профессией, а сейчас, с переходом США на летнее время, начало биржевой сессии уже не на полчаса, а на полтора накладывается на рабочее время по Москве, а есть еще премаркет, который открывается в 10:30 утра. В связи с этим, даже при желании я бы не смог контролировать глазами торговлю на всем протяжении, потому что в моем портфеле довольно много тикеров, и все невозможно отсматривать глазами, даже расположив графики мозаикой - пробовал, все это ерунда.Поэтому я использую, особенно на опционах, так называемые GTC (Good-Till-Cancelled) ордера, на то время как в период неопределенности отключена вся автоматизация.И вот как раз сегодня на открытии рынка сработал такой ордер, который вы видите на экране - опционная позиция была выкуплена за половину от входа в сделку, принеся прибыль около 1,5 тысяч долларов.

Услышав это, многие скажут, так и чего тебе не нравится, ты же сволочь на чистой спекуляции, не произведя добавленной стоимости, обогатился? Некоторые, уточнив город проживания Москву, еще и добавят что мол москвичи зажрались, полторы штуки баксов для них уже не деньги. Тем не менее, в ходе сегодняшнего сеанса разоблачения, я покажу, что на самом деле это отвратительная сделка, совершать ее в таком виде не следовало - а нормально отработать эту ситуацию помог бы примитивный алгоритм, который легко реализуется с помощью торгового робота.

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

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

Например, вы купили по бумагу за 10 долларов, и больше доллара терять не намерены - тогда стоп будет стоять на 9. Цена, конечно, может проскользнуть на низколиквидном инструменте за уровень стопа, но это не очень страшно. Что же с фиксацией прибыли - с тейк профитами? Ставить тейки по такому принципу тоже можно, но у вас немедленно включится жадность, и задаст вам вопрос - если бумага прет по тренду в вашем направлении, зачем вам фиксировать прибыль на определенном заранее уровне, если можно досмотреть это кино, до куда она все-таки дойдет? На эту тему недвусмысленно высказался Джесси Ливермор - режь убытки сразу, дай прибыли течь.

Посмотрим, что случилось с курсом выкупленного опциона дальше, в течении дня?

Уже сейчас видно, что с дать прибыли течь вообще не срослось. На самом деле в IB существует встроенный инструмент для подобных ситуаций - trail stop, однако есть проблема.

Дело в том, что вы должны определить размер максимальных колебаний отката, который начнет действовать сразу по активации ордера, в результате чего понемногу планка подтянется к цене, и при откате назад, позиция будет закрыта очень быстро, либо вы потеряете на крупном откате. Я пытался настроить активацию ордера на некие условия, но похоже, в IB для инструментов типа опционов это заблокировано, поэтому сделать это может только ваш самописный робот. При шортовой позиции на опцион, время работает на меня, поэтому я могу ждать до самой экспирации, а если опцион вошел в деньги, я могу перекатить его на будущий период, иногда даже диагонально, улучшив страйк - заплатив за него временной стоимостью. Когда время работает на меня, врубать такой trailing коробки - это глупость.

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

Но нетрудно заметить, что это было по цене 2.50, а цена немедленно бахнулась на 0.50, то есть с 5 опционов я недополучил 2*100*5, еще целую тысячу долларов. Отвратительная сделка. Стоит заметить, что обычно такого не происходит, и цена опускается медленно и нежно, и GTC ордера вполне хватает. И кто-то скажет, что всех денег не заработать, мол, ты не мог знать, куда пойдет курс опциона, поэтому можно не переживать. Некоторая вероятность того, что гэп премаркета закроется мгновенно, имеет место, но обычно это занимает до получаса, за которые можно было выкупить опцион по более выгодной цене.

Но в том-то и дело, за ночь и утро, на постмаркете и премаркете (где кстати вы тоже можете торговать, если отмечать в настройках графика и ордеров нужные галочки), на базовом активе образовался огромный гэп, то есть разрыв в курсах. Разумеется, я поленился, и вручную не проверил все курсы базовых активов по моим опционным позициям перед открытием рынка - я работаю и занят, да и вообще нет у меня такой привычки. А я бы увидел, что цена акции HGEN резко шмальнула вверх на премаркете и пересекла отметку страйка. Что означает ровно одно - внутренняя стоимость опциона превратилась в тыкву, и стала ноль долларов ноль центов. И у опциона осталась только временная стоимость на будущие три недели. Здесь можно было бы снова грузануть вас формулой Блэка-Шоулза:

Но черт с ней, давайте просто посмотрим, сколько стоял БА на закрытии - 14.05, и сколько стоял этот опцион в 15 страйке - 5.50, а внутри него сидело, вы не поверите, 95 центов внутренней стоимости. Стало быть, 4.55 доллара - это его временная стоимость на конец пятницы, когда он был примерно около 50 дельты (дельта - вероятность экспирации в деньгах). Обратите внимание, примерно столько же 5.50 маркет мейкер просит сейчас за опцион немного в деньгах. А за опцион в 10й дельте, в которую с пятницы на понедельник превратился страйк 15, просят 0.75 (на практике, цена падала и до 0.50, то есть дельта была еще меньше).

Таким образом повторюсь, единственное, что от меня требовалось, так как я мог посмотреть результаты торгов БА на премаркете, это рассчитать с помощью робота цену опциона, и понять, что моя лимитка стоит сильно выше (в 5, черт возьми, раз выше) справедливой цены, и на открытии рынка произойдет очень невыгодная сделка. Я не посмотрел, а вот маркетмейкер - да, у него работа такая, и обнаружив шикарную точку арбитража, он реализовал ее на 3 секунде работы биржи.

И вы таки спросите, если при торговле опираться на дельту, как я мог рассчитать дельту без опционной доски IB, и даже без его веб-калькулятора?

А вот по этой незамысловатой формуле опционного грека Дельта:

=N(d1) 1 where d1= (ln(S/K)+(r-q+^2/2)t)/ t

K - Option strike price N - Standard normal cumulative distribution function r - Risk free interest rate q - Dividend Yield - Volatility of the underlying S - Price of the underlying t - Time to option's expiry

Ну, формула-то незамысловатая, но в ней участвует волатильность, которую (имея в виду implied - Подразумеваемую), потребуется рассчитать. На скале функция выделит так:

def delta(tp :String,S:Double,K:Double,vol:Double,tt:Int,q:Double=0.0,r:Double=0.0) = {       val t = tt/366.0       val d1 = (scala.math.log(S/K)+(r-q+vol*vol/2)*t)/(vol*sqrt(t))       new NormalDistribution(0.0, 1.0).cumulativeProbability(d1) - (if (tp=="P") 1 else 0)     }  Сравниваем:[info] Done compiling.  -0.12389190331572086 

При этом, на четвертом знаке после запятой она перестает биться с калькулятором на сайте IB; мало этого, Dividend Yield по идее должна отниматься от Risk free interest rate, а в калькуляторе, если эти числа устанавливать одинаковыми, греки немного различаются - стало быть, там считают как-то иначе. Но как я говорил, для меня это хобби, и я не обязан разбираться в тонкостях.К тому же, я могу использовать встроенный функционал API IB для расчета волатильности, а также и для расчета справедливой цены опциона перед открытием.

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

Желаю вам удачи, до новых встреч!

Подробнее..

R, Монте-Карло и enterprise задачи, часть 2

05.04.2021 12:20:40 | Автор: admin

Как ни парадоксально, но пока еще нередко в enterprise встречаются задачи, отличные от построения еще одного личного кабинета, еще одного мониторинга или еще одного документооборота. Если немного подумать, а не хвататься сразу кодировать или искать специализированное ПО, то можно написать компактное, весьма элегантное и быстрое решение, используя метод Монте-Карло .


Задачи в Enterprise достаточны компактны для перебора и не требует точности 100 знаков после запятой. Не ракеты или реакторы запускаем и не научную теорию всего строим.


Рассмотрим далее на примере одной из нестандартных задач.


Является продолжением серии предыдущих публикаций.


Постановка задачи


Задача имеет корни в IoT для сельского хозяйства. А именно, расстановка датчиков на картофельном поле с круговой схемой полива. Попросим у гугла немного картинок: Разгадка тайны круглых полей: всё интересней, чем ты думаешь!. Нужно обеспечить нужные характеристики покрытия mesh сети, учитывая допустимые расстояния между соседями. При этом надо оптимизировать бюджет и выдать gps координаты на расстановку датчиков и сформировать кратчайшую схему обхода.


Решение


Подключаем пакеты


Пакеты
library(tidyverse)library(magrittr)library(scales)library(data.table)library(tictoc)library(stringi)library(arrangements)library(ggforce)

Декомпозиця


Сначала попробуем сформировать этапы.


  1. Берем N датчиков.
  2. Ищем оптимальную расстановку.
  3. Если характеристики не достигнуты, увеличиваем N на 1. Повторяем процедуру.
  4. Для найденной расстановки ищем оптимальный маршрут обхода.

План вроде простой. Но как будем решать? Напрашивается метод Монте-Карло.


Функции-хелперы


Оформим функцию для визуального отображения расположения зондов.


Визуализация поля
drawDisk <- function(df) {  # отрисуем расположение точек и действующие силы  # если силы не заданы, создадим их по умолчанию равными 0  for(col in c("force_x", "force_y")){    if (!(col %in% names(df))) df[, col] <- 0  }  ggplot(data = df, aes(x = x, y = y)) +    ggforce::geom_circle(aes(x0 = 0, y0 = 0, r = 1, colour = "red"),                          inherit.aes = FALSE) +    geom_point(size = 2, fill = "black", shape = 21) +    geom_text(aes(label = idx), hjust = 0.5, vjust = -1) +    # рисуем векторное поле    geom_segment(aes(xend = x + force_x / 10, yend = y + force_y / 10),                  colour = "red",                  arrow = arrow(length = unit(0.2,"cm")), size = 0.6) +     xlim(-1.5, 1.5) +    ylim(-1.5, 1.5) +    coord_fixed() +    labs(x = "Ось X", y = "Ось Y") +    theme_bw()}

Генерация первичной расстановки


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


Но есть выход можем вспомнить физику.


Предположим, что мы накидаем в круглую плоскую чашку N одинаково заряженных шариков. Они либо найдут устойчивую конфигурацию с минимум энергии, либо возникнут колебания. Непосредственно в этой задаче с большой вероятностью будет найдено стабильное расположение (из практики). Попробуем провести такую же симуляцию для расстановки зондов. Наделим каждый зонд единичным зарядом с единичной массой, радиус нашего картофельного поля выберем единичным. Для того, чтобы зонды не разлетелись в бесконечность, вокруг нашего поля поставим заряженных неподвижных охранников. И проведем симуляцию для случайной первичной расстановки.


Генерация первичной расстановки
# Генерим точки-зонды внутри окружности единичного радиуса.# Считаем, что все частицы единичного заряда, поэтому его опускаемcharges_dt <- tibble(idx = 1:13) %>%  mutate(angle = runif(n(), min = 0, max = 2*pi),          r = runif(n(), min = 0, max = 1),         x = r * cos(angle), y = r * sin(angle)) %>%  select(idx, x, y) %>%  setDT(key = "idx")# для сходимости задачи генерируем также зафиксированные точки на внешней окружностиkeepers_dt <- max(charges_dt$idx) %>%   {tibble(idx = (. + 1):(. + 40))} %>%  mutate(angle = (idx - 1) * (2 * pi / n()),         x = 1.3 * cos(angle), y = 1.3 * sin(angle)) %>%  select(idx, x, y) %>%  setDT(key = "idx")

Посмотрим, как расположились зонды при первичной установке


Визуализируем
full_dt <- rbindlist(list(charges_dt, keepers_dt))drawDisk(full_dt)

Поиск оптимального расположения


Здесь задействуем физику, расчитаем малое перемещение частиц за счет действия на них cилы электростатического взаимодействия (закон Кулона).
Для упрощения задачи будем считать


  • каждую новую итерацию как движение из статичного состояния (как-бы случайная расстановка зондов);
  • все зонды обладают единичным зарядом и единичной массой.

$s = at^2/2 = (F/m)t^2/2$


Для малых изменений получаем $\delta s = F \delta t$


Будем итеративно двигать, пока равнодействующие сил на зонды не станут меньше определенного порога.


Поиск оптимального расположения
max_force <- 10tic("Balancing charges")# Определяем точность моделирования!# Будем двигать заряды пока они не застабилизируются # Максимальная равнодействуюущая будет близка к 0 # и точки не стали разлетаться в бесконечностьwhile (max_force > 0.05 & nrow(charges_dt[x^2 + y^2 > 1.2]) == 0) {  # общий пул координат частиц на текущую итерацию  full_dt <- rbindlist(list(charges_dt, keepers_dt), fill = TRUE)  ff <- function(x0, y0){    # сила взаимодействия -- 1/r2, заряды единичные;    # проекция силы на оси, sqrt(r2) -- гипотенуза    # суперпозиция векторов даст результирующее воздействие на частицу    dx = full_dt$x - x0    dy = full_dt$y - y0    r2 = dx^2 + dy^2    # na.rm исключает NaN в т.ч.    list(sum(-dx * r2^(-1.5), na.rm = TRUE),         sum(-dy * r2^(-1.5), na.rm = TRUE))  }    # проведем расчет сил, итерируем по каждой "неприбитой гвоздями" точке  charges_dt[, c("force_x", "force_y") := ff(x0 = x, y0 = y), by = idx]  # определим максимальную силу, действующую на частицу  max_force <- charges_dt[, max(sqrt(force_x^2 + force_y^2), na.rm = TRUE)]  force_scale = if_else(max_force > 1, 1 / max_force / 1e2, 1/ max_force / 5e2)  # проводим передвижение точек  charges_dt %>%    .[, `:=`(x = x + force_x * force_scale,              y = y + force_y * force_scale)]}toc()full_dt <- rbindlist(list(charges_dt, keepers_dt), fill = TRUE)

Оптимизация маршрута обхода


Для выбора оптимального маршрута опять же используем Монте-Карло. Ряд соображений:


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

Оптимизация маршрута обхода
optimizePath <- function(dt) {  # попробуем оптимизировать маршрут обхода по предоставленным точкам  # 1. Выбираем в качестве начальной точки датчик, максимально близко расположенный к краю поля  dt[, r0 := sqrt(x^2+y^2)] %>%    setorder(-r0)  n1 <- dt[1, idx]  # теперь проводим симуляцию различных вариантов расстановки сенсоров  # получаем последовательность номеров и убираем n1, его будем принудительно ставить первым  points_in <- dt[idx != n1, idx]  # для каждой точки добавим еще ближайшую точку выхода   # (перпендикуляр к окружности, которая единичного радиуса)  augment_tbl <- dt %>%    mutate_at("idx", `*`, -1) %>%    mutate(r0 = sqrt(x^2 + y^2)) %>%    mutate_at(vars(x, y), ~(.x/r0)) %>%    bind_rows(dt) %>%    select(idx, x, y)  # однократно посчитаем матрицу расстояний между зондами  ll_tbl <- unique(augment_tbl$idx) %>%    tidyr::expand_grid(l = ., r = .) %>%    filter(l != r, (l > 0 | r > 0)) %>%    # построим уникальный идентификатор ребра    rowwise() %>%    # mutate(m = list(sort(c(l, r))))    mutate(edge_id = stri_c(sort(c(l, r)), collapse = "=")) %>%    ungroup() %>%    distinct(edge_id, .keep_all = TRUE) %>%    # подтягиваем координаты левой точки    left_join(select(augment_tbl, idx, l_x = x, l_y = y), by = c("l" = "idx")) %>%    # подтягиваем координаты левой точки    left_join(select(augment_tbl, idx, r_x = x, r_y = y), by = c("r" = "idx")) %>%    mutate(s = sqrt((l_x - r_x)^2 + (l_y - r_y)^2)) %>%    arrange(l, r)  points_seq <- arrangements::permutations(v = points_in, replace = FALSE,                                        layout = "column", nsample = 5000)  # добавляем точку входа в качестве первой и соотв. точку выхода в качестве последней  routes_lst <- points_seq %>%     rbind(-n1, n1, ., -tail(., 1)) %>%    as.data.frame() %>% as.list()  # превращаем все пути обхода в последовательности ребер  routes_dt <- data.table(route_id = seq_along(routes_lst), route = routes_lst) %>%    .[, .(from = unlist(route)), by = route_id] %>%    .[, to := shift(from, type = "lead"), by = route_id] %>%    # выкидываем все терминальные точки    na.omit() %>%    # строим нормализованный идентификатор ребра    .[, edge_id := stri_c(sort(unlist(.BY)), collapse = "="), by = .(from, to)] %>%    .[, .(route_id, edge_id)] %>%    # подтянем информацию о длине ребра из справочника    .[as.data.table(ll_tbl), s := i.s, on = "edge_id"]  # считаем длину маршрутов, оставляем кратчайший  best_routes <- routes_dt[, .(len = sum(s)), by = route_id] %>%    setorder(len) %>%    head(10) %T>%    print()  # сформируем ТП-10 лучших маршрутов  best_routes %>%    select(route_id) %>%    mutate(idx = routes_lst[route_id]) %>%    tidyr::unnest(idx) %>%    left_join(augment_tbl) %>%    tidyr::nest(data = -route_id) %>%    left_join(best_routes)}

Получаем табличку подобного рода


    route_id      len 1:     2070 8.332854 2:     2167 8.377680 3:     4067 8.384417 4:     3614 8.418678 5:     5000 8.471521 6:     4495 8.542041 7:     2233 8.598278 8:     4430 8.609391 9:     2915 8.61604810:     3380 8.695547

И посмотрим результат размещения


Визуализируем
tic("Route optimization")best_tbl <- optimizePath(charges_dt)toc()best_route_tbl <- best_tbl$data[[1]]full_dt <- rbindlist(list(best_route_tbl, keepers_dt), fill = TRUE)gp <- drawDisk(full_dt) +  # добавим маршрут обхода  geom_path(arrow = arrow(type = "closed"), data = best_route_tbl)gp

Маршрут обхода


Формирование задания


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


Полезные трюки


Как всегда, затронем вопросы производительности. Если писать не думая, то можно отправить машину на счет на часы или дни. tidyverse подход оказывается медленнее в $10^3$-$10^4$ раз. В приведенном выше коде расчеты по блокам занимают доли секунды. Этого достаточно для обычной задачи, но если нужно быстрее, то можно делать вставки на C++. В целом, скоростные характеристики достигаются результат рядом мер и методик.


  1. Для небольших циклических расчетов накладные расходы на разыменовывание переменных даже в data.table могут оказаться значительными. Base R в блоке поиска оптимального расположения дает выйгрыш на порядок.
  2. Если задачу можно распараллелить, то надо применять функциональные подходы. Проще будет сделать последующее распараллеливание.
  3. Для однородных величин работа с матрицами оказывается на несколько порядков быстрее работы с data.frame. Связано это со схемой выделения памяти и адресации к элементам. Про матрицы незаслуженно забывают при погружении в tidyverse.
  4. Все, что можно посчитать однократно и оформить в виде справочной таблицы, должно быть посчитано заранее.
  5. Монте-Карло очень хороший подход. Быстрое первичное применение может дать полезный результат, а также взглянуть на решение задачи и, возможно, найти какие-то упрощения и аналитики.
  6. Не стесняемся использовать методы аналогии. Они могут позволить построить упрощенную модель исходной задачи, которая вычислительно существенно проще исходной и легко перекладывается на Монте-Карло.

Предыдущая публикация Дети, русский язык и R.

Подробнее..

Вебинар Ускорение на простых типах данных и битовые операции

07.04.2021 14:14:13 | Автор: admin
15 апреля Яндекс.Практикум проводит открытый вебинар Ускорение на простых типах данных и битовые операции. Приглашаем разработчиков на Python и C/C++, которые хотят научиться трюкам для ускорения кода, а также программистов на других языках, которым интересны фишки, связанные с типами данных.

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

Вебинар будет состоять из двух частей: 80 минут обзор, 15 минут ответы на вопросы.



В программе вебинара


  1. Ускорение на простых типах данных. Введение.
  2. Что не так с символами и строками:
    Строка это массив символов или как ещё бывает в разных языках.
    Поиск подстроки в строке.
    Замена части строки.
    Что такое длина строки.
  3. Что не так с числами:
    Как хранятся числа.
    Что такое числа с плавающей запятой и частые ошибки.
    Переполнения и арифметика по модулю.
    Сколько места это всё занимает.
  4. Что не так с логическим типом данных:
    Приоритеты операций в разных языках.
    Как упростить сложное логическое выражение.
    Популярные задачи с собеседований.
  5. Ответы на вопросы.

Ведущая


Вебинар проведет Александра Воронцова разработчик в Joom и автор курса Алгоритмы для разработчиков в Яндекс.Практикуме.

Вебинар пройдет 15 апреля в 19.30 (Мск). Программа и регистрация.
Подробнее..

Перевод Пирамидальная сортировка, сортировка слиянием и выпуклая оболочка

07.04.2021 18:21:37 | Автор: admin

Базовые алгоритмы сортировки


Пирамидальная сортировка (или сортировка кучей, Heap Sort)

Куча (heap) это не что иное, как двоичное дерево с некоторыми дополнительными правилами, которым оно должно следовать: во-первых, оно всегда должно иметь структуру кучи, где все уровни двоичного дерева заполняются слева направо, и, во-вторых, оно должно быть упорядочено в виде max-кучи или min-кучи. В качестве примера я буду использовать min-кучу.

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

Ниже приведен псевдокод пирамидальной сортировки:

HeapSort(A[1n]):1 - H = buildHeap(A[1n])2 - for i = 1 to n do:3 -   A[i] = extract-min(H)

В самом начале мы имеем несортированный массив. Первый шаг взять этот массив и превратить его в кучу; в нашем случае мы хотим превратить его в min-кучу. Итак, нам нужно преобразовать данные несортированного массива и построить из них min-кучу. Обычно это инкапсулируется одной функцией, которую можно назвать, например, buildHeap.

Ниже приведен псевдокод buildHeap:

buildHeap():1 - Изначально H пустая2 - for i = 1 to n do:3 -   Add(H, A[i])

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

Теперь самый маленький элемент в куче находится в последнем узле, что нам и нужно. Мы знаем, что он находится в отсортирован относительно остальных элементов, поэтому его можно полностью удалить из кучи (функция extract-min). Но нам нужно сделать еще кое-что: убедиться, что новый элемент корневого узла находится на своем месте! Маловероятно, что элемент, который мы сделали корневым узлом, находится на своем месте, поэтому мы переместим элемент корневого узла вниз в его корректное положение, используя функцию, которая обычно называется heapify (приведение к виду кучи).

Ниже приведен псевдокод extract-min и heapify:

extract-min(H):1 - min = H[1]2 - H[1] = H[H.size()]3 - уменьшаем H.size() на 14 - heapify(H, 1)5 - return minheapify():1 - n = H.size()2 - while (LeftChild(i) <= n and H[i] > H[LeftChild(i)]) or (RightChild(i) <= n and H[i] > H[RightChild(i)]) do:3 -   if (H[LeftChild(i)] < H[RightChild(i)]) then:4 -     j = LeftChild(i)5 -   else:6 -     j = RightChild(i)7 -   меняем местами H[i] и H[j]8 -   i = j

Вот и все! Алгоритм продолжает повторять эти шаги до тех пор, пока куча не сократится до одного единственного узла. На момент, когда это произойдет, мы будем уверенны, что все элементы в несортированном массиве находятся в своих отсортированных позициях и что последний оставшийся узел в конечном итоге станет первым элементом в отсортированном массиве. Общее время работы этого алгоритма составляет O(n log n).

Сортировка слиянием (Merge Sort)

Алгоритм сортировки слиянием это алгоритм сортировки, который сортирует коллекцию, разбивая ее на половины. Затем он сортирует эти две половины и объединяет их вместе, чтобы сформировать одну полностью отсортированную коллекцию. В большинстве реализаций сортировки слиянием все это делается посредством рекурсии.

Сортировка слиянием это алгоритм разделяй и властвуй, который можно свести к 3 шагам:

  • Разделите (разбейте) задачу на минимально возможные подзадачи одного типа.

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

  • Объединяйте результаты и соединяйте более мелкие подзадачи, пока вы, наконец, не примените то же решение к более крупной и сложной задаче, с которой вы начинали!

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

Функция mergeSort в свою очередь состоит из двух функций:

  • функции слияния, которая фактически объединяет два списка вместе и сортирует их в правильном порядке.

  • функции mergeSort, которая будет продолжать разделять входной массив снова и снова (рекурсивно), а затем будет вызывать функцию слияния снова и снова (также рекурсивно).

Ниже приведен псевдокод сортировки слиянием:

Merge(A, B):1 - i = 1; j = 1; k = 1;2 - a_(m+1) = ; b_{n+1} = 3 - while (k <= m+n) do:4 -   if (a_i < b_j) then5 -     c_k = a_i; i++;6 -   else7 -     c_k = b_j; j++;8 -   k++;9 - return C = {c_1, c_2, , c_(m+n)}MergeSort(X, n)1 - if (n == 1) return X2 - middle = n/2 (round down)3 - A = {x_1, x_2, , x_middle}4 - B = {x_(middle+1), x_{middle+2), , x_n}5 - As = MergeSort(A, middle)6 - Bs = MergeSort(B, n - middle)7 - return Merge(As, Bs)

Именно потому, что сортировка слиянием реализована рекурсивно, это делает ее быстрее, чем другие алгоритмы, которые мы рассмотрели до сих пор. Сортировка слиянием имеет время выполнения O(n log n).

Выпуклая оболочка (или выпуклый контур, Convex Hull)

Нам дан набор точек на плоскости. Выпуклая оболочка множества это наименьший выпуклый многоугольник, содержащий все его точки.

Подход 1 Упаковка подарков (Gift Wrapping) O(n)

Идея проста, мы начинаем с самой левой точки (или точки с минимальным значением координаты x) и продолжаем оборачивать точки против часовой стрелки. Если точка p является текущей точкой, следующая точка выбирается как точка, которая перекрывает все остальные точки по ориентации против часовой стрелки. Ниже приводится псевдокод:

1 - ConvexHull = пустой список2 - Ищем точку с наименьшим x и присваиваем ее u (:= u_original)3 - Пусть L будет вертикальной линией, проходящей через u3 - Do:4 -   Пусть v будет точкой с наименьшим углом между L и u * v5 -   Добавляем v в ConvexHull6 -   Пусть L = uv линии7 -   u := v8 - Until v = u_original9 - Выводим ConvexHull

Подход 2 Алгоритм Грэхема (Graham Scan) O(n log n)

Этот алгоритм можно разделить на 2 этапа:

  • Этап 1 (сортировка точек): сначала мы находим самую нижнюю точку. Идея состоит в том, чтобы предварительно обработать точки, сортируя их по самой нижней точке. После сортировки точек они образуют простой замкнутый путь. Какими должны быть критерии сортировки? Вычисление фактических углов было бы неэффективным, поскольку тригонометрические функции не являются простыми в вычислении. Идея состоит в том, чтобы использовать ориентацию для сравнения углов без их фактического вычисления.

  • Этап 2 (включении или отбрасывание точек): после того, как у нас есть замкнутый путь, следующим шагом будет прохождение по пути и удаление из него вогнутых точек. Первые две точки в отсортированном массиве всегда являются частью выпуклой оболочки. Для оставшихся точек мы смотрим на последние три точки и находим угол, образованный ими. Пусть это три точки: prev(p), curr(c) и next(n). Если ориентация этих точек (рассматривая их в том же порядке) не против часовой стрелки, мы отбрасываем c, в противном случае мы оставляем ее.

1 - ConvexHull = пустой список2 - Пусть u - точка с наименьшим значением x (если таких точек несколько, выбираем ту, у которой наименьший y)3 - Сортируем оставшиеся точки по угловой фазе в порядке против часовой стрелки вокруг u4 - Добавляем u и первую точку в ConvexHull5 - For i = 2 to n - 1:6 -   While угол между предпоследней, последней и точкой i > 180 градусов:7 -     Удаляем точку из ConvexHull8 -   Добавляем i в ConvexHull9 - Return ConvexHull

Если вам интересно следить за моей работой в области компьютерных наук и машинного обучения, вы можете посетить мои Medium и GitHub, а также другие проекты на https://jameskle.com/. Вы также можете написать мне мне в Твиттере, но электронной почте или найти меня в LinkedIn. Или подписывайтесь на мою рассылку, чтобы получать мои последние статьи прямо на ваш почтовый ящик!


Перевод статьи подготовлен в преддверии старта курса Алгоритмы и структуры данных.

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

Подробнее..

Кто же ты такой, алгоритм?

13.04.2021 18:11:50 | Автор: admin

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

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

В университетах дела обстоят получше, однако автору этих строк на курсе по математической логике и теории алгоритмов пришлось столкнуться все с тем же винегретом из определения алгоритма и его свойств. Разберемся, что тут не так.

Бесконечность не предел

Но перед этим немного вспомним математику. Из школьного курса математики мы знаем, что чисел существует бесконечно много какое бы большое число мы не взяли, всегда можно прибавить единицу и получить число еще большее. Обычно в школе этим и ограничиваются. В университете на курсе высшей математики нам расскажут, что бесконечности на самом деле бывают разные: множества, элементы которого можно пронумеровать натуральными числами считаются счётно-бесконечными. К таким множествам относят сами натуральные числа (числу 1 мы дадим номер один, числу 2 номер два и т.д.), целые числа - натуральные плюс ноль и отрицательные целые числа (первый номер отдаем нулю, второй - числу 1, третий - числу -1, то есть каждой положительное число k получает номер 2k, а каждое отрицательное число -m получает номер 2m + 1). К счетно-бесконечным множествам относят четные, нечетные и даже рациональные числа (числа представимые в виде несократимой дроби m/n, где m - целое, n - натуральное). Получается, что натуральных чисел ровно столько же, сколько четных, и, в то же время, ровно столько же, сколько целых. Количество (мощность) множества натуральных чисел обозначается символом 0 (алеф-ноль).

Такой же трюк с нумерацией не пройдет для бесконечных непериодических дробей (иррациональных чисел). Допустим такое множество счетное, то есть элементы этого множества можно пронумеровать натуральными числами. Тогда рассмотрим бесконечную десятичную дробь с нулевой целой частью, у которой первая цифра после запятой не равняется цифре на той же позиции у дроби с номером 1, вторая цифра не равняется цифре на второй позиции у дроби с номером 2 и т.д. Тогда полученная дробь будет заведомо отличаться от всех дробей хотя бы одной цифрой. Получается для нее не нашлось номера в нашей бесконечной нумерации! Примененная схема доказательства называется канторовским диагональным методом в честь придумавшего ее математика Георга Кантора.

Про бесконечные дроби

Не стоит делать ошибку, записывая в иррациональные числа все бесконечные дроби. Иррациональными являются только те числа, которые нельзя представить в виде несократимой дроби вида m/n. В десятичной системе счисления дроби 1/3 и 2/7 тоже окажутся бесконечными, однако их бесконечность" обусловлена выбранной системой счисления. В системе счисления по основанию 21 эти дроби будут иметь конечное представление, а вот, например, дробь 1/2 окажется бесконечной (периодической).

Говорят, что множество бесконечных десятичных дробей имеет мощность континуум, которая обозначается символом 1 (алеф-один). В дальнейшем нам понадобится следующее множество. Рассмотрим некоторый алфавит (конечное множество символов). Теперь представим множество всех конечных цепочек символов алфавита A*. Коль скоро алфавит конечен, и каждая цепочка конечна, то множество таких цепочек счетно (их можно пронумеровать натуральными числами).

На сколько велика бесконечность?

Допустим в наш алфавит вошли все придуманные на земле символы: русский алфавит, японские иероглифы, шумерская клинопись и т.д. Тогда в наше множество войдут все написанные когда-либо книги, все книги, которые будут написаны и все книги, которые никто не стал бы писать (например, хаотичные последовательности символов). Кроме того, представим книгу, толщиной в Солнечную систему и диагональю листа равной диаметру Млечного Пути, набранную 12-м шрифтом. В наше придуманное множество войдут все такие книги, отличающиеся хотя бы одним символов, и не только они, ведь вселенная бесконечна! Кто мешает представить себе книгу, размером в миллиарды световых лет? А все такие книги? Уже на этом этапе воображение может давать сбои, а ведь наше множество всего лишь счетное. Чтобы дополнить множество до континуума, нужно рассмотреть бесконечную книгу, по сравнению с которой, предыдущие книги детские игрушки. Но и одной бесконечной книги нам не хватит, нужно рассмотреть все бесконечные книги.

Конструктивно оперировать континуальными бесконечностями невозможно. Даже работая со счетными множествами, мы не рассматриваем сами множества, а только говорим, что какой бы не был элемент N, всегда найдется элемент N+1. Если мы ставим себе прикладную задачу, появление в наших рассуждениях континуальной бесконечности должно служить нам тревожной лампочкой: осторожно, выход за пределы конструктивного.

Алгоритмы и вычислимость

Суть работы компьютера заключается в проведении некоторого вычисления преобразования одной порции информации в другую порцию. Причем результатом работы не обязательно должно быть число, главное, чтобы информация была представлена в некоторой объективной форме. Обычно под такой формой имеют в виду конечные цепочки символов некоторого алфавита. Получается, компьютерное вычисление есть некоторая функция в сугубо математическом смысле, с областью определения и значений в рассмотренном выше множестве A*. Именно тут возникают определенные проблемы. Если мы можем вычислить функцию, то можем записать промежуточные вычисления в виде текста. Более того, в виде тексте можно описать вообще правила вычисления. Мы знаем, что множество всех текстов счетное. Однако выясняется, что множество всех функций над натуральными числами имеет мощность континуум. Если мы пронумеруем все тексты, то получается функций вида A* -> A* тоже континуум. Получается, что некоторые функции вычислимы, а некоторые нет.

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

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

Частично-рекурсивные функции и тезис Черча

Все началось с того, что математик Давид Гильберт в 1900 году предложил список нерешенных на тот момент математических проблем. Позже выяснилось, что десятая проблема (проблема решения произвольного диофантового уравнения) оказалось неразрешимой, но для доказательства этого факта пришлось составить целую новую математическую теорию. Вопросами того, какие задачи можно конструктивно решить, и что такое конструктивное решение, занялись математики Курт Гедель, Стивен Клини, Алонсо Черч и Алан Тьюринг.

Курт Гедель наиболее известен тем, что сформулировал и доказал 2 теоремы о неполноте. Между прочим, сделал он это в возрасте всего лишь 24 лет.Курт Гедель наиболее известен тем, что сформулировал и доказал 2 теоремы о неполноте. Между прочим, сделал он это в возрасте всего лишь 24 лет.

Как выяснилось выше, континуальные бесконечности не всегда подходят под конструктивные рассуждения, поэтому Гедель и Клини предложили рассматривать только функции натурального аргумента (при необходимости любые функции над счетными множествами можно привести к натуральным функция путем замены элементов множеств их номерами). Изучая вычислимость таких функций, Гедель, Клини, Аккерман и другие математики пришли к так называемому классу частично-рекурсивных функций. В качестве определения этого класса рассматривается набор базовых, очень простых функций (константа, увеличение на единицу и проекция, которая сопоставляет функции многих аргументов один из ее аргументов) и операторов, позволяющих из функций строить новые функции (операторы композиции, примитивной рекурсии и минимизации). Слово частичные показывает, что эти функции определены лишь на некоторых числах. На остальных они не могут быть вычислены. Попытки расширить класс частично-рекурсивных функций ни к чему не привели, так как введение новых операций приводило к тому, что получалось множество функций, совпадающее с классом частично-рекурсивных. В дальнейшем Алонсо Черч отказался от попыток расширения этого класса, заявив, что, видимо:

Частично-рекурсивные функции соответствуют вычислимым функциям в любом разумном понимании вычислимости.

Это утверждение называют тезисом Черча. Стоит отметить, что тезис Черча не является теоремой или доказанным утверждением. Во-первых, не понятно, что такое разумное понимание, во-вторых, превратив тезис Черча в доказанный факт, мы лишаем себя перспектив дальнейшего исследования вычислимости и механизмов вычислений. Никто, впрочем, не мешает попробовать определить такой набор операций, который был бы мощнее базиса для частично-рекурсивных функций. Только вот, до сих пор это никому не удавалось сделать.

Ученые долго не могли привести пример частично-рекурсивной функции, не являющейся примитивно-рекурсивной (без оператора минимизации). Наконец это удалось Вильгельму Аккерману. Предложенная функция Аккермана растет так быстро, что количество чисел в порядке A(4,4) превосходит количество атомов во Вселенной. Ученые долго не могли привести пример частично-рекурсивной функции, не являющейся примитивно-рекурсивной (без оператора минимизации). Наконец это удалось Вильгельму Аккерману. Предложенная функция Аккермана растет так быстро, что количество чисел в порядке A(4,4) превосходит количество атомов во Вселенной.

Тезис Тьюринга

Формальная теория алгоритмов во многом построена аналогично теории вычислимости. Считается, что алгоритм есть некое конструктивное преобразование входного слова (цепочки символов некоторого алфавита) в некоторое выходное слово. Опять же, здесь мы имеем с функциями вида A*->A*. Конечно, предложенное описание не подходит под определение алгоритма, так как неясно, что же такое конструктивное преобразование. Хоть понятия алгоритма и вычислимой функции близки, не стоит их смешивать. Для одного и того же алгоритма может быть предъявлено сколько угодно его записей на каком-нибудь формальном языке, но соответствующая вычислимая функция всегда одна. Один из основателей формальной теории алгоритмов, Алан Тьюринг, предложил формальную модель автомата, известного как машина Тьюринга. Тезис Тьюринга гласит:

Каково бы не было разумное понимание алгоритма, любой алгоритм, соответствующий такому пониманию, может быть реализован на машине Тьюринга.

Любые попытки построить более мощные автомат заканчивались неудачей: для каждого такого автомата (машина Поста, нормальные алгоритмы Маркова, автоматы с регистрами и несколькими лентами) удавалось построить аналогичную машину Тьюринга. Некоторые ученые объединяют тезис Черча и тезис Тьюринга в тезис Черча-Тьюринга, так как они весьма близки по духу.

С помощью такого незамысловатого автомата можно формализовать любой алгоритм.С помощью такого незамысловатого автомата можно формализовать любой алгоритм.

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

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

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

Обязательные свойства

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

Еще одно достаточно очевидное свойство любого алгоритма его дискретность. Независимо от исполнителя, исполнение алгоритма представляет собой дискретный процесс, при рассмотрение распадающийся на элементарные действия. Понимать дискретность можно и в том смысле, что любая информация, над которой работает алгоритм может быть представлена в виде текста.

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

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

Винегрет из свойств из того же учебника по информатике.Винегрет из свойств из того же учебника по информатике.

Необязательные свойства

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

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

Про зависающие программы

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

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

Заключение

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

Материал данной статьи во многом опирается на 1-ый том Программирование: введение в профессию А. В. Столярова. Тем, кто хочет подробнее изучить вопросы, связанные с алгоритмами и теорией вычислимости, кроме этой книги, советую Босс В От Диофанта до Тьюринга и трехтомник А. Шеня по математической логике и теории алгоритмов.


Дата-центр ITSOFT размещение и аренда серверов и стоек в двух дата-центрах в Москве. За последние годы UPTIME 100%. Размещение GPU-ферм и ASIC-майнеров, аренда GPU-серверов, лицензии связи, SSL-сертификаты, администрирование серверов и поддержка сайтов.

Подробнее..

Рекомендации с обоснованием (2020). Часть первая

04.04.2021 00:20:12 | Автор: admin

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

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

Статья может быть интересна всем, кто желает составить целостное и подробное представление об истории развития рекомендательных систем, методах, которые в них применяются, методах оценки моделей с обоснованием и посмотреть на примеры использования рекомендаций с обоснованием в приложениях.

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

Оглавление

  1. Аннотация

  2. Введение

    1. Рекомендации с обоснованием

    2. Историческая справка

    3. Классификация методов

    4. Объяснимость и результативность

    5. Объяснимость и интерпретируемость

    6. Как читать данный обзор

  3. Список литературы

Рекомендации с обоснованием: обзор и новые перспективы

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

В данной статье мы приводим всесторонний обзор исследований в области рекомендаций с обоснованием. Сначала мы показываем место проблемы рекомендаций с обоснованием в общем направлении исследований по рекомендательным системам путем классификации актуальных проблем по пяти категориям под названием 5W: что, когда, кто, где и почему (what, when, who, where, why). Далее приводим полный обзор проблемы рекомендаций с обоснованием с трех точек зрения:

  1. Хронология исследований рекомендаций с обоснованием, включая ранние примитивные подходы и подходы более позднего времени, основанные на моделях.

  2. Двумерная таксономия для классификации направлений исследований в данной области: первое измерение источник рекомендаций, или форма их представления, и второе алгоритм генерации обоснований.

  3. Применение рекомендаций с обоснованием в различных задачах предоставления рекомендаций, таких как рекомендация продуктов, социальные рекомендации, рекомендации достопримечательностей (point-of-interest or POI recommendation).

Также один из разделов посвящен рассмотрению перспектив исследований проблемы рекомендаций с обоснованием в более широких областях исследований получения информации (information retrieval or IR), искуственного интеллекта (ИИ) и машинного обучения (МО). Статья завершается обсуждением потенциальных направлений исследований в будущем для дальнейшего развития данной области.

Раздел 1. Введение

1.1.Рекомендации с обоснованием

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

Для того, чтобы показать место проблемы рекомендаций с обоснованием в области исследований рекомендательных систем, мы приводим концептуальную таксономию. В частности, исследования в области рекомендаций с обоснованием могут быть классифицированы согласно 5W, т.е. на категории, отвечающие на вопросы когда, где, кто, что и почему (what, when, who, where, why), или, соотвественно, на рекомендации, зависящие от времени, от места, социальные рекомендации, рекоммендации в зависимости от предметной области и рекомендации с обоснованием.

Модели рекомендаций с обоснованиями могут разделяться на использующие внутренние модели (intristic models) и безмодельные (model-agnostic) (Lipton, 2018 [1]; Molnar, 2019 [2]). Подход с внутренними моделями предполагает создание интерпретируемой модели, где механизм принятия решений является прозрачным и, таким образом, становится возможным действительно обеспечить объяснение принятого решения (Zhang et al., 2014a [3]). В безмодельном подходе (Wang et al., 2018d [4]), который иногда так же называют подход с объяснениями постфактум (Peake and Wang, 2018 [5]), механизм принятия решения рассматривается как черный ящик, и модель объяснения генерируется уже после принятия решения. Основа философии обоих подходов лежит в области когнитивной психологии в одном случае человек принимает взвешенные, рациональные решения и может объяснить, почему данное решение было принято, а в другом случае сначала принимает решение и затем ищет объяснение, чтобы обосновать решение или оправдать себя. (Lipton, 2018 [1]; Miller, 2019 [6])

Область рекомендаций с обоснованием включает в себя не только разработку прозрачных моделей МО, получения информации (information retrieval) или глубинного анализа данных (data mining), но и также разработку эффективных методов представления рекомендаций или обоснований пользователям и разработчикам системы, т.к. человек [прямо или опосредованно] вовлечен в цикл решения задачи предоставления рекомендаций с обоснованием на протяжении всего времени. Значительные усилия в исследованиях поведения пользователей и взаимодействия человек-компьютер направлены на понимание того, как пользователи работают с обоснованиями.

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

1.2.Историческая справка

В данном разделе мы представим историю развития исследований в области рекомендаций с обоснованием. Несмотря на то, что сам термин рекомендации с обоснованием формально был введен недавно (Zhang et al., 2014a [3]), основная концепция, тем не менее, была введена в ранних работах по рекомендательным системам. Например, Schafer et al. (1999) [7] отмечает, что рекомендации могут быть объяснены с помощью других объектов, с которыми знаком пользователь, например обоснование продукт, который Вы ищете, похож на данные продукты, которые вам понравились ранее. Данное наблюдение приводит к фундаментальной идее коллаборативной фильтрации (КФ) (Collaborative filtering or CF), основанной на релевантных объектах (item-based collaborative filtering or item-based СF); Herlocker et al. (2000) [8] изучает проблему получения обоснований для рекомендаций, полученных с помощью КФ, в MovieLens, используя обзоры пользователей, а Sinha and Swearingen (2002) [9] подчеркивают роль прозрачности в рекомендательных системах. Кроме того, даже если рекомендации с обоснованием и ранее серьезно привлекали внимание исследователей, то на практике в основном использовались объяснения, полученные ручными или полуавтоматическими методами, например объяснение пользователи также просматривали в системах электронной торговли (Tintarev and Masthoff, 2007a [10]).

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

Ранние подходы к созданию персонализированных рекомендательных систем в основном фокусировались на использовании рекомендаций на основе контента (content-based) или на основе КФ (Ricci et al., 2011 [11]). Рекомендательные системы на основе контента моделируют пользовательские и объектные профайлы с помощью различной доступной контентной информации, такой, как цвет, цена, марка товаров в электронной торговле, или жанр, режиссер и длительность фильмов в системах обзоров (Balabanovic and Shoham, 1997 [12]; Pazzani and Billsus, 2007 [13]). Т.к. контент, относящийся к объекту, легко понимается пользователями, то объяснения для пользователей, почему объект был рекомендован, интуитивны. Например, одним из очевидных подходов является предоставление пользователю тех признаков контента, которые могут представлять для него интерес в рекомендованном объекте. Ferwerda et al. (2012) [14] проводит всестороннее исследование возможных способов предоставления обоснований для рекомендаций, основанных на контенте.

Тем не менее, сбор контентной информации для различных предметных областей может являться трудоемким процессом. С другой стороны, подходы, основанные на коллаборативной фильтрации, предпринимают попытку избежать данной проблемы, используя т. н. народную молву (Ekstrand et al., 2011 [15]). КФ, основанная на релевантных пользователях (user-based CF) - один из ранних алгоритмов КФ, применялась в GroupLens для рекомендательной системы новостей (Resnick et al., 1994 [16]). КФ, основанная релевантных пользователях, представляет каждого пользователя как вектор оценок, и предсказывает недостающие оценки сообщений, основываясь на взвешенном среднем оценок данного сообщения другими пользователями. Подобным образом Sarwar et al. (2001) [17] представляет метод КФ, основанной на релеватных объектах (item-based CF), и далее Linden et al. (2003) [18] описывает использование данного метода в рекомендательной системе товаров Амазон. КФ, основанная на релеватных объектах, представляет каждый объект как вектор оценок, и предсказывает недостающие значения, основываясь на взвешенном среднем оценок похожих элементов.

Несмотря на то, что механизм предсказаний, основанный на оценках, должен быть относительно сложным для понимания среднего пользователя, КФ, основанная на на релеватных пользователях или объектах, легко объясняется благодаря философии алгоритма. Например, объекты, рекомендованные КФ, основанной на релеватных пользователях, могут объясняться фразой пользователи со сходными интересами предпочитают данный продукт, в то время как КФ, основанная на релеватных объектах, объясняется фразой данный продукт похож на продукты, которые Вам понравились ранее. Тем не менее, несмотря на то, что идея КФ достигла значительного прогресса в улучшении точности рекомендаций, она менее интуитивна с точки зрения предоставления обоснований рекомендаций в сравнении с алгоритмами, основанными на контенте. Важность данной проблемы отмечается в ранних исследованиях в данной области (Herlocker and Konstan, 2000 [19]; Herlocker et al., 2000 [8]; Sinha and Swearingen, 2002 [9]).

Идея КФ достигла дальнейшего успеха в 2000х гг., когда в работе Koren (2008) [20] была представлена интеграция КФ и скрытой факторной модели (Latent Factor Models or LFM). Матричная факторизация (Matrix Factorization or MF) и ее разновидности оказались особенно успешны в задачах предсказания оценок (Koren et al., 2009 [21]). Исследования, посвященные скрытым факторным моделям, были ведущими в области рекомендательных систем в течении многих лет. Тем не менее, несмотря на хорошую производительность, показанную в задаче предоставления рекомендаций, скрытые факторы в скрытых факторных моделях не поддаются интуитивному объяснению, что делает трудным понимание, почему тот или иной объект получил высокий рейтинг рекомендации или почему был выбран именно он среди других объектов. Недостаточность объяснимости модели усложняет задачу предоставления обоснований пользователям, т.к. высокий рейтинг, полученный при решении задачи рекомендации не может служить удовлетворительным объяснением для конечного пользователя.

Для того, чтобы сделать рекомендательные модели более понятными, исследователи постепенно обратились к проблеме рекомендательных систем с обоснованием (Explainable Recommendation Systems), т.е. таких систем, в которых рекомендательный алгоритм не только выводит список рекомендаций, но также предоставляет обоснования для рекомендаций. Например, Zhang et al. (2014a) [3] определяет проблему рекомендаций с обоснованием и предлагает явную факторную модель (Explicit Factor Model or EFM) путем выравнивания скрытых измерений с явными признаками для получения рекомендаций с обоснованиями. Для решения проблемы обоснований были также предложены другие подходы, которые в дальнейшем будут рассмотрены в данной статье. Стоит отметить, что в последние годы появились примеры применения моделей глубокого обучения (deep learning or DL) для получения персонализированных рекомендаций. Мы полагаем, что использование моделей глубокого обучения для улучшения производительности в рекомендательных системах может иметь спорный результат (Dacrema et al., 2019 [22]) , но данная проблема выходит за рамки нашего исследования. В этом обзоре мы фокусируемся на том, что глубокие модели имеют природу черного ящика, и данная характеристика моделей вносит сложности в обеспечение обоснований. Далее мы рассмотрим исследования в области построения рекомендательных систем на основе моделей глубокого обучения.

В широком смысле, свойство объяснимости (explainability) систем ИИ уже было в центре обсуждения в 1980х в эпоху исследований старого, или логического ИИ, когда системы, основанные на знаниях (knowledge-based systems), выполняли задачи предсказания или диагностики с достаточным качеством, но не могли объяснить, почему был сделан тот или иной выбор. Работа (Clancey, 1982 [23]) показала, что предоставление обоснований требует гораздо больших знаний, чем простое предсказание. Недавний взрыв в области больших данных и быстрый рост вычислительных мощностей вывели производительность ИИ на новый уровень, но более широкое сообщество исследователей ИИ в последние годы вновь осознало важность ИИ с обоснованием (Explainable AI or XAI) (Gunning, 2017) [24]. Область ИИ с обоснованием включает исследование широкого круга задач обоснования ИИ в глубоком обучении, компьютерном зрении, системах беспилотного вождения и задачах обработки естественного языка. Тот факт, что данная задача является существенным направлением исследований в области ИИ, подчеркивает необходимость пристального внимания исследователей из области получения информации и рекомендательных систем (IR/RecSys) для решения задач обоснований в различных системах поиска и получения рекомендаций. Кроме того, проблема рекомендаций с обоснованием является подходящей задачей для основания нового направления исследований теорий и алгоритмов машинного обучения с обоснованием (Explainable Machine Learning).

1.3.Классификация методов

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

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

  1. Источник информации, или форма представления обоснований (напр., обоснование в виде предложений текста), что раскрывает аспект человекомашинного взаимодействия (HumanComputer Interaction or HCI) в исследованиях обоснования рекомендаций.

  2. Модели для генерации обоснований, что отражает аспект машинного обучения в данном направлении исследований. Потенциальные модели с обоснованием включают в себя метод ближайшего соседа (nearest-neighbor), матричную факторизацию, тематическое моделирование (topic modelling), графические модели (graph-models), глубокое обучение, механизмы вывода (knowledge reasoning), поиск по ассоциативным правилам (association rule mining) и др.

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

В таблице 1.1. показана классификация . Например, явная факторная модель для рекомендаций с обоснованием (Zhang et al., 2014a [3]) используется в сочетании с методом матричной факторизации, в результате обоснование представляется в виде предложения для рекомендованного объекта. Таким образом, данная модель попадает в категорию матричная факторизация с текстовым обоснованием. Интерпретируемая сверточная нейронная сеть (Seo et al., 2017 [25]), с другой стороны, предполагает использование модели глубокой сверточной нейронной сети и в качестве обоснований предлагает использование признаков объектов. Следовательно, она попадает в категорию глубокое обучение с обоснованием с помощью признаков объектов/пользователей. Другим примером является рекомендация с визуальным обоснованием (Chen et al., 2019b [26]), что предполагает использование модели глубокого обучения для генерации изображения-обоснования, что относится к категории глубокое обучение с визуальным обоснованием. Подобным образом мы классифицируем и другие исследования согласно данной таксономии, так что читатели могут проследить отношения между существующими методами рекомендаций с обоснованием.

Т.к. в данной области проведено множество исследований, таблица 1.1. является неполным перечислением методов рекомендаций с обоснованием. Мы приводим одну работу для каждой комбинации модель-информация в соответствующей ячейке. Тем не менее, в разделах 2 и 3 данного обзора мы рассмотрим детали множества методов рекомендаций с обоснованием.

Таблица 1.1 (начало)

Метод ближайшего соседа

Матричная факторизация

Тематическое моделирование

Графические методы

Релевантные пользователи или объекты

Herlocker et al., 2000 [8]

Abdollahi and Nasraoui, 2017 [32]

-

Heckel et al., 2017 [37]

Признаки пользователя или объекта

Vig et al., 2009 [30]

Zhang et al., 2014a [3]

McAuley and Leskovec, 2013 [34]

He et al., 2015 [38]

Текстовое обоснование, предложение

-

Zhang et al., 2014a [3]

-

-

Визульное обоснование

-

-

-

-

Социальное обоснование

Sharma and Cosley, 2013 [31]

-

Ren et al., 2017 [35]

Park et al., 2018 [39]

Кластер слов

-

Zhang, 2015 [33]

Wu and Ester 2015 [36]

-

Таблица 1.1 (продолжение)

Глубокое обучение

Системы, основанные на знаниях

Поиск правил

Объяснения постфактум

Релевантные пользователи или объекты

Chen et al., 2018c [40]

Catherine et al., 2017 [42]

Peake and Wang 2018 [5]

Cheng et al., 2019a [47]

Признаки пользователя или объекта

Seo et al., 2017 [25]

Huang et al., 2018 [43]

Davidson et al., 2010 [45]

McInerney et al., 2018 [48]

Текстовое обоснование,предложение

Li et al., 2017 [41]

Ai et al., 2018 [44]

Balog et al., 2019 [46]

Wang et al., 2018d [4]

Визульное обоснование

Chen et al., 2019b [26]

-

-

-

Социальное обоснование

-

-

-

-

Кластер слов

-

-

-

-

1.4.Объяснимость и результативность

Цели улучшить свойства объяснимости (explainability) и результативности (effectiveness) могут конфликтовать при разработке архитектуры модели, что приводит к необходимости компромисса (Ricci et al., 2011 [11]). Например, мы можем либо выбрать простую модель для улучшения качества объяснимости, либо выбрать сложную модель для улучшения точности в ущерб объяснимости. В то же время последние исследования показали, что обе цели необязательно являются конфликтующими в проектировании рекомендательных моделей (Bilgic et al., 2004 [27]; Zhang et al., 2014a [3]). Например, новейшие методы такие как глубокое обучение представлениям (deep representation learning) могут помочь проектировать рекомендательные модели, являющиеся одновременно и объяснимыми, и результативными. Разработка моделей глубокого обучения с обоснованием (explainable deep models) также является востребованным направлением в более широкой области ИИ, что приводит к дальнейшему прогрессу не только в области рекомендаций с обоснованиями, но и в фундаментальных проблемах машинного обучения с обоснованием (explainable machine learning).

При описании моделей рекомендаций с обоснованиями в следующих разделах мы также обсудим отношение между объяснимостью и результативностью в персонализированных рекомендациях.

1.5.Объяснимость и интерпретируемость

Объяснимость (explainability) и интерпретируемость (interpretability) являются тесно связанными концепциями в литературе. В целом, интерпретируемость один из подходов для достижения объяснимости. Более подробно, ИИ с обоснованием предназначен для разработки моделей, которые могут объяснить свои решения или решения других моделей пользователям или разработчикам. Для достижения данной цели, модели могут быть как интерпретируемыми, так и не интерпретируемыми. Например, в интерпретируемых моделях (такие как интерпретируемые модели машинного обучения) механизм вывода прозрачен в локальном или глобальном смыслах, и, таким образом, выходные данные модели обычно легко объяснимы. Выдающимися примерами интерпретируемых моделей может служить множество линейных моделей, таких как линейная регрессия и модели, основанные на деревьях, такие как деревья решений. Между тем, интерпретируемость является не единственным способом достигнуть объяснимости, например, некоторые модели могут раскрывать свой внутренний механизм принятия решений для предоставления обоснований с помощью сложных методов обоснования, таких как механизмы внимания (neural attention mechanisms), объяснения на естественном языке, а также существует множество моделей обоснования постфактум, широко использующихся при получении информации (IR), в задачах обработки естественного языка (NLP), компьютерном зрении (computer vision), анализе графов (graph analysis) и множестве других задач. Исследователи и практикующие специалисты могут разрабатывать и выбирать подходящие методы обоснований для достижения объяснимости в ИИ для различных задач.

1.6.Как читать данный обзор

Аудитория читателей данного обзора включает в себя как исследователей, так и практикующих специалистов, заинтересованных в области рекомендательных систем с обоснованиями. Читателям рекомендуется приобрести базовые представления о рекомендательных системах, например, о рекомендациях, основанных на контенте (Pazzani and Billsus, 2007 [13]), коллаборативной фильтрации (Ekstrand et al., 2011 [15]) и оценке рекомендательных систем (Shani and Gunawardana, 2011 [28]). Кроме того, читателям будет полезно ознакомиться с другими обзорами, посвященными теме обоснований в рекомендательных системах с точек зрения пользователя (Tintarev and Masthoff, 2007a [10]) или интерпретируемого машинного обучения (Lipton, 2018 [1]; Molnar, 2019 [2]), а также с ИИ с обоснованием в целом (Gunning, 2017 [24]; Samek et al., 2017 [29]).

Данный обзор структурирован следующим образом. В разделе 2 мы рассмотрим рекомендации с обоснованием с точки зрения взаимодействия с пользователем. В частности, мы обсудим различные источники информации, которые могут использоваться для получения обоснований, и тесно связанные с ними различные форматы представления информации. Раздел 3 фокусируется на аспекте машинного обучения в примении к решению задачи рекомендаций с обоснованиями и содержит классификацию моделей. В раздел 4 вводятся стандарты оценки рекомендаций с обоснованиями, а в разделе 5 обсуждается применение методов получения рекомендаций с обоснованием для решения практических задач в реальных рекомендательных системах. В разделе 6 мы завершаем обзор обсуждением нескольких открытых важных проблем и будущими направлениями исследований в данной области.

Список литературы

Список литературы

  1. Lipton, Z. C. (2018). The mythos of model interpretability. Communications of the ACM. 61(10): 3643.

  2. Molnar, C. (2019). Interpretable Machine Learning. Leanpub.

  3. Zhang, Y., G. Lai, M. Zhang, Y. Zhang, Y. Liu, and S. Ma (2014a). Explicit factor models for explainable recommendation based on phrase-level sentiment analysis. In: Proceedings of the 37th International ACM SIGIR Conference on Research & Development in Information Retrieval. ACM. 8392.

  4. Wang, X., Y. Chen, J. Yang, L. Wu, Z. Wu, and X. Xie (2018d). A reinforcement learning framework for explainable recommendation. In: 2018 IEEE International Conference on Data Mining (ICDM). IEEE. 587596.

  5. Peake, G. and J. Wang (2018). Explanation mining: Post hoc interpretability of latent factor models for recommendation systems. In: Proceedings of Beyond Personalization 2005: A Workshop on the Next Stage of Recommender Systems Research at the 2005 International Conference on Intelligent User Interfaces, San Diego, CA, USA. ACM. 20602069.

  6. Miller, T. (2019). Explanation in artificial intelligence: Insights from the social sciences. Artificial Intelligence. 267: 138.

  7. Schafer, J. B., J. Konstan, and J. Riedl (1999). Recommender systems in e-commerce. In: Proceedings of the 1st ACM Conference on Electronic Commerce. ACM. 158166.

  8. Herlocker, J. L., J. A. Konstan, and J. Riedl (2000). Explaining collaborative filtering recommendations. In: Proceedings of the 2000 ACM Conference on Computer Supported Cooperative Work. ACM. 241250.

  9. Sinha, R. and K. Swearingen (2002). The role of transparency in recommender systems. In: CHI02 Extended Abstracts on Human Factors in Computing Systems. ACM. 830831.

  10. Tintarev, N. and J. Masthoff (2007a). A survey of explanations in recommender systems. In: Data Engineering Workshop, 2007 IEEE 23rd International Conference. IEEE. 801810.

  11. Ricci, F., L. Rokach, and B. Shapira (2011). Introduction to recommender systems handbook. In: Recommender Systems Handbook. Springer. 135.

  12. Balabanovic, M. and Y. Shoham (1997). Fab: Content-based, collaborative recommendation. Communications of the ACM. 40(3): 6672.

  13. Pazzani, M. J. and D. Billsus (2007). Content-based recommendation systems. In: The Adaptive Web. Springer. 325341.

  14. Ferwerda, B., K. Swelsen, and E. Yang (2012). Explaining contentbased recommendations. New York. 124.

  15. Ekstrand, M. D. et al. (2011). Collaborative filtering recommender systems. Foundations and Trends in HumanComputer Interaction. 4(2): 81173.

  16. Resnick, P., N. Iacovou, M. Suchak, P. Bergstrom, and J. Riedl (1994). GroupLens: An open architecture for collaborative filtering of netnews. In: Proceedings of the 1994 ACM Conference on Computer Supported Cooperative Work. ACM. 175186.

  17. Sarwar, B., G. Karypis, J. Konstan, and J. Riedl (2001). Item-based collaborative filtering recommendation algorithms. In: Proceedings of the 10th International Conference on World Wide Web. ACM. 285295.

  18. Linden, G., B. Smith, and J. York (2003). Amazon.com recommendations: Item-to-item collaborative filtering. IEEE Internet Computing. 7(1): 7680.

  19. Herlocker, J. L. and J. A. Konstan (2000). Understanding and Improving Automated Collaborative Filtering Systems. University of Minnesota Minnesota.

  20. Koren, Y. (2008). Factorization meets the neighborhood: A multifaceted collaborative filtering model. In: Proceedings of the 14th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining. ACM. 426434.

  21. Koren, Y., R. Bell, and C. Volinsky (2009). Matrix factorization techniques for recommender systems. Computer. 42(8): 4249.

  22. Dacrema, M. F., P. Cremonesi, and D. Jannach (2019). Are we really making much progress? A worrying analysis of recent neural recommendation approaches. In: Proceedings of the 13th ACM Conference on Recommender Systems. ACM. 101109.

  23. Clancey, W. J. (1982). The epistemology of a rule-based expert system: A framework for explanation. Tech. Rep. Department of Computer Science, Stanford University, CA.

  24. Gunning, D. (2017). Explainable artificial intelligence (XAI). Defense Advanced Research Projects Agency (DARPA).

  25. Seo, S., J. Huang, H. Yang, and Y. Liu (2017). Interpretable convolutional neural networks with dual local and global attention for review rating prediction. In: Proceedings of the 11th ACM Conference on Recommender Systems. ACM. 297305.

  26. Chen, X., H. Chen, H. Xu, Y. Zhang, Y. Cao, Z. Qin, and H. Zha (2019b). Personalized fashion recommendation with visual explanations based on multimodal attention network: Towards visually explainable recommendation. In: Proceedings of the 42nd International ACM SIGIR Conference on Research and Development in Information Retrieval. ACM. 765774.

  27. Bilgic, M., R. Mooney, and E. Rich (2004). Explanation for recommender systems: Satisfaction vs. promotion. Computer Sciences Austin, University of Texas. Undergraduate Honors. 27.

  28. Shani, G. and A. Gunawardana (2011). Evaluating recommendation systems. In: Recommender Systems Handbook. Springer. 257297.

  29. Samek, W., T. Wiegand, and K.-R. Mller (2017). Explainable artificial intelligence: Understanding, visualizing and interpreting deep learning models. arXiv preprint arXiv:1708.08296.

  30. Vig, J., S. Sen, and J. Riedl (2009). Tagsplanations: Explaining recommendations using tags. In: Proceedings of the 14th International Conference on Intelligent User Interfaces. ACM. 4756.

  31. Sharma, A. and D. Cosley (2013). Do social explanations work?: Studying and modeling the effects of social explanations in recommender systems. In: Proceedings of the 22nd International Conference on World Wide Web. ACM. 11331144.

  32. Abdollahi, B. and O. Nasraoui (2017). Using explainability for constrained matrix factorization. In: Proceedings of the 11th ACM Conference on Recommender Systems. ACM. 7983.

  33. Zhang, Y. (2015). Incorporating phrase-level sentiment analysis on textual reviews for personalized recommendation. In: Proceedings of the 8th ACM International Conference on Web Search and Data Mining. ACM. 435440.

  34. McAuley, J. and J. Leskovec (2013). Hidden factors and hidden topics: understanding rating dimensions with review text. In: Proceedings of the 7th ACM Conference on Recommender Systems. ACM. 165172.

  35. Ren, Z., S. Liang, P. Li, S. Wang, and M. de Rijke (2017). Social collaborative viewpoint regression with explainable recommendations. In: Proceedings of the 10th ACM International Conference on Web Search and Data Mining. ACM. 485494.

  36. Wu, Y. and M. Ester (2015). Flame: A probabilistic model combining aspect based opinion mining and collaborative filtering. In: Proceedings of the 8th ACM International Conference on Web Search and Data Mining. ACM. 199208.

  37. Heckel, R., M. Vlachos, T. Parnell, and C. Duenner (2017). Scalable and interpretable product recommendations via overlapping coclustering. In: 2017 IEEE 33rd International Conference on Data Engineering (ICDE). IEEE. 10331044.

  38. He, X., T. Chen, M.-Y. Kan, and X. Chen (2015). Trirank: Review-aware explainable recommendation by modeling aspects. In: Proceedings of the 24th ACM International on Conference on Information and Knowledge Management. ACM. 16611670.

  39. Park, H., H. Jeon, J. Kim, B. Ahn, and U. Kang (2018). UniWalk: Explainable and accurate recommendation for rating and network data. arXiv preprint arXiv:1710.07134.

  40. Chen, X., H. Xu, Y. Zhang, Y. Cao, H. Zha, Z. Qin, and J. Tang (2018c). Sequential recommendation with user memory networks. In: Proceedings of the 11th ACM International Conference on Web Search and Data Mining. ACM.

  41. Li, P., Z. Wang, Z. Ren, L. Bing, and W. Lam (2017). Neural rating regression with abstractive tips generation for recommendation. In: Proceedings of the 40th International ACM SIGIR conference on Research and Development in Information Retrieval. ACM. 345354.

  42. Catherine, R., K. Mazaitis, M. Eskenazi, and W. Cohen (2017). Explainable entity-based recommendations with knowledge graphs. In: Proceedings of the Poster Track of the 11th ACM Conference on Recommender Systems. ACM.

  43. Huang, J., W. X. Zhao, H. Dou, J.-R. Wen, and E. Y. Chang (2018). Improving sequential recommendation with knowledge-enhanced memory networks. In: The 41st International ACM SIGIR Conference on Research & Development in Information Retrieval. ACM. 505514.

  44. Ai, Q., V. Azizi, X. Chen, and Y. Zhang (2018). Learning heterogeneous knowledge base embeddings for explainable recommendation. Algorithms. 11(9): 137.

  45. Davidson, J., B. Liebald, J. Liu, P. Nandy, T. Van Vleet, U. Gargi, S. Gupta, Y. He, M. Lambert, B. Livingston, et al. (2010). The YouTube video recommendation system. In: Proceedings of the 4th ACM conference on Recommender systems. ACM. 293296.

  46. Balog, K., F. Radlinski, and S. Arakelyan (2019). Transparent, scrutable and explainable user models for personalized recommendation. In: Proceedings of the 42nd International ACM SIGIR Conference on Research and Development in Information Retrieval. ACM.

  47. Cheng, W., Y. Shen, L. Huang, and Y. Zhu (2019a). Incorporating interpretability into latent factor models via fast influence analysis. In: Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. ACM. 885893.

  48. McInerney, J., B. Lacker, S. Hansen, K. Higley, H. Bouchard, A. Gruson, and R. Mehrotra (2018). Explore, exploit, and explain: Personalizing explainable recommendations with bandits. In: Proceedings of the 12th ACM Conference on Recommender Systems. ACM. 3139.

Подробнее..

Продолжаем интернационализацию поиска по адресам с помощью Sphinx или Manticore. Теперь Metaphone

05.04.2021 08:09:25 | Автор: admin

Это продолжение публикации Интернационализация поиска по городским адресам. Реализуем русскоязычный Soundex на Sphinx Search, в которой я разбирал, как реализовать поддержку фонетических алгоритмов Soundex в Sphinx Search, для текста написанного кириллицей. Для текста на латинице поддержка Soundex уже есть. С Metphone аналогично, для латиницы есть, для кириллицы не очень, но попытаемся исправить этот досадный факт с помощью транслитерации, регулярных выражений и напильника.

Это прямое продолжение, в котором разберём как реализовать оригинальный Metaphone, русскийMetaphone (в том смысле что транслитерация не понадобится), Caverphone, и не сможем сделать DoubleMetaphone.

Реализация подойдёт как для использования на платформе SphinxSearch, так и ManticoreSearch.

В конце, в качестве бонуса, посмотрим как Metaphone воспримет "ракомакофон".

Докер образ

Подготовил докер образ tkachenkoivan/searchfonetic, для того чтобы можно было "пощупать" результат. В образ добавлены все индексы и из этой публикации и из предыдущей, но, внимание, названия индексов из предыдущей публикации не соответствуют тому что хранится в образе. Почему? Потому что хорошая мысля приходит опосля.

Описание алгоритмов, всё так же, брал из публикации "фонетические алгоритмы". Постараюсь как можно меньше дублировать текст, написанный в ней.

Оригинальный Metaphone

Реализуется элементарно, создаются регулярные выражения для транслитерации:

regexp_filter = (А|а) => aregexp_filter = (Б|б) => bregexp_filter = (В|в) => v

И включаем metaphone:

morphology = metaphone

Всё, как и с оригинальным Soundex. В прошлый раз, мы пришли к выводу, что лучше всего, из всех Soundex алгоритмов, использовать именно оригинальный Soundex, единственный недостаток которого коллизии, разрешается вычислением расстояния Левенштейна.

В этот раз, забегая вперёд, скажу, что снова сделал бы свой выбор в пользу оригинального Metaphone + транслит. А вот причина небанальна.

Дело в том что у Sphinx есть в такой параметр blend_chars. Смысл следующий, Sphinx индексирует по словам, а слова он находит по разделителям, например, если между буквами есть пробел, значит буквы два разных слова, перенос строки, табуляция, знаки препинания и т.д., и т.п. Но могут быть символы, которые одновременно разделяют слова, а могут быть и частью одного слова, например, &. M&Ms это сколько слов? А Рога&Копыта? Для таких случаев и существует blend_chars.

И тут можно пойти на хитрость, и добавить в blend_chars пробел:

blend_chars = U+0020

Теперь, когда кто-то будет искать улицу Мориса Тореза, то найдёт её, даже если подумает, что это одно слово. Да что там слитное написание, он её найдёт даже если решит, что Мать Тереза и Морис Торез родственники.

mysql> select * from metaphone where match('Морисатереза');+------+--------------------------------------+-----------+---------------------------+| id   | aoguid                               | shortname | offname                   |+------+--------------------------------------+-----------+---------------------------+| 1130 | e21aec85-0f63-4367-b9bb-1943b2b5a8fb | ул        | Мориса Тореза             |+------+--------------------------------------+-----------+---------------------------+

Можем увидеть, как работает индекс для Мориса Тореза, вызвав call keywords:

mysql> call keywords ('Мориса Тореза', 'metaphone');+------+---------------+------------+| qpos | tokenized     | normalized |+------+---------------+------------+| 1    | morisa toreza | MRSTRS     || 1    | morisa        | MRS        || 2    | toreza        | TRS        |+------+---------------+------------+

Обратите внимание, что два слова было воспринято как три: morisa, toreza и morisa toreza, притом при создании кода Metaphone, пробел был съеден.

Это особенность реализации Metaphone в Sphinx Search. Самостоятельно, с помощью регулярных выражений её не реализовать. Нет, конечно мы можем добавить регулярное выражение, удаляющее пробелы:

regexp_filter = [ ] => 

но тогда Мориса Тореза, и другие, будут всегда восприниматься как одно слово, а нам этого не надо.

Давайте сравним как работают другие индексы, в том числе те, которые ещё разобрать не успели, но они будут ниже.

Caverphone пробел сохраняет, поэтому при слитном написании просто не находит.

mysql> call keywords ('Мориса Тореза', 'caverphone');+------+-----------+------------+| qpos | tokenized | normalized |+------+-----------+------------+| 1    | mrsa trza | mrsa trza  || 1    | mrsa      | mrsa       || 2    | trza      | trza       |+------+-----------+------------+mysql> select * from caverphone where match('Морисатереза');Empty set (0.00 sec)

Оригинальный Soundex (из предыдущей публикации), в котором используется базовая реализация Sphinx, просто сходит с ума, и не понимает, как кодировать слово, в котором встретился пробел, morisa и toreza закодирован, а morisa toreza нет:

mysql> call keywords ('Мориса Тореза', 'simple_soundex');+------+---------------+---------------+| qpos | tokenized     | normalized    |+------+---------------+---------------+| 1    | morisa toreza | morisa toreza || 1    | morisa        | m620          || 2    | toreza        | t620          |+------+---------------+---------------+

Потому не включайте пробел в blend_chars в большинстве случаем это не просто бесполезно, но и вредно. Единственно исключение metaphone. И это позволяет решить самую сложную для исправления опечатку (с машинной точки зрения) опечатку в пробелах: как наличие лишних, так и отсутствие нужных.

А это дорогого стоит.

Double Metaphone

Для двойного Metaphone планировал использовать два индекса, вместо одного, как обычно, а затем искать поочерёдно в обоих.

К сожалению, я не понял, как реализовать двойной Metaphone с помощью регулярных выражений. Когда я начал искать по нему описание, меня как будто в гугле забанили, зато нашёл много реализаций на различных языках программирования, например, DoubleMetaphone.java. Я даже попытался понять этот алгоритм и реализовать его в виде регулярок, но дошёл до буквы C, и понял, что рассудок покидает меня.

Если Вы гуру гуглапоиска, или просто знаете способ, как сделать реализацию с помощью регулярных выражений пишите, пишите мне, пишите публикацию на хабр, придумаем как прикрутить его к Sphinx и Manticore.

Но будет нечестно, если про двойной Metaphone совсем ничего не написать. Опишу как бы я его сделал, если было бы ну очень нужно. Sphinx не понадобится. Но придётся программировать.

Для начала я бы подключил уже готовую библиотеку в которой он реализован, благо их нашлось множество для разных языков, я использую Java, поэтому подключаю Commons Codec. Библиотека не работает с кириллицей требуется транслитерация, и кодирует только первое слово. Поэтому если вам надо реализовать кодирование большого текста, разделённого пробелами то самостоятельно придётся разбивать строку на слова, и в цикле их перебирать.

В таблице БД, где хранятся данные, я бы добавил две новые колонки, в которых бы хранил закодированное значение. Это один из недостатков данной реализации требуется допиливать таблицы в БД.

Затем, перед вставкой новых записей в таблицу, я бы вызывал:

DoubleMetaphone dm = new DoubleMetaphone();String metaphone1 = dm.doubleMetaphone("Text", false);String metaphone2 = dm.doubleMetaphone("Text", true);

И сохранял metaphone1 и metaphone2 вместе с данными.

Это вторая большая проблема вся вставка теперь должна проходить через эту процедуру.

При поиске значений в таблице, кодируем поисковой запрос с помощью CommonsCodec. И теперь ищем по столбцам, в которых код сохранялся. Особенность двойного Metaphone в том, что кода мы кодируем поисковый запрос двумя реализациями, то мы получившиеся оба результата ищем и в первом столбце и во втором. А не первый код в первом столбце, второй код во втором: и первый, и второй код в первом столбце и первый и второй код во втором, и все их комбинации.

Без Sphinx всё стало очень неудобно.

Русский Metaphone

Не подойдёт для целей интернационализации.

Зато подразумевает отсутствие транслитерации. А это может быть весьма удобно, ибо транслитерация сама по себе несёт небольшое искажение. Получает как в игре глухой телефон, только глухой Metaphone.

Если вы создаёте поиск исключительно для тех, кто печатает кириллицей, то разумно прибегнуть именно этой версии, а не городить транслитерации всякие.

Ну и ещё один момент, который нужно учесть при выборе, алгоритм не сжимает окончания. Значит, если улица Ленина, то надо так и писать Ленина, Ленин без окончания, не найдётся:

mysql> call keywords ('Ленина Ленин', 'rus_metaphone');+------+--------------+--------------+| qpos | tokenized    | normalized   |+------+--------------+--------------+| 1    | линина       | линина       || 2    | линин        | линин        |+------+--------------+--------------+

Реализуем регулярные выражения. Полный конфигурационный файл, как и ранее, лежит на GitHub Gist manticore.conf.

  • Переделываем гласные:

regexp_filter = (?i)(йо|ио|йе|ие) => иregexp_filter = (?i)(о|ы|я) => аregexp_filter = (?i)(е|ё|э) => иregexp_filter = (?i)(ю) => у
  • Для всех согласных букв, за которыми следует любая согласная, кроме Л, М, Н или Р, провести оглушение:

regexp_filter = (?i)(б)(б|в|г|д|ж|з|й|к|п|с|т|ф|х|ц|ч|ш|щ) => п\2regexp_filter = (?i)(г)(б|в|г|д|ж|з|й|к|п|с|т|ф|х|ц|ч|ш|щ) => к\2regexp_filter = (?i)(в)(б|в|г|д|ж|з|й|к|п|с|т|ф|х|ц|ч|ш|щ) => ф\2regexp_filter = (?i)(д)(б|в|г|д|ж|з|й|к|п|с|т|ф|х|ц|ч|ш|щ) => т\2regexp_filter = (?i)(ж)(б|в|г|д|ж|з|й|к|п|с|т|ф|х|ц|ч|ш|щ) => ш\2regexp_filter = (?i)(з)(б|в|г|д|ж|з|й|к|п|с|т|ф|х|ц|ч|ш|щ) => с\2
  • Для согласных на конце слова, провести оглушение

regexp_filter = (?i)б\b => пregexp_filter = (?i)г\b => кregexp_filter = (?i)в\b => фregexp_filter = (?i)д\b => тregexp_filter = (?i)ж\b => шregexp_filter = (?i)з\b => з
  • Склеиваем ТС и ДС в Ц

regexp_filter = (?i)(тс|дс|ц) => ц

Caverphone

Здесь сначала транслитерация.

  • Затем, нужно перевести транслитерированное в нижний регистр:

regexp_filter = (A|a) => aregexp_filter = (B|b) => b

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

  • Удалить e на конце

regexp_filter = e\b =>
  • Происходит преобразование начала слова, но это актуально для новозеландских фамилий, этот шаг можно и пропустить:

regexp_filter = \b(cough) => cou2fregexp_filter = \b(rough) => rou2f
  • Провести замены символов

regexp_filter = (cq) => 2qregexp_filter = (ci) => si
  • Заменить все гласные в начале слова на a, в остальных случаях на 3

regexp_filter = (?i)\b(a|e|i|o|u|y) => Aregexp_filter = (?i)(a|e|i|o|u|y) => 3
  • Провести очередные замены

regexp_filter = (j) => yregexp_filter = \b(y3) => Y3
  • Удалить все цифры 2

regexp_filter = 2 => 
  • Если на конце слова осталась цифра 3, то заменить её на A

regexp_filter = 3\b => A
  • Удалить все цифры 3

regexp_filter = 3 =>

До 10 символов не сокращаю и не дополняю.

Проверим:

mysql> select * from caverphone where match ('Ленина');+------+--------------------------------------+-----------+------------------+| id   | aoguid                               | shortname | offname          |+------+--------------------------------------+-----------+------------------+|    5 | 01339f2b-6907-4cb8-919b-b71dbed23f06 | ул        | Линейная         ||  387 | 4b919f60-7f5d-4b9e-99af-a7a02d344767 | ул        | Ленина           |+------+--------------------------------------+-----------+------------------+

Кроме Ленина находит и Линейная. Согласен, некоторое сходство есть, другие алгоритмы так не смогли, ну разве что Daitch Mokotoff Soundex из предыдущей публикации выкинул что-то подобное с Лунная:

mysql> select * from daitch_mokotoff_soundex where match ('Ленина');+------+--------------------------------------+-----------+--------------+| id   | aoguid                               | shortname | offname      |+------+--------------------------------------+-----------+--------------+|  387 | 4b919f60-7f5d-4b9e-99af-a7a02d344767 | ул        | Ленина       ||  541 | 69b8220e-a42d-4fec-a346-1df56370c363 | ул        | Лунная       |+------+--------------------------------------+-----------+--------------+

Можем посмотреть как это всё кодируется:

mysql> call keywords ('Ленина Линейная Лунная', 'caverphone');+------+-----------+------------+| qpos | tokenized | normalized |+------+-----------+------------+| 1    | lnna      | lnna       || 2    | lnna      | lnna       || 3    | lna       | lna        |+------+-----------+------------+mysql> call keywords ('Ленина Линейная Лунная', 'daitch_mokotoff_soundex');+------+-----------+------------+| qpos | tokenized | normalized |+------+-----------+------------+| 1    | 866       | 866        || 2    | 8616      | 8616       || 3    | 866       | 866        |+------+-----------+------------+

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

Бонус: ищем ракомакофон. Вместо заключения

Это лишено практического смысла, но наблюдение забавное, поэтому напишу. Just for fun.

Помните ракомакофон, который слышится вместо rock the microphone?! Было интересно, сможет ли Metaphone понять ракомакофон. И ведь почти!

Во-первых, добавляем пробел в blend_chars, ведь нам надо чтобы три слова rock the microphone, воспринимались как одно:

blend_chars = U+0020

Поскольку у нас только один алгоритм умеет адекватно работать в такой ситуации - оригинальный metaphone, то его и применим.

Проверим с помощью keywords как оно воспринимается Sphinx:

mysql> call keywords ('ракомакофон', 'metaphone');+------+-------------+------------+| qpos | tokenized   | normalized |+------+-------------+------------+| 1    | rakomakofon | RKMKFN     |+------+-------------+------------+

И rock the microphone:

mysql> call keywords ('rock the microphone', 'metaphone');+------+---------------------+------------+| qpos | tokenized           | normalized |+------+---------------------+------------+| 1    | rock the microphone | RK0MKRFN   || 1    | rock                | RK         || 2    | the                 | 0          || 3    | microphone          | MKRFN      |+------+---------------------+------------+

Получилось RK0MKRFN, и RKMKFN, расстояние Левенштейна между ними всего 2(!). А если найти способ исключить the из кодирования, то получится RKMKRFN:

mysql> call keywords ('rock microphone', 'metaphone');+------+-----------------+------------+| qpos | tokenized       | normalized |+------+-----------------+------------+| 1    | rock microphone | RKMKRFN    || 1    | rock            | RK         || 2    | microphone      | MKRFN      |+------+-----------------+------------+

Между RKMKRFN и RKMKFN, расстояние Левенштейна всего 1! Мы почти у цели.

Проблема убрать the, параметр stopwords здесь не поможет, ибо из-за blend_chars = U+0020 the не будет восприниматься самостоятельно. Но даже если удастся сделать предобработку, то всё равно расстояние в 1, не позволит обнаружить похожий.

Надежда на qsuggest не оправдалась, - не даст подсказок. Почему? Можно заметить, что при вызове keywords есть два столбца tokenized и normalized, qsuggest даёт подсказку по столбцу tokenized и измеряет расстояние Левенштейна относительно него, qsuggest всё равно, что там, в normalized, расстояние равно 1.

Поэтому наблюдение забавное, но не практичное.

Подробнее..

Как преобразовать текст в алгебру примеры

10.04.2021 22:12:11 | Автор: admin

В предыдущей статье было разработано представление знаковых последовательностей полиномами матричных единиц на примере языкового текста. Текст превращается в алгебраический объект. С текстом можно совершать все алгебраические операции, необходимые для структуризации -- вычисления заголовков, словарей, аннотаций, смысловой разметки. В данной статье приведены два примера алгебраической структуризации текстов иной природы. Азбука Морзе выбрана из-за предельной краткости словаря, а математические формулы как пример обратной задачи.

1 Код Морзе-Вейля-Герке как алгебра матричных единиц

В азбуке Морзе знаковые последовательности (тексты) 26 латинских букв состоят из точек и тире. Пример выбран из-за предельной краткости словаря ("точка" и "тире").

Слова здесь - точки или тире. 26 букв азбуки - тексты из таких слов. У каждого слова две координаты. Первая координата номер слова (точки или тире) в этой букве (от одного до четырех). Вторая координата номер в словаре (1 или 2). Словарь E11 ("точка") и E22 ("тире").

D_R=E_{11}+E_{22}Таблица 1. Азбука Морзе: латинские буквы как знаковые последовательности (тексты)Таблица 1. Азбука Морзе: латинские буквы как знаковые последовательности (тексты)

Каждой букве (знаковой последовательности) с номером из Таблицы 1 можно поставить в соответствие матричный полином P из матричных единиц 4x4 по формуле (8) из статьи [1].

Таблица 2: Азбука Морзе: буквы как матричные полиномыТаблица 2: Азбука Морзе: буквы как матричные полиномы

Например, букве Q (17) ставится в соответствие матричный полином:

E_{12}+E_{22}+E_{31}+E_{42}= \begin{Vmatrix} 0 & 1 & 0 & 0\\ 0 & 1 & 0 & 0\\ 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 \end{Vmatrix}.

Свойством всех 26 полиномов-букв таблицы 2 является то, что крайними правыми сомножителями являются только три матричные единицы E12, E21, E32

Если все 26 полиномов Таблицы 2 представить столбцом ||P||, а также из того, что для матриц и столбцов выполняется:

 \begin{Vmatrix} a_{11} & \ldots & a_{1n}\\ \ldots & \ldots & \ldots\\ a_{m1} & \ldots & a_{mn} \end{Vmatrix} \begin{Vmatrix} b_{1} \\ \ldots \\ b_{n} \end{Vmatrix}= \begin{Vmatrix} a_{11} \\ \ldots \\ a_{m1} \end{Vmatrix}b_1+\ldots + \begin{Vmatrix} a_{1n} \\ \ldots \\ a_{mn} \end{Vmatrix}b_n,

то азбука Морзе структурируется в три левые идеалы наборов матричных полиномов Таблицы 2 с базисами ||P||1, ||P||2, ||P||3.

 \left\|P\right\|=\left\|P\right\|_1\left\|P\right\|_1=\left\|P\right\|_2\left\|P\right\|_2=\left\|P\right\|_3\left\|P\right\|_3,

где

\left\|P\right\|_1=\begin{Vmatrix} E_{12} \\ E_{21} \\ E_{32} \end{Vmatrix}, \left\|P\right\|_2=\begin{Vmatrix} E_{12} \\ E_{21}E_{12} \\ E_{12}+E_{21}E_{12} \\ E_{12}E_{21} \\ E_{21} \\ E_{21}+E_{12}E_{21} \\ E_{32} E_{21} + E_{43}E_{32} E_{21} \\ E_{43}E_{32} E_{21} \\ E_{32} E_{21} \\ E_{32} \\ E_{32} + E_{43}E_{32} \\ E_{43}E_{32} \end{Vmatrix}, \left\|P\right\|_3=\begin{Vmatrix} E_{12}E_{21} \\ E_{12} \\ E_{21} \\ E_{21}E_{12} \\ E_{32}E_{21} \\ E_{32} \\ E_{43}E_{32} E_{21} \\ E_{43}E_{32} \end{Vmatrix}, (1.1)

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

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

Азбука Морзе с алгебраически структурирована в три идеала (класса) с базисами (1.3). Представление азбуки через идеалы описывает все подобные коды с базисами (1.3). Представление азбуки через идеалы приведено в Таблицах 3 и 4:

Таблица 3: Прямая индексацияТаблица 3: Прямая индексацияТаблица 4: Обратная индексацияТаблица 4: Обратная индексация

Азбука Морзе: ABCDEFGHIJKLMNOPQRSTUVWXYZ

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

E12 - заголовок тех букв, которые имеют знак тире на первом месте 4-знаковой последовательности:

_BCD__G___K_MNO_Q__T___XYZ (13 букв)

E21 - заголовок тех букв, которые имеют знак точка на втором месте 4-знаковой последовательности:

_BCD_F_HI_K__N____S_UV_XY_ (13 букв)

E32 - заголовок тех букв, которые имеют знак тире на третьем месте 4-знаковой последовательности:

__C__F___JK ___OP____U_W_Y_ (9букв)

2 Алгебра математического текста

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

Формулы объема конуса VK, цилиндра Vци тора VТ:

 V_K=\frac{1}{3}\pi R_1^2H_1, V_{\text{Ц}}=\pi R_2^2H_2, V_T=\pi^2\left(R_3+R_4\right)r,\ \ \ \ \ \ \ \ \ (2.1)

рассматриваются как тексты. Это означает, что входящие в тексты знаки не являются математическими объектами и для них отсутствуют алгебраические операции. Например, R12 этоR1R1, R1 это не произведение двух чисел, а просто последовательность двух знаков. Знаки в (1): R1и H1 радиус основания и высота конуса,R2 иH2 радиус основания и высота цилиндра, R3 внутренний радиус тора, R4 внешний радиус тора, r радиус образующей окружности тора, это число .

Для семиотического анализа формул как текстов важно наличие повторов знаков. Повторы определяют закономерности. В формулах (2.1) повторов знаков на самом деле больше, чем указанные повторы знака . ЗнакиR1, R2, R3, R4, H1, H2 и r это длины отрезков. Тогда один из знаков, например , является простым (эталон длины), а остальные знаки составными: R1=ar, R2=br, R3=cr, R4=dr, H1=er, H2=fr . Тогда правые части формул (2.1):

\begin{gathered} \frac{1}{3}\pi ararer \\ \pi brbrfr \\ \pi \pi \left(c+d \right)rr \end{gathered} \ \ \ \ \ \ \ \ \ \ \ \ (2.2)

Или в индексной форме:

\begin{gathered} \left(\frac{1}{3}\right)_{1,1}(\pi)_{2,2}(a)_{3,3} (r)_{4,4} (a)_{5,3} (r)_{6,4} (e)_{7,7} (r)_{8,4} \\ (\pi)_{9,2} (b)_{10,10} (r)_{11,4} (b)_{12,10} (r)_{13,4} (f)_{14,14} (r)_{15,4} \\ (\pi)_{16,2} (\pi)_{17,2} \left(c+d \right)_{18,18} (r)_{19,4}(r)_{20,4} \end{gathered} \ \ \ \ \ \ \ \ \ (2.3)

Формулы (2.2) как полином матричных единиц из трех фрагментов

 P=F_1(P)+F_2(P)+F_3(P), \ \ \ \ \ \ \ \ \ \ (2.4)

где:

\begin{gathered} F_1(P) = D_L\left(E_{1,1}+E_{2,2}+E_{3,3}+E_{4,4}+E_{5,3}+E_{6,4}+E_{7,7}+E_{8,4}\right)D_R \\ F_2(P) = D_L\left(E_{9,2}+E_{10,10}+E_{11,4}+E_{12,10}+E_{13,4}+E_{14,14}+E_{15,4}\right) D_R \\ F_3(P) = D_L\left(E_{16,2}+E_{17,2}+E_{18,18}+E_{19,4}+E_{20,4}\right) D_R \\ D_R = E_{1,1}+E_{2,2}+E_{3,3}+E_{4,4}+E_{7,7}+E_{10,10}+E_{14,14}+E_{18,18} \\ D_L = E_{1,1}+E_{2,2}+E_{3,3}+E_{4,4}+E_{5,5}+E_{6,6}+E_{7,7}+ \ldots + E_{20,20} = E \\ D_L=D_R+E_{5,5}+E_{6,6}+E_{5,5}+E_{8,8}+E_{5,5}+E_{9,9} \end{gathered}

Или в блочно-матричной форме:

В столбцах P находятся знаки из трех формул (2.1) . Если в столбце два нуля, это означает, что соответствующий знак имеется только в одной формуле. Например, знак 1/3 (или E1,1), два знака a (или E3,3+E5,3) , один знак e (или E7,7) имеются только в первой формуле для конуса (первая строка (2.5)). Только в цилиндре (вторая строка (2.5)) имеются два знака b (или E11,11+E13,11) и один f (или E15,15). Только в торе (третья строка (2.5)) имеется знак (c+d) (или E20,20). Общие знаки конуса, цилиндра и тора находятся во втором и четвертом столбцах (2.5). Тогда:

\begin{gathered} P = P_{\text{частн}_1}P_{\text{дел}_1}+P_{\text{ост}} \\ P = P_{\text{частн}_2}P_{\text{дел}_1}+P_{\text{ост}} \end{gathered}

где:

 \begin{gathered} P_{\text{частн}_1} = \left(E_{2,18}+E_{4,12}+E_{6,14}+E_{8,16}\right) +\left(E_{10,18}+E_{12,12}+E_{14,4}+E_{16,16}\right)+\\ +\left(E_{18,18}+E_{19,19}+E_{21,12}+E_{22,14}\right), \\ P_{\text{частн}_2} = (E_{2,2}+E_{4,4}+E_{6,4}+E_{8,4})+(E_{10,2}+E_{12,4}+E_{14,4}+E_{16,4})+ \\ +(E_{18,2}+E_{19,2}+E_{21,4}+E_{22,4}), \\ P_{\text{дел}_1} = E_{18,2} + E_{19,2}+E_{12,4} + E_{14,4} + E_{16,4}, \\ P_{\text{дел}_2} = E_{2,2} + E_{4,4}, \\ P_{\text{ост}} = E_{1,1}+E_{3,3} + E_{5,3}+E_{7,7}+E_{11,11} + E_{13,11}+E_{15,15}+E_{20,20}.\\ \end{gathered}

В (2.6) матричный текст раскладывается по разным базисам Pдел1 и Pдел2. Базис Pдел1учитывает взаимные положения между повторяющимися знаками относительно тора в формулах (2.1). Базис Pдел2 учитывает положения между повторяющимися знаками относительно знаков словаря DR в формулах (2.1). В общем случае учет положения знаков в формулах существенен, если знаки некоммутативны (например, знаки это матрицы, вектора, тензоры, гиперкомплексные числа). Но и в скалярном это полезно, например, канонической является формула площади круга r2, а не r2 .

Базис Гребнёра-Ширшова для (2.6):

 \begin{gathered} P_{\text{дел}_1}+P_{\text{ост}} \\ P_{\text{дел}_2}+P_{\text{ост}} \end{gathered}

Тогда:

 \begin{gathered} P= P_{\text{частн}_1} \left( P_{\text{дел}_1}+P_{\text{ост}} \right) \\ P= P_{\text{частн}_2} \left( P_{\text{дел}_2}+P_{\text{ост}} \right) \end{gathered}

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

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

  • Pдел1и Pдел2 (общие и r в разных местах формул),

  • общее число слагаемых в круглых скобках Pчастн1и Pчастн2 (четыре),

  • соотношения числа и r в круглых скобках Pчастн1и Pчастн2 (1,1,2 и 3,3,2),

  • сомножители мультипликативной формы Pчастн1 и Pчастн2,

  • всевозможные фрагменты Pост(вычеты, как класс формул с остатком-фрагментом).

Наименования классов совпадает с наименование признаков и их сочетаний.

Литература

[1] Пшеничников C.Б. Алгебра текста. Researchgate Preprint, 2021

Подробнее..

Сколько стоит расписание

10.04.2021 22:12:11 | Автор: admin

Основные данные вычислительных экспериментов по реорганизации ярусно-параллельной формы (ЯПФ) информационных графов алгоритмов (ТГА) приведены в предыдущей публикации (http://personeltest.ru/aways/habr.com/ru/post/545498/). Цель текущей публикации показать окончательные результаты исследований разработки расписаний выполнения параллельных программ в показателях вычислительной трудоёмкости собственно преобразования и качества полученных расписаний. Данная работа является итогом вполне определённого цикла исследований в рассматриваемой области.

Как и было сказано ранее, вычислительную трудоёмкости (ВТ) в данном случае будем вычислять в единицах перемещения операторов с яруса на ярус в процессе реорганизации ЯПФ. Этот подход близок классической методике определения ВТ операций упорядочивания (сортировки) числовых массивов, недостатком является неучёт трудоёмкости процедур определения элементов для перестановки.

Т.к. в принятой модели ЯПФ фактически определяет порядок выполнения операторов параллельной программы (операторы выполняются группами по ярусам поочерёдно), в целях сокращения будем иногда использовать саму аббревиатуру ЯПФ в качестве синонима понятия плана (расписания) выполнения параллельной программы. По понятным причинам исследования проводились на данных относительно небольшого объёма в предположении сохранения корректности полученных результатов при обработке данных большего размера. Описанные в данной публикации исследования имеют цель продемонстрировать возможности имеющегося инструментария при решении поставленных задач. При желании возможно исследовать произвольный алгоритм, описав и отладив его в модуле Data-Flow (http://personeltest.ru/aways/habr.com/ru/post/535926/) с последующим импортом в формате информационного графа в модуль SPF@home для дальнейшей обработки.

Основной целью преобразований ЯПФ продолжаем считать получение максимальной плотности кода (фактически максимальная загрузка имеющихся в наличии отдельных вычислителей параллельной вычислительной системы). Кстати, именно с этими понятиями связано известное зло-ироничное высказывание об излишнем количестве NOP-инструкций в связках сверхдлинного машинного слова в вычислителях VLIW-архитектуры (даже при наличии участков полностью последовательного кода лакуны в сверхдлинном слове формально должны быть заполнены некоей операцией-пустышкой)

Как и ранее, будем ссылаться на разработанные эвристические методы (иногда сокращённо именуемые просто эвристиками), реализованные на языке Lua и управляющие преобразованиями ЯПФ. Также не учитываем возможности одновременного выполнения команд нескольких процессов на параллельном поле вычислителей (хотя теоретически это может привести к повышению плотности кода).

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

Полученные результаты предназначаются для улучшения качества разработки расписаний выполнения параллельных программ в распараллеливающих компиляторах будущих поколений. При этом внутренняя реализация данных конечно, совсем не обязана предусматривать явного построения ЯПФ в виде двумерного массива, как для большей выпуклости показано на рис.2 в публикации http://personeltest.ru/aways/habr.com/ru/post/530078/ и выдаётся программным модулем SPF@home (http://vbakanov.ru/spf@home/content/install_spf.exe). Она может быть любой удобной для компьютерной реализации например, в наивном случае устанавливающей однозначное соответствие между формой ИГА в виде множества направленных дуг {k,l} (матрица смежности) и двоек номеров вершин ik,jk и il,jl, где i,j номера строк и столбцов в ЯПФ (процедуру преобразования ИГА в начальную ЯПФ провести всё равно придётся, ибо в данном случае именно она выявляет параллелизм в заданном ИГА алгоритме; только после этого можно начинать любые преобразования ЯПФ).

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

Для каждой из группы рассматриваемых задач (преобразования с сохранением высоты исходной ЯПФ или при возможности увеличения высоты оной) рассмотрим по две методики (эвристики, ибо так согласились именовать разработки) для перового случая это 1-01_bulldozer vs 1-02_bulldozer, для второго - WidthByWidtn vs Dichotomy. Мне стыдно повторять это, но высота ЯПФ определяет время выполнения программы

1. Получение расписания параллельного выполнения программ при сохранении высоты ЯПФ

Сохранение высоты исходной ЯПФ равносильно выполнению алгоритма (программы) за минимально возможное время. Наиболее равномерная нагрузка на все вычислители параллельной системы будет при одинаковой ширине всех ярусов ЯПФ (среднеарифметическое значение). В ЯПФ реальных алгоритмов и размерности обрабатываемых данных среднеарифметическое значение является довольно большим (практически всегда много большим числа отдельных вычислителей в параллельной системе). Т.о. рассматриваемый случай не часто встречается в практике, но всё же должен быть разобран.

Для сравнения выберем часто анализируемые ранее алгоритмы и два эвристических метода целенаправленного преобразования их ЯПФ эвристики 1-01_bulldozer и 1-02_bulldozer.

Результаты применения этих эвристик приведены на рис. 1-3; обозначения на этих рисунках (по осям абсцисс отложены показатели размерности обрабатываемых данных):

  • графики a), b) и с) ширина ЯПФ, коэффициент вариации (CV ширин ярусов ЯПФ), число перемещений (характеристика вычислительной трудоёмкости) операторов соответственно;

  • сплошные (красная), пунктирные (синяя) и штрих-пунктирные (зелёная) линии исходные данные, результат применения эвристик 1-01_bulldozer и 1-02_bulldozer cответственно.

Рисунок 1. Параметры плана параллельного выполнения при сохранении высоты ЯПФ для алгоритма умножения квадратных матриц 2,3,5,7,10-го порядков (соответствует нумерации по осям абсцисс) классическим методомРисунок 1. Параметры плана параллельного выполнения при сохранении высоты ЯПФ для алгоритма умножения квадратных матриц 2,3,5,7,10-го порядков (соответствует нумерации по осям абсцисс) классическим методомРисунок 2. Параметры плана параллельного выполнения при сохранении высоты ЯПФ для алгоритма вычисления коэффициента парной корреляции по 5,10,15,20-ти точкам (соответствует нумерации по осям абсцисс)Рисунок 2. Параметры плана параллельного выполнения при сохранении высоты ЯПФ для алгоритма вычисления коэффициента парной корреляции по 5,10,15,20-ти точкам (соответствует нумерации по осям абсцисс)Рисунок 3. Параметры плана параллельного выполнения при сохранении высоты ЯПФ для алгоритма решения системы линейных алгебраических уравнений (СЛАУ) для 2,3,4,5,7,10-того порядка (соответствует нумерации по осям абсцисс) прямым (неитерационным) методом ГауссаРисунок 3. Параметры плана параллельного выполнения при сохранении высоты ЯПФ для алгоритма решения системы линейных алгебраических уравнений (СЛАУ) для 2,3,4,5,7,10-того порядка (соответствует нумерации по осям абсцисс) прямым (неитерационным) методом Гаусса

Данные рис. 1-3 показывают, что во многих случаях удаётся приблизиться к указанной цели. Напр., рис. 1a) иллюстрирует снижение ширины ЯПФ до 1,7 раз (метод 1-01_bulldozer) и до 3 раз (метод 1-02_bulldozer) при умножении матриц 10-го порядка.

Коэффициент вариации ширин ярусов ЯПФ (рис. 1b) приближается к 0,3 (граница однородности набора данных) при использовании эмпирики 1-02_bulldozer и, что немаловажно, достаточно стабилен на всём диапазоне размерности данных.

Трудоёмкость достижения результата (рис. 1c) при использовании метода 1-02_bulldozer значительно ниже (до 3,7 раз при порядке матриц 10) метода 1-01_bulldozer.

Важно, что эффективность метода возрастает с ростом размерности обрабатываемых данных.

Не менее эффективным показал себя метод 1-02_bulldozer на алгоритме вычисления коэффициента парной корреляции (рис. 2).

Попытка реорганизации ЯПФ алгоритма решения системы линейных алгебраических уравнений (СЛАУ) порядка до 10 обоими методами (рис. 3) оказалась малополезной. Ширину ЯПФ снизить не удалось вообще (рис. 3a), снижение CV очень мало (рис. 3b), однако метод 1-02_bulldozer немного выигрывает в трудоёмкости (рис. 3c).

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

2. Получение расписания параллельного выполнения программ на фиксированном числе параллельных вычислителей

Эта постановка задачи максимально близка к реальному случаю составления расписания для VLIW-машины с заданным числом процессорных команд в машинном слове (размер связки, бандла сверхдлинного машинного слова). При этом достигается и повышение плотности кода.

Ниже рассматривается распространенный случай выполнения программы на заданном гомогенном поле из W параллельных вычислителей (от W=W0 до W=1, где W0 ширина ЯПФ, а нижняя граница соответствует полностью последовательному выполнению). Сравниваем два метода реорганизации ЯПФ Dichotomy и WidthByWidtn:

  • Dichotomy. Цель получить вариант ЯПФ с c шириной не более заданного W c увеличением высоты методом перенесения операторов с яруса на вновь создаваемый ярус ниже данного. Если ширина яруса выше W, ровно половина операторов с него переносится на вновь создаваемый снизу ярус и так далее, пока ширина станет не выше заданной W. Метод работает очень быстро, но грубо (высота ЯПФ получается явно излишней и неравномерность ширин ярусов высока).

  • WidthByWidtn. Подлежат переносу только операторы яруса с числом операторов выше заданного N>W путём создания под таким ярусом число ярусов М, равное:

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

На рис. 4,5 показаны результаты выполнения указанных эвристик в применении к двум распространенным алгоритмам линейной алгебры - умножение квадратных матриц классическим методом и решение системы линейных алгебраических уравнений прямым (неитерационным) методом Гаусса; красные и синие линии на этих и последующих рисунках соответствуют эвристикам WidthByWidtn и Dichotomy соответственно. Не забываем, что ширина реформированной ЯПФ здесь соответствует числу команд в связке сверхдлинного машинного слова.

Рисунок 4. Возрастание высоты (ординаты) при ограничении ширины ЯПФ (абсциссы), разы; алгоритм умножения квадратных матриц классическим методом 5 и 10-го порядков рис. a) и b) соответственноРисунок 4. Возрастание высоты (ординаты) при ограничении ширины ЯПФ (абсциссы), разы; алгоритм умножения квадратных матриц классическим методом 5 и 10-го порядков рис. a) и b) соответственноРисунок 5. Возрастание высоты (ординаты) при ограничении ширины ЯПФ (абсциссы), разы; алгоритм решения системы линейных алгебраических уравнений прямым (неитерационным) методом Гаусса 5 и 10-го порядков рис. a) и b) соответственноРисунок 5. Возрастание высоты (ординаты) при ограничении ширины ЯПФ (абсциссы), разы; алгоритм решения системы линейных алгебраических уравнений прямым (неитерационным) методом Гаусса 5 и 10-го порядков рис. a) и b) соответственно

Как видно из рис. 4 и 5, оба метода на указанных алгоритмах приводят к близким результатам (из соображений представления ЯПФ плоской таблицей и инвариантности общего числа операторов в алгоритме это, конечно, гипербола!). При большей высоте ЯПФ увеличивается время жизни данных, но само их количество в каждый момент времени снижается.

Однако при всех равно-входящих соответствующие методу WidthByWidtn кривые расположены ниже, нежели по методу Dichotomy; это соответствует несколько большему быстродействию. Полученные методом WidthByWidtn результаты практически совпадают с идеалом высоты ЯПФ, равным Nсумм./Wсредн. , где Nсумм. общее число операторов, Wсредн. среднеарифметическое числа операторов по ярусам ЯПФ при заданной ширине ея.

Рисунок 6. Число перемещений операторов между ярусами - a) и коэффициент вариации CV - b) при снижении ширины ЯПФ для алгоритма умножения квадратных матриц 10-го порядка классическим методом (ось абсцисс ширина ЯПФ после реформирования)Рисунок 6. Число перемещений операторов между ярусами - a) и коэффициент вариации CV - b) при снижении ширины ЯПФ для алгоритма умножения квадратных матриц 10-го порядка классическим методом (ось абсцисс ширина ЯПФ после реформирования)Рисунок 7. Число перемещений операторов между ярусами - a) и коэффициент вариации CV - b) при снижении ширины ЯПФ для алгоритма решения системы линейных алгебраических уравнений 10-го порядка прямым (неитерационным) методом Гаусса (ось абсцисс ширина ЯПФ после реформирования)Рисунок 7. Число перемещений операторов между ярусами - a) и коэффициент вариации CV - b) при снижении ширины ЯПФ для алгоритма решения системы линейных алгебраических уравнений 10-го порядка прямым (неитерационным) методом Гаусса (ось абсцисс ширина ЯПФ после реформирования)

Анализ результатов, приведённый на рис. 6 и 7, более интересен (хотя бы потому, что имеет чисто практический интерес вычислительную трудоёмкость преобразования ЯПФ). Как видно из рис. 6 и 7, для рассмотренных случаев метод WidthByWidtn имеет меньшую (приблизительно в 3-4 раза) вычислительную трудоёмкость (в единицах числа перестановок операторов с яруса на ярус) относительно метода Dichotomy (хотя на первый взгляд ожидается обратное). Правда, при этом метод (эвристика) WidthByWidtn обладает более сложной внутренней логикой по сравнению с Dichotomy (в последнем случае она примитивна).

Т.о. проведено сравнение методов реорганизации (преобразования) ЯПФ конкретных алгоритмов с целью их параллельного выполнения на заданном числе вычислителей. Сравнение проведено по критериям вычислительной трудоёмкости преобразований и неравномерности загрузки параллельной вычислительной системы.

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

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


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

Подробнее..

Неявные нейронные представления с периодическими функциями активации

13.04.2021 10:07:06 | Автор: admin
Знакомые с нейронными сетями читатели скорее всего слышали про термин функция активации. Такие варианты функции активации, как сигмоида, гиперболический тангенс (TanH) и ReLU (линейный выпрямитель), активно применяются в нейронных сетях и широко известны энтузиастам, занимающимся экспериментами с нейронными архитектурами. Исследователи нейронных сетей не останавливаются на достигнутом и подбирают альтернативы, позволяющие расширить границы возможностей. Один из вариантов подхода, предложенного в 2020 году, показывает выдающиеся результаты по сравнению с классическими функциями активации. Про впечатляющие отличия и пойдет речь в этой статье: на основе материала Vincent Sitzmann, Julien N. P. Martel, Alexander Bergman, David B. Lindell, Gordon Wetzstein и кода на нескольких наглядных примерах будет продемонстрировано превосходство нового метода.


Для работы с сигналами можно использовать различные формы их представления. Простой пример сигнал в форме таблицы, где для каждого временного шага имеется определенное значение переменной (температуры или др.). Такой сигнал будет представлен в дискретной форме, что в ряде случаев неудобно (так, при воспроизведении звука необходимо перейти от дискретного представления к непрерывному). Другой формой представления сигнала может быть заданная функция зависимости переменной f от времени t, тогда можно говорить о функции f(t). Такая форма представления сигнала имеет ряд преимуществ, например, возможность экстраполяции. Сигнал, разложенный на ряд Фурье, также представляет из себя удобную форму, позволяющую рассчитать спектр мощности, выделить характерные частоты и др. Интересно, что нейронная сеть также является формой представления сигнала, и свойства такого представления напрямую связаны с функцией активации нейронов.

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

Для использования синусоидальной функции активации получаем следующую формулу:


Здесь i: RMi RNi обозначает i-ый слой сети, Wi R NiMi представляет собой матрицу весов, ответственную за аффинные преобразования сигнала, а bi RNi представляет собой байесову добавку к вектору xi RMi

Интересно, что производная от предложенной функции активации также является синусоидальной функцией, т.к. производная от функции синуса является функция косинуса, которая в свою очередь является сдвинутой по фазе функцией синуса. Кроме того, синусоидальная функция активации позволяет аппроксимировать тождественную функцию около начала координат.

Таким образом, синусоидальная функция активации обладает рядом полезных свойств, таких, как нелинейность, непрерывная дифференцируемость и возможность аппроксимации тождественной функции около начала координат. Теперь давайте перейдем к теме статьи Vincent Sitzmann, Julien N. P. Martel, Alexander Bergman, David B. Lindell, Gordon Wetzstein.


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

Форма представления сигнала, параметризованная с помощью нейронной сети, и обладающая свойствами неявного определения, непрерывности и дифференцируемости, представляет собой отдельное направление в развитии нейронных архитектур. Такой подход способен предложить ряд преимуществ по сравнению с традиционными представлениями и поэтому может быть выделен в отдельную парадигму. Авторы статьи, в рамках новой парадигмы, предлагают использовать функции периодической активации для неявных нейронных представлений. Новый подход, под названием Сети Синусоидального Представления (sinusoidal representation networks) или SIREN, в работе авторов демонстрирует впечатляющие результаты в приложении к сложным сигналам физической природы и их производным.

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

Baselines


Следующие результаты сравнивают SIREN с различными сетевыми архитектурами. TanH, ReLU, Softplus и т. д. означает Multi Layer Perceptron одинакового размера с соответствующей функцией нелинейности. Авторы также сравнивают недавно предложенное позиционное кодирование (Partial Encoding) в сочетании с нелинейной функцией активации ReLU, обозначенной, как ReLU P.E. SIREN существенно превосходит все baseline результаты, сходится значительно быстрее и является единственной архитектурой, точно отражающей градиенты сигнала, позволяя тем самым использование в решении краевых задач.

Представление изображений


SIREN, отображающая координаты 2D-пикселей в цвета, может быть использована для параметризации изображений. В данном случае авторы напрямую активируют SIREN, используя истинные значения пикселей. В данных примерах SIREN удается успешно аппроксимировать изображение, получая на 10 дБ более высокое значение PSNR в условиях меньшего количества итераций по сравнению с конкурентами. Кроме того, SIREN является единственным представителем Multi Layer Perceptron, которому удается точно отразить производные первого и второго порядка.



Краткое демо


(код взят из https://colab.research.google.com/github/vsitzmann/siren/blob/master/explore_siren.ipynb)

Для начала, импортируем библиотеки и добавим вспомогательную функцию для визуализации

import torchfrom torch import nnimport torch.nn.functional as Ffrom torch.utils.data import DataLoader, Datasetimport osfrom PIL import Imagefrom torchvision.transforms import Resize, Compose, ToTensor, Normalizeimport numpy as npimport skimageimport matplotlib.pyplot as pltimport timedef get_mgrid(sidelen, dim=2):'''Generates a flattened grid of (x,y,...) coordinates in a range of -1 to 1.sidelen: intdim: int'''tensors = tuple(dim * [torch.linspace(-1, 1, steps=sidelen)])mgrid = torch.stack(torch.meshgrid(*tensors), dim=-1)mgrid = mgrid.reshape(-1, dim)return mgrid

Теперь добавим реализацию синусоидального слоя, который будет основным строительным блоком SIREN. В данном случае используется гораздо более лаконичная реализация, чем в основном коде авторов, однако упрощение оправдано тем, что здесь не преследуется цель сравнения с baseline решениями.

class SineLayer(nn.Module):# See paper sec. 3.2, final paragraph, and supplement Sec. 1.5 for discussion of omega_0.# If is_first=True, omega_0 is a frequency factor which simply multiplies the activations before the# nonlinearity. Different signals may require different omega_0 in the first layer - this is a# hyperparameter.# If is_first=False, then the weights will be divided by omega_0 so as to keep the magnitude of# activations constant, but boost gradients to the weight matrix (see supplement Sec. 1.5)def __init__(self, in_features, out_features, bias=True,is_first=False, omega_0=30):super().__init__()self.omega_0 = omega_0self.is_first = is_firstself.in_features = in_featuresself.linear = nn.Linear(in_features, out_features, bias=bias)self.init_weights()def init_weights(self):with torch.no_grad():if self.is_first:self.linear.weight.uniform_(-1 / self.in_features,1 / self.in_features)else:self.linear.weight.uniform_(-np.sqrt(6 / self.in_features) / self.omega_0,np.sqrt(6 / self.in_features) / self.omega_0)def forward(self, input):return torch.sin(self.omega_0 * self.linear(input))def forward_with_intermediate(self, input):# For visualization of activation distributionsintermediate = self.omega_0 * self.linear(input)return torch.sin(intermediate), intermediateclass Siren(nn.Module):def __init__(self, in_features, hidden_features, hidden_layers, out_features, outermost_linear=False,first_omega_0=30, hidden_omega_0=30.):super().__init__()self.net = []self.net.append(SineLayer(in_features, hidden_features,is_first=True, omega_0=first_omega_0))for i in range(hidden_layers):self.net.append(SineLayer(hidden_features, hidden_features,is_first=False, omega_0=hidden_omega_0))if outermost_linear:final_linear = nn.Linear(hidden_features, out_features)with torch.no_grad():final_linear.weight.uniform_(-np.sqrt(6 / hidden_features) / hidden_omega_0,np.sqrt(6 / hidden_features) / hidden_omega_0)self.net.append(final_linear)else:self.net.append(SineLayer(hidden_features, out_features,is_first=False, omega_0=hidden_omega_0))self.net = nn.Sequential(*self.net)def forward(self, coords):coords = coords.clone().detach().requires_grad_(True) # allows to take derivative w.r.t. inputoutput = self.net(coords)return output, coordsdef forward_with_activations(self, coords, retain_grad=False):'''Returns not only model output, but also intermediate activations.Only used for visualizing activations later!'''activations = OrderedDict()activation_count = 0x = coords.clone().detach().requires_grad_(True)activations['input'] = xfor i, layer in enumerate(self.net):if isinstance(layer, SineLayer):x, intermed = layer.forward_with_intermediate(x)if retain_grad:x.retain_grad()intermed.retain_grad()activations['_'.join((str(layer.__class__), "%d" % activation_count))] = intermedactivation_count += 1else:x = layer(x)if retain_grad:x.retain_grad()activations['_'.join((str(layer.__class__), "%d" % activation_count))] = xactivation_count += 1return activations

И, наконец, дифференциальные операторы, которые позволяют использовать torch.autograd для вычисления градиентов и лапласианов.

def laplace(y, x):grad = gradient(y, x)return divergence(grad, x)def divergence(y, x):div = 0.for i in range(y.shape[-1]):div += torch.autograd.grad(y[..., i], x, torch.ones_like(y[..., i]), create_graph=True)[0][..., i:i+1]return divdef gradient(y, x, grad_outputs=None):if grad_outputs is None:grad_outputs = torch.ones_like(y)grad = torch.autograd.grad(y, [x], grad_outputs=grad_outputs, create_graph=True)[0]return grad

Для экспериментов используется классическое изображение оператора.

def get_cameraman_tensor(sidelength):img = Image.fromarray(skimage.data.camera())transform = Compose([Resize(sidelength),ToTensor(),Normalize(torch.Tensor([0.5]), torch.Tensor([0.5]))])img = transform(img)return img

Далее просто фитируем это изображение. В процессе аппроксимации мы стремимся параметризовать изображение f(x) в оттенках серого с пиксельными координатами x с помощью функции (x). То есть мы ищем функцию такую, что функционал L = (x) f(x)dx минимизируется. При этом является областью изображения. Используем небольшой датасет, который вычисляет попиксельные координаты:

class ImageFitting(Dataset):def __init__(self, sidelength):super().__init__()img = get_cameraman_tensor(sidelength)self.pixels = img.permute(1, 2, 0).view(-1, 1)self.coords = get_mgrid(sidelength, 2)def __len__(self):return 1def __getitem__(self, idx):if idx > 0: raise IndexErrorreturn self.coords, self.pixels

Далее создадим экземпляр датасета и SIREN. Поскольку пиксельные координаты являются двумерным вектором, SIREN получает на вход два признака, а поскольку изображение в оттенках серого, размерность выходного канала равна 1.

cameraman = ImageFitting(256)dataloader = DataLoader(cameraman, batch_size=1, pin_memory=True, num_workers=0)img_siren = Siren(in_features=2, out_features=1, hidden_features=256,hidden_layers=3, outermost_linear=True)img_siren.cuda()

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

total_steps = 500 # Since the whole image is our dataset, this just means 500 gradient descent steps.steps_til_summary = 10optim = torch.optim.Adam(lr=1e-4, params=img_siren.parameters())model_input, ground_truth = next(iter(dataloader))model_input, ground_truth = model_input.cuda(), ground_truth.cuda()for step in range(total_steps):model_output, coords = img_siren(model_input)loss = ((model_output - ground_truth)**2).mean()if not step % steps_til_summary:print("Step %d, Total loss %0.6f" % (step, loss))img_grad = gradient(model_output, coords)img_laplacian = laplace(model_output, coords)fig, axes = plt.subplots(1,3, figsize=(18,6))axes[0].imshow(model_output.cpu().view(256,256).detach().numpy())axes[1].imshow(img_grad.norm(dim=-1).cpu().view(256,256).detach().numpy())axes[2].imshow(img_laplacian.cpu().view(256,256).detach().numpy())plt.show()optim.zero_grad()loss.backward()optim.step()






Представление аудио


Начнем с небольшого эксперимента.

Будем использовать SIREN для параметризации аудиосигнала, то есть стремимся параметризовать звуковую волну f(t) в моменты времени t с помощью функции . Для этого ищем функцию такую, что: функция потерь L = (t) f(t)dt минимизируется, где является звуковой волной. Для эксперимента будем использовать сонату Баха:

import scipy.io.wavfile as wavfileimport iofrom IPython.display import Audioif not os.path.exists('gt_bach.wav'):!wget https://vsitzmann.github.io/siren/img/audio/gt_bach.wav

Создадим небольшой датасет, который позволяет перевести аудиофайл в более удобный для работы формат:

class AudioFile(torch.utils.data.Dataset):def __init__(self, filename):self.rate, self.data = wavfile.read(filename)self.data = self.data.astype(np.float32)self.timepoints = get_mgrid(len(self.data), 1)def get_num_samples(self):return self.timepoints.shape[0]def __len__(self):return 1def __getitem__(self, idx):amplitude = self.datascale = np.max(np.abs(amplitude))amplitude = (amplitude / scale)amplitude = torch.Tensor(amplitude).view(-1, 1)return self.timepoints, amplitude

Далее создадим экземпляр SIREN. Поскольку звуковой сигнал имеет гораздо более высокую пространственную частоту в диапазоне от -1 до 1, поэтому увеличиваем 0 в первом слое SIREN.

bach_audio = AudioFile('gt_bach.wav')dataloader = DataLoader(bach_audio, shuffle=True, batch_size=1, pin_memory=True, num_workers=0)# Note that we increase the frequency of the first layer to match the higher frequencies of the# audio signal. Equivalently, we could also increase the range of the input coordinates.audio_siren = Siren(in_features=1, out_features=1, hidden_features=256,hidden_layers=3, first_omega_0=3000, outermost_linear=True)audio_siren.cuda()

Давайте прослушаем исходные данные:

rate, _ = wavfile.read('gt_bach.wav')model_input, ground_truth = next(iter(dataloader))Audio(ground_truth.squeeze().numpy(),rate=rate)

Далее начнем обучение нейронной сети:

total_steps = 1000steps_til_summary = 100optim = torch.optim.Adam(lr=1e-4, params=audio_siren.parameters())model_input, ground_truth = next(iter(dataloader))model_input, ground_truth = model_input.cuda(), ground_truth.cuda()for step in range(total_steps):model_output, coords = audio_siren(model_input)loss = F.mse_loss(model_output, ground_truth)if not step % steps_til_summary:print("Step %d, Total loss %0.6f" % (step, loss))fig, axes = plt.subplots(1,2)axes[0].plot(coords.squeeze().detach().cpu().numpy(),model_output.squeeze().detach().cpu().numpy())axes[1].plot(coords.squeeze().detach().cpu().numpy(),ground_truth.squeeze().detach().cpu().numpy())plt.show()optim.zero_grad()loss.backward()optim.step()





Послушаем, что получается в итоге:

final_model_output, coords = audio_siren(model_input)Audio(final_model_output.cpu().detach().squeeze().numpy(),rate=rate)

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


Представление видео


Использование SIREN совместно на координатах пикселей и времени позволяет параметризовать видео. В данном случае SIREN непосредственно активируется на истинных значениях пикселей и позволяет параметризовать видео существенно лучше, чем ReLU Multi Layer Perceptron.


Решение уравнения Пуассона


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



Представление фигур путем решения уравнения Эйконала Interactive 3D SDF Viewer используйте мышь для навигации по сценам


Решая краевую задачу в форме уравнений Эйконала, мы можем восстановить SDF из облака точек и нормалей поверхности. Подход SIREN позволяет восстановить сцену масштаба комнаты на основе только облака точек и нормалей поверхности, при этом удается точно воспроизвести мелкие детали, а для обучения требуется менее одного часа. В отличие от недавних работ по объединению воксельных сеток с нейронными неявными представлениями, в предлагаемом подходе полное представление хранится в весах одной пятислойной нейронной сети, без 2D или 3D-сверток, и поэтому требует гораздо меньшего количества параметров. Важно обратить внимание на то, что полученные SDF не обучаются на исходных значениях SDF, а скорее являются результатом решения вышеупомянутой эйкональной краевой задачи. Такая постановка задачи является существенно более сложной и требует обучения с учителем по градиентам (детали в статье). В результате архитектуры сетей, градиенты которых хуже контролируются, показывают качество ниже, чем в подходе SIREN.



Решение уравнения Гельмгольца


В данном случае авторы используют подход SIREN для решения неоднородного уравнения Гельмгольца. Архитектуры на основе ReLU и Tanh не позволяют полностью решить эту задачу.


Решение волнового уравнения


При работе со временем, подход SIREN позволяет успешно решить волновое уравнение, в отличие от архитектуры на основе Tanh, которая не смогла сойтись к правильному решению.


* * *


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

В данный момент мы рассматриваем возможности проверить SIREN на наших задачах в Центре компетенции больших данных и искусственного интеллекта ЛАНИТ. Хочется надеяться, что предлагаемый подход покажет свою продуктивность не только на простых примерах, но и на нетривиальных прикладных задачах.
Подробнее..

АТАТА распутываем задачу про палиндром

13.04.2021 12:15:42 | Автор: admin
Очень часто авторы алгоритмических задач делают ход конём: они берут задачу с простыми формулировками, заменяют их сложными и непонятными эквивалентами и выдают вам сложную задачу. В этом посте мы разберём пример одной такой задачи и обсудим пару полезных для её решения приёмов. Задача будет про палиндром.



Продолжение под катом.

Что такое палиндром


Палиндромом называется строка, которая одинаково читается как слева направо, так и справа налево. Например, слово АТАТА это палиндром, а вот слово АЙАЙАЙ нет.


Пример палиндрома из латинских слов: он составлен таким образом, что в каком бы направлении вы ни начали читать текст, получится одно и то же

Известный кинематографический палиндром название вышедшего в 2020 году фильма Довод (англ. Tenet). Русская адаптация в каком-то плане уникальна, потому что у нас нашлась подходящая альтернатива слову tenet, которая тоже является палиндромом. На многих других языках (в том числе славянских) название фильма оставили как есть. Например, на украинском это ТЕНЕТ (Википедия).



Постановка задачи


Итак, задача. Подготовьтесь морально.
Нечётным палиндромом будем называть такую строку, у которой все подстроки нечётной длины являются палиндромами. Суть задачи в том, чтобы в данной строке заменить не более K символов так, чтобы максимизировать длину самой длинной подстроки, которая является нечётным палиндромом.
Всё, клубок запутался. Начнём распутывать.

Вот несколько примеров нечётных палиндромов: ATATA, KKKKKKKK, ABA, ZO.

Рассмотрим подробнее первую строку АТАТА. Выпишем все её подстроки нечётной длины:

  • A, T, A, T, A однобуквенное слово всегда палиндром
  • ATA, TAT, ATA очевидно, палиндромы
  • ATATA тоже

В слове ZO нет подстрок нечётной длины больше чем в одну букву. И Z, и O палиндромы, поэтому ZO нечётный палиндром.

Пусть нам дана строка ABCDEF, и мы можем заменить не более одного символа (K=1), чтобы сделать из неё нечётный палиндром. Оптимальным решением было бы, например, заменить первую букву на C, тогда мы получили бы CBCDEF, где длина наибольшей подстроки, являющейся нечётным палиндромом, была бы равна трём (это CBC).

С тем же успехом мы могли бы прийти к варианту ABCFEF.

А вот если изначально у нас была строка ZXXXZ, и опять можно изменить не более одного символа, то надо заменить средний, так как ZXX и XXZ не являются палиндромами. В итоге мы получим ZXZXZ.

Структура нечётного палиндрома


Теперь заметим кое-что в рассмотренных примерах. Все нечётные палиндромы имеют схожую структуру: в них чередуются буквы (или все буквы одинаковые). И это действительно единственная форма, которую имеет нечётный палиндром. Почему это так?

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


На рисунке выше показано, как получается чередующаяся структура строки. Одинаковым цветом выделены одинаковые символы. Сначала посмотрим на палиндром длины 3, который начинается в самом первом символе исходной строки. Тогда 1 и 3 символ можно пометить зеленым. Про 2-й символ пока ничего непонятно. Сдвинем палиндром на единицу вправо, получим, что 2 и 4 символы можно покрасить в один цвет. Так, сдвигаясь каждый раз на единицу, мы получим, что все символы на нечётных позициях зелёные, а на чётных синие. Более строго можно доказать этот факт с помощью метода математической индукции, например.

Теперь, когда мы поняли, что надо искать, вернёмся непосредственно к задаче. Для краткости будем называть подстроки, которые являются нечётными палиндромами, хорошими. Надо изменить не более K символов так, чтобы максимизировать длину хорошей подстроки в последовательности.

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

Чтобы сделать как можно меньше замен, стоит выбрать в качестве единого символа самый частый среди тех, что стоят на чётных или нечётных позициях. Найти самый частый символ можно с помощью словаря (хеш-мапа, хеш-таблицы) отдельно для чётных и нечётных позиций. Алфавит в текущей задаче ограничен 26 символами, поэтому счётчик будет занимать константное количество дополнительной памяти.

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

Наивное решение


Теперь попробуем сделать как можно более длинную хорошую подстроку, которая начинается строго в символе с номером L. Указатель R будет отмечать ту позицию, до которой мы сумели расширить хорошую подстроку. Будем шагать указателем R вправо, начиная от позиции L. На каждом шаге будем учитывать в счётчике символов для чётных и нечётных позиций очередной символ. Прежде чем передвинуть R на шаг вправо, проверим по счётчикам, что сделать подстроку с L до R хорошей можно не более чем за K операций.

Если применить описанные действия независимо для всех L от 0 до n 1, где n длина исходной строки, а затем найти наиболее длинную найденную хорошую подстроку, то мы решим задачу. Однако временная сложность данного решения составит O(n^2), так как для каждой позиции L мы сделаем в худшем случае примерно n L шагов при передвижении R.

Улучшаем асимптотику решения


Мы можем ускорить это решение с помощью техники двух указателей. Не будем обнулять счётчики и сбрасывать позицию R после того, как максимально отошли вправо от L. Переиспользуем текущую информацию при переходе от L к L+1. Для этого надо убрать из счётчиков элемент на позиции L и всё. Затем можно продолжать делать проверки и отодвигать R вправо до тех пор, пока не исчерпаются K операций изменения элементов.


На рисунке выше показан ход указателей L и R, K=2. Подчёркнутые символы будут изменены при соответствующих L и R

Оценим сложность новой версии алгоритма. Указатель R суммарно сделает не более n шагов вправо, указатель L тоже. Передвижение указателя сопровождается обновлением счётчиков и проверкой числа изменений для получения хорошей подстроки эти действия выполняются за константное время, O(1). Таким образом мы получаем сложность O(n).

Мы выпутались из этой задачи, теперь можно запутываться в какую-нибудь другую.

Подробнее про метод двух указателей и про другие интересные приёмы мы рассказываем на курсе Алгоритмы и структуры данных. Если вам интересна эта тема, приглашаю на наш курс.
Подробнее..

Рекомендации Друзей ВКонтакте ML на эго-графах

13.04.2021 14:10:30 | Автор: admin

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

Меня зовут Женя Замятин, я работаю в команде Core ML ВКонтакте. Хочу рассказать, как устроены рекомендации, которые делают ближе пользователей самой крупной социальной сети рунета.

Обзор

Современные рекомендательные системы зачастую состоят из двух уровней, и наша не исключение. Задача первого уровня искать наиболее релевантных кандидатов среди всего множества пользователей (у нас их сотни миллионов). Такая постановка задачи подразумевает высокую скорость работы. Обычно здесь используют простые в применении модели вроде матричных факторизаций или эвристики на базе числа общих друзей. Полученные на первом уровне кандидаты отправляются на второй. Здесь на модель уже не накладываются такие жёсткие ограничения по скорости, её главная задача обеспечить максимальную точность предсказаний и сформировать список, который увидит пользователь. В этой статье мы рассмотрим только первый этап уровень отбора кандидатов.

Прежде всего сформулируем задачу, которую будем решать: для каждого пользователя необходимо найти k кандидатов, которых он с наибольшей вероятностью добавит в друзья. Метрика, на которую будем ориентироваться, recall@k. Она идеально описывает задачу: на первом уровне нам не интересен порядок кандидатов, но важна их релевантность.

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

Ещё один важный метод рекомендаций Adamic/Adar. В его основе лежит всё тот же анализ общих друзей, но с модификацией: авторы предлагают учитывать число друзей у общего друга. Чем больше это значение, тем меньше информации о релевантности он несёт.

Кроме методов на основе анализа общих друзей, довольно распространены рекомендации на базе эмбеддингов. В Лаборатории искусственного интеллекта ВКонтакте в МФТИ мы провели исследование: сравнили эффективность разных подходов к задаче предсказания дружб в VK. Результаты совпали с нашим опытом решения на базе графовых эмбеддингов у нас работают плохо. Учитывая это, мы стали развивать систему отбора кандидатов по пути анализа общих друзей.

EGOML

Общая схема нашего метода продолжает идеи числа общих друзей и Adamic/Adar. Финальная мера релевантности E(u, v), с помощью которой мы будем отбирать кандидатов, всё так же раскладывается в сумму по общим друзьям u и v. Ключевое отличие в форме слагаемого под суммой: в нашем случае это мера ez_c(u, v).

Сначала попробуем понять физический смысл меры ez_c(u, v). Представим, что мы взяли пользователя c и спросили у него: Насколько вероятно, что два твоих друга, u и v, подружатся? Чем больше информации для оценки он учтёт, тем точнее будет его предсказание. Например, если c сможет вспомнить только число своих друзей, его рассуждения могут выглядеть следующим образом: Чем больше у меня друзей, тем менее вероятно, что случайные двое из них знакомы. Тогда оценка вероятность дружбы u и v (с точки зрения c) может выглядеть как 1/log(n), где n число друзей. Именно так устроен Adamic/Adar. Но что если c возьмёт больше контекста?

Прежде чем отвечать на этот вопрос, разберёмся, почему ez_c(u, v) важно определять через пользователя c. Дело в том, что в таком виде очень удобно решать задачу распределённо. Представим, что теперь мы разослали всем пользователям платформы анкету с просьбой оценить вероятность дружбы в каждой паре их друзей. Получив все ответы, мы можем подставить значения в формулу E(u, v). Именно так выглядит вычисление E(u, v) с помощью MapReduce:

  • Подготовка. Для каждого c выделяется тот контекст, который он будет учитывать для вынесения оценок. Например, в Adamic/Adar это будет просто список друзей.

  • Map. Спрашиваем у каждого c, что он думает про возможность дружбы в каждой паре его друзей. По сути, вычисляем ez_c(u, v) и сохраняем в виде (u, v) ez_c(u, v) для всех u, v in N(c). В случае Adamic/Adar: (u, v) 1/log|N(c)|.

  • Reduce. Для каждой пары (u, v) суммируем все соответствующие ей значения. Их будет ровно столько, сколько общих друзей у u и v.

Таким образом мы получаем все ненулевые значения E(u, v). Заметим: необходимое условие того, что E(u, v) > 0, существование хотя бы одного общего друга у u и v.

Эго-граф ХоппераЭго-граф Хоппера

Контекстом пользователя c в случае меры ez_c будет тот же список друзей, но дополненный информацией о связях внутри этого списка. Такую структуру в науке называют эго-графом. Если более формально, эго-граф вершины x это такой подграф исходного графа, вершинами которого являются все соседи x и сама x, а рёбрами все рёбра исходного графа между этими вершинами. Коллеги из Одноклассников написали подробную статью об эго-графах и затронули в ней вопрос их эффективного построения.

Ключевая идея меры ez_c в том, что её можно сделать обучаемой. Для каждого пользователя c, его эго-графа и всех пар пользователей u, v внутри него мы можем посчитать много разных признаков, например:

  • число общих друзей u и v внутри эго-графа c;

  • число общих друзей u и c;

  • интенсивность взаимодействий между v и c;

  • время, прошедшее с последней дружбы между u и кем-либо из эго-графа c;

  • плотность эго-графа c;

  • и другие.

Таким образом мы получим датасет с признаками. Но для обучения нужны ещё и метки. Пусть датасет был построен по состоянию графа на момент времени T. Тогда в качестве положительных примеров возьмём те пары пользователей, которые не были друзьями на момент T, но подружились к T + T. А как отрицательные все остальные, не подружившиеся, пары пользователей. Заметим: поскольку мы решаем задачу предсказания новых дружб, те пары пользователей, которые уже дружат на момент T, учитывать не нужно ни на обучении, ни на применении.

В конечном счёте мы получаем датасет следующего вида:

  • для каждой пары пользователей u и v, а также их общего друга c, посчитаны признаки по эго-графу c;

  • пара пользователей u и v встречается в датасете ровно столько раз, сколько у них общих друзей;

  • все пары пользователей в датасете не являются друзьями на момент времени T;

  • для каждой пары u и v проставлена метка подружились ли они в течение определённого промежутка времени начиная с T.

По такому датасету мы и будем обучать нашу меру ez_c. В качестве модели выбрали градиентный бустинг с pairwise функцией потерь, где идентификатором группы выступает пользователь u.
По сути, мера ez_c(u, v) определяется как предсказание описанной выше модели. Но есть один нюанс: при pairwise-обучении распределение предсказаний модели похоже на нормальное. Поэтому, если в качестве определения меры ez_c(u, v) взять сырое предсказание, может возникнуть ситуация, когда мы будем штрафовать финальную меру E(u, v) за общих друзей, так как значения предсказаний бывают отрицательными. Это выглядит не совсем логично хочется, чтобы с ростом числа общих друзей мера E(u, v) не убывала. Так что поверх предсказания модели мы решили взять экспоненту:

Такой подход хорошо себя показывает на небольших графах. Но чтобы применить его на реальных данных, необходимо выполнить ещё одно действие. Суть проблемы такая: мы не можем вычислять признаки и применять модель для каждой пары пользователей всех эго-графов это слишком долго. Для решения мы придумали специальный трюк. Представим, что наш градиентный бустинг обучился таким образом, что каждое дерево использует признаки только одного пользователя: либо u, либо v. Тогда мы могли бы разделить весь ансамбль на две группы: к группе A мы бы отнесли деревья, которые используют только признаки пользователя u, к B пользователя v. Предсказание такой модели можно представить в виде:

Имея такую модель, мы могли бы получить предсказания для всех пар пользователей одного эго-графа быстрее. Достаточно применить модели A и B для каждого пользователя, а затем сложить соответствующие парам предсказания. Таким образом, для эго-графа из n вершин мы могли бы сократить число применений модели с O(n^2) до O(n). Но как получить такую модель, каждое дерево которой зависит только от одного пользователя? Для этого сделаем следующее:

  1. Исключим из датасета все признаки, которые одновременно зависят и от u и от v. Например, от признака число общих друзей u и v внутри эго-графа c придётся отказаться.

  2. Обучим модель A, используя только признаки на базе u, c и эго-графа c.

  3. Для обучения модели B оставим только признаки на базе v, c и эго-графа c. Также в качестве базовых предсказаний передадим предсказания модели A.

Если объединим модели A и B, получим то что нужно: первая часть использует признаки u, вторая признаки v. Совокупность моделей осмысленна, поскольку B была обучена корректировать предсказания A. Эта оптимизация позволяет ускорить вычисления в сотни раз и делает подход применимым на практике. Финальный вид ez_c(u, v) и E(u, v) выглядит так:

Вычисление меры E в онлайне

Заметим, что E(u, v) можно представить в виде:

Эта формула скалярное произведение разреженных векторов, индексами которых являются пользователи, а значениями экспоненты предсказаний модели. Ненулевые значения здесь проставлены только у друзей u по сути это просто списки друзей с дополнительными значениями.

При построении рекомендаций мы уже вычислили предсказания моделей для всех существующих дружб. Поэтому для каждого пользователя мы можем собрать векторы и сложить их в доступное онлайн key-value хранилище. После этого сможем получать значение E(u, v) для любой пары пользователей в онлайне простой операцией перемножения векторов. Это даёт возможность использовать E(u, v) как лёгкую функцию релевантности в нагруженных местах либо как дополнительный признак финальной модели ранжирования.

Итог

В результате система EGOML позволяет:

  1. Распределённо отбирать кандидатов для каждого пользователя в офлайне. Асимптотическая сложность оптимизированного алгоритма составляет O(|E|) вычислений признаков и применений модели, где |E| число связей в графе. На кластере из 250 воркеров время работы алгоритма составляет около двух часов.

  2. Быстро вычислять меру релевантности E(u, v) для любой пары пользователей в онлайне. Асимптотическая сложность операции O(|N(u)| + |N(v)|).

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

В конечном счёте мы перешли со способа отбора кандидатов с использованием Adamic/Adar к системе EGOML и внедрили в модель второй уровень признаков на основе меры E(u, v). И это позволило увеличить количество подтверждённых дружб со всей платформы на несколько десятков процентов.

Благодарность

Хочу сказать спасибо руководителю команды Core ML Андрею Якушеву за помощь в разработке метода и подготовке статьи, а также всей команде Core ML за поддержку на разных этапах этой работы.

Подробнее..

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

16.04.2021 18:11:03 | Автор: admin

Оптимизаторы важный компонент архитектуры нейронных сетей. Они играют важную роль в процессе тренировки нейронных сетей, помогая им делать всё более точные прогнозы. Специально к старту нового потока расширенного курса помашинному и глубокому обучению, делимся с вами простым описанием основных методик, используемых оптимизаторами градиентного спуска, такими как SGD, Momentum, RMSProp, Adam и др.


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

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

Большинство популярных библиотек глубокого обучения, например PyTorch и Keras, имеют множество встроенных оптимизаторов, базирующихся на использовании алгоритма градиентного спуска, например SGD, Adadelta, Adagrad, RMSProp, Adam и пр.

Но почему алгоритмов оптимизации так много? Как выбрать из них правильный?

В документации к каждому из таких оптимизаторов приводится формула, с помощью которой такие оптимизаторы обновляют параметры модели. Что же означает каждая формула, и в чём её смысл?

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

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

Кривая потерь

Начнём с того, что рассмотрим на 3D-изображении, как работает стандартный алгоритм градиентного спуска.

Кривая потерь для алгоритма градиентного спускаКривая потерь для алгоритма градиентного спуска

На рисунке показана сеть с двумя весовыми параметрами:

  • На горизонтальной плоскости размещаются две оси для весов w1 и w2, соответственно.

  • На вертикальной оси откладываются значения потерь для каждого сочетания весов.

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

Синяя линия соответствует траектории алгоритма градиентного спуска при оптимизации:

  • Работа алгоритма начинается с выбора некоторых случайных значений обоих весов и вычисления значения потери.

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

  • В конечном итоге алгоритм достигает цели, то есть приходит в нижнюю точку кривой в точку с самым низким значением потерь.

Вычисление градиента

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

Обновление параметра градиентного спускаОбновление параметра градиентного спуска

Градиент измеряет уклон и рассчитывается как значение изменения в вертикальном направлении (dL), поделённое на значение изменения горизонтальном направлении (dW). Другими словами, для крутых уклонов значение градиента большое, для пологих маленькое.

Вычисление градиентаВычисление градиента

Практическое применение алгоритма градиентного спуска

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

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

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

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

  • Ландшафт круто уходит вниз в одном направлении, но, чтобы попасть в самую нижнюю точку, нужно двигаться в направлении с меньшими изменениями высоты?

  • Весь ландшафт представляется алгоритму плоским во всех направлениях?

  • Алгоритм попадает в глубокую канаву? Как ему оттуда выбраться?

Давайте рассмотрим несколько примеров кривых, перемещение по которым может создавать трудности.

Трудности при оптимизации градиентного спуска

Локальные минимумы

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

Локальный минимум и глобальный минимумЛокальный минимум и глобальный минимум

Седловые точки

Ещё одной важной проблемой является прохождение алгоритмом "седловых точек". Седловой называют точку, в которой в одном направлении, соответствующем одному параметру, кривая находится на локальном минимуме; во втором же направлении, соответствующем другому параметру, кривая находится на локальном максимуме.

Седловая точкаСедловая точка

Какую опасность таят в себе седловые точки, которые алгоритм может встретить на своём пути? Опасность в том, что область, непосредственно окружающая седловую точку, как правило, довольно плоская, она напоминает плато. Плоская область означает практически нулевые градиенты. Оптимизатор начинает колебаться (осциллировать) вокруг седловой точки в направлении первого параметра, не догадываясь спуститься вниз по уклону в направлении второго параметра.

Алгоритм градиентного спуска при этом ошибочно полагает, что минимум им найден.

Овраги

Ещё одна головная боль алгоритма градиентного спуска пересечение оврагов. Овраг это протяжённая узкая долина, имеющая крутой уклон в одном направлении (т.е. по сторонам долины) и плавный уклон в другом (т.е. вдоль долины). Довольно часто овраг приводит к минимуму. Поскольку навигация по такой форме кривой затруднена, такую кривую часто называют патологическим искривлением (Pathological Curvature).

ОврагиОвраги

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

Алгоритм ванильного градиентного спуска, несмотря на его недостатки, по-прежнему считается основным, в частности, по той причине, что существуют специальные методы его оптимизации.

Первое улучшение алгоритма градиентного спуска стохастический градиентный спуск (SGD)

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

Также применяется промежуточный стохастический градиентный спуск с мини-пакетами вариант, при котором коэффициенты меняются после обсчета N элементов выборки, то есть для каждой тренировочной итерации алгоритм выбирает случайное подмножество набора данных.

Случайность выбора хороша тем, что она помогает глубже исследовать ландшафт потерь.

Ранее мы говорили, что кривая потерь получается за счёт изменения параметров модели с сохранением фиксированного набора входных данных. Однако если начать изменять входные данные, выбирая различные данные в каждом мини-пакете, значения потерь и градиентов также будут меняться. Другими словами, изменяя набор входных данных, для каждого мини-пакета мы получим собственную кривую потерь, которая немного отличается от других.

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

Второе усовершенствование алгоритма градиентного спуска накопление импульса (Momentum)

Динамическая корректировка количества обновлений

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

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

Алгоритм градиентного спуска обновляет веса на каждом шаге, используя в качестве параметров значения градиента и скорости обучения. Таким образом, для изменения количества обновлений можно выполнить два действия:

  • Скорректировать градиент.

  • Скорректировать значение скорости обучения.

SGD с функцией накопления импульса и обычный SGD

Первое действие, то есть корректировка градиента, выполняется с помощью функции накопления импульса.

В SGD учитывается только текущее значение градиента, а значения всех прошлых градиентов игнорируются. Это означает, что, если алгоритм внезапно натолкнётся на аномалию на кривой потерь, он может пойти не по нужной траектории.

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

  1. Сразу встаёт вопрос как далеко можно углубляться в прошлое? Чем глубже мы погрузимся в прошлое, тем меньше вероятность воздействия аномалий на конечный результат.

  2. И второй вопрос все ли градиенты из прошлого можно расценивать одинаково? Вполне логично рассудить, что вещи из недавнего прошлого должны иметь более высокую значимость, чем вещи из далёкого прошлого. Поэтому, если изменение ландшафта представляет собой не аномалию, а естественное структурное изменение, на такие изменения нужно реагировать соответственно и менять курс постепенно.

Функция накопления импульса использует при работе экспоненциальное скользящее среднее, а не текущее значение градиента.

Переходы через овраги с помощью функции накопления импульса

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

Функция накопления импульса помогает преодолевать оврагиФункция накопления импульса помогает преодолевать овраги

С помощью функции накопления импульса можно сгладить зигзагообразные скачки алгоритма SGD.

  • Для первого параметра с крутым уклоном большое значение градиента приведёт к резкому повороту от одной стороны оврага к другой. Однако на следующем шаге такое перемещение будет скомпенсировано резким поворотом в обратном направлении.

  • Если же взять другой параметр, мелкие обновления на первом шаге будут усиливаться мелкими обновлениями на втором шаге, так как эти обновления имеют то же направление. И именно такое направление вдоль оврага будет направлением, в котором необходимо двигаться алгоритму.

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

  • SGD с накоплением импульса

  • Ускоренный градиент Нестерова

Третье усовершенствование алгоритма градиентного спуска изменение скорости обучения (на базе значения градиента)

Как уже было сказано выше, вторым способом изменения количества обновлений параметров является изменение скорости обучения.

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

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

Мы можем использовать это обстоятельство, чтобы адаптировать скорость обучения к каждому параметру. Чтобы выбрать скорость обучения для данного параметра, мы можем обратиться к прошлым градиентам (для каждого параметра отдельно).

Данная функциональность реализована в нескольких алгоритмах оптимизации, использующих разные, но похожие методы, например Adagrad, Adadelta, RMS Prop.

Например, метод Adagrad возводит в квадрат значения прошлых градиентов и суммирует их в равных весовых пропорциях. Метод RMSProp также возводит в квадрат значения прошлых градиентов, но приводит их к экспоненциальному скользящему среднему, тем самым придавая более высокую значимость последним по времени градиентам.

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

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

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

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

Четвёртое усовершенствование алгоритма градиентного спуска изменение скорости обучения (на базе тренировочной выборки)

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

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

Заключение

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

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Одномерный поиск образца с использованием дискретного преобразования Фурье

17.04.2021 18:19:17 | Автор: admin

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

Потому занялся дополнительным поиском готовых реализаций, но к сожалению не смотря на обилие упоминаний идеи 1974 года[2], реализаций алгоритма, даже на законодателе моды в вычислительной математике Фортране я не обнаружил. В семинарах и лекциях да и в диссертациях описание не блистало целостностью, потому собрав с десяток статей и обсуждений в кучу появилось желание написать статью для тех кто простейшую реализацию поиска подстроки хочет "подержать в руках".

И так, написание алгоритмических программ обычно произвожу сначала в математических пакетах, и только после выяснения численной устойчивости и корректности работы алгоритма перевожу в код на компилируемые или байт ориентированные языки. Таков уж мой опыт, - или считать медленно но точно, или быстро но то что уже практически известно. Потому для отладочного иллюстративного кода использовал Wolfram Language и набор функций пакета Mathematica V 12.

Собственно в чем ценность идеи: использование дискретного Фурье преобразования (DFT) сокращает сложность вычисления от "наивного" O(n*m) до O(n Log(n)), где n - размер текста а m - размер искомого образца. Более того расширения метода позволяют производить поиск с "Джокером", - символом обозначающим любой другой символ в алфавите, в то время как суффиксные реализации это сделать не позволяют.

Описание "наивного" подхода:

Для массива T размером n и образца P размером m, вычислим функцию квадратов разницы значений элементов. Нумерация в массиве начинается с нуля.

S_i^F = \sum\nolimits_{j = 0}^{m - 1} {({t_{i + j}}} - {p_j}{)^2} = \sum\nolimits_{j = 0}^{m - 1} {t_{i + j}^2} - 2\sum\nolimits_{j = 0}^{m - 1} {{t_{i + j}}} {p_j} + \sum\nolimits_{j = 0}^{m - 1} {p_j^2} = T{T_i} - 2P{T_i} +P{P_i}

Очевидно что в точке соответствия искомая сумма показывает минимум, если точнее обнуляется. После раскрытия квадрата под суммой получаются три слагаемых, последнее из которых имеет постоянное значение. Соответственно для поиска минимума необходимо вычислить только первые две суммы. Можно увидеть что прямое вычисление всех элементов S требует O((n-m+1)*m) операций, или оценочно O(n*m).

В качестве массива в котором будем искать образец используем строчку из картинки:

"Test.png""Test.png"

Произведем прямое вычисление искомой функции:

{S_i} = \sum\nolimits_{j = 0}^{m - 1} {t_{i + j}^2} - 2\sum\nolimits_{j = 0}^{m - 1} {{t_{i + j}}} {p_j} = T{T_i} - 2P{T_i}
Img = ColorConvert[Import["Test.png"], "Grayscale"];{W, H} = ImageDimensions[Img];   y = IntegerPart[H/2];                                (*Pattern width and coordinates*)x = IntegerPart[W/4]; w = IntegerPart[W/8];            L = Part[ImageData[ImageTake[Img, {y, y}]],1];       (*Stripe Array*)T[i_] := Table[Part[L[[i + j - 1]], 1], {j, 1, w}] ;      (*Sample data*)P[i_] := Table[Part[L[[j + x - 1]], 1], {j, 1, w}] ;      (*Pattern data*)TT = Table[Sum[(T[i][[j]]* T[i][[j]]), {j, 1, w}], {i, 1, W - w + 1}];PT = Table[Sum[(P[i][[j]]* T[i][[j]]), {j, 1, w}], {i, 1, W - w + 1}];ListPlot[TT - 2 PT, Joined -> True, PlotRange -> All]
Результат вычисления квадрата разницы без постоянного слагаемогоРезультат вычисления квадрата разницы без постоянного слагаемого

Как видно в точке (x=175), где был взят образец, функция показала минимальное значение и повторила его значение ведь изображение почти дублируется.

Свойства дискретного Фурье преобразования.

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

Вычисление PT

PolyT = {1, 2, 3, 4, 5};               LT = Length[PolyT];PolyP = {1, 2, 3};                     LP = Length[PolyP];PolyR = {};                            LR = LT + LP - 1;eT = Table[If[i > LT, 0, PolyT[[i]]], {i, 1, LR}]eP = Table[If[i > LP, 0, PolyP[[i]]], {i, 1, LR}]PolyR = InverseFourier[   Fourier[eT, FourierParameters -> {1, 1}]*   Fourier[eP, FourierParameters -> {1, 1}]  , FourierParameters -> {1, 1}]PolyR[[LP ;; LT]]

результат действия такого кода:

{1, 2, 3, 4, 5, 0, 0} (* PolyT *){1, 2, 3, 0, 0, 0, 0} (* PolyP *){1., 4., 10., 16., 22., 22., 15.} (* PolyR = PolyT * PolyP *){10., 16., 22.}                   (* PolyR starting at LP to LT*)

Итак, если массив значений представить полиномами, то получаемое значение тоже полином размером n+m-1.

\left( {1 + 2x + 3{x^2} + 4{x^3} + 5{x^4}} \right)\left( {1 + 2x + 3{x^2}} \right) = 1 + 4x + 10{x^2} + 16{x^3} + 22{x^4} + 22{x^5} + 15{x^6}

Более того, начиная с позиции m (если нумерация начинается с единицы) мы получаем сумму перекрестного произведения элементов для окна длинны m:

10 = 1*3+2*2+3*116 = 2*3+3*2+4*1...

Потому для вычисления элементов PT искомый образец P разворачивается. Всего получается n-m+1 значений.

Вычисление TT

PolyT = {1, 2, 3, 4, 5};            LT = Length[PolyT];PolyP = {1, 1, 1};                  LP = Length[PolyP];PolyR = {};                         LR = LT + LP - 1;eT = Table[If[i > LT, 0, PolyT[[i]]], {i, 1, LR}]eP = Table[If[i > LP, 0, PolyP[[i]]], {i, 1, LR}]PolyR = InverseFourier[   Fourier[eT, FourierParameters -> {1, 1}]*   Fourier[eP, FourierParameters -> {1, 1}]  , FourierParameters -> {1, 1}]PolyR[[LP ;; LT]]

результат действия кода:

{1, 2, 3, 4, 5, 0, 0}                 (* PolyT *){1, 1, 1, 0, 0, 0, 0}                 (* PolyP *){1., 3., 6., 9., 12., 9., 5.}         (* PolyR = PolyT * PolyP *){6., 9., 12.}                         (* PolyR starting at LP to LT*)
6 = 1*1+2*1+3*19 = 2*1+3*1+4*1...

Учитывая предыдущий пример, в массив текста заносятся квадраты значений элементов, а в массив искомого образца единицы, длинна последовательности единиц - m.

Сборка и сравнение

Вычисление PP и TT с использованием DFT:

Tt = Table[If[1 <= i <= W, Part[L[[i]], 1], 0], {i, 1, Wt}] ;    (*Sample data*)Ft = Fourier[Tt, FourierParameters -> {1, 1}];Ts = Table[If[1 <= i <= W, (Part[L[[i]], 1])^2, 0], {i, 1, Wt}]; (*Sample squared data*)Fs = Fourier[Ts, FourierParameters -> {1, 1}];Es = Table[If[1 <= i <= w, 1, 0], {i, 1, Wt}] ;                  (*m size unit array*)Fe = Fourier[Es, FourierParameters -> {1, 1}];Pa = Table[If[1 <= i <= w, Part[L[[x + w - i]], 1], 0], {i, 1, Wt}];                                                             \Fp = Fourier[Pa, FourierParameters -> {1, 1}];                   (*Inverse pattern data*)TTf = InverseFourier[Fs*Fe, FourierParameters -> {1, 1}][[w ;; W]];PTf = InverseFourier[Ft*Fp, FourierParameters -> {1, 1}][[w ;; W]];

Сравниваем полученные значения:

ListPlot[{TT - 2 PT, TTf - 2 PTf, TT - 2 PT - TTf + 2 PTf}, Joined -> True, PlotRange -> All]
Три графика, два совпадающих и один показывающий разницу между ними, совпадает с осью.Три графика, два совпадающих и один показывающий разницу между ними, совпадает с осью.

Выводы

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

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

  1. http://personeltest.ru/aways/habr.com/ru/post/266129/

  2. https://eduardgorbunov.github.io/assets/files/amc_778_seminar_08.pdf

Подробнее..
Категории: Алгоритмы , Search engine , Dft

Прикручиваем ИИ оптимизация работы банкоматов

20.04.2021 10:21:38 | Автор: admin
Всем привет! Это небольшой рассказ про то, как команда Центра компетенции больших данных и искусственного интеллекта в ЛАНИТ оптимизировала работу банкоматной сети. Упор в статье сделан не на описание подбора параметров и выбор лучшего алгоритма прогнозирования, а на рассмотрение концепции нашего подхода к решению поставленной задачи. Кому интересно, добро пожаловать под кат.

источник

Банки теряют довольно много денег из-за того, что они просто лежат в банкоматах. Вместо этого деньги можно было бы успешно вложить и получать доход. Сегодня крупные банки с сильными аналитическими центрами имеют свои решения для того, чтобы рассчитать количество купюр и время, на которое их нужно заложить в банкоматы до следующей инкассации. Небольшие же игроки, не имея таких решений, просто ориентируются на средние значения, полученные в ходе предыдущих поездок инкассаторов.

Таким образом, формальная постановка задачи выглядит так.

На входе:

  • есть история снятия/приема наличности в банкоматах (в нашем случае это были данные за полтора года);
  • стоимость потерь от нахождения денег в банкоматах (от простаивающих запасов) зависит от ставки рефинансирования (параметр q); стоимость можно оценить как $S*X*(\frac{q}{365})$, где S сумма, X количество дней;
  • стоимость поездки инкассаторов, si (меняется со временем и зависит от местоположения банкомата и маршрута инкассаторов).

На выходе ожидается:

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

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

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

Предположим, что в день снимают S руб. Помимо суммы снятий, введем также переменную X число дней между инкассациями, меняя которую будем дальше искать минимум затрат банка. Логично, что сумма, которую выгоднее всего положить в банкомат, зная, что инкассация будет через X дней это S*X. При таком подходе за день до инкассации в банкомате будет находиться S руб., за два дня 2*S руб., за три дня 3*S руб. и т. д. Другими словами, наш ряд можно рассматривать, двигаясь от конца к началу, тогда это будет возрастающая арифметическая прогрессия. Поэтому за период между двумя инкассациями в банкомате будет лежать (S+S*X)/2 руб. Теперь, исходя из ставки рефинансирования, остаётся посчитать стоимость простаивающих запасов этой суммы за X дней и дополнительно прибавить стоимость совершённых инкассаторских поездок. Если между инкассациями X дней, то за n дней будет совершено $[\frac{n}{X}]+1$ (где $[\frac{n}{X}]$ это целочисленное деление) инкассаций, поскольку ещё один раз придётся приехать, чтобы вывести остаток денег.

Таким образом, получившаяся функция выглядит так:

$TotalCost(S, X, n, q, si) = (S + S*X)/2*\frac{q}{365}+si*([\frac{n}{X}]+1)$


где:

  • S сумма снятий, руб./день,
  • X количество дней между инкассациями,
  • n рассматриваемый период в днях,
  • q ставка рефинансирования,
  • si стоимость инкассации.

Однако в реальности каждый день снимают разные суммы, поэтому у нас есть ряд снятий/внесений купюр, каждый день этот ряд пополняется новыми значениями. Если это учесть, функция примет следующий вид:

$TotalCost = \sum_{i=1}^{n}Q_{i}*\frac{q}{365} + si*([\frac{n}{X}]+1) \\ q - ставка\, рефинансирования, \\ n - количество\, рассматриваемых\, дней, \\ X - количество\, дней\, между\, инкассациями, \\ Q_{i} - сумма\, в\, банкомате\, на\, iй\, день,\, Q_{i} = encash_{i} - \sum_{k=[\frac{i}{X}]*X}^{i}S_{k} \\ S_{k} - изменение\, суммы\, в\, банкомате\, на\, kй\, день, \\ encash_{i} - сумма\, последней\, на\, iй\, день\, инкассации, \\ encash_{i} = \begin{cases} \sum_{k=[\frac{i}{X}]*X}^{([\frac{i}{X}]*X+1)*X}S_{k}, \,\,\, если\,сумма\, убывающая \\ \\ \sum_{k=[\frac{i}{X}]*X}^{[\frac{i}{X}]*X+3}S_{k}^{-}, \,\,\, если\,сумма\, возрастающая \end{cases} \\ S_{k}^{-} - сумма\, снятий\, за\, kй\, день \\$


Что такое убывающие и возрастающие суммы: в зависимости от того, больше кладут или больше снимают, есть купюры, по которым сумма в банкомате накапливается, а есть купюры, по которым сумма в банкомате убывает. Таким образом формируются возрастающие и убывающие суммы купюр. В реализации было сделано три ряда: incr_sums возрастающие купюры, decr_sums убывающие купюры и withdrawal_sums ряд сумм выдач банкомата (там присутствуют купюры, которые идут только на выдачу).

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

Помимо прочего, чтобы применить описанную функцию, нужно учесть следующие моменты.

  • Самое главное, сложное, и интересное в момент инкассации мы не знаем, какие это будут суммы, их нужно прогнозировать (об этом ниже).
  • Банкоматы бывают следующих типов:

    только на внос/вынос,
    на внос и вынос одновременно,
    на внос и вынос одновременно + ресайклинг (за счёт ресайклинга у банкомата есть возможность выдавать купюры, которые в него вносят другие клиенты).
  • Описанная функция также зависит от n количества рассматриваемых дней. Если подробнее рассмотреть эту зависимость на реальных примерах, то получится следующая картинка:

Рис. 1. Значения функции TotalCost в зависимости от X (Days betw incas) и n (Num of considered days)

Чтобы избавиться от n, можно воспользоваться простым трюком просто разделить значение функции на n. При этом мы усредняем и получаем среднюю величину стоимости затрат в день. Теперь функция затрат зависит только от количества дней между инкассациями. Это как раз тот параметр, по которому мы будем её минимизировать.

Учитывая вышесказанное, реальная функция будет иметь следующий шаблон:

TotalCost(n, x, incr_sums, decr_sums, withdrawal_sums, si), где

  • x максимальное количество дней между инкассациями
  • n количество дней, которые отслеживаем, то есть мы смотрим последние n значений подаваемых на вход временных рядов (как написано выше, функция не зависит от n, этот параметр добавлен, чтобы можно было экспериментировать с длиной подаваемого временного ряда)
  • incr_sums ряд спрогнозированных сумм по купюрам только на внос,
  • decr_sums ряд спрогнозированных сумм по купюрам только на вынос,
  • withdrawal_sums ряд спрогнозированных сумм выдач банкомата (т.е. здесь сумма по купюрам in минус сумма по out), заполняется 0 для всех банкоматов кроме ресайклинговых,
  • si стоимость инкассации.

Функция проходит непересекающимся окном величиной X по входным рядам и считает суммы внесенных денег внутри окна. В отличие от первоначальной функции сумма арифметической прогрессии здесь превращается в обычную сумму (это та сумма, которая была заложена при инкассации). Далее внутри окна в цикле по дням происходит кумулятивное суммирование/вычитание сумм, которые ежедневно клались/снимались из банкомата. Это делается для того, чтобы получить сумму, которая лежала в банкомате на каждый день.

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

Реализация
def process_intervals(n, x, incr_sums, decr_sums, withdrawal_sums):# генератор количества сумм, которые# остаются в банкомате на каждый день# incr_sums  ряд возрастающих сумм# decr_sums  ряд убывающих сумм# withdrawal_sums  ряд сумм выдач банкомата (там присутствуют купюры, которые идут только на выдачу)# заполняется 0 для всех банкоматов кроме ресайклинговых# x  количество дней между инкассациями# n  количество дней, которые отслеживаемif x>n: returnfor i in range(n//x):decr_interval = decr_sums[i*x:i*x+x]incr_interval = incr_sums[i*x:i*x+x]withdrawal_interval = withdrawal_sums[i*x:i*x+x]interval_sum = np.sum(decr_interval)interval_sum += np.sum(withdrawal_interval[:3])for i, day_sum in enumerate(decr_interval):interval_sum -= day_suminterval_sum += incr_interval[i]interval_sum += withdrawal_interval[i]yield interval_sum# остаток сумм. Берется целый интервал.# но yield только для остатка рядаdecr_interval = decr_sums[(n//x)*x:(n//x)*x+x]incr_interval = incr_sums[(n//x)*x:(n//x)*x+x]withdrawal_interval = withdrawal_sums[(n//x)*x:(n//x)*x+x]interval_sum = np.sum(decr_interval)interval_sum += np.sum(withdrawal_sums[:3])for i, day_sum in enumerate(decr_interval[:n-(n//x)*x]):interval_sum -= day_suminterval_sum += incr_interval[i]interval_sum += withdrawal_sums[i]yield interval_sumdef waiting_cost(n, x, incr_sums, decr_sums, withdrawal_sums, si):# incr_sums  ряд возрастающих сумм# decr_sums  ряд убывающих сумм# withdrawal_sums  ряд сумм выдач банкомата (там присутствуют купюры, которые идут только на выдачу)# заполняется 0 для всех банкоматов кроме ресайклинговых# si  стоимость инкассации# x  количество дней между инкассациями# n  количество дней, которое отслеживаемassert len(incr_sums)==len(decr_sums)q = 4.25/100/365processed_sums = list(process_intervals(n, x, incr_sums, decr_sums, withdrawal_sums))# waiting_cost = np.sum(processed_sums)*q + si*(x+1)*n//xwaiting_cost = np.sum(processed_sums)*q + si*(n//x) + si# делим на n, чтобы получить среднюю сумму в день (не зависящее от количества дней)return waiting_cost/ndef TotalCost (incr_sums, decr_sums, withdrawal_sums, x_max=14, n=None, si=2500):# x  количество дней между инкассациями# n  количество дней, которое отслеживаемassert len(incr_sums)==len(decr_sums) and len(decr_sums)==len(withdrawal_sums)X = np.arange(1, x_max)if n is None: n=len(incr_sums)incr_sums = incr_sums[-n:]decr_sums = decr_sums[-n:]        withdrawal_sums = withdrawal_sums[-n:]waiting_cost_sums = np.zeros(len(X))for i, x in enumerate(X):waiting_cost_sums[i] = waiting_cost(n, x, incr_sums, decr_sums, withdrawal_sums, si)return waiting_cost_sums

Теперь применим эту функцию к историческим данным наших банкоматов и получим следующую картинку:

Рис. 2. Оптимальное количество дней между инкассациями

Резкие перепады на некоторых графиках объясняются провалами в данных. А то, что они произошли в одинаковое время, скорее всего можно объяснить техническими работами, на время которых банкоматы не работали.

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

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

Подробно останавливаться на том, как делается прогноз снятий и зачислений не буду. Если есть интерес к этой теме, то можно посмотреть видеодоклад о решении подобной задачи исследователями из Сбербанка (Data Science на примере управления банкоматной сетью банка).

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

Prophet показал себя плохо. SARIMA не стали пробовать, поскольку в видео выше о нем плохие отзывы.

Используемые признаки (всего их было 139, после признака приведено его обозначение на графике feature importance ниже)

  • Временные лаги целевых значений переменной, lag_* (их количество можно варьировать, но мы остановились на 31. К тому же, если мы хотим прогнозировать не на день вперед, а на неделю, то и первый лаг смотрится не за вчерашний день, а за неделю назад. Таким образом, максимально далеко мы смотрели на 31+14=45 дней назад).
  • Дельты между лагами, delta_lag*-lag*.
  • Полиномиальные признаки от лагов и их дельт, lag_*^* (использовались только первые 5 лагов и их дельт, обозначались).
  • День недели, месяца, номер месяца, weekday, day, month (категориальные переменные).
  • Тригонометрические функции от временных значений из пункта выше, weekday_cos и т.д.
  • Статистика (max, var, mean, median) для этого же дня недели, месяца, weekday_max, weekday_mean, (брались только дни, находящиеся раньше рассматриваемого в обучающей выборке).
  • Бинарные признаки выходных дней, когда банкоматы не работают, is_weekend
  • Значения целевой переменной за этот же день предыдущей недели/месяца, y_prev_week, y_prev_month.
  • Двойное экспоненциальное сглаживание + сглаживание по значениям целевой функции за те же дни предыдущих недели/месяца, weekday_exp_pred, monthday_exp_pred.
  • Попробовали tsfresh, tsfresh+PCA, но потом отказались от этого, поскольку этих признаков очень много, а объектов в обучающей выборке у нас было мало.

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

Рис. 3.Feature importance используемых признаков

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

Сам график прогноза выглядит следующим образом (по оси x отложены дни, по оси y количество купюр):

Рис. 4 Прогноз CatBoostRegressor

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

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

Общий пайплайн работы выглядит следующим образом (пересчёт всех значений происходит раз в день).

  1. Для каждой купюры каждого atm на каждый прогнозируемый день своя модель (поскольку прогнозировать на день вперед и на неделю вперед разные вещи и снятия по различным купюрам также сильно разнятся), поэтому на каждый банкомат приходится около 100 моделей.
  2. По историческим данным банкомата при помощи функции TotalCost находится оптимальное количество дней до инкассации.
  3. Если найденное значение меньше 14 дней, то следующий день инкассации и закладываемая сумма подбираются по прогнозу, который кладется в функцию TotalCost, иначе по историческим данным.
  4. На основе прогноза либо исторических данных снятий/внесений наличности рассчитывается сумма, которую нужно заложить (т.е. количество закладываемых купюр).
  5. В банкомат закладывается сумма + ошибка.
  6. Ошибка: при закладывании денег в банкомат необходимо заложить больше денег, оставив подушку безопасности, на случай, если вдруг люди дружно захотят обналичить свои сбережения (чтобы перевести их во что-то более ценное). В качестве такой суммы можно брать средние снятия за последние 2-3 дня. В усложнённом варианте можно прогнозировать снятия за следующие 2-3 дня и дополнительно класть эту сумму (выбор варианта зависит от качества прогноза на конкретном банкомате)
  7. Теперь с каждым новым днём приходят значения реальных снятий, и оптимальный день инкассации пересчитывается. Чем ближе день инкассации, полученный по предварительному прогнозу, тем больше реальных данных мы кладём в TotalCost вместо прогноза, и точность работы увеличивается.

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

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

atm profit(relative) profit/day (руб.)
a 0.61 367
b 0.68 557
с 0.70 470
d 0.79 552
e -0.30 -66
f 0.49 102
g 0.41 128
h 0.49 98
i 0.34 112
j 0.48 120
k -0.01 -2
l -0.43 -26
m 0.127 34
n -0.03 -4
o -0.21 -57
p 0.14 24
q -0.21 -37

Подходы и улучшения, которые интересно рассмотреть, но пока не реализованы на практике (в силу комплексности их реализации и ограниченности во времени):

  • использовать нейронные сети для прогноза, возможно даже RL агента,
  • использовать одну модель, просто подавая в неё дополнительный признак, отвечающий за горизонт прогнозирования в днях.
  • построить эмбеддинги для банкоматов, в которых сагрегировать информацию о географии, посещаемости места, близости к магазинам/торговым центрам и т. д.
  • если оптимальный день инкассации (на втором шаге пайплайна) превышает 14 дней, рассчитывать оптимальный день инкассации по прогнозу другой модели, например, Prophet, SARIMA, или брать для этого не исторические данные, а исторические данные за прогнозируемый период с прошлого года/усредненный за последние несколько лет.
  • для банкоматов, у которых отрицательный профит, можно пробовать настраивать различные триггеры, при срабатывании которых работа с банкоматами ведется в старом режиме, либо инкассаторские поездки совершаются чаще/реже.

В заключение можно отметить, что временные ряды снятий/внесений наличности поддаются прогнозированию, и что в целом предложенный подход к оптимизации работы банкоматов является довольно успешным. При грубой оценке текущих результатов работы в день получается сэкономить около 2400 руб., соответственно, в месяц это 72 тыс. руб., а в год порядка 0,9 млн руб. Причём чем больше суммы денег, находящихся в обращении у банкомата, тем большего профита можно достичь (поскольку при небольших суммах профит нивелируется ошибкой от прогноза).

За ценные советы при подготовке статьи большая благодарность vladbalv и art_pro.

Спасибо за внимание!
Подробнее..

Перевод Разработка критически важных алгоритмов, часть 3 Интеграция

20.04.2021 14:21:11 | Автор: admin
  1. Проектирование
  2. Реализация
  3. Интеграция


Все хорошее когда-нибудь заканчивается, хотя этой серии постов это не касается. Хоть они скоро и закончатся, далеко не факт, что они хороши.

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

Хочу предварить этот пост освобождением от ответственности: серебряной пули для интеграции вашего кода не существует (если бы она была, я бы давал консультации и разбогател). Я могу привести лишь рекомендации, которые позволят сделать этот процесс менее болезненным.

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

Но в результате получается что-то, что работает, и это круто. Но, возможно, что еще более важно, вы прокачаетесь и станете немного лучше, сделав это в первый и самый худший раз. (Я бы сказал, что это похоже на написание академической статьи).

Интеграция другими словами


По сути, интеграция это подключение, поиск проблем и их устранение. Иными словами, это длительный и неприятный процесс устранения неполадок.

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

Сам я могу предложить следующее:

  1. Осознайте цепочку событий в вашем процессе или алгоритме
  2. Разработайте тесты, чтобы определить, где в цепочке событий возникает проблема
  3. Решите проблему
  4. Повторите


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

Тем не менее, на макроуровне алгоритмы в значительной степени последовательны (если вы следовали нашим предыдущим советам).

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

Тестировать поток исполнения можно как простыми методами (расставлять везде операторы печати и проверять вывод), так и сложными (составлять специальные тестовые примеры поведения в соответствии с функциональной спецификацией).

В идеальном мире у нас всегда были бы идеальные спецификации и прекрасные тестовые примеры, которые соответствовали бы требованиям, но в реальном мире так бывает далеко не всегда. Спецификации могут быть неправильными или двусмысленными, интерфейсы API могут не поддаваться тестированию, и всегда существует насущная проблема целесообразности. Все нужно выпускать вовремя и платить за это.

Делайте все, что поможет протестировать ваш код. Я предпочитаю самыми простыми и быстрыми способами нахождения и воспроизведения ошибок.

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

Тогда проблема будет решена.

На самом деле нет. Если только нам не повезет (или если мы хороши в написании кода!). Обычно одна проблема скрывает под собой другие, либо ваш код работает верно только в пределах данного ODD.

Именно так и можно вкратце описать процесс интеграции. Это не что иное, как переименованное устранение неполадок. По описанию кажется, что это долго, утомительно и болезненно, и процесс, безусловно, может быть таким. Тем не менее, все это можно упростить, и у меня есть несколько советов для этого.

Советы по снижению болезненности интеграции



Интеграционное тестирование

Сюрприз! Мы вернулись к тестированию.

Вообще говоря, интеграционные тесты это когда несколько компонентов работают согласованно. Здесь мне нравится определять компонент как один узел.

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

Существует множество инструментов для интеграционного тестирования. В области ROS есть замечательный фреймворк launch_testing (разработанный нашим другом Питом, абсолютным мастером). Если это не в вашем вкусе, вы также можете запрограммировать интеграционные тесты в стандартной среде GTest.

Кстати, я отвечал за первые тесты в Apex.AI. Это была ужасная однострочная команда на bash, которая запускала исполняемый файл и проверяла, ничего ли не вылетает.

И это нормально (хотя есть способы сделать это лучше). Дымовое тестирование это нормально, и любое тестирование, которое вы проводите, обычно избавляет вас потенциальной боли в будущем.

Возвращаемся к состояниям

Некоторые читатели могут вспомнить, что в предыдущем тексте я много говорил о состоянии объектов и о том, что понимание и защита этих состояний (на мой взгляд) могут стать ключом к написанию хороших классов.

Что ж, мы снова к этому вернулись.

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

  1. Помните об инвариантах
  2. Убедитесь, что вы их соблюдаете
  3. Минимизируйте и упрощайте состояние объекта


Первые два довольно просты (и могут быть реализованы с помощью контрактных механизмов), а последний представляет собой дополнение KISS. Если состояние меньше, значит и проблем с ним может возникнуть меньше.

Понять связаны ли ваши проблемы с состояниями позволит B2B-тестирование. Интересно, что именно эту форму тестирования рекомендуют в ISO26262, так что это еще одна веская причина для ее использования.

В качестве примера можно отметить, что одна из основных ошибок в реализации MPC была связана с состоянием и могла быть обнаружена при последовательном тестировании (back-to-back test). Все тесты в мире проверяют, что ваши алгоритмы работают только один раз, так что добавление последовательного теста хороший способ убедиться, что вы охватываете случаи с i + 1.

Валидация ввода

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

С точки зрения алгоритма входные данные обычно не проверяются, и из-за этого может возникать множество ошибок. В конечном итоге ответственность за обеспечение соответствия входных данных любым требованиям ложится на алгоритм.

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

Конкретный пример ввод траектории для MPC, где проверяются две важные вещи: заголовок и периоды выборки.

Хотя комплексное представление заголовка позволяет легко избежать сингулярностей в пространстве SO(2), представление действительно только тогда, когда норма двух чисел равна единице. Для некоторых контроллеров заголовок даже не имеет значения, но для других, например MPC, это очень важно. На всякий случай все значения заголовков проверяются и, при необходимости, нормализуются.

Впрочем, не все контроллерам строго обязательна метка времени для опорной траектории. Некоторым нужен только набор инвариантных во времени точек в пространстве, представляющих путь. С MPC все иначе. Для него мы проверяем, что каждая из временных точек, связанных с каждой точкой траектории, достаточно близка к требуемой MPC дискретизации времени, а если нет, мы пытаемся исправить ее с помощью интерполяции.

Литература и прошлые наработки

Если вы следили за этими текстами, то, возможно, помните, что я ранее выступал за написание объемной проектной документации. Угадайте, что? Я снова говорю об этом!

Главной звездой в контексте интеграции является обзор литературы и документ о прошлых наработках.

Если у вас нет идей, как заставить что-то работать, всегда полезно сравнить нефункциональную реализацию с функциональной.

Например, можно вспомнить реализацию NDT в Autoware. Autoware столкнулась со значительными проблемами с центральным коридором на карте, которую мы использовали для тестирования. Сравнение функций показало, что (в интересах целесообразности) нам не хватало нескольких улучшений по сравнению с исходной версией Autoware.AI, а именно:

  1. Обеспечения хорошей обусловленности ковариационных матриц
  2. Линейного поиска More-Theunte
  3. Поиска по нескольким ячейкам


Мой дорогой (и разочарованный) коллега Юнус пошел дальше и попробовал каждый из этих вариантов, чтобы увидеть, какие из них улучшат производительность. Нам хватило обеспечения обусловленности ковариационных матриц и реализации поиска More-Theunte

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

Подводя итог этому разделу, могу дать следующие рекомендации:

  1. Сравните свой код с рабочими реализациями
  2. Изучите литературу: я люблю использовать поисковые запросы прикладное использование <название алгоритма>, <название алгоритма> на практике и приложения <название алгоритма>.


Сократите цикл проверки

Теперь начинается печальная часть истории. К сожалению, у меня нет конкретных и полезных советов, которые могли бы довести до идеала ваш процесс интеграции. Вместо этого все, что у меня осталось, это советы по облегчению боли.

Первый из этих советов (который хорошо согласуется с большинством современных практик разработки программного обеспечения) сократить цикл процесса исправления-проверки-повторения. Для этого нужны:

  1. Процесс сборки одной кнопкой (или команда, для тех из вас, кто предпочитает командную строку)
  2. Тестирование одной кнопкой


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

Займитесь чем-то еще

Наконец, я могу посоветовать только собрать игрушки и отправиться домой.

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

Что дальше?


Таким был процесс разработки NDT и MPC, и наш первый проход по V-образному циклу разработки. Мы начали с идеи, дополнили ее планом, создали продукт в соответствии с планом и убедились, что все работает. Теперь у нас есть два алгоритма, которые по меньшей мере делают то, что заявлено локализуются по некоторой эталонной карте и следуют некоторой эталонной траектории.

Что дальше?

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

Технический долг это факт жизни, особенно когда мы прилагаем все усилия, чтобы создать минимально жизнеспособный продукт. Всегда приходится разбираться с множеством таких долгов. В контексте NDT мы можем улучшить некоторые архитектурные аспекты, добавить больше интеграционных тестов и повысить надежность и устойчивость. Мой малоизвестный коллега Игорь уже начал работать над улучшением архитектуры. Для MPC мы аналогичным образом можем улучшить интеграцию, реализовать изящную обработку краевых случаев и повысить надежность и стабильность решателя.

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

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

Однако в конечном итоге направление проекта определяется людьми, стоящими во главе, и людьми, оплачивающими счета. Я не принадлежу к числу этих людей, поэтому мое слово не имеет особого значения.

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




image

Вакансии
НПП ИТЭЛМА всегда рада молодым специалистам, выпускникам автомобильных, технических вузов, а также физико-математических факультетов любых других высших учебных заведений.

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

В компании организован специальный испытательный центр, дающий возможность проводить исследования в области управления ДВС, в том числе и в составе автомобиля. Испытательная лаборатория включает моторные боксы, барабанные стенды, температурную и климатическую установки, вибрационный стенд, камеру соляного тумана, рентгеновскую установку и другое специализированное оборудование.

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



О компании ИТЭЛМА
Мы большая компания-разработчик automotive компонентов. В компании трудится около 2500 сотрудников, в том числе 650 инженеров.

Мы, пожалуй, самый сильный в России центр компетенций по разработке автомобильной электроники. Сейчас активно растем и открыли много вакансий (порядка 30, в том числе в регионах), таких как инженер-программист, инженер-конструктор, ведущий инженер-разработчик (DSP-программист) и др.

У нас много интересных задач от автопроизводителей и концернов, двигающих индустрию. Если хотите расти, как специалист, и учиться у лучших, будем рады видеть вас в нашей команде. Также мы готовы делиться экспертизой, самым важным что происходит в automotive. Задавайте нам любые вопросы, ответим, пообсуждаем.

Подробнее..

Категории

Последние комментарии

© 2006-2021, personeltest.ru