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

Искусственный интеллект

Книга Надежность нейронных сетей укрепляем устойчивость ИИ к обману

24.02.2021 12:05:30 | Автор: admin
image Привет, Хаброжители! Глубокие нейронные сети (DNN) становятся неотъемлемой частью IT-продуктов, провоцируя появление нового направления кибератак. Хакеры пытаются обмануть нейросети с помощью данных, которые не смогли бы обмануть человека.

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

Для кого предназначена книга


Целевая аудитория этой книги:

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

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

Схемы атак против реальных систем


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

Схемы атак


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

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

Существующие схемы атак можно разбить на такие основные категории, как:

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

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

image

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

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

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

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

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

Прямая атака


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

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

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

Проведение прямой атаки весьма непростая задача. Чтобы найти идеально подходящий вредоносный входной сигнал, используя один из методов черного ящика, например граничную атаку, нужно выполнить очень много итераций (десятки тысяч). Каждая итерация, в свою очередь, может включать в себя несколько запросов к целевой ГНС. Таким образом, в итоге мы имеем громадное количество запросов, выполнение которых вряд ли останется незамеченным защищающейся организацией! Более того, ограничения по пропускной способности и времени задержки, характерные для коммерческого развертывания, будут замедлять скорость обработки этих запросов. На самом деле целевая система может даже специально ограничить количество запросов или ввести временную задержку перед выдачей ответов, чтобы защитить себя от такой атаки. Если злоумышленнику настолько повезет, что у него будет доступ к показателям, возвращаемым целевой системой, он может сократить объем запросов за счет использования таких интеллектуальных стратегий, как генетический алгоритм, рассмотренный в разделе Методы черного ящика с оценкой на с. 163. Однако, как уже говорилось ранее, в большинстве случаев есть лишь ограниченный доступ к этим оценкам.

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

Атака с копированием


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

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

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

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

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

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

Атака с переносом


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

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

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

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

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

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

  • Отображение входа на выход. Включает в себя формат и точность входных данных и допустимых выходных данных. В случае ГНС для классификации данных мы имеем набор категорий и, возможно, некоторую иерархическую структуру этих категорий (например, категория собака является подмножеством категории животное).
  • Внутренняя архитектура. Тип сети (например, LSTM или СНС), тип и количество слоев, количество узлов в каждом слое. Включает в себя особенности используемых функций активации фактически те аспекты модели, которые определяются до проведения обучения.
  • Параметры. Веса и пороги, определяемые в ходе обучения.

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

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

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

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

image

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

image

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

Универсальная атака с переносом


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

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

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

Гипотетический пример: обход классификации видеоданных

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

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

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

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

Об авторе


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

Более подробно с книгой можно ознакомиться на сайте издательства
Оглавление
Отрывок

Для Хаброжителей скидка 25% по купону Сети

По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Подробнее..

Перевод Как распознать рукописный текст с помощью ИИ на микроконтроллерах

18.02.2021 18:15:13 | Автор: admin


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

Хотя учебные пособия по ML с использованием TensorFlow и MNIST стали привычными, до недавнего времени они обычно демонстрировались в полнофункциональных средах обработки с архитектурой x86 и графическими процессорами класса рабочих станций. Однако сегодня можно создать полнофункциональное приложение для распознавания рукописного ввода MNIST даже на 8-разрядном микроконтроллере. Чтобы продемонстрировать это, мы собираемся создать полнофункциональное приложение для распознавания рукописного ввода MNIST, используя TensorFlow Lite для получения результатов ИИ на маломощном микроконтроллере STMicroelectronics на базе процессора ARM Cortex M7.



В этой статье предполагается, что вы знакомы с C/C++ и машинным обучением, но не волнуйтесь, если это не так. Вы всё равно можете изучить предлагаемый пример и попробовать развернуть проект на собственном устройстве дома! Подобная реализация демонстрирует возможность создания надёжных приложений на основе машинного обучения даже на автономных устройствах с батарейным питанием в самых разных сценариях для Интернета вещей и портативных устройств.



Для создания этого проекта потребуется несколько компонентов:


Код этого проекта можно найти на GitHub.

Краткий обзор


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

  1. Обучить прогнозирующую модель на основе набора данных (рукописные цифры MNIST).
  2. Преобразовать модель в формат TensorFlow Lite.
  3. Создать встроенное приложение.
  4. Создать образцы данных.
  5. Развернуть и протестировать приложение.

Чтобы ускорить и упростить этот процесс, я создал записную книжку Jupyter в Google Colab, чтобы сделать первые два шага за вас из вашего браузера, не устанавливая и не настраивая Python на вашем компьютере. Она также может служить справочным материалом для других проектов, поскольку содержит весь код, необходимый для обучения и оценки модели MNIST с помощью TensorFlow, а также для преобразования модели в целях автономного использования в TensorFlow Lite для микроконтроллеров и создания версии кода массива Си модели для простой компиляции в любую программу на C++.

Чтобы перейти к встроенному приложению на шаге 3, сначала в меню записной книжки нажмите Runtime Run All (Время выполнения > Выполнить всё), чтобы создать файл model.h. Загрузите его из списка файлов на левой стороне. Также можно загрузить предварительно созданную модель из репозитория GitHub, чтобы включить её в проект.

Чтобы выполнить эти действия локально на своём компьютере, убедитесь, что используете платформу TensorFlow версии 2.0 или более поздней и дистрибутив Anaconda для установки и использования Python. Если вы используете упомянутую ранее записную книжку Jupyter, о которой говорилось выше, вам не придётся беспокоиться об установке TensorFlow 2.0, так как эта версия входит в состав этой записной книжки.

Обучение модели TensorFlow с использованием MNIST


Keras это высокоуровневая библиотека Python для нейронных сетей, часто используемая для создания прототипов ИИ-решений. Она интегрирована с TensorFlow, а также содержит встроенный набор данных MNIST из 60 000 изображений и 10 000 тестовых образцов, доступных прямо в TensorFlow.

Чтобы прогнозировать рукописные цифры, этот набор данных использовался для обучения относительно простой модели, в которой изображение 2828 принимается в качестве входной формы и выводятся до 10 категорий результатов с помощью функции активации Softmax с одним скрытым слоем между входным и выходным слоями. Этого было достаточно для достижения точности 96,6 %, но при желании можно добавить больше скрытых слоёв или тензоров.

За более глубоким обсуждением работы с набором данных MNIST в TensorFlow я рекомендую обратиться к некоторым (из многих) замечательным учебным пособиям по TensorFlow в Интернете, таким как Not another MNIST tutorial with TensorFlow, автор О'Рейли (O'Reilly). Вы также можете обратиться к примеру синусоидальной модели TensorFlow в этой записной книжке, чтобы ознакомиться с обучением и оценкой моделей TensorFlow и преобразованием модели в формат TensorFlow Lite для микроконтроллеров.



Преобразование модели в формат TensorFlow Lite


Созданная на первом шаге модель полезна и очень точна, но размер файла и использование памяти делают её недоступной для переноса или использования на встроенном устройстве. Именно здесь на помощь приходит TensorFlow Lite, так как данная среда выполнения оптимизирована для мобильных, встроенных устройств и устройств Интернета вещей и обеспечивает низкую задержку при очень небольших требованиях к размеру (всего несколько килобайт!). Это позволяет найти компромисс между точностью, скоростью и размером и выбрать модель в соответствии со своими потребностями.

В этом случае платформа TensorFlow Lite нужна, чтобы приложение занимало как можно меньше места во флеш-памяти и ОЗУ, оставаясь при этом быстрым, чтобы можно было немного понизить точность, не жертвуя слишком многим.

Чтобы ещё больше уменьшить размер, преобразователь TensorFlow Lite поддерживает дискретизацию модели, чтобы перейти в вычислениях от 32-разрядных значений с плавающей запятой к 8-разрядным целым числам, так как часто высокая точность значений с плавающей запятой не требуется. В результате также значительно уменьшается размер модели и повышается производительность.

Мне не удалось получить дискретизированную модель для правильного и согласованного использования функции Softmax. На моём устройстве STM32F7 Discovery возникает ошибка не удалось вызвать. Преобразователь TensorFlow Lite постоянно развивается, и некоторые конструкции моделей ещё не поддерживаются. Например, этот инструмент преобразует некоторые веса в значения типа int8 вместо uint8, а тип int8 не поддерживается. По крайней мере пока.

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

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

Я добавил скрипт Python в конец своей записной книжки, чтобы обработать эту часть и превратить её в файл model.h. При желании в Linux с помощью команды оболочки xxd -i созданный tflite-файл также можно преобразовать в массив Си. Загрузите этот файл из меню слева и приготовьтесь добавить его в проект встроенного приложения на следующем шаге.

import binasciidef convert_to_c_array(bytes) -> str:  hexstr = binascii.hexlify(bytes).decode("UTF-8")  hexstr = hexstr.upper()  array = ["0x" + hexstr[i:i + 2] for i in range(0, len(hexstr), 2)]  array = [array[i:i+10] for i in range(0, len(array), 10)]  return ",\n  ".join([", ".join(e) for e in array])tflite_binary = open("model.tflite", 'rb').read()ascii_bytes = convert_to_c_array(tflite_binary)c_file = "const unsigned char tf_model[] = {\n  " + ascii_bytes +   "\n};\nunsigned int tf_model_len = " + str(len(tflite_binary)) + ";"# print(c_file)open("model.h", "w").write(c_file)

Создание встроенного приложения


Теперь мы готовы взять нашу обученную модель MNIST и реализовать её на реальном маломощном микроконтроллере. Ваши конкретные действия могут зависеть от используемого набора инструментов, но с моими интегрированной средой разработки PlatformIO и устройством STM32F746G Discovery мною были предприняты следующие действия.

Сначала создан новый проект приложения с настройками для соответствующего устройства на базе ARM Cortex-M и подготовлены основные функции setup и loop. Я выбрал структуру Stm32Cube, чтобы выводить результаты на экран. Если вы используете Stm32Cube, вы можете загрузить файлы stm32_app.h и stm32_app.c из репозитория и создать файл main.cpp с функциями setup и loop, например, как здесь:

#include "stm32_app.h"void setup() {}void loop() {}



Добавьте или загрузите библиотеку TensorFlow Lite Micro. Я предварительно настроил библиотеку для интегрированной среды разработки PlateformIO, чтобы вы могли загрузить папку tfmicro отсюда в папку lib проекта и добавить её в качестве зависимости библиотеки в файл platformio.ini:

[env:disco_f746ng]platform = ststm32board = disco_f746ngframework = stm32cubelib_deps = tfmicro

В верхней части своего кода укажите заголовки библиотек TensorFlowLite, например, как здесь:

#include "stm32_app.h"#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h"#include "tensorflow/lite/experimental/micro/micro_error_reporter.h"#include "tensorflow/lite/experimental/micro/micro_interpreter.h"#include "tensorflow/lite/schema/schema_generated.h"#include "tensorflow/lite/version.h"void setup() {}void loop() {}

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

#include "model.h"



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

// Globalsconst tflite::Model* model = nullptr;tflite::MicroInterpreter* interpreter = nullptr;tflite::ErrorReporter* reporter = nullptr;TfLiteTensor* input = nullptr;TfLiteTensor* output = nullptr;constexpr int kTensorArenaSize = 5000; // Just pick a big enough numberuint8_t tensor_arena[ kTensorArenaSize ] = { 0 };float* input_buffer = nullptr;

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

void setup() {  // Load Model  static tflite::MicroErrorReporter error_reporter;  reporter = &error_reporter;  reporter->Report( "Let's use AI to recognize some numbers!" );  model = tflite::GetModel( tf_model );  if( model->version() != TFLITE_SCHEMA_VERSION ) {reporter->Report(   "Model is schema version: %d\nSupported schema version is: %d",   model->version(), TFLITE_SCHEMA_VERSION );return;  }   // Set up our TF runner  static tflite::ops::micro::AllOpsResolver resolver;  static tflite::MicroInterpreter static_interpreter(  model, resolver, tensor_arena, kTensorArenaSize, reporter );  interpreter = &static_interpreter;   // Allocate memory from the tensor_arena for the model's tensors.  TfLiteStatus allocate_status = interpreter->AllocateTensors();  if( allocate_status != kTfLiteOk ) {reporter->Report( "AllocateTensors() failed" );return;  }  // Obtain pointers to the model's input and output tensors.  input = interpreter->input(0);  output = interpreter->output(0);  // Save the input buffer to put our MNIST images into  input_buffer = input->data.f;}

Подготовьте TensorFlow к выполнению на устройстве ARM Cortex-M при каждом вызове функции loop с короткой задержкой (одна секунда) между обновлениями, например, как здесь:

void loop() {  // Run our model  TfLiteStatus invoke_status = interpreter->Invoke();  if( invoke_status != kTfLiteOk ) {reporter->Report( "Invoke failed" );return;  }   float* result = output->data.f;  char resultText[ 256 ];  sprintf( resultText, "It looks like the number: %d", std::distance( result, std::max_element( result, result + 10 ) ) );  draw_text( resultText, 0xFF0000FF );  // Wait 1-sec til before running again  delay( 1000 );}

Приложение готово к работе. Оно просто ждёт, когда мы скормим ему несколько забавных тестовых изображений MNIST для обработки!

Создание образца данных MNIST для встраивания


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

Чтобы добавить эти изображения в программу независимо от внешнего хранилища, мы можем заранее преобразовать 100 изображений MNIST из формата JPEG в чёрно-белые изображения, сохранённые в виде массивов, так же как и наша модель TensorFlow. Для этого я использовал веб-инструмент с открытым исходным кодом под названием image2cpp, который выполняет большую часть этой работы за нас в одном пакете. Если вы хотите сгенерировать их самостоятельно, проанализируйте пиксели и закодируйте по восемь в каждый байт и запишите их в формате массива Си, как показано ниже.

ПРИМЕЧАНИЕ. Веб-инструмент генерирует код для интегрированной среды разработки Arduino, поэтому в коде найдите и удалите все экземпляры PROGMEM, а затем компилируйте код в среде PlatformIO.

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



// 'mnist_0_1', 28x28pxconst unsigned char mnist_1 [] PROGMEM = {  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00,  0x00, 0x3f, 0xe0, 0x00, 0x00, 0x7f, 0xf0, 0x00, 0x00, 0x7e, 0x30, 0x00, 0x00, 0xfc, 0x38, 0x00,  0x00, 0xf0, 0x1c, 0x00, 0x00, 0xe0, 0x1c, 0x00, 0x00, 0xc0, 0x1e, 0x00, 0x00, 0xc0, 0x1c, 0x00,  0x01, 0xc0, 0x3c, 0x00, 0x01, 0xc0, 0xf8, 0x00, 0x01, 0xc1, 0xf8, 0x00, 0x01, 0xcf, 0xf0, 0x00,  0x00, 0xff, 0xf0, 0x00, 0x00, 0xff, 0xc0, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

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

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

const unsigned char* test_images[] = {  mnist_1, mnist_2, mnist_3, mnist_4, mnist_5,   mnist_6, mnist_7, mnist_8, mnist_9, mnist_10,  mnist_11, mnist_12, mnist_13, mnist_14, mnist_15,   mnist_16, mnist_17, mnist_18, mnist_19, mnist_20,  mnist_21, mnist_22, mnist_23, mnist_24, mnist_25,   mnist_26, mnist_27, mnist_28, mnist_29, mnist_30,  mnist_31, mnist_32, mnist_33, mnist_34, mnist_35,   mnist_36, mnist_37, mnist_38, mnist_39, mnist_40,  mnist_41, mnist_42, mnist_43, mnist_44, mnist_45,   mnist_46, mnist_47, mnist_48, mnist_49, mnist_50,  mnist_51, mnist_52, mnist_53, mnist_54, mnist_55,   mnist_56, mnist_57, mnist_58, mnist_59, mnist_60,  mnist_61, mnist_62, mnist_63, mnist_64, mnist_65,   mnist_66, mnist_67, mnist_68, mnist_69, mnist_70,  mnist_71, mnist_72, mnist_73, mnist_74, mnist_75,   mnist_76, mnist_77, mnist_78, mnist_79, mnist_80,  mnist_81, mnist_82, mnist_83, mnist_84, mnist_85,   mnist_86, mnist_87, mnist_88, mnist_89, mnist_90,  mnist_91, mnist_92, mnist_93, mnist_94, mnist_95,   mnist_96, mnist_97, mnist_98, mnist_99, mnist_100,};

Не забудьте включить в верхнюю часть кода заголовок нового изображения:

#include "mnist.h"

Тестирование изображений MNIST


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

void bitmap_to_float_array( float* dest, const unsigned char* bitmap ) { // Populate input_vec with the monochrome 1bpp bitmap  int pixel = 0;  for( int y = 0; y < 28; y++ ) {for( int x = 0; x < 28; x++ ) {  int B = x / 8; // the Byte # of the row  int b = x % 8; // the Bit # of the Byte  dest[ pixel ] = ( bitmap[ y * 4 + B ] >> ( 7 - b ) ) & 0x1 ? 1.0f : 0.0f;  pixel++;}  }}void draw_input_buffer() {  clear_display();  for( int y = 0; y < 28; y++ ) {for( int x = 0; x < 28; x++ ) {  draw_pixel( x + 16, y + 3, input_buffer[ y * 28 + x ] > 0 ? 0xFFFFFFFF : 0xFF000000 );}  }}

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

void loop() {  // Pick a random test image for input  const int num_test_images = ( sizeof( test_images ) / sizeof( *test_images ) );  bitmap_to_float_array( input_buffer,  test_images[ rand() % num_test_images ] );  draw_input_buffer();   // Run our model  ...}

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




Что дальше?


Теперь, когда вы узнали о возможностях маломощных микроконтроллеров ARM Cortex-M, позволяющих использовать возможности глубокого обучения с помощью TensorFlow, вы готовы сделать гораздо больше! От обнаружения животных и предметов различных типов до обучения устройства понимать речь или отвечать на вопросы вы со своим устройством можете открыть новые горизонты, которые ранее считались возможными только при использовании мощных компьютеров и устройств.

На GitHub доступны несколько потрясающих примеров TensorFlow Lite для микроконтроллеров, разработанных командой TensorFlow. Ознакомьтесь с этимирекомендациями, чтобы убедиться, что вы максимально эффективно используете свой проект ИИ, работающий на устройстве Arm Cortex-M. А если хотите прокачать себя в Machine Learning, Data Science или поднять уровень уже имеющихся знаний приходите учиться, будет сложно, но интересно.



image
Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодом HABR:

Подробнее..

Перевод Как классифицировать мусор с помощью Raspberry Pi и машинного обучения Arm NN

18.02.2021 20:14:00 | Автор: admin


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

Производительность таких решений сильно зависит от пропускной способности сети и задержки. Кроме того, отправка данных внешнему сервису может привести к проблемам с конфиденциальностью. В этой статье демонстрируется возможность переноса ИИ из облачной среды на периферию. Чтобы продемонстрировать ML с использованием периферийных ресурсов, мы будем использовать API-интерфейсы Arm NN для классификации изображений мусора с веб-камеры, подключённой к компьютеру Raspberry Pi, который покажет результаты классификации.



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

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



Что нам нужно для создания устройства и приложения?



  • Устройство Raspberry Pi. Урок проверен на устройствах Pi 2, Pi 3 и Pi 4 модели B.
  • Карту MicroSD.
  • Модуль камеры USB или MIPI для Raspberry Pi.
  • Чтобы создать собственную библиотеку Arm NN, также потребуется хост-компьютер Linux или компьютер с установленной виртуальной средой Linux.
  • Стекло, бумагу, картон, пластик, металл или любой другой мусор, который Raspberry Pi поможет вам классифицировать.



Конфигурация устройства


Я использовал Raspberry Pi 4 модель B с четырёхъядерным процессором ARM Cortex A72, встроенной памятью объёмом 1 ГБ, картой MicroSD объёмом 8 ГБ, аппаратным ключом WiFi и USB-камерой (Microsoft HD-3000).

Перед включением устройства необходимо установить ОС Raspbian на карту MicroSD, как описано в руководстве по настройке Raspberry Pi. Чтобы облегчить установку, я использовал установщик NOOBS.

Затем я загрузил Raspberry Pi, сконфигурировал Raspbian и настроил систему удалённого доступа к рабочему столу VNC для удалённого доступа к моему компьютеру Raspberry Pi. Подробные инструкции по настройке VNC можно найти на странице VNC (Virtual Network Computing) сайта RaspberryPi.org.

После настройки оборудования я занялся программным обеспечением. Данное решение состоит из трёх компонентов:
  • camera.hpp реализует вспомогательные методы захвата изображений с веб-камеры;
  • ml.hpp содержит методы загрузки модели машинного обучения и классификации изображений мусора на основе входных данных с камеры;
  • main.cpp содержит метод main, который объединяет указанные выше компоненты. Это точка входа в приложение.

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

Настройка камеры


Для получения изображений с веб-камеры я использовал библиотеку OpenCV с открытым исходным кодом для компьютерного зрения. Эта библиотека предоставляет удобный интерфейс для захвата и обработки изображений. Один и тот же API-интерфейс легко использовать для различных приложений и устройств, от Интернета вещей до мобильных устройств и настольных ПК.
Самый простой способ включить OpenCV в свои приложения Raspbian для Интернета вещей установить пакет libopencv-dev с помощью программы apt-get:

sudo apt-get updatesudo apt-get upgradesudo apt-get install libopencv-dev

После загрузки и установки пакетов можно приступать к захвату изображений с веб-камеры. Я начал с реализации двух методов: grabFrame и showFrame (см. camera.hpp в сопутствующем коде):

Mat grabFrame(){  // Open default camera  VideoCapture cap(0);  // If camera was open, get the frame  Mat frame;  if (cap.isOpened())  {cap >> frame;imwrite("image.jpg", frame);   }  else  {printf("No valid camera\n");  }  return frame;} void showFrame(Mat frame){  if (!frame.empty())  {imshow("Image", frame);waitKey(0);  }}

Первый метод, grabFrame, открывает веб-камеру по умолчанию (индекс 0) и захватывает один кадр. Обратите внимание, что интерфейс C++ OpenCV для представления изображений использует класс Mat, поэтому grabFrame возвращает объекты этого типа. Доступ к необработанным данным изображения можно получить, считав элемент данных класса Mat.

Второй метод, showFrame, используется для отображения захваченного изображения. С этой целью в showFrame для создания окна, в котором отображается изображение, используется метод imshow из библиотеки OpenCV. Затем вызывается метод waitKey, чтобы окно изображения отображалось до тех пор, пока пользователь не нажмёт клавишу на клавиатуре. Время ожидания было указано с помощью параметра waitKey. Здесь использовалось бесконечное время ожидания, представленное значением 0.

Для тестирования указанных выше методов я вызвал их в методе main (main.cpp):

int main(){  // Grab image  Mat frame = grabFrame();   // Display image  showFrame(frame);   return 0;}

Для создания приложения я использовал команду g++ и связал библиотеки OpenCV посредством pkg-config:

g++ main.cpp -o trashClassifier 'pkg-config --cflags --libs opencv'

После этого я запустил приложение, чтобы захватить одно изображение:



Набор данных о мусоре и обучение модели


Модель классификации TensorFlow была обучена на основе набора данных, созданного Гэри Тунгом (Gary Thung) и доступного в его репозитории Github trashnet.

При обучении модели я следовал процедуре из учебного руководства по классификации изображений TensorFlow. Однако я обучал модель на изображениях размером 256192 пикселя это половина ширины и высоты исходных изображений из набора данных о мусоре. Вот из чего состоит наша модель:

# Building the modelmodel = Sequential()# 3 convolutional layersmodel.add(Conv2D(32, (3, 3), input_shape = (IMG_HEIGHT, IMG_WIDTH, 3)))model.add(Activation("relu"))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Conv2D(64, (3, 3)))model.add(Activation("relu"))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Conv2D(64, (3, 3)))model.add(Activation("relu"))model.add(MaxPooling2D(pool_size=(2,2)))model.add(Dropout(0.25))# 2 hidden layersmodel.add(Flatten())model.add(Dense(128))model.add(Activation("relu"))model.add(Dense(128))model.add(Activation("relu"))# The output layer with 6 neurons, for 6 classesmodel.add(Dense(6))model.add(Activation("softmax"))

Модель достигла точности около 83 %. С помощью преобразователя tf.lite.TFLiteConverter мы преобразовали её в формат TensorFlow Lite trash_model.tflite.

converter = tf.lite.TFLiteConverter.from_keras_model_file('model.h5') model = converter.convert()file = open('model.tflite' , 'wb') file.write(model)

Настройка пакета средств разработки Arm NN


Следующий шаг подготовка пакета средств разработки (SDK) Arm NN. При создании библиотеки Arm NN для Raspberry Pi можно последовать учебному руководству Cross-compile Arm NN and Tensorflow for the Raspberry Pi компании Arm или выполнить автоматический сценарий из репозитория Github Tool-Solutions компании Arm для кросс-компиляции пакета средств разработки. Двоичный tar-файл Arm NN 19.08 для Raspberry Pi можно найти на GitHub.

Независимо от выбранного подхода скопируйте полученный tar-файл (armnn-dist) в Raspberry Pi. В этом случае я использую VNC для передачи файлов между моим ПК для разработки и Raspberry Pi.

Затем задайте переменную среды LD_LIBRARY_PATH. Она должна указывать на подпапку armnn/lib в armnn-dist:

export LD_LIBRARY_PATH=/home/pi/armnn-dist/armnn/lib

Здесь я предполагаю, что armnn-dist находится в папке home/pi.

Использование Arm NN для получения логических выводов на основе машинного обучения на устройстве


Загрузка меток вывода модели


Для интерпретации выходных данных модели необходимо использовать метки вывода модели. В нашем коде мы создаём строковый вектор для хранения 6 классов.

const std::vector<std::string> modelOutputLabels = {"cardboard", "glass", "metal", "paper", "plastic", "trash"};

Загрузка и предварительная обработка входного изображения


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

На входе нашей модели находится слой Conversion 2D (преобразование 2D) с идентификатором conv2d_input. Её выход слой активации функции Softmax с идентификатором activation_5/Softmax. Свойства модели извлекаются с помощью Tensorboard, инструмента визуализации, предоставленного в TensorFlow для проверки модели.

const std::string inputName = "conv2d_input";const std::string outputName = "activation_5/Softmax";const unsigned int inputTensorWidth = 256;const unsigned int inputTensorHeight = 192;const unsigned int inputTensorBatchSize = 32;const armnn::DataLayout inputTensorDataLayout = armnn::DataLayout::NHWC;

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

// Load and preprocess input imageconst std::vector<TContainer> inputDataContainers ={ PrepareImageTensor<uint8_t>("image.jpg" , inputTensorWidth, inputTensorHeight, normParams, inputTensorBatchSize, inputTensorDataLayout) } ;

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

Создание синтаксического анализатора и загрузка сети


Следующий шаг при работе с Armn NN создание объекта синтаксического анализатора, который будет использоваться для загрузки файла сети. В Arm NN есть синтаксические анализаторы для файлов моделей различных типов, включая TFLite, ONNX, Caffe и т. д. Синтаксические анализаторы обрабатывают создание базового графа Arm NN, поэтому вам не нужно создавать граф модели вручную.

В этом примере используется модель TFLite, поэтому мы создаём синтаксический анализатор TfLite для загрузки модели, используя указанный путь.

Наиболее важный метод в ml.hpp это loadModelAndPredict. Сначала он создаёт синтаксический анализатор модели TensorFlow:

// Import the TensorFlow model. // Note: use CreateNetworkFromBinaryFile for .tflite files.armnnTfLiteParser::ITfLiteParserPtr parser =   armnnTfLiteParser::ITfLiteParser::Create();armnn::INetworkPtr network =   parser->CreateNetworkFromBinaryFile("trash_model.tflite");

Затем вызывается метод armnnTfLiteParser::ITfLiteParser::Create, синтаксический анализатор используется для загрузки файла trash_model.tflite.

После анализа модели создаются привязки к слоям с помощью метода GetNetworkInputBindingInfo/GetNetworkOutputBindingInfo:

// Find the binding points for the input and output nodesconst size_t subgraphId = 0;armnnTfParser::BindingPointInfo inputBindingInfo = parser->GetNetworkInputBindingInfo(subgraphId, inputName);armnnTfParser::BindingPointInfo outputBindingInfo = parser->GetNetworkOutputBindingInfo(subgraphId, outputName);

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

// Output tensor size is equal to the number of model output labelsconst unsigned int outputNumElements = modelOutputLabels.size();std::vector<TContainer> outputDataContainers = { std::vector<uint8_t>(outputNumElements)};

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


Необходимо оптимизировать сеть и загрузить её на вычислительное устройство. Пакет средств разработки Arm NN поддерживает внутренние интерфейсы оптимизированного выполнения на центральных процессорах Arm, графических процессорах Mali и устройствах DSP. Внутренние интерфейсы идентифицируются строкой, которая должна быть уникальной для всех выходных интерфейсов. Можно указать один или несколько внутренних интерфейсов в порядке предпочтения.

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

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

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

// Optimize the network for a specific runtime compute // device, e.g. CpuAcc, GpuAccarmnn::IRuntime::CreationOptions options;armnn::IRuntimePtr runtime = armnn::IRuntime::Create(options);armnn::IOptimizedNetworkPtr optNet = armnn::Optimize(*network,   {armnn::Compute::CpuAcc, armnn::Compute::CpuRef},     runtime->GetDeviceSpec());

Механизм логического вывода в пакете Arm NN SDK предоставляет мост между существующими платформами нейронных сетей и центральными процессорами Arm Cortex-A, графическими процессорами Arm Mali и устройствами DSP. При получении логических выводов на основе машинного обучения с помощью Arm NN SDK алгоритмы машинного обучения оптимизируются для используемого оборудования.

После оптимизации сеть загружается в среду выполнения:

// Load the optimized network onto the runtime devicearmnn::NetworkId networkIdentifier;runtime->LoadNetwork(networkIdentifier, std::move(optNet));

Затем выполните прогнозы с помощью метода EnqueueWorkload:

// Predictarmnn::Status ret = runtime->EnqueueWorkload(networkIdentifier,  armnnUtils::MakeInputTensors(inputBindings, inputDataContainers),  armnnUtils::MakeOutputTensors(outputBindings, outputDataContainers));

На последнем шаге получаем результат прогнозирования.

std::vector<uint8_t> output = boost::get<std::vector<uint8_t>>(outputDataContainers[0]);size_t labelInd = std::distance(output.begin(), std::max_element(output.begin(), output.end()));std::cout << "Prediction: ";std::cout << modelOutputLabels[labelInd] << std::endl;

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

Объединение всех компонентов и создание приложения


Наконец, я собрал все компоненты вместе в методе main (main.cpp):

#include "camera.hpp"#include "ml.hpp" int main(){  // Grab frame from the camera  grabFrame(true);   // Load ML model and predict  loadModelAndPredict();   return 0;}

Обратите внимание, что у метода grabFrame есть дополнительный параметр. Если этот параметр имеет значение true, изображение камеры преобразуется в оттенки серого с изменением размеров до 256192 пикселей в соответствии с входным форматом модели машинного обучения, а затем преобразованное изображение передаётся методу loadModelAndPredict.
Для создания приложения требуется использовать команду g++ и связать библиотеку OpenCV и пакет Arm NN SDK:

g++ main.cpp -o trashClassifier 'pkg-config --cflags --libs opencv' -I/home/pi/armnn-dist/armnn/include -I/home/pi/armnn-dist/boost/include -L/home/pi/armnn-dist/armnn/lib -larmnn -lpthread -linferenceTest -lboost_system -lboost_filesystem -lboost_program_options -larmnnTfLiteParser -lprotobuf

Опять же, я предполагаю, что пакет Arm NN SDK находится в папке home/pi/armnn-dist. Запустите приложение и сделайте снимок какого-нибудь картона.

pi@raspberrypi:~/ $ ./trashClassifierArmNN v20190800Running network...Prediction: cardboard

Если во время выполнения приложения отображается сообщение error while loading shared libraries: libarmnn.so: cannot open shared object file: No such file or directory (ошибка при загрузке общих библиотек: libarmnn.so, не удаётся открыть общий объектный файл: нет такого файла или каталога), убедитесь, что ваша переменная среды LD_LIBRARY_PATH задана правильно.

Данное приложение также можно улучшить, реализовав запуск захвата и распознавания изображений по внешнему сигналу. Для этого требуется изменить метод loadAndPredict в модуле ml.hpp и отделить загрузку модели от прогнозирования (логического вывода). А если хотите прокачать себя в Data Science, Machine Learning или поднять уровень уже имеющихся знаний приходите учиться, будет сложно, но интересно.

image
Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодом HABR, который даст еще +10% скидки на обучение:

Подробнее..

Перевод Как за 60 создать систему распознавания лиц с помощью Python и Nvidia Jetson Nano 2GB

02.03.2021 16:05:07 | Автор: admin
Теперь с помощью алгоритмов распознавания лиц и Python вы сможете с лёгкостью отслеживать всех людей, которые приближаются к вашей двери.Теперь с помощью алгоритмов распознавания лиц и Python вы сможете с лёгкостью отслеживать всех людей, которые приближаются к вашей двери.

Новый набор инструментов для разработчиков Nvidia Jetson Nano 2GB представляет собой одноплатный компьютер с графическим ускорителем стоимостью 59$, работающий под управлением программного обеспечения с искусственным интеллектом.

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


Что такое Nvidia Jetson Nano 2GB?

Jetson Nano 2GB это одноплатный компьютер с четырёхъядерным ARM-процессором 1,4 ГГц и встроенным графическим процессором Nvidia Maxwell. Это самая дешёвая модель Nvidia Jetson практически того же класса, что и одноплатный компьютер Raspberry Pi.

Компьютер Nvidia Jetson Nano 2GB имеет много общего с Raspberry Pi оба представляют собой одноплатные компьютеры на ОС Linux. Отличие состоит в том, что с целью ускорения работы моделей глубокого обучения модуль Nvidia Jetson оснащён 128-ядерным графическим процессором Nvidia и поддерживает программно-аппаратную архитектуру параллельных вычислений (CUDA).Компьютер Nvidia Jetson Nano 2GB имеет много общего с Raspberry Pi оба представляют собой одноплатные компьютеры на ОС Linux. Отличие состоит в том, что с целью ускорения работы моделей глубокого обучения модуль Nvidia Jetson оснащён 128-ядерным графическим процессором Nvidia и поддерживает программно-аппаратную архитектуру параллельных вычислений (CUDA).

Если вы уже знакомы с линейкой продуктов Raspberry Pi, то вам всё должно быть понятно. Единственным различием двух одноплатных компьютеров является то, что в Jetson Nano установлен графический процессор Nvidia. Компьютер может запускать приложения с ускорением вычислений на графических процессорах (например, модели глубокого обучения) намного быстрее, чем одноплатный Raspberry Pi, у которого нет графического процессора, способного работать с большинством технологий глубокого обучения.

Наборов инструментов для разработчиков с ИИ и модулями ускорителя существует множество, однако у продукта Nvidia есть перед ними одно важное преимущество он непосредственно совместим с библиотеками ИИ для настольных систем и не требует преобразования моделей глубокого обучения ни в какие специальные форматы для их запуска. Для ускорения вычислений на графическом процессоре в продукте Nvidia используются те же самые библиотеки CUDA, что и практически во всех базирующихся на Python системах глубокого обучения. Другими словами, вы можете взять существующую программу глубокого изучения на Python и запустить её на Jetson Nano 2GB (лишь немного её изменив) и в результате получить довольно сносную производительность (при условии, что ваше приложение сможет работать на 2 Гб оперативной памяти). Возможность взять код в Python, который был написан для мощнейшего сервера, и практически без изменений перенести его на автономное устройство стоимостью 59 долларов это ли не заманчиво?

В новом наборе инструментов Jetson Nano 2GB реализованы некоторые новые функции, которых не было в предыдущих версиях оборудования Nvidia. В первой модели Jetson Nano по непонятной причине не была реализована поддержка WiFi, данная же модель поставляется с подключаемым модулем WiFi, поэтому вам не придётся возиться с ethernet-кабелями. В новом модуле также был заменён вход по питанию установлен более современный порт USB-C, а также усовершенствованы некоторые возможности программного обеспечения. Теперь вам не нужно беспокоиться, например, о включении файла подкачки, модуль сделает это за вас.

Приобретение простого в использовании аппаратного устройства с настоящим графическим процессором обойдется вам менее чем в 60$ этот шаг со стороны Nvidia следует признать достаточно агрессивным. По всей видимости, этим они желают показать, что хотят составить прямую конкуренцию Raspberry Pi и пытаются захватить рынок образования / продуктов для программистов-любителей. Будет весьма интересно наблюдать, как на это отреагирует рынок.

Что ж, давайте приступим к сборке нашей системы

Как и при сборке любых аппаратных устройств, вначале нужно подобрать все необходимые детали:

1. Одноплатный компьютер Nvidia Jetson Nano 2GB (59$).

Продукт доступен для предварительного заказа (по состоянию на 5 октября 2020 г.). Сам продукт, как ожидается, будет выпущен в конце октября. Я не знаю, какую цену установит Nvidia на продукт после его релиза, но следует иметь в виду, что предыдущая модель Jetson Nano в течение нескольких месяцев после релиза была в дефиците.

Полная открытость: модуль Jetson Nano 2GB мне достался бесплатно за рецензию на продукты Nvidia, однако с Nvidia у меня нет ни финансовых, ни редакционных отношений. Вот как получилось, что мне удалось составить данную инструкцию до выхода продукта.

2. Адаптер питания с разъёмом USB-C (возможно, такой у вас уже есть?).

В новом компьютере Jetson Nano 2GB для питания используется разъём USB-C. Адаптер питания в комплект не входит, но у вас наверняка завалялся где-то лишний.

3. Камера: либо веб-камера с USB-выходом (возможно, у вас такая уже имеется?), либо камера Raspberry Pi Camera Module v2.x (прибл. 30$).

Если вы захотите поместить небольшую камеру в чехол, отличным выбором станет модуль Raspberry Pi Camera v2.x (примечание: модуль v1.x работать не будет). Приобрести их можно на Amazon или у множества реселлеров.

Некоторые веб-камеры с USB-выходом, например Logitech C270 или C920, прекрасно работают с Jetson Nano 2GB, так что, если у вас уже есть такая камера, можно, не задумываясь, использовать её. Ниже приводится неполный перечень USB-камер, которые должны работать.

Прежде чем торопиться покупать что-то новое, сначала проверьте: а может быть, вполне подойдут уже имеющиеся у вас USB-устройства? Не на всех из них может быть реализована поддержка драйверов Linux, но, возможно, вам удастся найти устройство с соответствующей поддержкой. Я подключил обычный адаптер HDMI-на-USB за 20$ (заказывал на Amazon), и он подошёл идеально. В качестве источника видеосигнала по HDMI мне удалось задействовать свою высококлассную цифровую камеру, причём без каких бы то ни было дополнительных настроек. Красота!

Вам понадобятся ещё несколько других компонентов, но, я полагаю, все они у вас уже есть в наличии:

  • Карта памяти microSD, на ней должно быть не менее 32 ГБ свободного пространства. На неё мы установим Linux. Если у вас имеется любая карта microSD, можете использовать её.

  • Устройство чтения карт microSD, с помощью которого мы установим на компьютер программное обеспечение Jetson.

  • Проводная USB-клавиатура и проводная USB-мышь. Эти устройства будут использоваться для управления Jetson Nano.

  • Любой монитор или телевизионный экран, на которые можно напрямую подавать сигнал HDMI (не через переходник HDMI-на-DVI). Монитор будет использоваться для визуального контроля ваших действий. Монитор вам потребуется для первоначальной настройки Jetson Nano (даже если потом Jetson Nano будет запускаться без монитора).

Загрузка программного обеспечения Jetson Nano 2GB

Перед подключением компонентов к Jetson Nano необходимо вначале загрузить образ программного обеспечения для Jetson Nano. Стандартный образ программного обеспечения Nvidia включает в себя Ubuntu Linux 18.04 с предустановленными Python 3.6 и OpenCV.

Ниже приводится инструкция по загрузке программного обеспечения Jetson Nano на SD-карту:

  1. Скачайте Jetson Nano Developer Kit SD Card Image на сайте Nvidia.

  2. Скачайте Etcher программу, которая запишет образ программного обеспечения Jetson на SD-карту.

  3. Запустите программу Etcher и используйте её для записи загруженного образа Jetson Nano Developer Kit SD Card Image на SD-карту. Это займёт приблизительно 20 минут.

Пора распаковывать остальное оборудование!

Подключаем всё что можно

Прежде всего выньте модуль Jetson Nano 2GB из коробки:

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

Затем к одному из USB-портов необходимо подключить входящий в комплект WiFi-адаптер с разъёмом USB:

После этого подключается камера.

Если используется модуль камеры Raspberry Pi v2.x, его необходимо подсоединить с помощью ленточного соединительного кабеля. Найдите разъём для ленточного соединительного кабеля на Jetson, поднимите крышку разъёма, вставьте в разъём кабель и закройте крышку разъёма. Убедитесь, что металлические контакты на ленточном соединительном кабеле смотрят внутрь радиатора:

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

Теперь подключаем всё остальное:

  • подключите мышь и клавиатуру к USB-портам;

  • подключите монитор с помощью HDMI-кабеля;

  • подключите кабель питания USB-C, чтобы система могла загрузиться.

Если используется модуль камеры Raspberry Pi, в результате у вас получится примерно такая конструкция:

Если используется источник видео с USB, конструкция получится примерно такая:

При подключении кабеля питания начнётся автоматическая загрузка Jetson Nano. Через несколько секунд на мониторе появится экран инициализации Linux. Следуйте инструкциям на экране для создания учётной записи и подключения к WiFi. Все действия будут элементарные.

Установка необходимых библиотек Linux и Python для распознавания лиц

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

На рабочем столе Jetson Nano откройте окно LXTerminal и выполните следующие команды. Если система запросит пароль, нужно будет ввести тот же пароль, который был указан при создании пользовательской учётной записи:

sudo apt-get updatesudo apt-get install python3-pip cmake libopenblas-dev liblapack-dev libjpeg-dev

Вначале обновляем apt стандартный инструмент для установки программного обеспечения Linux, его мы будем использовать для установки других необходимых нам системных библиотек. Затем устанавливаем несколько Linux-библиотек, без которых наша программа не будет работать (эти библиотеки не предустановлены).

В последнюю очередь устанавливаем библиотеку Python face_recrecognition и её зависимости, в том числе библиотеку машинного обучения dlib. Это можно сделать автоматически, запустив единственную команду:

sudo pip3 -v install Cython face_recognition

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

Когда процесс завершится, ваш компьютер Jetson Nano 2GB с графическим ускорителем и программно-аппаратной архитектурой параллельных вычислений (CUDA) будет готов к распознаванию лиц. Теперь переходим к самому интересному!

Запуск демонстрационной версии приложения распознавания лиц для домофона

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

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

Сначала скачайте код. Полный код с комментариями я разместил здесь, однако вы можете поступить ещё проще загрузить код на Jetson Nano из командной строки:

wget -O doorcam.py tiny.cc/doorcam2gb

В верхней части программы необходимо отредактировать строку кода, чтобы указать, используете ли вы USB-камеру или модуль камеры Raspberry Pi. Файл можно изменить таким образом:

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

gedit doorcam.py

На рабочем столе появится окно с видео. Каждый раз, когда новый человек будет подходить к камере, система будет регистрировать его лицо и рассчитает продолжительность нахождения такого человека перед дверью. Если один и тот же человек уйдет и вернётся через 5 минут или позже, система зарегистрирует новый визит и заново рассчитает продолжительность. Чтобы в любое время выйти из программы, нужно нажать на клавиатуре клавишу 'q'.

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

Превращаем систему в автономное аппаратное устройство

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

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

7-дюймовый сенсорный HDMI-экран, способный запитываться от USB-порта:

И обычный зарядный батарейный блок (power bank) с выходом USB-C для подачи питания:

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

Всё работает идеально. Сенсорный экран работает как обычная USB-мышь без дополнительных настроек. Есть единственный недостаток: если Jetson Nano 2GB станет потреблять больше энергии, чем может выдавать USB-аккумулятор, быстродействие графического процессора может снизиться. Но и в том, и в другом случае результаты получаются вполне удовлетворительные.

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

Обзор кода Python для домофона

Хотите понять, как работает код? Давайте разберёмся.

Сначала в код необходимо импортировать библиотеки, которые мы собираемся использовать. Самые важные из них OpenCV (в Python она называется cv2), которую мы будем использовать для считывания изображений с камеры, и face_recognition, которую мы будем использовать для обнаружения и сравнения лиц.

import face_recognitionimport cv2from datetime import datetime, timedeltaimport numpy as npimport platformimport pickle

После этого нам нужно указать, как будет осуществляться доступ к камере, так как процесс получения изображения с модуля камеры Raspberry Pi работает иначе, чем процесс получения изображения с USB-камеры. Для этого нужно просто изменить эту переменную на True или False, в зависимости от оборудования:

# Set this depending on your camera type:# - True = Raspberry Pi 2.x camera module# - False = USB webcam or other USB video input (like an HDMI capture device)USING_RPI_CAMERA_MODULE = False

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

known_face_encodings = []known_face_metadata = []

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

У нас имеется функция, сохраняющая и загружающая данные об известных лицах. Это функция сохранения (save):

def save_known_faces():    with open("known_faces.dat", "wb") as face_data_file:        face_data = [known_face_encodings, known_face_metadata]        pickle.dump(face_data, face_data_file)        print("Known faces backed up to disk.")

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

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

def register_new_face(face_encoding, face_image):    known_face_encodings.append(face_encoding)known_face_metadata.append({        "first_seen": datetime.now(),        "first_seen_this_interaction": datetime.now(),        "last_seen": datetime.now(),        "seen_count": 1,        "seen_frames": 1,        "face_image": face_image,    })

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

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

def lookup_known_face(face_encoding):    metadata = None    if len(known_face_encodings) == 0:        return metadata    face_distances = face_recognition.face_distance(        known_face_encodings,         face_encoding    )    best_match_index = np.argmin(face_distances)    if face_distances[best_match_index] < 0.65:        metadata = known_face_metadata[best_match_index]        metadata["last_seen"] = datetime.now()        metadata["seen_frames"] += 1        if datetime.now() - metadata["first_seen_this_interaction"]                  > timedelta(minutes=5):            metadata["first_seen_this_interaction"] = datetime.now()            metadata["seen_count"] += 1    return metadata

В этой части кода мы делаем несколько важных вещей:

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

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

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

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

def main_loop():    if USING_RPI_CAMERA_MODULE:        video_capture =             cv2.VideoCapture(                get_jetson_gstreamer_source(),                 cv2.CAP_GSTREAMER            )    else:        video_capture = cv2.VideoCapture(0)

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

Итак, начинаем захватывать кадры с видео:

while True:    # Grab a single frame of video    ret, frame = video_capture.read()    # Resize frame of video to 1/4 size    small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)    # Convert the image from BGR color    rgb_small_frame = small_frame[:, :, ::-1]

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

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

Теперь мы можем обнаружить все лица на изображении и преобразовать каждое лицо в кодированное изображение. Это делается всего двумя строками кода:

face_locations = face_recognition.face_locations(rgb_small_frame)face_encodings = face_recognition.face_encodings(                     rgb_small_frame,                      face_locations                  )

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

for face_location, face_encoding in zip(                       face_locations,                        face_encodings):metadata = lookup_known_face(face_encoding)    if metadata is not None:        time_at_door = datetime.now() -             metadata['first_seen_this_interaction']        face_label = f"At door {int(time_at_door.total_seconds())}s"    else:        face_label = "New visitor!"        # Grab the image of the face        top, right, bottom, left = face_location        face_image = small_frame[top:bottom, left:right]        face_image = cv2.resize(face_image, (150, 150))        # Add the new face to our known face data        register_new_face(face_encoding, face_image)

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

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

for (top, right, bottom, left), face_label in                   zip(face_locations, face_labels):    # Scale back up face location    # since the frame we detected in was 1/4 size    top *= 4    right *= 4    bottom *= 4    left *= 4    # Draw a box around the face    cv2.rectangle(        frame, (left, top), (right, bottom), (0, 0, 255), 2    )    # Draw a label with a description below the face    cv2.rectangle(        frame, (left, bottom - 35), (right, bottom),         (0, 0, 255), cv2.FILLED    )    cv2.putText(        frame, face_label,         (left + 6, bottom - 6),         cv2.FONT_HERSHEY_DUPLEX, 0.8,         (255, 255, 255), 1    )

Я также хотел вывести в верхней части экрана пополняемый список последних посетителей с указанием количества посещений вашего дома:

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

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

number_of_recent_visitors = 0for metadata in known_face_metadata:    # If we have seen this person in the last minute    if datetime.now() - metadata["last_seen"]                          < timedelta(seconds=10):# Draw the known face image        x_position = number_of_recent_visitors * 150frame[30:180, x_position:x_position + 150] =              metadata["face_image"]number_of_recent_visitors += 1        # Label the image with how many times they have visited        visits = metadata['seen_count']        visit_label = f"{visits} visits"if visits == 1:            visit_label = "First visit"cv2.putText(            frame, visit_label,             (x_position + 10, 170),             cv2.FONT_HERSHEY_DUPLEX, 0.6,             (255, 255, 255), 1        )

Наконец, мы можем вынести текущий кадр видео на экран и поверх этого кадра вывести все наши пояснения:

cv2.imshow('Video', frame)

Чтобы не утратить данные в случае программного сбоя, через каждые 100 кадров будем сохранять на диск список известных лиц:

if len(face_locations) > 0 and number_of_frames_since_save > 100:    save_known_faces()    number_of_faces_since_save = 0else:    number_of_faces_since_save += 1

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

Код запуска программы находится в самом низу:

if __name__ == "__main__":    load_known_faces()    main_loop()

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

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

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

Расширение возможностей программы

Данная программа пример того, как с небольшим количеством кода Python 3, запущенного на дешёвом одноплатном компьютере Jetson Nano 2GB, можно создать мощную систему.

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

Также можно попробовать превратить эту программу во что-то совершенно другое. Схема, при которой осуществляется чтение кадра видео, затем выполняется поиск чего-то конкретного на изображении, затем выполняется соответствующее действие, это основа основ всех видов систем машинного зрения. Попробуйте изменить код и подумайте, где ещё его можно применить! Как вам, например, такая идея каждый раз, когда вы приходите домой и открываете входную дверь, вас будет встречать любимая музыка? Ознакомьтесь с некоторыми другими примерами использования библиотеки Python face_recognition и узнайте, как можно реализовать нечто подобное.

Узнайте больше о платформе Nvidia Jetson

Узнайте больше о том, как создавать устройства с помощью аппаратной платформы Nvidia Jetson, запишитесь на новый бесплатный учебный курс Nvidia по Jetson. Более подробная информация приведена на сайте Nvidia. Вы также также можете посетить отличные общественные ресурсы, например сайт JetsonHacks.

Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодомHABR, который даст еще +10% скидки на обучение.

Другие профессии и курсы
Подробнее..

Змейка, мышь и Гамильтон

01.03.2021 02:23:51 | Автор: admin
Добрый времени суток. Давайте научим компьютер играть в змейку.

Собственно, это первая часть статьи о том, как можно попробовать решить эту задачку. Скажем так разминочная, перед основным боем.


С чего всё началось


Дабы быть полностью откровенным, началось все с просмотра видео чувака из Австрали именующего себя "Code Bullet"
Он в нем пытается решить с помощь различных алгоритмов AI простую игру в змейку.
У него толком ничего не получается, и вот почему Текущие алгоритмы, которые предоставляет сейчас сообщество AI решают только две задачи либо Классификации, либо Регрессии (предсказания). А вот змейка ни туда ни туда не вписывается. Ибо идея о том, что съесть мышь это хорошо, а врезаться плохо. Разбивается об хвост, который постоянно растет и растет пока не займет всё поле. Так вот написать полноценный самообучающий AI для такой задачи показалось прикольной идей, но для начала я решил размяться и написать просто алгоритм, который решал бы задачку в лоб, без обучения. Собственно, о таком алгоритме и пойдёт речь.

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

Пишем игру


Перед тем как, играть, напишем саму игру.

Все расчеты будем производить на сервере, отображать змейку в браузере, а инфой будем обмениваться через WebSocket (SignalR).

Скучный код без которого ничего не работает
Отображение максимально простое. У нас есть Store который получает информацию об положении змеи и размере поля и состоянии игры.

    isLife: boolean,    isWin: boolean,    xSize: number,    ySize: number,    mouse: Vector2,    piton: Vector2[]

snake.ts

import { HubConnection, HubConnectionBuilder } from "@microsoft/signalr";import { observable } from "mobx";import { start } from "repl";export enum Cell {    None = "None",    Mouse = "Mouse",    Snake = "Snake"}export interface Vector2 {    x: number,    y: number}interface StateBoard {    isLife: boolean,    isWin: boolean,    xSize: number,    ySize: number,    mouse: Vector2,    piton: Vector2[]    hamiltonPath: Vector2[]}class Snake {    @observable    public state?: StateBoard;    private connection: HubConnection;    constructor() {        this.connection = new HubConnectionBuilder()            .withUrl("http://localhost:5000/snake")            .withAutomaticReconnect()            .build();        this.start();    }    private start = async () => {        await this.connection.start();        this.connection.on("newState", (board: string) => {            var state = JSON.parse(board) as StateBoard;            if (state.isLife) {                this.state = state;            } else {                this.state!.isLife = false;                this.state!.isWin = state.isWin;                if (this.state!.isWin) {                    this.state = state;                }            }        });    }}export const snake = new Snake();

И React компонент, который просто все это дело рисует.

App.tsx

import { snake } from './shores/snake';import { useObserver } from 'mobx-react-lite';import cs from 'classnames';const App = (): JSX.Element => {  const cellRender = (indexRow: number, indexColumn: number): JSX.Element => {    const state = snake.state!;    const isMouse = state.mouse.x == indexColumn && state.mouse.y == indexRow;    if (isMouse) {      return <div key={`${indexRow}_${indexColumn}`} className='cell mouse'></div>;    }    const indexCellSnake = state.piton.findIndex(p => p.x == indexColumn && p.y == indexRow);    if (indexCellSnake == -1) {      return <div key={`${indexRow}_${indexColumn}`} className='cell'></div>;    }    const prewIndexCellSnake = indexCellSnake - 1;    const prewCellPiton = 0 <= prewIndexCellSnake ? state.piton[prewIndexCellSnake] : null;    const cellPiton = state.piton[indexCellSnake];    const nextIndexCellSnake = indexCellSnake + 1;    const nextCellPiton = nextIndexCellSnake < state.piton.length ? state.piton[nextIndexCellSnake] : null;    let up = false, left = false, down = false, rigth = false;    if (!!prewCellPiton) {      if (prewCellPiton.x < cellPiton.x) {        left = true;      }      if (prewCellPiton.y < cellPiton.y) {        up = true;      }      if (cellPiton.x < prewCellPiton.x) {        rigth = true;      }      if (cellPiton.y < prewCellPiton.y) {        down = true;      }    }    if (!!nextCellPiton) {      if (cellPiton.x < nextCellPiton.x) {        rigth = true;      }      if (cellPiton.y < nextCellPiton.y) {        down = true;      }      if (nextCellPiton.x < cellPiton.x) {        left = true;      }      if (nextCellPiton.y < cellPiton.y) {        up = true;      }    }    return <div key={`${indexRow}_${indexColumn}`} className='cell'>      <table className='shake'>        <tbody>          <tr>            <td width="10%" height="10%" />            <td height="10%" className={cs({              'shake-segment': up            })} />            <td width="10%" height="10%" />          </tr>          <tr>            <td width="10%" className={cs({              'shake-segment': left            })} />            <td className='shake-segment' />            <td width="10%" className={cs({              'shake-segment': rigth            })} />          </tr>          <tr>            <td width="10%" height="10%" />            <td height="10%" className={cs({              'shake-segment': down            })} />            <td width="10%" height="10%" />          </tr>        </tbody>      </table>    </div>;  }  const rowRender = (indexRow: number): JSX.Element => {    const state = snake.state!;    const cells: JSX.Element[] = [];    for (let j = 0; j < state.ySize; j++) {      cells.push(cellRender(indexRow, j));    }    return (<>{cells}</>);  }  const tableRender = (): JSX.Element => {    const state = snake.state!;    const rows: JSX.Element[] = [];    for (let i = 0; i < state.ySize; i++) {      rows.push(        (<div key={i.toString()} className="row">          {rowRender(i)}        </div>)      );    }    return (<>{rows}</>);  }  return useObserver(() => {    console.log(snake.state);    if (!snake.state) {      return <div />    }    let state: string = "идет игра";    if (snake.state.isLife == false) {      state = snake.state.isWin ? "Победа" : "Поражение"    }    return (<>      <span className="state">{state}</span>      <div className="table">        {tableRender()}      </div>    </>);  });}export default App;

С беком тоже мудрить не будем:

    public class SnakeSender    {        class Vector2        {            public int X { get; set; }            public int Y { get; set; }            public Vector2(int x, int y)            {                this.X = x;                this.Y = y;            }        }        class Vector2Comparer : IEqualityComparer<Vector2>        {            public bool Equals([AllowNull] Vector2 value1, [AllowNull] Vector2 value2)            {                return value1.X == value2.X && value1.Y == value2.Y;            }            public int GetHashCode([DisallowNull] Vector2 obj)            {                return 0;            }        }        private readonly static Vector2Comparer vector2Comparer = new Vector2Comparer();        [JsonConverter(typeof(StringEnumConverter))]        enum Cell        {            None,            Mouse,            Snake        }        enum Directions        {            Up,            Left,            Down,            Rigth        }        class StateBoard        {            public bool IsLife { get; set; }            public bool IsWin { get; set; }            public int XSize { get; set; }            public int YSize { get; set; }            public Vector2 Mouse { get; set; }            public List<Vector2> Piton { get; set; }            public List<Vector2> HamiltonPath { get; set; }        }        const int xSize = 12, ySize = 12;      ...private void CheckDead()        {            Vector2 head = this.Piton.Last();            if (head.X < 0             || head.Y < 0             || xSize <= head.X             || ySize <= head.Y             || this.Piton.SkipLast(1).Contains(head, vector2Comparer))            {                this.IsLife = false;                this.IsWin = false;                return;            }        } private void Render()        {            var hubContext = (IHubContext<SnakeHub>)this.ServiceProvider.GetService(typeof(IHubContext<SnakeHub>));            var piton = this.Piton.ToList();            piton.Reverse();            hubContext.Clients?.All.SendAsync("newState", JsonConvert.SerializeObject(new StateBoard()            {                IsLife = this.IsLife,                IsWin = this.IsWin,                XSize = xSize,                YSize = ySize,                Mouse = this.Mouse,                Piton = piton,                HamiltonPath = HamiltonPath            }));        }private List<Vector2> GetEmptyCells()        {            List<Vector2> emptyCells = new List<Vector2>(xSize * ySize);            for (int i = 0; i < ySize; i++)            {                for (int j = 0; j < xSize; j++)                {                    if (!this.Piton.Contains(new Vector2(j, i), vector2Comparer))                    {                        emptyCells.Add(new Vector2(j, i));                    }                }            }            return emptyCells;        }}


В начале игры у нас есть одна красная мышь, а одноклеточная змея.

А теперь нам, как-то надо начать играть.

Вообще играть в змейку очень просто нужно просто проходить по один раз по каждой клеточке в матрице. И всё задача решена Happy End.

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

Где-то 4 августа 1805 2 сентября 1865 в Ирландии жил некий Гамильтон Уильям Роуэн, исследовал задачу кругосветного путешествия по додекаэдру. В этой задаче вершины додекаэдра символизировали известные города, такие как Брюссель, Амстердам, Эдинбург, Пекин, Прага, Дели, Франкфурт и др., а рёбра соединяющие их дороги. Путешествующий должен пройти вокруг света, найдя путь, который проходит через все вершины ровно один раз. Чтобы сделать задачу более интересной, порядок прохождения городов устанавливался заранее. А чтобы было легче запомнить, какие города уже соединены, в каждую вершину додекаэдра был вбит гвоздь, и проложенный путь отмечался небольшой верёвкой, которая могла обматываться вокруг гвоздя. Однако такая конструкция оказалась слишком громоздкой, и Гамильтон предложил новый вариант игры, заменив додекаэдр плоским графом, изоморфным графу, построенному на рёбрах додекаэдра.

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

Визуально это можно представить как


В нашем случае так.


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

Суть в том, что общий подход к нахождению Гамильтонов цикла подразумевает полный перебор и ничего более оптимального вроде как нет. А у нас при матрице 12 на 12 только вершин 144 и для каждой нужно проверить от 2, до 4-х ребер. В общем, там где-то сложность n!..

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

Тогда построить Гамильтонов цикл не составляет труда.

private void CreateHamiltonPath()        {            this.HamiltonPath.Clear();            this.HamiltonPath.Add(new Vector2(0, 0));            HamiltonStep(this.HamiltonPath.Last());        }        private bool HamiltonStep(Vector2 current)        {            if (HamiltonPath.Count == HamiltonPath.Capacity)            {                var first = HamiltonPath.First();                return (first.X == current.X && first.Y == current.Y - 1)                    || (first.X == current.X && first.Y == current.Y + 1)                    || (first.X - 1 == current.X && first.Y == current.Y)                    || (first.X + 1 == current.X && first.Y == current.Y);            }            foreach (var direction in new[] { Directions.Down, Directions.Rigth, Directions.Up, Directions.Left })            {                Vector2 newElement = null;                switch (direction)                {                    case Directions.Up:                        newElement = new Vector2(current.X, current.Y - 1);                        break;                    case Directions.Left:                        newElement = new Vector2(current.X - 1, current.Y);                        break;                    case Directions.Down:                        newElement = new Vector2(current.X, current.Y + 1);                        break;                    case Directions.Rigth:                        newElement = new Vector2(current.X + 1, current.Y);                        break;                }                if (0 <= newElement.X && newElement.X < xSize                    && 0 <= newElement.Y && newElement.Y < ySize                    && !HamiltonPath.Contains(newElement, vector2Comparer))                {                    HamiltonPath.Add(newElement);                    if (HamiltonStep(newElement))                    {                        return true;                    }                    HamiltonPath.Remove(newElement);                }            }            return false;        }

И да это идею я заимствовал эту идею у Code Bullet, а он её еще у одного чувака в интеренте.

Короче, как сказал Пабло Пикассо:
Хорошие художники копируют, великие художники воруют



И так, мы получаем змейку которая ходит поэтому циклу до победы:


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

А с точки зрения математики мы получаем сложность в (O)n^n-1

Т.к. каждый раз Каждый. Нам нужно обходить всё по циклу

Проще говоря устанем ждать Так, что решение хоть и хорошее, но есть проблема времени требует много

Оптимизация


Что мы знаем о змее. Её длина изменяется при поедании мыши. И идеальной траекторией движения для нее является Гамельтонов путь. А самое короткое расстояние между двумя точками это прямая.

Начнем с вызова рекурсии:

private void CalculatePath()        {            this.StepsCountAfterCalculatePath = 0;            int finalIndexPoint = this.HamiltonPath.FindIndex(p => p.X == this.Mouse.X && p.Y == this.Mouse.Y);            List<Vector2> tempPath = new List<Vector2>();            List<Vector2> stepPiton = new List<Vector2>(this.Piton);            Debug.WriteLine($"Piton length: {this.Piton.Count}");            int index = 0;            var result = StepTempPath(ref index, GetInvert(stepPiton, this.Mouse), this.Piton.Last(), finalIndexPoint, stepPiton, tempPath);            if (result.PathIsFound)            {                this.TempPath = new Queue<Vector2>(tempPath);                this.InvertHamiltonPath = result.InvertHamiltonPath;            }        }

А вот рекурсивную часть разберем по отдельности.

Основная часть максимально простая.

Мы упорно приближаемся к цели:

if (current.X < finalPoint.X)                {                    newElement = new Vector2(current.X + 1, current.Y);                }                else if (finalPoint.X < current.X)                {                    newElement = new Vector2(current.X - 1, current.Y);                }                else if (current.Y < finalPoint.Y)                {                    newElement = new Vector2(current.X, current.Y + 1);                }                else if (finalPoint.Y < current.Y)                {                    newElement = new Vector2(current.X, current.Y - 1);                }

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

Проверка финального состояния выглядит как-то так для начала.

if (current.X == finalPoint.X && current.Y == finalPoint.Y)            {                var tempPiton = stepPiton.TakeLast(this.Piton.Count).ToList();                for (int i = 1; i < this.Piton.Count; i++)                {                    var hamiltonPoint = (finalIndexPoint + i < this.HamiltonPath.Count) ? this.HamiltonPath[finalIndexPoint + i] : this.HamiltonPath[finalIndexPoint + i - this.HamiltonPath.Count];                    if (tempPiton.TakeLast(this.Piton.Count).Contains(hamiltonPoint, vector2Comparer))                    {                        return false;                    }                    tempPiton.Add(hamiltonPoint);                }                return true;            }

Что мы тут собственно делаем.

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

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

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

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

Изменим проверку.

if (current.X == finalPoint.X && current.Y == finalPoint.Y)            {                if (this.Piton.Count == 1)                {                    return new ResultAnlaizePath(true);                }                foreach (var d in new[] { false, true })                {                    var tempPiton = stepPiton.TakeLast(this.Piton.Count).ToList();                    bool isFound = true;                    bool invertHamiltonPath = d;                    for (int j = 1; j < this.Piton.Count; j++)                    {                        Vector2 hamiltonPoint;                        if (invertHamiltonPath)                        {                            hamiltonPoint = (finalIndexPoint - j >= 0) ? this.HamiltonPath[finalIndexPoint - j] : this.HamiltonPath[this.HamiltonPath.Count - j];                        }                        else                        {                            hamiltonPoint = (finalIndexPoint + j < this.HamiltonPath.Count) ? this.HamiltonPath[finalIndexPoint + j] : this.HamiltonPath[finalIndexPoint + j - this.HamiltonPath.Count];                        }                        if (tempPiton.TakeLast(this.Piton.Count).Contains(hamiltonPoint, vector2Comparer))                        {                            isFound = false;                            break;                        }                        tempPiton.Add(hamiltonPoint);                    }                    if (isFound)                    {                        return new ResultAnlaizePath(true, invertHamiltonPath);                    }                }                return new ResultAnlaizePath(false);            }

И кстати, упомянутый Code Bullet до этого финта ушами не додумался. Я про инвертирование, да он срезал углы, по некому своему алгоритму, который остался в тайне покрытым мраком. Но точно могу сказать, что в его решения был косячок, из-за которого провалился его проход.



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

if (!stepPiton.TakeLast(this.Piton.Count).Contains(newElement, vector2Comparer))            {                tempPath.Add(newElement);                stepPiton.Add(newElement);                var retult = StepTempPath(ref index, !invert, newElement, finalIndexPoint, stepPiton, tempPath);                if (retult.PathIsFound)                {                    return retult;                }                if (this.HamiltonPath.Count < index)                {                    return new ResultAnlaizePath(false);                }                tempPath.Remove(newElement);                stepPiton.Remove(newElement);            }            Vector2 nextFinalPoint;            if (this.InvertHamiltonPath)            {                nextFinalPoint = (finalIndexPoint - 1 < 0) ? this.HamiltonPath[this.HamiltonPath.Count - 1] : this.HamiltonPath[finalIndexPoint - 1];            }            else            {                nextFinalPoint = (finalIndexPoint + 1 == this.HamiltonPath.Count) ? this.HamiltonPath[0] : this.HamiltonPath[finalIndexPoint + 1];            }            List<Directions> directions = new List<Directions>(4);            directions.Add(finalPoint.Y < nextFinalPoint.Y ? Directions.Up : Directions.Down);            directions.Add(finalPoint.X < nextFinalPoint.X ? Directions.Left : Directions.Rigth);            directions.Add(finalPoint.Y < nextFinalPoint.Y ? Directions.Down : Directions.Up);            directions.Add(finalPoint.X < nextFinalPoint.X ? Directions.Rigth : Directions.Left);            foreach (var direction in directions)            {                switch (direction)                {                    case Directions.Up:                        newElement = new Vector2(current.X, current.Y - 1);                        break;                    case Directions.Left:                        newElement = new Vector2(current.X - 1, current.Y);                        break;                    case Directions.Down:                        newElement = new Vector2(current.X, current.Y + 1);                        break;                    case Directions.Rigth:                        newElement = new Vector2(current.X + 1, current.Y);                        break;                }                if (0 <= newElement.X && newElement.X < xSize                 && 0 <= newElement.Y && newElement.Y < ySize                 && !stepPiton.TakeLast(this.Piton.Count).Contains(newElement, vector2Comparer))                {                    tempPath.Add(newElement);                    stepPiton.Add(newElement);                    var retult = StepTempPath(ref index, GetInvert(stepPiton, finalPoint), newElement, finalIndexPoint, stepPiton, tempPath);                    if (retult.PathIsFound)                    {                        return retult;                    }                    if (this.HamiltonPath.Count < index)                    {                        return new ResultAnlaizePath(false);                    }                    tempPath.Remove(newElement);                    stepPiton.Remove(newElement);                }            }            return new ResultAnlaizePath(false);

В целом ничего сложного.

Можно остановиться только на этом.

Здесь я пытаюсь пусть поиск в противоход прямому движению к мыше описанному выше.

            List<Directions> directions = new List<Directions>(4);            directions.Add(finalPoint.Y < nextFinalPoint.Y ? Directions.Up : Directions.Down);            directions.Add(finalPoint.X < nextFinalPoint.X ? Directions.Left : Directions.Rigth);            directions.Add(finalPoint.Y < nextFinalPoint.Y ? Directions.Down : Directions.Up);            directions.Add(finalPoint.X < nextFinalPoint.X ? Directions.Rigth : Directions.Left);

Т.е. сделать шаг в другую строну дабы обойти препятствие которое было найдено на прямом пути.

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

Полный код файла логики
using Microsoft.AspNetCore.SignalR;using Newtonsoft.Json;using Newtonsoft.Json.Converters;using System;using System.Collections;using System.Collections.Generic;using System.Diagnostics;using System.Diagnostics.CodeAnalysis;using System.Linq;using System.Runtime.ExceptionServices;using System.Threading.Tasks;using System.Timers;namespace SnakeApplication.WebApp.Hubs{    public class SnakeHub : Hub    {    }    public class SnakeSender    {        class Vector2        {            public int X { get; set; }            public int Y { get; set; }            public Vector2(int x, int y)            {                this.X = x;                this.Y = y;            }        }        class Vector2Comparer : IEqualityComparer<Vector2>        {            public bool Equals([AllowNull] Vector2 value1, [AllowNull] Vector2 value2)            {                return value1.X == value2.X && value1.Y == value2.Y;            }            public int GetHashCode([DisallowNull] Vector2 obj)            {                return 0;            }        }        private readonly static Vector2Comparer vector2Comparer = new Vector2Comparer();        [JsonConverter(typeof(StringEnumConverter))]        enum Cell        {            None,            Mouse,            Snake        }        enum Directions        {            Up,            Left,            Down,            Rigth        }        class StateBoard        {            public bool IsLife { get; set; }            public bool IsWin { get; set; }            public int XSize { get; set; }            public int YSize { get; set; }            public Vector2 Mouse { get; set; }            public List<Vector2> Piton { get; set; }            public List<Vector2> HamiltonPath { get; set; }        }        const int xSize = 12, ySize = 12;        private Random Rand { get; }        private IServiceProvider ServiceProvider { get; }        private bool IsLife { get; set; }        private bool IsWin { get; set; }        Directions Direction { get; set; }        private Vector2 Mouse { get; set; }        private Queue<Vector2> Piton { get; set; }        private bool InvertHamiltonPath { get; set; }        private List<Vector2> HamiltonPath { get; }        private Queue<Vector2> TempPath { get; set; }        private int StepsCountAfterCalculatePath { get; set; }    public SnakeSender(IServiceProvider serviceProvider)        {            this.Rand = new Random();            this.ServiceProvider = serviceProvider;            this.TempPath = new Queue<Vector2>();            this.HamiltonPath = new List<Vector2>(xSize * ySize);            this.CreateHamiltonPath();            this.CreateBoard();        }        private void CreateHamiltonPath()        {            this.HamiltonPath.Clear();            this.HamiltonPath.Add(new Vector2(0, 0));            HamiltonStep(this.HamiltonPath.Last());        }        private bool HamiltonStep(Vector2 current)        {            if (HamiltonPath.Count == HamiltonPath.Capacity)            {                var first = HamiltonPath.First();                return (first.X == current.X && first.Y == current.Y - 1)                    || (first.X == current.X && first.Y == current.Y + 1)                    || (first.X - 1 == current.X && first.Y == current.Y)                    || (first.X + 1 == current.X && first.Y == current.Y);            }            foreach (var direction in new[] { Directions.Down, Directions.Rigth, Directions.Up, Directions.Left })            {                Vector2 newElement = null;                switch (direction)                {                    case Directions.Up:                        newElement = new Vector2(current.X, current.Y - 1);                        break;                    case Directions.Left:                        newElement = new Vector2(current.X - 1, current.Y);                        break;                    case Directions.Down:                        newElement = new Vector2(current.X, current.Y + 1);                        break;                    case Directions.Rigth:                        newElement = new Vector2(current.X + 1, current.Y);                        break;                }                if (0 <= newElement.X && newElement.X < xSize                    && 0 <= newElement.Y && newElement.Y < ySize                    && !HamiltonPath.Contains(newElement, vector2Comparer))                {                    HamiltonPath.Add(newElement);                    if (HamiltonStep(newElement))                    {                        return true;                    }                    HamiltonPath.Remove(newElement);                }            }            return false;        }        private void CreateBoard()        {            Task.Run(async () =>            {                this.Piton = new Queue<Vector2>();                //for (int i = 0; i < 1; i++)                //{                //    this.Piton.Enqueue(new Vector2(ySize / 2, xSize / 2 - i));                //}                this.Piton.Enqueue(new Vector2(0, 0));                this.IsLife = true;                this.Direction = Directions.Up;                this.CreateMouse();                while (this.IsLife)                {                    this.LifeCycle();                    await Task.Delay(100);                }            });        }        private void LifeCycle()        {            this.SetDirection();            this.Step();            this.CheckDead();            this.Render();        }        private void SetDirection()        {            Vector2 head = this.Piton.Last();            int currentIndnex = this.HamiltonPath.FindIndex(p => p.X == head.X && p.Y == head.Y);            Vector2 currentElement = this.HamiltonPath[currentIndnex];            Vector2 nextElement = null;            if (this.TempPath.Count > 0)            {                nextElement = this.TempPath.Dequeue();            }            else            {                this.StepsCountAfterCalculatePath++;                if (this.InvertHamiltonPath)                {                    nextElement = (currentIndnex - 1 < 0) ? this.HamiltonPath[this.HamiltonPath.Count - 1] : this.HamiltonPath[currentIndnex - 1];                }                else                {                    nextElement = (currentIndnex + 1 == this.HamiltonPath.Count) ? this.HamiltonPath[0] : this.HamiltonPath[currentIndnex + 1];                }            }            if (currentElement.X == nextElement.X && currentElement.Y < nextElement.Y)            {                this.Direction = Directions.Down;                return;            }            if (currentElement.X == nextElement.X && nextElement.Y < currentElement.Y)            {                this.Direction = Directions.Up;                return;            }            if (currentElement.X < nextElement.X && currentElement.Y == nextElement.Y)            {                this.Direction = Directions.Rigth;                return;            }            if (nextElement.X < currentElement.X && currentElement.Y == nextElement.Y)            {                this.Direction = Directions.Left;                return;            }            throw new NotImplementedException();        }        private void Step()        {            Vector2 head = this.Piton.Last();            switch (this.Direction)            {                case Directions.Up:                    this.Piton.Enqueue(new Vector2(head.X, head.Y - 1));                    break;                case Directions.Left:                    this.Piton.Enqueue(new Vector2(head.X - 1, head.Y));                    break;                case Directions.Down:                    this.Piton.Enqueue(new Vector2(head.X, head.Y + 1));                    break;                case Directions.Rigth:                    this.Piton.Enqueue(new Vector2(head.X + 1, head.Y));                    break;            }            if (this.Piton.Contains(this.Mouse, vector2Comparer))            {                CreateMouse();             }            else            {                this.Piton.Dequeue();            }            if (this.Piton.Count < this.StepsCountAfterCalculatePath) {                this.CalculatePath();            }        }        private void CheckDead()        {            Vector2 head = this.Piton.Last();            if (head.X < 0             || head.Y < 0             || xSize <= head.X             || ySize <= head.Y             || this.Piton.SkipLast(1).Contains(head, vector2Comparer))            {                this.IsLife = false;                this.IsWin = false;                return;            }        }        private void Render()        {            var hubContext = (IHubContext<SnakeHub>)this.ServiceProvider.GetService(typeof(IHubContext<SnakeHub>));            var piton = this.Piton.ToList();            piton.Reverse();            hubContext.Clients?.All.SendAsync("newState", JsonConvert.SerializeObject(new StateBoard()            {                IsLife = this.IsLife,                IsWin = this.IsWin,                XSize = xSize,                YSize = ySize,                Mouse = this.Mouse,                Piton = piton,                HamiltonPath = HamiltonPath            }));        }        private void CreateMouse()        {            List<Vector2> emptyCells = GetEmptyCells();            if (emptyCells.Count > 0)            {                this.Mouse = emptyCells[this.Rand.Next(emptyCells.Count)];                this.CalculatePath();            }            else            {                this.IsLife = false;                this.IsWin = true;            }        }        private void CalculatePath()        {            this.StepsCountAfterCalculatePath = 0;            int finalIndexPoint = this.HamiltonPath.FindIndex(p => p.X == this.Mouse.X && p.Y == this.Mouse.Y);            List<Vector2> tempPath = new List<Vector2>();            List<Vector2> stepPiton = new List<Vector2>(this.Piton);            Debug.WriteLine($"Piton length: {this.Piton.Count}");            int index = 0;            var result = StepTempPath(ref index, GetInvert(stepPiton, this.Mouse), this.Piton.Last(), finalIndexPoint, stepPiton, tempPath);            if (result.PathIsFound)            {                this.TempPath = new Queue<Vector2>(tempPath);                this.InvertHamiltonPath = result.InvertHamiltonPath;            }        }        private bool GetInvert(List<Vector2> stepPiton, Vector2 finalPoint)        {            if (this.Piton.Count > 1)            {                int pitonDirection = stepPiton.Last().Y - stepPiton[stepPiton.Count - 2].Y;                int mouseDirection = stepPiton.Last().Y - finalPoint.Y;                return (pitonDirection < 0 && mouseDirection < 0) || (pitonDirection > 0 && mouseDirection > 0);            }            return false;        }        class ResultAnlaizePath        {            public bool PathIsFound { get; set; }            public bool InvertHamiltonPath { get; set; }            public ResultAnlaizePath(bool pathIsFound, bool invertHamiltonPath = false)            {                PathIsFound = pathIsFound;                InvertHamiltonPath = invertHamiltonPath;            }        }        private ResultAnlaizePath StepTempPath(ref int index, bool invert, Vector2 current, int finalIndexPoint, List<Vector2> stepPiton, List<Vector2> tempPath)        {            index++;            if (this.HamiltonPath.Count < index)            {                return new ResultAnlaizePath(false);            }            Debug.WriteLine($"index {index} {tempPath.Count}");            var finalPoint = this.HamiltonPath[finalIndexPoint];            if (current.X == finalPoint.X && current.Y == finalPoint.Y)            {                if (this.Piton.Count == 1)                {                    return new ResultAnlaizePath(true);                }                foreach (var d in new[] { false, true })                {                    var tempPiton = stepPiton.TakeLast(this.Piton.Count).ToList();                    bool isFound = true;                    bool invertHamiltonPath = d;                    for (int j = 1; j < this.Piton.Count; j++)                    {                        Vector2 hamiltonPoint;                        if (invertHamiltonPath)                        {                            hamiltonPoint = (finalIndexPoint - j >= 0) ? this.HamiltonPath[finalIndexPoint - j] : this.HamiltonPath[this.HamiltonPath.Count - j];                        }                        else                        {                            hamiltonPoint = (finalIndexPoint + j < this.HamiltonPath.Count) ? this.HamiltonPath[finalIndexPoint + j] : this.HamiltonPath[finalIndexPoint + j - this.HamiltonPath.Count];                        }                        if (tempPiton.TakeLast(this.Piton.Count).Contains(hamiltonPoint, vector2Comparer))                        {                            isFound = false;                            break;                        }                        tempPiton.Add(hamiltonPoint);                    }                    if (isFound)                    {                        return new ResultAnlaizePath(true, invertHamiltonPath);                    }                }                return new ResultAnlaizePath(false);            }            if ((xSize + ySize * 2) <= tempPath.Count)            {                return new ResultAnlaizePath(false);            }            Vector2 newElement = null;            if (invert)            {                if (current.X < finalPoint.X)                {                    newElement = new Vector2(current.X + 1, current.Y);                }                else if (finalPoint.X < current.X)                {                    newElement = new Vector2(current.X - 1, current.Y);                }                else if (current.Y < finalPoint.Y)                {                    newElement = new Vector2(current.X, current.Y + 1);                }                else if (finalPoint.Y < current.Y)                {                    newElement = new Vector2(current.X, current.Y - 1);                }            }            else            {                if (current.Y < finalPoint.Y)                {                    newElement = new Vector2(current.X, current.Y + 1);                }                else if (finalPoint.Y < current.Y)                {                    newElement = new Vector2(current.X, current.Y - 1);                }                else if (current.X < finalPoint.X)                {                    newElement = new Vector2(current.X + 1, current.Y);                }                else if (finalPoint.X < current.X)                {                    newElement = new Vector2(current.X - 1, current.Y);                }            }            if (!stepPiton.TakeLast(this.Piton.Count).Contains(newElement, vector2Comparer))            {                tempPath.Add(newElement);                stepPiton.Add(newElement);                var retult = StepTempPath(ref index, !invert, newElement, finalIndexPoint, stepPiton, tempPath);                if (retult.PathIsFound)                {                    return retult;                }                if (this.HamiltonPath.Count < index)                {                    return new ResultAnlaizePath(false);                }                tempPath.Remove(newElement);                stepPiton.Remove(newElement);            }            Vector2 nextFinalPoint;            if (this.InvertHamiltonPath)            {                nextFinalPoint = (finalIndexPoint - 1 < 0) ? this.HamiltonPath[this.HamiltonPath.Count - 1] : this.HamiltonPath[finalIndexPoint - 1];            }            else            {                nextFinalPoint = (finalIndexPoint + 1 == this.HamiltonPath.Count) ? this.HamiltonPath[0] : this.HamiltonPath[finalIndexPoint + 1];            }            List<Directions> directions = new List<Directions>(4);            directions.Add(finalPoint.Y < nextFinalPoint.Y ? Directions.Up : Directions.Down);            directions.Add(finalPoint.X < nextFinalPoint.X ? Directions.Left : Directions.Rigth);            directions.Add(finalPoint.Y < nextFinalPoint.Y ? Directions.Down : Directions.Up);            directions.Add(finalPoint.X < nextFinalPoint.X ? Directions.Rigth : Directions.Left);            foreach (var direction in directions)            {                switch (direction)                {                    case Directions.Up:                        newElement = new Vector2(current.X, current.Y - 1);                        break;                    case Directions.Left:                        newElement = new Vector2(current.X - 1, current.Y);                        break;                    case Directions.Down:                        newElement = new Vector2(current.X, current.Y + 1);                        break;                    case Directions.Rigth:                        newElement = new Vector2(current.X + 1, current.Y);                        break;                }                if (0 <= newElement.X && newElement.X < xSize                 && 0 <= newElement.Y && newElement.Y < ySize                 && !stepPiton.TakeLast(this.Piton.Count).Contains(newElement, vector2Comparer))                {                    tempPath.Add(newElement);                    stepPiton.Add(newElement);                    var retult = StepTempPath(ref index, GetInvert(stepPiton, finalPoint), newElement, finalIndexPoint, stepPiton, tempPath);                    if (retult.PathIsFound)                    {                        return retult;                    }                    if (this.HamiltonPath.Count < index)                    {                        return new ResultAnlaizePath(false);                    }                    tempPath.Remove(newElement);                    stepPiton.Remove(newElement);                }            }            return new ResultAnlaizePath(false);        }        private List<Vector2> GetEmptyCells()        {            List<Vector2> emptyCells = new List<Vector2>(xSize * ySize);            for (int i = 0; i < ySize; i++)            {                for (int j = 0; j < xSize; j++)                {                    if (!this.Piton.Contains(new Vector2(j, i), vector2Comparer))                    {                        emptyCells.Add(new Vector2(j, i));                    }                }            }            return emptyCells;        }    }}


Собственно как всё это ползает.


Всем спасибо за уделенное внимание.

Теперь осталось написать для нее нормальный AI.
Подробнее..

Дайджест интересных материалов для мобильного разработчика 382 (15 21 февраля)

21.02.2021 14:07:08 | Автор: admin
В этом выпуске цвета Swift, переиспользуемый чистый Kotlin, выход первой версии Android 12 и страсти по IDFA, дефекты Qt и бриллиантовый чекаут, секреты маркетинга приложений, игровые боты, знания за 5 минут и многое другое.



Этот дайджест доступен в виде еженедельной рассылки. А ежедневно новости мы рассылаем в Telegram-канале.

iOS

Предотвращаем мерж-конфликты с XcodeGen
Цвета в Swift: UIColor
Распознание блоков текста в iOS-приложении с помощью Vision
Apple начала бороться с иррационально высокими ценами в приложениях?
Забанила ли Apple аналитические SDK? Ээ ну
Взлом нативных двоичных файлов ARM64 для запуска на симуляторе iOS
Погружение в CFRunLoop
Создайте новостное приложение в SwiftUI 2.0 (Combine, API, MVVM & Swift Package Manager)
Используем Charles для переписывания ответов при разработке приложений для iOS
Clubhouse-подобное изображение в профиле на Swift
Создаем анимированные круговые и кольцевые диаграммы в SwiftUI
Создание рулетки на SwiftUI
OnTap: документация по SwiftUI
WatchLayout: круги в UICollectionView
SPAlert: уведомления в стиле Apple

Android

Как писать и переиспользовать код на чистом Kotlin. Заметки Android-разработчика
Как найти подходящую абстракцию для работы со строками в Android
Темы, стили и атрибуты
Вышла превью-версия Android 12
GitHub Actions для Android-разработки
Как мы ускорили запуск приложения Dropbox для Android на 30%
Как изменится дизайн в Android 12
Контрольный список качества приложения
Анти-паттерны RecyclerView
StateFlow с одно- и двусторонним DataBinding-ом на Android
Как на самом деле работает RxJava
Готовим наши приложения к Jetpack Compose
Простое создание параллакса на Jetpack Compose
5 расширений Kotlin, которые сделают ваш Android-код более выразительным
IridescentView: переливающиеся изображения для Android
stackzyr: Jetpack Compose для десктопов

Разработка

Обработка дат притягивает ошибки или 77 дефектов в Qt 6
Запуск топ-приложения в одиночку, бесплатно и без кодинга (ну почти)
Как мы накосячили пока делали Бриллиантовый чекаут 9 месяцев, а планировали 2
1 год с Flutter в продакшне
Тесты должна писать разработка (?)
Опыт разработки первой мобильной игры на Unity или как полностью перевернуть свою жизнь
О поиске утечек памяти в С++/Qt приложениях
Стратегия тестирования краткосрочного проекта
Готовим Большую Фичу на Kotlin Multiplatform. Доклад Яндекса
ZERG что за зверь?
Podlodka #203: платежи
Microsoft открывает Dapr для простого развертывания микросервисов
Задачи с собеседований: 2 в 64 степени
Дизайн приложений: примеры для вдохновения #32
Как сделать инсайты UX-исследований видимыми, прослеживаемыми и увлекательными?
5 вопросов на интервью для выявления выдающихся программистов
Как создать простое шахматное приложение с помощью Flutter
Создавая бэкенд Uber: пошаговое руководство по системному дизайну
5 удивительных преимуществ обмена знаниями в качестве разработчика
Чтение кода это навык
Почему я перестал читать статьи Как стать разработчиком программного обеспечения
Психология дизайна и нейробиология, стоящая за классным UX
Удаленное определение частоты пульса с помощью веб-камеры и 50 строк кода
Как разозлить разработчика
7 обязательных навыков, чтобы стать выдающимся разработчиком

Аналитика, маркетинг и монетизация

Кратко о продуктовых метриках
Маркетологи в мобайле: Денис Нуждин (Пятёрочка Доставка)
Секреты маркетинга приложений для знакомств новое руководство Adjust
Среда совместного программирования Replit получила $20 млн
Photomath получил еще $23 млн.
Post-IDFA Alliance открыл сайт Нет IDFA? Нет проблем
Взрослые в США в 2020 прибавили сразу час цифрового времени
ВКонтакте запустил новый инструмент для автоматизированной рекламы приложений
Отчет Состояние рынка приложений для фитнеса и здоровья 2021
Jigsaw получает $3.7 млн на дейтинг с головоломкой
Uptime: знания за пять минут
Как запустить wellness-стартап на свои деньги, совмещать с постоянной работой и не сойти с ума
Что будет с трекингом мобильных приложений в 2021 году
Новая норма: обучение в приложениях и как добиться успеха в меняющиеся времена
Лучшие маркетинговые метрики для отслеживания показателей роста
Вот почему разработчикам не удается добиться успеха в карьере
Как я занимался маркетингом своей игры, продажи которой за год составили 128 тысяч долларов

AI, Устройства, IoT

Cчетчик газа в Home Assistant без паяльника
Устройство игрового бота: 16-е место в финале Russian AI Cup 2020 (и 5-е после)
Умный дом с нуля своими руками или путешествие длиною в год
Как распознать рукописный текст с помощью ИИ на микроконтроллерах
Часы для обнаружения жестов на основе машинного обучения, ESP8266 и Arduino
Как преобразовать текст в речь с использованием Google Tesseract и Arm NN на Raspberry Pi
Быстрый прототип IIoT-решения на Raspberry PI и Yandex IoT. Часть вторая
Первый опыт с Raspberry Pi или микросервисы для дома
Google сворачивает Swift для TensorFlow

< Предыдущий дайджест. Если у вас есть другие интересные материалы или вы нашли ошибку пришлите, пожалуйста, в почту.
Подробнее..

Устройство игрового бота 16-е место в финале Russian AI Cup 2020 (и 5-е после)

19.02.2021 22:06:58 | Автор: admin

Эта статья об участии в чемпионате по написанию игрового искусственного интеллекта Russian AI Cup


Игра


Дисклеймер, пока все не разбежались


Хоть в финале я и был 16-м, статья описывает бота, удерживавшего 5-е место в общем зачете песочницы на момент её остановки.


5 место в песочнице


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


Вступление


Меня зовут Андрей Рыбалка (вдруг Вы робот и не смогли распознать текст на картинке выше), я уже восьмой год подряд участвую в Russian AI Cup. Это чемпионат для программистов по написанию игрового искусственного интеллекта. Задачей является написание бота, который будет играть в игру против ботов, написанных другими участниками.


Короче говоря,


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

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


А пока...


О задаче


Постараюсь описать покороче. Более подробное описание есть в статье GreenTea, занявшего 4-е место. Вообще, задача этого года предполагала стратегию в реальном времени (RTS) в космическом сеттинге. Но космический арт получился неудачным, юниты были практически неотличимы друг от друга визуально, поэтому все переключали визуализатор в упрощённый режим, который Вы видели на картинке в начале статьи, и забывали о космических кораблях как о страшном сне. А в упрощённом режиме на квадратиках юнитов были нарисованы меч, лук и молоток, поэтому все воспринимали игру именно в средневековом сеттинге. Так же поступлю и я в этой статье.


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


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


Все игроки ходят одновременно. Лучник может выстрелить в цель на расстоянии до пяти клеток. Для измерения расстояний использовалась манхэттенская метрика. Лучник убивает другого лучника с двух выстрелов, причем, выстрелы просчитываются раньше, чем ходы. Таким образом, если два лучника оказываются в 5 клетках друг от друга, это ведёт к обоюдному гарантированному уничтожению в 2 хода. Если два лучника выходят против одного, он успевает сделать один выстрел и получить два в ответ, что ведёт к смерти одиночки в один ход и потере половины хит-поинтов (далее ХП) у одного из двух атакующих.


Бой 2х1


А вот в бою 2 на 2 юнита или более, исход зависит от того, на чьей стороне перевес, у кого лучше расстановка, кто правильнее переместит свои войска и оптимальнее распределит цели при стрельбе.


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


Рабочие добывают ресурсы, на эти ресурсы мы покупаем новых рабочих, либо строим здания, либо строим армию. Зданий в игре всего 4 типа: база рабочих, база мечников, база лучников и дом. Первые три умеют производить юниты соответствующих типов. То есть, чтобы начать строить лучников, нужно в начале построить их базу. Дома нужны для того, чтобы увеличивать лимит юнитов, которых можно произвести. Каждый дом позволяет купить 5 дополнительных юнитов.


Стоимость покупки юнитов растёт на $1 за каждого уже существующего юнита этого типа. Таким образом, первый рабочий стоит $10, второй $11 и т.д. Поэтому, если строить слишком много юнитов, в какой-то момент каждый последующий получается непомерно дорогим и это тоже нужно контролировать.


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


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


Этапы


Чемпионат состоит из двух раундов и финала. В каждом раунде правила несколько меняются.


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

Раунд 1


  • Во 2 раунде также 4 участника, изначально у каждого построена только база рабочих и в игре действует туман войны.

Раунд 2


  • Правила финала повторяют правила второго раунда, но играем 1 на 1. Выглядит примерно так:

Раунд 2


Техническая часть


Стратегия состоит из следующих основных модулей:


  1. Подготовка
  2. Экономика
  3. Строительство и ремонт
  4. Сбор ресурсов
  5. Производство юнитов
  6. Бой
  7. Перемещение по миру (поиск пути; отправка юнитов к различным целям; контроль карты)

О них и поговорим подробнее.


В поисках грааля


Я, традиционно, писал на Java. Так что таймауты мой вечный попутчик на этом чемпионате. Но в этот раз почему-то ситуация с таймаутами была гораздо плачевнее, чем в предыдущие годы. По словам организаторов, они не меняли инфраструктуру, поэтому я не знаю, чем объяснить случившееся, но я ловил таймауты даже при минимуме вычислений. Локально стратегия летает, а на сервере превышает лимит в 40 секунд процессорного времени на игру. В попытках бороться с этим, я добавил логирование суммарного реального времени и был, мягко говоря, удивлён, увидев, что локально на домашнем ПК, моя стратегия тратит суммарно на все вычисления 3 секунды на всю игру, и при этом не укладывается на сервере в отведённые 40 сек. Дебагер показал, что более 90% всего времени сжирает VM джавы, с стратегии остаются лишь оставшиеся жалкие 7-10%. Я начал бить тревогу. И выяснилось, что примерно ту же самую картину видят все, кто пишет на Java или Kotlin.


Поскольку я не джавист и совершенно не разбираюсь в настройке VM, я пытался скооперироваться с теми, кто что-нибудь в этом понимает. К примеру, в воскресенье между 1 и 2 раундом мы просидели несколько часов в скайпе с победителем этого года, Commandos-ом (который давно плюнул на эти проблемы и перешёл на C++), пытаясь добиться вменяемого быстродействия. Настройкой VM, получилось ускорить примерно вдвое, но этого тоже было слишком мало.


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


Решение нашёл участник под ником karloid, писавший на котлине. Он предложил собирать нативный PE файл средствами GraalVM.


Грааль, как и полагается граалю, сотворил чудо. Собранный exe файл у меня тратил в 5 раз меньше процессорного времени. Ещё спустя пару дней организаторы добавили поддержку GraalVM в тестирующей системе. В общем, только с того момента у меня началось полноценное участие. К сожалению, на тот момент прошло уже 2.5 недели, а это больше половины чемпионата, и оставалось всего пару дней до старта второго раунда. В общей сложности, на протяжении всего чемпионата, примерно треть всего времени ушла на всевозможные оптимизации, а не на написание стратегии. А учитывая, что на поднятие с 16 на 5 место понадобилось суммарно 10-12 часов программирования, я именно с этими проблемами связываю не самый хороший результат финала. В общем, имеем что имеем, дарёному коню в зубы не смотрят, да и поскольку решение теперь известно, я полагаю, в следующий раз Грааль будет доступен изначально.


0. Подготовка


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


Обновление мира


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


Контролируемые области


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


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


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


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


Линия фронта


Линия из квадратиков некрасивого цвета это и есть линия фронта.


Слоты для добычи ресурсов


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


Карта проходимости


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


Карта проходимости


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


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


1. Экономика


Здесь особо нечего описывать. Жуткие эвристические формулы из кучи составляющих. Общая суть такова:


  • Если нужно, строим базу лучников. Есть несколько условий, когда провоцируют её строительство у нас уже есть определенное количество рабочих, либо враг собрал $350+ денег (т.е. вот-вот начнёт строить базу), либо достигнут 220-й тик.
  • Если осталось меньше X юнитов до лимита, строим дома. X = 5 до тех пор, пока количество рабочих < 15, затем X = 10 (т.е. можно строить 2 дома одновременно)
  • Если мы активно дерёмся, строим армию
  • Если нет, производим рабочих, если ещё не упёрлись в текущий лимит. Лимит вычисляем так:


    double scale = Game.duel_mode ? 0.2 : (Game.fog_of_war ? 0.25 : 0.1);boolean builders_limit_not_reached = num_builders < Math.max(Game.duel_mode ? 60 : 50, World.food_slots.size() * scale);
    

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



2. Строительство и ремонт


Строительство домов и баз работает по-разному.


База лучников


В дуэли, как только будут построены 20 рабочих, группа из 6 юнитов бежит по направлению к центру карты, до тех пор, пока клетка [35, 35] не будет разведана, если только раньше не выполнится какое-то из условий срочного строительства базы. Затем они пытаются построить базу в координатах, приближенных к клетке [30, 30]. Я видел, что у большинства других участников на строительство базы выделяется 10+ рабочих, но мои тесты показывали наилучший результат именно при количестве 6. Также, я почти в самом начале резервирую какое-то место для базы лучников возле базы рабочих, на тот случай, если карта окажется "закрытой" и со свободным местом будут проблемы. Чтобы не пришлось строить базу лучников где-то на фланге, ибо это сильно снижает возможность оборонять второй фланг и в большинстве случаев ведёт к поражению.


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


Дома


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


Далее, я проверяю, не заблокирует ли дом единственный проход между двумя областями карты. Для этого я не придумал ничего лучше, чем выбирать по свободной клетке слева и справа от дома и искать между ними путь A*-ом, считая область, где я планирую строить, занятой. Затем беру пару клеток сверху и снизу и делаю то же самое. Если оба пути найдены, можно строить. У этого подхода есть недостатки. К примеру, если в упор к дому будет "карман", то я не смогу найти из него путь и дом построен не будет, даже если на самом деле он ничего не блокирует. Всё это можно было легко исправить, но были более срочные задачи, так что руки так и не дошли.


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


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


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


Турели


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


3. Сбор ресурсов


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


  1. Собираем все слоты для добычи ресурсов в список (на картинке отмечены желтыми крестиками).


    Слоты для добычи еды


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


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


  4. Итерируем по количеству шагов от 1 до 20 (моя максимальная дистанция поиска).


  5. Для каждого шага, для каждого слота, обрабатываем все клетки открытого списка, которые находятся на удалении, совпадающем с шагом.


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


  7. Для тех рабочих, которые после окончания этого алгоритма остались незадействоанными, просто ищем ближайшие свободные слоты и идём к ним. Когда расстояние станет <= 20, его подхватит вышеописанный алгоритм.



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


Кроме того, если вокруг рабочего есть более одного ресурса, он добывает тот их них, который имеет меньше всего граничащих слотов


Добыча самого замурованного ресурса


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


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


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


Убегание рабочих от врага


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


А вот само вычисление опасных клеток было немножко интереснее.


  1. Отмечаем все клетки в радиусе поражения вражеских юнитов как опасные.
  2. Для вражеских лучников, вычисляем все клетки, куда они могут дойти за количество тиков, равное радиусу поражения, и добавляем их в открытый и закрытый списки. Т.е. для лучников все клетки, до которых он доходит ровно за 5 шагов. Ниже объясню, зачем это надо.
  3. Добавляем в эти же списки позиции всех моих войск.
  4. Пускаем BFS из всех клеток в открытом списке. Рассматриваем только клетки в радиусе 7 единиц от юнитов. В свойство каждой просмотренной клетки я записываю, была ли она достигнута из моей клетки или из вражеской.

Таким образом, в радиусе семи клеток от каждого вражеского лучника, я оценивал, может ли враг атаковать эту клетку раньше, чем в неё подойдут мои войска. При этом, врагу достаточно было оказаться на расстоянии выстрела от клетки, а моим лучникам нужно было её занять. Т.е. у вражеских лучников была "фора" в 5 ходов. Именно поэтому во 2-м пункте я добавлял в список клетки, достижимые ими за 5 ходов, в то время, как для моих войск я добавлял только их реальные позиции. Расстояние в 7 клеток было получено путём тестирования. При значениях больше мои рабочие погибали гораздо реже, но и еды добывали меньше. При 7 клетках коэффициент побед был наивысшим.


Ещё рабочие могли было ремонтировать (лечить) других юнитов. Мало кто из участников активно использовал эту возможность. У меня, как и у многих других, лечение было случайным. То есть если раненый юнит проходил мимо рабочего, рабочий его лечил, но специально ни врачи к пациентам ни пациенты к врачам не ходили. Лечил я только на протяжении одного тика, с 5 ХП до 6 (при максимуме в 10). Так что поваляться на больничном у них особой возможности не было. Я не видел смысла тратить 5 тиков на полное восстановление ХП лучника, который, будучи вылеченным, умрёт с двух выстрелов (выстрел снимает 5 ХП), если можно было вылечивать всего 1 ХП за 1 тик с точно таким же исходом: лучник умрёт с двух выстрелов.


4. Производство юнитов


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


5. Бой


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


Боёвку я переписывал три раза, но до финала (включительно) всё равно работала первая версия, ибо остальные показывали худший результат. Первая версия минимакс с альфа-бета отсечением.


Юниты делятся на группы. В финале работало следующее разбиение:


  1. Сортирую своих юнитов по количеству противников в радиусе 5, затем 6, затем 7
  2. Создаю бой и добавляю в него первого из отсортированных юнитов, затем рекурсивно всех его врагов, всех врагов его врагов и т.д., пока есть кого добавлять
  3. Если моих юнитов в бою уже 5, больше в этот бой не добавляю. То же самое с противниками.

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


  • Юниты на расстоянии 7 клеток от врага не идут в клетки на 8
  • Генерация всех возможных ходов для моих и вражеских юнитов была достаточно дорогой операцией, и в раздумьях о том, как её оптимизировать, пришёл в голову следующий ход конём: я понял, что моя команда никогда не подходит в упор к вражеской команде (не считая варианта с мечниками, которых по факту практически не использовали). А потому, каким бы ни был ход моей команды, он никак не влияет на возможные ходы врага. Это позволило генерировать вражеские ходы только один раз за тик и затем тянуть их из кеша.
  • Юниты не умеют меняться местами и два юнита не могут идти в одну и ту же клетку.

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


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


  1. Сортирую своих юнитов по количеству противников в радиусе 5, затем 6, затем 7
  2. Беру первого юнита из отсортированного списка, рекурсивно добавляю вместе с ним в бой всех своих юнитов на расстоянии 3. Затем обхожу всех моих юнитов в этом бою и добавляю в него всех врагов на расстоянии <= 7

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


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


  1. Сортирую своих юнитов по количеству противников в радиусе 5, затем 6, затем 7
  2. Те, у кого уже есть противники в радиусе 5, никуда не ходят и просто стреляют
  3. Обхожу моих юнитов в отсортированном списке
  4. Для каждого, считаю общее количество врагов в зонах 6 и 7 клеток
  5. Беру ближайшего к нему врага и считаю количество моих юнитов только в зоне 6 клеток
  6. Если число из пункта 5 больше числа из пункта 4, юнит считается атакующим.
  7. Если меньше убегаем, если равное количество стоим на месте.

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


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


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


Юнит "C" имеет только одну цель в радиусе выстрела "3", в то время, как юниты "A" и "B" имеют по 2 цели. Если бы юниты "A" и "B" стреляли в цель "3", выстрел юнита "C" не принёс бы никакой пользы. Поэтому у меня первым стреляет юнит "C", ибо у него всего одна возможная цель, а затем "A" и "B" решают, куда стрелять им, чтобы максимизировать потери противника.


6. Перемещение по миру


Движение рабочих описано выше, так что здесь речь о войсках. Точнее, для тех из войск, кто не находится в состоянии боя. Первым делом в дело вступал алгоритм спецназа Commandos (все совпадения с топ1 участниками этого года случайны). Он состоял из четырёх задач:


Охота на вражеских рабочих


  1. Разбиваю всех видимых вражеских рабочих на группы (в цикле, если рабочий находится в пределах 5 единиц он какой-нибудь группы, добавляю его в неё и пересчитываю её центр. Иначе, добавляю в новую группу)
  2. Считаю score каждой группы: по 3 очка за каждого юнита, который добывает еду, и по 1 очку за остальных
  3. Сортирую по убыванию score и уже привычным движением руки, добавляю их в открытый и закрытый списки.
  4. BFS-ом ищу ближайшего свободного лучника.
  5. Скачу пугать и убивать.

К моему удивлению, одним из самых значимых изменений после окончания чемпионата, которое подняло меня с 8-10 мест места на 4-5, было изменение одной единственной константы, которая заставила охотников при поиске пути бояться вражеских солдатов.
Причём это было в последний день, за несколько часов до остановки песочницы, и локальные тесты показали улучшение всего на 30%, так что я даже сомневался, релизить ли, чтобы не потерять имеющуюся позицию. Дело в том, что в этом году у меня на протяжении всего чемпионата постоянно случалось такое, что новая версия локально выигрывала от 65% до 95% игр, а будучи залитой на сайт, против других играла так же, как предыдущая, или хуже. Вообще практически все мои релизные версии выигрывали не менее 2/3 игр против предыдущей. А тут всего-лишь 30%. В общем, я рискнул и риск оправдался.


Round 1 Opening


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


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


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


Защита базы


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


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


Обход по флангам


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


Для этой задачи я пускал 3 луча из точки [79, 79] (это угол на базе противника) влево и столько же вниз, с поворотом в 9 градусов между ними. И отправлял по одному лучнику вдоль каждого луча. Точнее, луч бился на сегменты и юнит стремился к дальнему от вражеской базы сегменту. Если этот сегмент недавно был посещён, юнит шёл к следующему и таким образом продвигался к вражеской базе по флангу.


Обход по флангам


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


Перемещение по карте


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


Итак, перемещение:


  1. Инициализирую ПП. По сути это просто двумерный массив чисел, хранящий потенциалы каждой клетки. Из-за моих проблем с быстродействием до того момента, когда был найден грааль, я использовал сетку размером в 2 клетки. Позже ресурсов уже хватало и на нормальную сетку, но весь остальной код на тот момент уже полагался именно на этот размер, а времени переписывать уже не было. Короче говоря, на переправе коней не меняют. Поэтому, до самого конца у меня так и используется сетка 40х40 поверх поля 80х80.
  2. Расставляем эмиттеры. Т.е. точки, которые излучают положительный или отрицательный потенциал в определенной области. Все эмиттеры были линейными. Угадайте, почему. Правильно быстродействие! Считать квадратные корни или возводить в иные степени дорогое удовольствие. С граалем я уже мог себе это позволить, но это нарушило бы всю хрупкую экосистему и пришлось бы искать новый баланс.
    Эмиттеров было достаточно много. Вот несколько основных:
    • Отталкивающее поле радиусом в несколько клеток в позиции каждого лучника. Я изначально решил, что мои юниты будут разбредаться по всей карте, чтобы во-первых, минимизировать туман войны и во-вторых, я стремился к тому, чтобы в любой точке карты, где срочно понадобятся дополнительные юниты, кто-нибудь оказался поблизости.
    • Притягивающие поля на вражеских юнитах и зданиях, на моих турелях, на моих строящихся зданиях
    • Отталкивающее поле в точке [0, 0], чтобы при прочих равных, юниты не толпились на базе
    • Кроме того, как я уже упоминал выше, здесь также работал алгоритм с лучами, только из точки [0, 0]. Я запускал 6 лучей по флангам и ставил эмиттеры в тех местах, где эти лучи пересекались с линией фронта (с некоторым сдвигом вперёд). это заставляло юнитов стремиться в позиции между моими рабочими и вражеской армией.

Эмиттеры


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


Эмиттеры


Блеклые красные это эмиттеры с отрицательным потенциалом.


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


То есть из каждого юнита я поиском в ширину делал 25 шагов и затем рассматривал все пройдённые клетки. Потенциал каждой клетки относительно юнита считался как сумма_потенциалов_всех_пройденных_клеток + потенциал_в_целевой_клетке * (25 - количество_сделанных_шагов). То есть я "дошагивал" все оставшиеся ходы в конечной клетке, для того, чтобы я затем смог полноценно сравнивать все эти пути, имея цифры одного порядка. Ну и как несложно догадаться, я выбирал клетку с наивысшим относительным потенциалом и шёл к ней.


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


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


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


Поиск пути


Банальный A* со штрафами. Штрафы давались за прохождение через юнитов, через места, где планируется стройка, большой штраф давался за проход в зоне поражения вражескими турелями. За прохождение через ресурсы давался штраф, равный количеству ходов, которые потребуются, чтобы прорубить через них проход. И так далее. Некоторые из этих штрафов применялись или игнорировались в зависимости от аргументов. К примеру, рабочие сильно штрафовались за прохождение через зону стрельбы вражеских лучников, а войска нет.


Также, юниты умели толкать рабочих. Тут есть два варианта:


  • Рабочий толкает рабочего: При поиске пути для рабочего, я накладывал маленький штраф за прохождение через клетку, "забитую" другим рабочим. Штрафа хватало только на то, чтобы при выборе между двумя одинаковыми по длине путями, выбрать тот, где нет рабочих. В остальном же, при поиске пути, клетка с рабочим считалась пустой. Когда рабочий должен был на текущем ходу шагнуть в клетку, занятую другим рабочим, он сдвигался вперёд, вдоль пути, толкая паровозиком всех остальных рабочих перед собой до первой свободной клетки.


    Проталкивание рабочих


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


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



Тестирование


Тесты я гонял на 3-х компьютерах. В этом году даже не пришлось считать, является ли результат статистически значимым, ибо игры считались достаточно быстро, так что гонять их можно было много, и при этом, практически каждая моя следующая версия выигрывала у предыдущей 2/3 игр или больше, при количестве сыгранных игр не менее 500. Т.е. результат был заведомо статистически значим и без вычислений. При этом, как я уже упоминал выше, в этом году постоянно получалось так, что моя новая версия, без шансов обыгрывающая предыдущую, но против других противников играет лишь немногим лучше (если повезёт), а то и хуже (если нет). Апогеем стала версия, которая в локальных тестах выиграла у предыдущей со счётом 480:20, но после релиза показала нулевое преимущество против других участников.


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


Подбор констант, коих было много, осуществлялся скриптом на python. Он брал значения из командной строки и для каждого набора создавал некоторое количество игр (обычно 200) против той же версии с дефолтными константами. Что-то типа такого:


python search.py run &mdash;p1 test.exe &mdash;p2 prev.exe &mdash;count 200 &mdash;teams 2 &mdash;nthreads 3 &mdash;level Finals &mdash;params "CMD_MAX_DIST_FROM_BASE_TO_COUNTER:50/100|ENEMY_UNIT_ATTRACTION:100/300|FRIENDLY_PUSH_OFF_MULT:2.5/7.5" &mdash;output tests_v42-r3-1

Конкретно эта строка создала бы 6 сетов по 200 игр в 3 потока в режиме дуэли. Первый сет игр был бы со значением CMD_MAX_DIST_FROM_BASE_TO_COUNTER = 50, второй CMD_MAX_DIST_FROM_BASE_TO_COUNTER = 100 и т.д. Можно было передавать и несколько констант за один раз. Сами тестируемые значения я обычно проверял парами брал значение заметно больше текущего и заметно меньше. В примере выше, дефолтное значение константы CMD_MAX_DIST_FROM_BASE_TO_COUNTER было 75, поэтому я тестировал значения 50 и 100.


Визуализация


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



Заключение


На этом у меня всё. Спасибо организаторам за очередной крутой контест. Задача этого года, как по мне, была одной из самых интересных.
Ждём новых чемпионатов!

Подробнее..

Перевод 10 полезных расширений для дата-сайентистов

23.02.2021 14:15:47 | Автор: admin

Эти расширения Jupyter Notebook облегчают жизнь дата-сайентиста


Каждый специалист по Data Science тратит большую часть своего времени на визуализацию данных, их предварительную обработку и настройку модели на основе полученных результатов. Для каждого исследователя данных именно эти моменты самая сложная часть процесса, поскольку хорошую модель можно получить при условии, что вы точно выполните все эти три шага. И вот 10 очень полезных расширений Jupyter Notebook, которые помогут вам выполнить эти шаги.



1. Qgrid


Qgrid это виджет Jupyter Notebook, который использует SlickGrid, чтобы рендерить фреймы данных pandas в Jupyter Notebook. Это позволяет исследовать ваши фреймы данных с помощью интуитивно понятных элементов управления прокруткой, сортировкой и фильтрацией, а также редактировать фреймы, дважды щёлкая ячейки.


Установка


pip install qgrid #Installing with pipconda install qgrid #Installing with conda

2. itables


ITables превращает фреймы данных и серии pandas в интерактивные таблицы данных и в ваших блокнотах, и в их HTML-представлении. ITables применяет простой Javascript, из-за чего работает только в Jupyter Notebook, но не в JupyterLab.


Установка

pip install itables

Активируйте интерактивный режим для всех серий и фреймов данных вот так:

from itables import init_notebook_modeinit_notebook_mode(all_interactive=True)import world_bank_data as wbdf = wb.get_countries()df

3. Jupyter DataTables


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

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

Чтобы нарисовать таблицу, jupyter-datatables использует jupyter-require.



Установка

pip install jupyter-datatables

Как пользоваться расширением?


from jupyter_datatables import init_datatables_modeinit_datatables_mode()

4. ipyvolume


ipyvolume помогает в 3d-графике на Python в Jupyter, в качестве основы используя IPython и WebGL в нём.

Сегодня Ipyvolume может:

  • Сделать множественный объёмный рендеринг.
  • Отрисовать точечные диаграммы (до ~1 миллиона глифов).
  • Отрисовать графики колчана (например, разброс, но со стрелкой в определённом направлении).
  • Поддерживает произвольные области, которые вы рисуете мышью.
  • Рендерит в стереообъём для виртуальной реальности с помощью Google Cardboard.
  • Анимирует в стиле d3, например, если координаты x или цвет точечных диаграмм изменяются.
  • Анимация или последовательности, все свойства точечной диаграммы или quiver plot (векторный график) могут быть списком массивов, которые, в свою очередь, могут представлять снапшоты и т. д.



Установка

pip install ipyvolume #Installing with pipconda install -c conda-forge ipyvolume #Installing with conda

5. bqplot


bqplot это система визуализации в 2D для Jupyter, основанная на конструкциях Grammar of Graphics.



Задачи библиотеки


  • Полноценный фреймворк для 2D визуализаций с помощью API на Python.
  • Здравое API, чтобы добавлять пользовательские взаимодействия (панорамирование, масштабирование, выделение и т. д.).

Представлены два API

  • Пользователи могут создавать настраиваемые визуализации, используя внутреннюю объектную модель, которая вдохновлена конструкциями Gramamr of Graphics (рисунок, метки, оси, шкалы), и обогащать их визуализацию нашим слоем взаимодействий.
  • Или можно воспользоваться контекстным API, подобным pyplot у Matplotlib, который обеспечивает разумный выбор по умолчанию для большинства параметров.

Установка

pip install bqplot #Installing with pipconda install -c conda-forge bqplot #Installing with conda

6. livelossplot


Не обучайте модели глубокого обучения вслепую! Смотрите на каждую эпоху вашего обучения!

livelossplot предоставляет в Jupyter Notebook график потерь в реальном времени для моделей Keras, PyTorch и других фреймворков.



Установка

pip install livelossplot

Как пользоваться расширением?


from livelossplot import PlotLossesKerasmodel.fit(X_train, Y_train,epochs=10,validation_data=(X_test, Y_test),callbacks=[PlotLossesKeras()],verbose=0)

7. TensorWatch


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


Установка

pip install tensorwatch

8. Polyaxon


Polyaxon это платформа для создания, обучения и мониторинга крупномасштабных приложений глубокого обучения. Мы создаём систему для решения задач воспроизводимости, автоматизации и масштабируемости приложений машинного обучения. Polyaxon развёртывается в любом центре обработки данных, у любого облачного провайдера или может размещаться и управляться компанией Polyaxon, платформа поддерживает все основные фреймворки глубокого обучения, такие как Tensorflow, MXNet, Caffe, Torch и т. д.



Установка

pip install -U polyaxon

9. handcalcs


handcalcs это библиотека для автоматического рендеринга кода вычислений Python в Latex, но таким образом, чтобы имитировать формат вычисления так, как если бы они были написаны карандашом: напишите символическую формулу, сопровождённую числовыми заменами, а затем результат.



Установка

pip install handcalcs

10. jupyternotify


jupyternotify предоставляет магическое значение %%notify, которое уведомляет пользователя о завершении потенциально длительной работы ячейки с помощью push-уведомлений браузера. Примеры применения содержат модели машинного обучения, которые долго обучаются, поиск по сетке или вычисления Spark. %%notify позволяет вам перейти к другой работе и получить уведомление в момент, когда ваша ячейка завершает работу.


Установка

pip install jupyternotify


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

image
Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодом HABR, который даст еще +10% скидки на обучение:

Подробнее..

Перевод Что такое Жизнь во Вселенной четыре базовых принципа вместо трёх характерных функций

23.02.2021 08:13:43 | Автор: admin
Кадр из https://www.youtube.com/watch?v=_7wKjTf_RlIКадр из https://www.youtube.com/watch?v=_7wKjTf_RlI

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

(Является переводом довольно любопытной научной статьи от 16 апреля 2020 года).

Введение. Зачем нужно новое определение для жизни?

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

  • Попасть сложно из-за слишком маленькой целевой зоны

  • Это не самая выгодная зона для попадания (гораздо лучше целиться в 3х60)

  • Есть множество других довольно выгодных участков для прицеливания

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

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

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

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

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

Характерные функции жизни

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

Другой взгляд предполагает существование неких "законов сохранения", подобных тем, что играют важную роль в физике и инженерии. То есть какие-то материальные и функциональные ограничения, которые присущи для жизни с самого начала. Любая система, которая не соответствует этим ограничениям не может называться жизнью (в рамках этого подхода).
Одним из примеров такого подхода является "принцип сохранения химии", на который ссылаются в [3]. Их аргумент выглядит таким образом: "Химические свойства организмов более консервативны, чем изменяющаяся окружающая среда, и поэтому сохраняют информацию о древних условиях окружающей среды" [4]. В соответствии с этим принципом поиск контекста происхождения жизни, по сути, представляет собой поиск пребиотических сред, максимально приближенных к клеточным условиям.

Рисунок 1. Три распространённых примера характерных функций жизни в теориях её происхождения.Рисунок 1. Три распространённых примера характерных функций жизни в теориях её происхождения.

Примерами характерных функций являются репликация РНК по шаблонам, циклы реакций, формирующие ранние виды метаболизма и изоляция систем липидными мембранами от окружающего мира (рис. 1). Это приводит к возникновению различных "первичных" теорий возникновения жизни. Например, в так называемой "гипотезе мира РНК" считается, что "согласно теории эволюции Дарвина неживая материя должна самоорганизоваться до получения поведения, которое мы приписываем биологии"[5], а наступление дарвинизма произойдет с абиотическим образованием длинноцепочечных РНК-полимеров.
И наоборот, во многих "метаболических" теориях жизнь рассматривается как "лишь один из примеров самоорганизующихся систем, которые возникают по всей Вселенной как неизбежный результат термодинамических неравновесий"[6]. С этой точки зрения жизнь, прежде всего, служит рассеиванию (диссипации) специфических неравновесий, используя их энергию для поддержания своего низкоэнтропийного состояния [7,8].
В "изоляционных" теориях "компартментализация примитивных биохимических реакций внутри микрокапель воды, ограниченной мембраной, считается существенным шагом в зарождении жизни" [9]. Пространственная организация и концентрация веществ посредством самосборных липидных везикул придает реакциям между биомолекулами более высокие функциональные качества [10,11].

Сосредоточение внимания на характерных функциях жизни приводит каждую из этих теорий к различным взглядам на благоприятные условия для возникновения жизни - и это является серьёзным источником напряжения в научном сообществе.
Лабораторные эксперименты показывают, что приповерхностные среды, подверженные УФ-излучению и (возможно, эпизодическому) взаимодействию с атмосферными условиями и/или снабжаемые подходящими молекулами из внешних источников, идеально подходят для синтеза РНК, например [12].
С другой стороны, глубоководные щелочные гидротермальные источники являются центрами фокусировки окислительно-восстановительных процессов и неравновесия рН, которые могут приводить к возникновению протометаболических циклов, например, [6].
Однако, геотермальные пузырьки благоприятны для спонтанной самосборки липидных везикул, что отчасти обусловлено низкими концентрациями двухвалентных катионов [13].

Каждая теория, пытающаяся объяснить какую-то характерную функцию, присущую земной биологии, содержит неявное предположение о том, что эта самая характерная функция присутствовала при появлении жизни и что она является основополагающей.
Однако, учитывая изначальное отсутствие ископаемых или геологических свидетельств, нет почти никаких доказательств того, что какая-либо из этих функций действительно присутствовала в начале жизни.
Основываясь на филогенетических исследованиях, наши знания о самых ранних формах жизни на Земле прекращаются на последнем универсальном общем предке (last universal common ancestor, LUCA). Такие исследования пролили свет на природу LUCA - скорее всего, это клеточное существо с хемиосмотическим метаболизмом, генетика которого была записана в ДНК/РНК. Однако, LUCA почти наверняка не являлся исходной формой жизни на Земле [14]. Кроме того, филогенетические реконструкции LUCA по своей природе проблематичны и потенциально ненадежны в связи с ограничениями биоинформационных техник.

Горизонт событий исследований происхождения жизни

Рисунок 2. Горизонт событий в исследованиях происхождения жизни.Рисунок 2. Горизонт событий в исследованиях происхождения жизни.

Существует "горизонт событий", как подходящий по смыслу термин из астрофизики, в исследованиях происхождения жизни (рис. 2).
Подходы "сверху-вниз", такие как молекулярная филогенетика, используют подсказки из сохранившейся жизни, чтобы проследить историю жизни в направлении ее происхождения. Такой подход может привести нас только до LUCA.
Подходы "снизу-вверх", направленные на моделирование синтеза пребиотических молекул и/или наступление протоживых структур и функций, могут в один прекрасный день привести к созданию абиогенеза. Однако такое "пробное происхождение" не будет являться LUCA.

Рисунок 3. Невозможность различия сценариев происхождения жизни при подходе "сверху-вниз".Рисунок 3. Невозможность различия сценариев происхождения жизни при подходе "сверху-вниз".

Кроме того, нисходящие подходы ограничены в своем понимании происхождения жизни, поскольку дерево жизни, которое мы видим сегодня, может быть достигнуто самыми разными теоретическими сценариями (рис. 3). Эти сценарии могут включать в себя множество никогда не сходящихся генов, в результате чего образуются совершенно разные геномные ветви, которые либо уже вымерли, либо все ещё присутствуют в качестве "теневой биосферы" [15].
Правдоподобные сценарии появления жизни могут включать горизонтальный перенос генов между различными геномами, которые, возможно, беспорядочно в протобиологическом рибофильме [16], так что LUCA на самом деле является смесью из целого множества жизненных форм - некая "последняя универсальная общая группа предков" (LUCAS) [17].
Если это так, то "дерево" жизни может быть даже более подходящей метафорой, чем мы могли первоначально подумать - в то же время как оно разветвляется вверх в разнообразие существ, населявших нашу планету после LUCA(S), оно также разветвляется и вниз в запутанную сеть ранних живых экспериментов, о которых мы никогда не сможем получить сведений.

Исторический vs Синтетический vs Универсальный сценарии происхождения

Рисунок 4. Сценарии происхождения жизни (OoL).Рисунок 4. Сценарии происхождения жизни (OoL).

Существует более глубокая проблема - исследователи происхождения жизни разных направлений, возможно, не ищут более общих объяснений. В работе [18] определены три различные категории описания происхождения: историческое, синтетическое и универсальное (рис. 4).

Исторические сценарии происхождения жизни целиком основаны земных наблюдениях и ограничены нашими знаниями об окружающей среде на ранних стадиях развития Земли и основаны на двух концепциях - LUCA и современное древо жизни. Все теории о "характерных функциях" являются историческими сценариями.

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

Универсальные сценарии описывают шаги, необходимые для абиогенеза в любых условиях. Они практически не ограничены условиями ранней Земли, траекторией развития биосферы на нашей планете или химической природой жизни в том виде, в котором мы ее знаем, - все это является лишь одним из множества возможных вариантов действия универсального сценария.
Пока нет уверенности в том, что универсальные сценарии существуют, но предпринимаются некоторые попытки объяснить функции жизни с абстрактной и фундаментальной физической точки зрения [22,23,24,25].

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

Определение Y-жизни

Примечание переводчика - авторы предлагают использовать нестандартное слово для нового, более широкого, определения жизни - Lyfe (в отличии от стандартного английского "life"). Для русского языка не придумал способа как-либо приемлемо исказить привычное слово, поэтому далее будет применяться термин "Y-жизнь".
Напишите в комментариях, может быть у вас будут более удачные варианты.

Мы стремимся переопределить сам термин "жизнь" в более широком смысле, однако не собираемся смешивать это определение с тем конкретным видом жизни, который мы видим на Земле. Мы придумали новый термин - "Y-жизнь". Отныне мы будем называть земную жизнь (такую, какой мы её знаем) "жизнью", а термин "Y-жизнь" будет являться термином с наиболее общим смыслом. Эти два обозначения различаются следующим образом:

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

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

Понятие "жизнь, какой мы ее не знаем" не ново. Тем не менее, традиционные определения жизни не позволяют строго разграничить понятия "жизнь, какой мы ее знаем" и "жизнью, какой мы ее не знаем". Их расплывчатость не позволит нам вынести какой-то определённый вердикт, если разница между двумя категориями велика.
Например, согласно определению жизни, данному НАСА (см. введение), следует ли считать самоподдерживающуюся химическую систему, которая развивается не по Дарвину, "жизнью, какой мы её не знаем" или вообще не стоит её считать жизнью?

Чтобы исправить это, мы и разработали наши критерии Y-жизни на основе четырех фундаментальных процессов.
Мы согласны с высказыванием [26], что "жизнь - это глагол, а не существительное", которое основано на том, что жизнь действует за счет рассеивания планетарных окислительно-восстановительных градиентов, манипуляций с электронами и преобразования одних динамических неравновесий в другие [27].
Хотя диссипация свободной энергии, безусловно, является первым необходимым аспектом жизни, мы утверждаем, что оно должно сопровождаться тремя другими процессами - автокатализом, гомеостазом и обучением - и тогда описание жизненного процесса будет полным.

Это и есть 4 базовых принципа из заголовка статьи и их подробное описание выглядит так:

  1. Диссипация - Y-жизнь не может существовать в равновесии. Второй закон термодинамики, при наличии механизмов передачи свободной энергии, позволяет соединять экзергонические реакции с эндергоническими, что необходимо для организации Y-жизни.
    Используя массив наноразмерных молекулярных машин, жизнь рассеивает внешние химические неравновесия и/или преобразует низкоэнтропийные фотоны в высокоэнтропийное отработанное тепло, преобразуя одни неравновесия в другие (например, эндергонически зарождающиеся протонные градиенты и реакцию [АТФ]/[АДФ]). Для выполнения полезной работы жизнь преобразует АТФАДФ+H3PO4, который рассеивает неравновесие [АТФ]/[АДФ] [28,29].

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

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

  4. Обучение - способность системы записывать информацию о своем внешнем окружении и внутреннем состоянии, обрабатывать эту информацию и осуществлять действия, которые положительно влияют на её вероятность выживания/процветания.
    Дарвиновская эволюция является одним из наиболее часто упоминаемых биологических процессов обучения (например, [30,31,32]) среди гораздо более широкого набора процессов обучения, выполняемых живыми системами.
    Например, существуют широко изученные примеры биологического обучения из области нейронаук, обусловленные целым рядом нейронных и синаптических взаимодействий (например, [33,34,35]).
    Кроме того, растет список безнейронных систем обучения, в том числе сетей генной регуляции [36,37,38], сетей белковых взаимодействий [39,40] и других эпигенетических механизмов (например, [41,42]).
    Многие примеры относятся к общим принципам ассоциативного обучения, которое демонстрируют безнейронные организмы, такие как
    слизевики [43,44]. Дарвинизм смешивается с этими процессами обучения (и с другими, возможно, не открытыми), создавая невероятное разнообразие и сложность биосферы. Следовательно, "обучение" является зонтичным термином для этого большого и неполноценного множества процессов.

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

Мы хотим подчеркнуть, что наше определение Y-жизни применяется на системном уровне. Принципы, которые могут быть применены к некой системе, зависят, в частности, от границ, которыми мы обозначаем эту систему. Например, какие принципы выполняют вирусы? Один вирус по отдельности не соответствует ни одному из принципов.
Вирусы в системе, состоящей из вирусов, бактерий и питательных веществ, могут совершать автокатализ и, путём разрушения своих жертв, диссипировать.
Вирусы в биосфере в целом, могут совершать не только автокатализ и диссипацию, но и обучаться (в процессе эволюции).
В некоторых экосистемах вирусы могут даже придавать системе гомеостатические атрибуты, вводя клеткам-хостам вспомогательные метаболические гены и поглощая органическое вещество через лизис [45].
Таким образом, аргумент о том, жив ли вирус, становится неактуальным при предположении, что "живость" возникает не на молекулярном, клеточном или организменном уровне. Как и некоторые другие исследователи [46,47], мы утверждаем, что живое состояние лучше всего оценивать в экосистемном или планетарном масштабе.

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

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

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

Y-субжизнь

Рисунок 5. Диаграмма Венна для 4 принципов. Y-псевдожизнь отмечена зонами с 1 по 8.Рисунок 5. Диаграмма Венна для 4 принципов. Y-псевдожизнь отмечена зонами с 1 по 8.

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

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

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

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

  4. Диссипация и гомеостаз - затухающий гармонический осциллятор преобразует кинетическую энергию в тепловую и всегда возвращается в положение равновесия.

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

  6. Рассеивание, автокатализ и обучение - живая система, которая попала в ловушку трагедии общин. Примерами могут служить привнесенные на остров инвазивные виды, которые уничтожают источники пищи настолько быстро, что источники пищи более не подлежат восстановлению.
    В качестве другого примера можно также привести антропогенное изменение климата. Обратите внимание, что эти случаи в решающей степени зависят от того, где проводится граница системы (например, при включении или не включении человека).
    В действительности, такая форма субжизни или Y-субжизни менее вероятна, потому что если система способна учиться, то в принципе она может научиться регулировать себя гомеостатически (если может учиться достаточно быстро).

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

  8. Рассеивание, автокатализ и гомеостаз - например, реакция Белоусова-Жаботинского. Показано, что некоторые неравновесные химические реакции растут экспоненциально, а также способны регулировать собственную локальную температуру [52,53,54,55].

  9. Все 4 - Y-жизнь (и жизнь, как её подмножество).

Что касается гомеостаза в системах равновесия (область 2 на Рис. 5), то здесь есть некоторые тонкости. Наше утверждение о том, что гомеостаз происходит в изолированных системах, заключается просто в том, что они являются архетипами устойчивости (по определению). Однако этот вопрос может быть спорным. Можно утверждать, что в момент флуктуации происходит мгновенное создание свободной энергии. Однако, использование такого колебания потребовало бы измерений и обработки информации, и, как показано в [56], для этого в системе с ограниченным количеством памяти потребуется стирание, которое не может быть сделано бесплатно, а значит, свободной энергии, создаваемой колебаниями системы равновесия, на самом деле не существует.
В целом любое возмущение такой системы исчезнет за конечное время по мере уравновешивания системы, поэтому мы допускаем гомеостаз в системах равновесия (однако, биологические гомеостатические процессы, как правило, происходят за счет потребления свободной энергии).

Существует несколько примечательных макромолекулярных комплексов, которые являются как диссипативными, так и автокаталитическими, но не обязательно гомеостатическими (области 3 и 6 на рис. 5).
Выше мы уже обсуждали вирусы, которые являются белково-геномными комплексами. Прионы являются, по сути, пептидными конформационными вирусами, так как они размножаются через патологическое распространение собственной конформации на существующие пептиды (различных конформаций) [57,58,59].
Существуют также автокаталитические гены, известные как транспозоны: "транспонируемые или подвижные элементы, способные к паразитоподобной пролиферации в геноме хозяина" [60,61,62,63].
Если мы рассмотрим также биохимические примеры самореплицирующихся мицелл и капель [64,65], то увидим, что подмножество сущностей, которые являются диссипативными и автокаталитическими, потенциально заслуживают выделения в отдельную категорию. Мы отмечаем, что такая категория будет также включать в себя явления и на более высоких уровнях иерархии жизни.
Например, интернет-мемы явно являются автокаталитическими и диссипативными (учитывая затраты на вычислительную энергию, связанные с их распространением и коммуникацией). Кроме того, система "участие в социальных медиа плюс мемы" учится коллективно, и тем самым оказывается в регионе 6 на рис. 5.
Кроме того, по аналогии с биологическими вирусами, фейковые новости, которые лишь слабо коррелируют с реальностью, могут усиливаться различными эффектами до такой степени, что вызывают разрушительные социально-политические последствия. (Прим. переводчика - это одна из двух основных причин, почему я решил перевести данную статью).

В этом главе мы представили список все более Y-жизнеподобных явлений, которые могут создавать иллюзию того, что происхождение Y-жизни всегда происходит простым, поэтапным образом - т.е. пребиотическая диссипативная структура должна сначала демонстрировать экспоненциальный рост (возможно, репликацию), приобретать гомеостатические регуляторные механизмы, а затем, наконец, учиться.
На наш взгляд, вероятно также, что относительно простые системы, способные к рудиментарной обработке информации, могут возникнуть изначально и что способность этих систем со временем оптимизировать свои диссипативные, автокаталитические и гомеостатические черты определит их конечную судьбу (см. также [66]).
Например, первая жизнь на Земле почти наверняка не использовала ДНК для хранения информации или какие-либо узнаваемые ферменты в своей метаболической сети. После бесчисленных колебаний между случайностью и необходимостью эволюция всё же породила знакомые нам макромолекулы, которые мы наблюдаем сегодня. Далее мы покажем, как понятие "Y-жизнь" может изменить наши подходы к исследованию истоков жизни.

Y-жизнь и исследования происхождения жизни

Что касается сценариев происхождения жизни, Y-жизнь включает в себя любую систему, которая удовлетворяет четырем принципам, описанным выше, но может выполнять три классические характерные функции - репликацию, метаболизм и компартментализацию - используя компоненты, которые недоступны для земной жизни.
Идея о том, что эти три характерные функции определяют необходимые и достаточные условия жизни, была подробно теоретически исследована в области создания искусственной жизни и концепций аутопоэзис [67] и "Хемотон" [68].

Рисунок 6.Рисунок 6.

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

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

Рисунок 7.Рисунок 7.

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

Учитывая, что компоненты локомотива могут взаимозаменяться между собой человеческими руками, эта аналогия может показаться неуместной с точки зрения общепринятого представления о биологической эволюции как о постепенном процессе, движущей силой которого является естественный отбор микроулучшений к уже существующим элементам. Например, в так называемых "луковых гипотезах" об эволюции жизни [69,70,71] предполагается, что жизнь начиналась с метаболического ядра реакций, вокруг которого складывались все дальнейшие слои сложности.

Однако, как утверждается в [72], эволюция жизни прошла через несколько "крупных переходов", многие из которых изменили возможности биологии по обработке информации.
Вполне возможно, что на самых ранних этапах жизни такие переходы были лавинными: примитивные живые системы боролись за господство в относительно плоском ландшафте приспособляемости, легко обменивались компонентами посредством горизонтальных передач информации и материала. С изобретением механизма, который давал большее селективное преимущество, чем остальные, этот механизм - будь то рибосома или АТФ-синтез - стал бы стабильным аттрактором в эволюционной траектории земной биологии.

В книге "Seven Clues to the Origin of Life" [73], Кэрн-Смит приводит аналогию о родословной жизни как о длинной веревке, изготовленной из перекрёстных волокон, и в которой ни одно волокно не протягивается от начала до конца. Автор пишет: "Существует намного более простой способ обновления доминирующих структурных особенностей организмов: путем постепенного поглощения. Веревка из волокон конопли на одном конце могла бы постепенно трансформироваться в веревку, в которой находились бы только волокна сизали, за счет постепенного исчезания волокон конопли и увеличения количества волокон сизали".

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

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

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

Также вполне вероятно, что современные компоненты основных биохимических систем изначально выполняли различные функции. Биосфера изобилует примерами такой экзаптации: смена функции или кооптация компонента для другого использования. Перья изначально давали тепло и сигнальные способности и только позже стали орудиями полета. Крылья, вероятно, использовались для увеличения скорости бега прежде чем стали использоваться для полета [74]. Водный окислительный комплекс, возникший в эпоху кислородного фотосинтеза, возможно, изначально использовался для окисления марганца [75]. Дальнейшие примеры экзаптации смотрите в работе [76].

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

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

Какой может быть Y-жизнь? Примеры альтернативных компонентов в гипотезах происхождения жизни

Сообщество исследователей происхождения жизни выдвигает множество гипотез, которые предполагают поэтапное возникновение через Y-жизнеподобные фазы. Используя нашу классификацию, эти возникающие системы можно было бы классифицировать как Y-живые или Y-субживые, потому что они используют радикально отличные от современной жизни компоненты для достижения одного или более из базовых принципов жизни. Данная глава описывает несколько важных примеров гипотез происхождения, которые используют альтернативные компоненты.
Некоторые авторы выдвигают гипотезу о том, что самые ранние метаболические системы могли быть тиоэфирными [70] (также обращают внимание на опасения, высказанные в [78]), а не фосфатно-ориентированными из-за недоступности неорганических фосфатов на ранней Земле [79] (также обращают внимание на противоречивые мнения [80]).
Фосфор - одна из ключевых составляющих современной биологии, он присутствует в АТФ - универсальной энергетической валюте жизни, метаболических кофакторах типа NADH и молекулах хранения информации типа ДНК и РНК. Однако, согласно [81] с помощью системного биологического подхода выявлен вероятный метаболизм, не содержащий фосфатов. В биосфере существует фосфатно-независимый метаболизм, сильно зависящий от ферментов, основанных на железе-сере и переходных металлах, которые сами по себе были связаны с геохимическими сценариями возникновения биохимии [82,83,84]. Сетевые алгоритмы предполагают, что при различных параметрах среды может возникнуть протометаболическая сеть на тиоэфирной основе, аналогичная восстановительному циклу трикарбоновых кислот [85]. Это привело бы к появлению "тиоэфирного мира", который после биодоступности фосфатов перешел в современный "фосфатный мир", в котором присутствуют АТФ и нуклеотиды. Таким образом, мир тиоэфира, если бы он вообще существовал, был бы примитивной, но процветающей биосферой Y-жизненных форм.

Саморазмножающиеся глинистые минералы предложены в качестве первых информационных структур жизни [86,87]. В этой гипотезе информация содержится в дефектах, неровностях и апериодических особенностях химической и пространственной структуры кристаллов. Поскольку рост кристаллов происходит за счет добавления плоских, комплементарных слоев, эта информация может быть "реплицирована" при разрыве растущих кристаллов. Эти кристаллические генотипы могут проявляться в виде фенотипов, влияющих на среду, в которой происходит рост кристалла, а также на каталитическую силу кристалла [73].
Особый интерес представляет автокаталитический потенциал глинисто-органических систем. Например, было высказано предположение, что в происхождении жизни могли участвовать богатые железом глины, которые могли бы осуществлять светоиндуцированный перенос заряда для снижения содержания CO2 в функциональных органических молекулах [69]. Известно также, что глинистые минералы способствуют синтезу полинуклеотидов [88,89]. В этой гипотезе возникновение жизни в конечном итоге последует за "органическим поглощением", в котором органическо-органический автокатализ заменит глинисто-органические системы. Таким образом, в этом научном повествовании есть глинистые минералы, являющиеся "сценой" для выхода органической биохимии - глинисто-органическая форма Y-жизни.

Недавно в качестве основного семени жизни была предложена зеленая ржавчина (формально известная как фугерит) - оксигидроксид Fe2+/Fe3+ [90]. Предполагается, что этот метастабильный минерал входил в состав подводной щелочной среды вблизи гидротермальных жерл в эру Катархея. Считается, что в этих условиях зеленая ржавчина, благодаря своим восстановительно-окислительным свойствам, является важным драйвером органосинтеза. Внутри его гибких безводных слоёв нитраты могли быть преобразованы в аммоний [91,92], и предполагается, что неорганический углерод может быть преобразован в пируват, который затем может быть аминирован в аланин. В то же время теория предполагает, что зеленая ржавчина может действовать как примитивный неорганическая пирофосфатаза, что делает ее одним из первых нанодвигателей жизни [93]. Некоторые даже выдвинули гипотезу о том, что переменные катионы Fe2+/Fe3+ могли служить примитивной информационно-накопительной системой [86,93,94]. Если зеленая ржавчина (или подобный минерал) играла роль молекулярной машины при появлении жизни, то эту протометаболическую систему можно было бы классифицировать как Y-жизнь.

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

Другие исследователи рассматривают амилоиды-полипептиды с уникальной складкой -листа как первое самореплицирующееся биологическое существо. Синтез и полимеризация РНК является давней проблемой в большинстве абиотических сред (например, [96,97]), хотя в последнее время в этой области был достигнут ряд успехов (например, [98]). Репликаторы на основе амилоидов предлагают альтернативу РНК - их мономеры легче синтезируются в пребиотических сценариях; они стабильны в ранних земных условиях; они самособираются, реплицируются, катализируются и могут быть способны адаптироваться к изменениям в окружающей их среде [57,58]. Несмотря на то, что амилоидоз печально известен из-за его связи со многими болезнями человека [99], современная жизнь извлекает пользу от использования функциональных амилоидных белков - от формирования биопленок и устойчивости к дегидратации до использования их в качестве долговременной памяти [58]. Если бы самые ранние живые существа представляли собой "амилоидный мир", а не "мир РНК", то это представляло бы собой уникальный тип Y-жизни.

Y-Жизнь на Титане

Биохимия жизни - от её водной природы до сильной зависимости от атомов C H N O P S - является отражением физических и химических условий Земли. Другие миры, в которых имеются потенциально схожие места обитания, например, гидротермальные системы в Европе или Энцеладе, могут быть населены живыми существами, биохимически сходными с жизнью. Однако в мирах, которые занимают радикально иные физические и химические пространства, чем на Земле, любая существующая экзобиология, несомненно, будет Y-жизнью, а не жизнью.
Одной из интригующих возможностей является Титан, единственный другой мир в Солнечной системе, который, как известно, обладает на своей поверхности устойчивыми озёрами жидкости. Несмотря на широкое сходство Титана с Землей (атмосферное давление, геоморфология, активный метановый цикл, схожий с гидрологическим циклом Земли и т.д.), если бы экзобиология существовала на Титане, она возникла бы и развивалась в среде, невероятно непохожей на Землю. К основным отличиям окружающей среды относятся: крайне низкие поверхностные температуры 94 K (что приводит к экзотической органической химии, основанной на силах Ван-дер-Ваальса [100,101]); недостаток кислорода для биохимии (что приводит к гипотезам для N-замещенных биохимий [102]); и неполярный растворитель CH4-C2H6 (что приводит к догадкам об альтернативных мембранных структурах).

К слову о последнем тезисе, в источнике [103] теоретизировали, что акрилонитрил (C2H3CN) может образовывать устойчивые мембраноподобные структуры, называемые азотосомами в озерах и морях Титана. Хотя акрилонитрил недавно был обнаружен в атмосфере Титана [104], последние квантово-механические расчеты показали, что азотосомы не самособираются в титаноподобных условиях [105]. Однако это не исключает возможности того, что Y-жизнеподобные существа могут их строить. Кроме того, мембраны могут не потребоваться для Y-жизни на Титане, поскольку чрезвычайно низкие температуры уже препятствуют растворению макромолекул, а мембраноподобные структуры будут ингибировать диффузию метаболитов в холодную, стационарную Y-живую систему и из нее. Если такое безмембранное экзобиологическое образование будет существовать, то это, безусловно, будет провокативным примером Y-жизни.

Механотрофы

В духе дальнейших размышлений о жизни, какой мы ее не знаем, мы представляем вам гипотетическую форму жизни, которая использует альтернативную диссипативную/метаболическую систему - преобразование механической работы в химическое неравновесие. Земная жизнь использует различные внешние источники свободной энергии, от окислительно-восстановительных пар до солнечного излучения и градиентов плотности протонов и даже электронов (например, электротрофы [106]). Известно, что макроскопические организмы эксплуатируют абиотические источники механической работы и используют их в своих интересах; в качестве примера можно привести наезд лосося на турбулентные вихри для плавания вверх по течению [107,108], птиц, улавливающих восходящие потоки воздуха для взлёта на большие высоты, а также строительство человеком ветряных турбин и гидроэлектростанций. Однако каталогизированного примера организма, преобразующего механическую работу непосредственно в свой метаболизм, не существует.

Мы находим это довольно удивительным, так как обратный процесс является такой фундаментальной составляющей всей жизни, т.е. преобразование хемиосмотических градиентов в ротационное движение с помощью семейства молекулярных моторов АТФ-синтазы [109]. Это ротационное движение наиболее хорошо известно при синтезе АТФ из АДФ и фосфорной кислоты, но оно также используется для получения клеточных движений в жидких средах. Это происходит через жгутиковые моторные белки, близкие родственники АТФ-синтаз [110,111]. Жгутиковые моторы рассеивают свободную энергию гидролиза АТФ в эукариотах, или ионные движущие силы в прокариотах, для производства вращательных движений. Вращение центрального ротора вызывает вращение жгутиковой нити, а структура нити позволяет вращательному движению продуцировать трансляционные движения организма в воде (это движение сравнимо с механизмом, с помощью которого штопоры выталкиваются из бутылки при вращении штопора [112]).

Рисунок 8.Рисунок 8.

Почему этот процесс не может быть обращен вспять для питания механотрофного организма? Представьте себе одноклеточный организм в проточной воде, прикрепленный к скале через тросоподобную нить. Предположим, что такой организм оснащен типом жгутиковых моторных белков, которые были изучены у плавающих бактерий или одноклеточных эукариот. Поток воды вызывает вращательное движение жгутиковых нитей, и это вращательное движение используется для синтеза АТФ (или, возможно, какого-либо другого эндергонического химического шага в более экзотической Y-жизни), как показано на рис. 8. Реально ли это, зависит от термодинамических и гидродинамических условий.

Обратите внимание, что такой механотрофный организм может иметь конструкцию, отличающуюся от той, которую архетипичная бактерия использует для плавания. Например, он может использовать жёсткий к скручиванию трос, прикрепленный к молекуле молекулярного генератора (жгутик приводится в движение), и вся клетка может вращаться вокруг комплекса трос-генератор. В этом случае жгутик фиксируется относительно тела ячейки (на противоположном конце тросо-генераторного комплекса) и вместе с ячейкой вращается. Гидродинамические детали, эффективность и энергия такого организма будут детально изучены в будущей работе. Здесь мы просто хотим подчеркнуть возможность существования такого организма. Это был бы наглядный пример Y-жизненной формы, хотя не исключено, что такой организм существует на Земле, но до сих пор просто остался незамеченным.

Заключение (от переводчика)

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

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

Подробнее..

История нейронных сетей в СССР

25.02.2021 14:18:30 | Автор: admin
Сегодня нейронные сети широко известны благодаря достижениям таких учёных как Джеффри Хинтон, Йошуа Бенджио и Ян ЛеКун. Но далеко не все открытия в области коннекционизма сделаны на Западе. Над нейронными сетями начиная с конца 50-х годов активно работали и в Советском союзе, хотя за исключением специалистов сегодня немногие знают о подробностях этих исследований. Поэтому мы решили напомнить о работе советских учёных, рассказав историю отечественного коннекционизма.

Учёные Галушкин А.И. и Ивахненко А.Г.

1960-е стали золотым веком советской науки. К 1975 году от всего количества учёных в мире работала в СССР, при этом большое внимание уделялось точным наукам, плоды которых часто имели прикладное значение. Не обходили стороной и кибернетику, в которой видели огромный потенциал. Под влиянием военного и учёного Анатолия Китова она была реабилитирована после недолгой опалы. Шла работа в области автоматического управления, машинного перевода, сетевых технологий Сейчас бы мы сказали, что в СССР существовала целая школа искусственного интеллекта!

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

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

Шла машина из Тамбова или чем занимался Маккарти в СССР


Несмотря на железный занавес, советские исследователи не варились в собственном соку. Существовал интенсивный обмен идеями между нашими и заграничными учёными. Если говорить про кибернетику, в 1965 году в рамках международного обмена группа западных исследователей посетила СССР. В составе делегации был и Джон Маккарти, автор термина искусственный интеллект. После открытия III Всесоюзного совещания по автоматическому управлению (технической кибернетике) в Одессе и его продолжения на пароходе Адмирал Нахимов, делегация отправилась в тур по советским научно-исследовательским институтам. Сначала они заехали в Киев и познакомились с академиком Виктором Глушковым, автором концепции ОГАС, а также с профессором Киевского политехнического института Алексеем Ивахненко, о котором речь пойдёт далее. Затем последовал визит в Тбилиси, где западных гостей встречал директор Института систем управления Академии наук Грузинской ССР Арчил Элиашвили. Там над многослойными (или, как их называли в советской литературе, многорядными) перцептронами работали исследователи, имена которых сейчас даже человеку, подкованному в теме, мало что скажут. Они занимались в том числе системами распознавания речи. Уинстон Нельсон (Winston Nelson) из Лаборатории Белла, участвовавший в делегации, описывает визит в грузинскую лабораторию так:
Там на полу был небольшой робот, и он передвигался согласно произносимым вслух командам. <...> А затем мы вернулись в офис директора, где стоял длинный стол, уставленный вазами с фруктами, хачапури и превосходным грузинским коньяком.

Помимо Одессы, Киева и Тбилиси, делегация посетила Баку, Москву, Минск, Ленинград и несколько других городов союзных республик. В Москве Маккарти встретился со своим старым знакомым академиком Андреем Ершовым. Коллеги познакомились в декабре 1958 года в Великобритании на Конференции по автоматизации мыслительных процессов. После визита в Москву Маккарти в сопровождении Ершова отправился в новосибирский Академгородок, откуда через Москву вернулся домой (в реалиях холодной войны, когда Новосибирск был одним из полузакрытых научных центров, Ершову стоило больших трудов согласовать этот визит).

Отношения между Ершовым и Маккарти, судя по их переписке, были вполне дружеские, а не только профессиональные. Например, Маккарти в одном из писем просит Ершова прислать запись песни Шла машина из Тамбова. Или вот другой пример: когда Маккарти гостил в Союзе, в рабочей группе Международной федерации по обработке информации произошёл конфликт относительно стандартов разработки языка Алгол 68. Тогда Никлаус Вирт откололся от большинства и начал работу над языком Паскаль. Маккарти и Ершов сочинили и записали в ответ на это шуточную песню, которую адресовали раскольникам. Маккарти привёз запись на плёнке на очередное заседание рабочей группы. Произведение исполнялось, как вспоминали авторы, на мелодию русской народной песни Это не я, глупышка (на самом деле имеется в виду песня It aint me, babe Боба Дилана). У песни был и английский, и русский варианты. Приведём припев последнего:

Подайте нам язык другой,
Чтобы не было в нем обмана,
Чтоб на нем могла писать
Любая обезьяна
Нет, нет, нет, это не наш язык


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

Алексей Ивахненко и Метод группового учёта аргументов


В 1965-м году в издательстве Мир впервые увидел свет перевод на русский язык книги Фрэнка Розенблатта Принципы нейродинамики. Учитывая, что оригинал вышел в 1962-м, можно предположить, что переводить её начали почти сразу после английской публикации. Одним из самых горячих поклонников подхода, использованного Розенблаттом, в СССР стал Алексей Григорьевич Ивахненко. Он начал заниматься перцептронами ещё до выхода этой книги и был знаком с ранними публикациями Розенблатта. В момент выхода перевода Принципов нейродинамики Ивахненко уже был признанным учёным, его книга Техническая кибернетика, вышедшая до перевода принципов нейродинамики, не только выдержала два издания на русском языке, но также была опубликована на английском, немецком, болгарском, польском и румынском.

Киев: Гостехиздат УССР, 1962

Научный интерес Ивахненко к самоорганизующимся системам проявился ещё в 1950-е годы: в 1959 году он успешно собрал и испытал собственную версию перцептрона машину Альфа, названную, по всей видимости, в честь -перцептрона Розенблатта. С 1963 года Ивахненко работал под руководством знаменитого академика Виктора Михайловича Глушкова. Впрочем, в отношениях учёных не всё было гладко: в 1959 году Глушков пишет письмо коллеге, что в книге Ивахненко сделана попытка объявить элементарные самонастраивающиеся системы более высокими кибернетическими устройствами, чем вычислительные машины, которые якобы способны реализовать лишь жёсткие алгоритмы. Похоже, Глушков обвинял Ивахненко в желании подмять под себя кибернетику. Хотя, судя по другим свидетельствам, конфликт не был таким серьёзным, каким мог показаться. Один из сотрудников Ивахненко, Михаил Шлезингер, до работы с учёным был сотрудником института Глушкова, где занимался ничем иным, как симуляцией нейронных сетей на цифровой электронной машине Киев! Даже после перехода Ивахненко под руководство Глушкова работы над нейронными сетями не были прекращены. То есть, несмотря на разногласия, учёные продолжали работать вместе. Скорее всего, Глушков боялся, что приоритет будет неверно отдан развитию нейрокомпьютеров, с помощью которых большинство задач, особенно прикладных, на тот момент решить было нельзя. То есть, он скорее ратовал за правильное распределение ресурсов, чем за прекращение работы над нейронными сетями. Кстати, разногласия Глушкова и Ивахненко касались актуального и на сегодняшний день противопоставления символьного подхода и коннекционизма. Представителей последнего в СССР называли сторонниками недетерминистического подхода (в терминах Ивахненко подхода самоорганизации) в противовес детерминистическому символьному подходу. Споры эти в СССР, как и на Западе, носили весьма ожесточённый характер.

Важным результатом, полученным Ивахненко, стало создание и развитие Метода группового учёта аргументов (МГУА), одного из первых в истории алгоритмов глубокого обучения. Как и для Якова Цыпкина, для Ивахненко самообучение распознающей системы обозначало процесс автоматического, то есть проходящего без вмешательства человека, установления границы, разделяющей пространство входных сигналов и признаков на области, отвечающие отдельным образам. Уже в начале 1970-х годов Ивахненко и его коллегам удавалось обучать восьмислойные нейронные сети, в основе которых лежал искусственный нейрон, основанный на интерполяционном полиноме Колмогорова Габора.

Некоторые исследователи на Западе примерно в то же время или несколько раньше Ивахненко обучали сети с одним промежуточным слоем. Например, этим занимались коллеги Розенблатта Сэм Виглион (Sam S. Viglione) и Роджер Дэвид Джозеф (Roger David Joseph), в честь которых получил своё название алгоритм Джозефа Виглиона. Однако сети Ивахненко, содержащие восемь слоёв, явно опережали своё время. Впрочем, сами подходы, использованные им в МГУА и Виглионом с Джозефом, отдалённо напоминают друг друга. Алгоритм Джозефа Виглиона шаг за шагом генерирует и оценивает двухслойные нейронные сети с прямым распространением, автоматически идентифицируя небольшие подмножества признаков, которые обеспечивают лучшую классификацию примеров из обучающей выборки. Полученные сети затем подвергаются валидации (проверке) на части данных, не включенных в обучающую выборку. В МГУА в нейронную сеть на каждом шаге добавляются дополнительные слои, обучаемые с использованием регрессионного анализа (таким образом, МГУА восходит к методам, разработанным ещё в XIX веке в работах Лежандра и Гаусса). Затем применяется процедура сокращения слоя. Для этого точность предсказаний каждого из нейронов оценивается при помощи валидационной выборки, а затем наименее точные нейроны удаляются.

Книга Предсказание случайных процессов, написанная Ивахненко в соавторстве с Валентином Лапой и увидевшая свет в 1969 году, стала своеобразным компендиумом техник, исследовавшихся советскими коннекционистами, а книга 1971-го года Системы эвристической самоорганизации в технической кибернетике содержит не только подробное описание МГУА, но и множество примеров его применения для решения прикладных задач. В этой книге Ивахненко писал:
Уже в 1990-м году кибернетические системы автоматизированного управления производством (в масштабе завода, отрасли и всей страны) сократят потребность в рабочей силе на 50%, то есть, рабочая неделя может быть снижена до трёх рабочих дней.
Удивительно, как Ивахненко удалось угадать важнейшие тренды в развитии вычислительной техники!

Скриншот из книги с предполагаемой хронологией достижений ИИ.
Ивахненко А.Г., Лапа В.Г. Предсказание случайных процессов, издательство Наукова думка, 1971 г..

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

Хотя нейросетевые модели наших дней, лежащие в основе новой коннекционистской весны, лишь отдалённо похожи на сети, построенные при помощи МГУА, именно данный метод в некоторой степени стал поворотным пунктом в коннекционистских исследованиях. Исследователями в полной мере был осознан тот факт, что обучение глубоких нейронных сетей в принципе возможно, и что именно их использование может обеспечить прорывные результаты в решении тех или иных задач машинного обучения. Многие работы Ивахненко и его коллег были переведены на английский язык, и отсылки к ним мы можем увидеть в современных работах по глубокому обучению. Метод группового учёта аргументов используется исследователями и в наши дни, хотя занимается им лишь небольшое количество специалистов на территории бывшего СССР и в дальнем зарубежье. Кстати, несмотря на непопулярность метода, его реализация даже есть на гитхабе.

Метод без названия от Александра Галушкина


В августе 1974-го года тиражом 8 000 экземпляров вышла книга Александра Ивановича Галушкина, тогда сотрудника МФТИ, под названием Синтез многослойных систем распознавания образов. С точки зрения современной терминологии название книги можно понимать как Обучение многослойных нейронных сетей. Книга Галушкина стала первым систематическим изложением идей учёного, развитых им и его коллегами в предшествующие годы.


Москва: Энергия, 1974

Первые публикации Галушкина на тему создания и обучения многослойных нейронных сетей относятся к 19711973 годам. Учёный прекрасно знал о работах западных коннекционистов. Полюбуйтесь списком систем распознавания образов, который приводит Галушкин в своей книге: Mark-I, Mark-II, Papa, Kybertron, Altron, Konflex, Albert-1, Adalin, Madalin, Minos-2, Illiak-2, Illiak-3, Tobermory, GHILD, Astropower, Adapt-1, Adapt-2, DSK, Ziklop-1, Simisor, Auditran, Shubocs, Gaku и др.

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

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

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

Галушкин рассматривает задачу обучения нейронной сети в качестве задачи градиентного спуска. Он применяет цепное правило для вычисления градиента, подробно рассматривает случай обучения сетей с двумя обучаемыми слоями, а также коротко показывает, как следует решать задачу в случае многослойных сетей и сетей с рекуррентными связями, но не даёт используемому им методу какого-либо собственного названия. При этом вопрос конкретной топологии сетей в книге практически не затрагивается, зато уделено внимание сетям с переменным числом слоёв: Галушкин описывает подход, напоминающий МГУА с послойным обучением и постепенно наращиваемыми слоями. Автор также приводит результаты множества экспериментов по обучению сетей с различными параметрами процесса обучения, стремясь оценить их влияние на сходимость модели. Кроме математических моделей, Галушкин совместно с В. Х. Наримановым ещё в начале 1970-х годов сконструировал собственную версию аппаратного перцептрона.

Александр Иванович Галушкин продолжал работу над нейросетевыми технологиями до самого конца своей жизни и был одним из наиболее ярких лидеров этого направления в Советском союзе, а затем и в России, на протяжении более чем сорока лет. В 2007 году издательство Springer Science & Business Media выпустила книгу Галушкина Теория нейронных сетей [Neural Networks Theory] на английском языке с вводными словами Лотфи Заде, Сюнъити Амари и одного из знаменитых пионеров нейросетевого подхода Роберта Хехта-Нильсена (Robert Hecht-Nielsen, 19472019). В новом тысячелетии работы Галушкина неоднократно выходили в международных научных изданиях и оказали влияние на состояние современной нейросетевой науки.

Терминология имеет значение


В те годы и в западном, и в советском мире нейроны не называли нейронами: у Розенблата в Принципах нейродинамики они называются модулями (units), а то, что мы сейчас знаем как нейросети, в советской традиции имело несколько других названий. Многослойный перцептрон (multilayer perceptron) у Ивахненко назывался многорядным, вместо нейронов учёный использовал термин переменные, вместо сети фильтр. У Галушкина сеть называлась системой распознавания, нейрон линейно-пороговым элементом, а обученная сеть (то есть сеть, предназначенная только для выполнения [inference]) сетью с разомкнутым контуром. Почему учёные старались не использовать слово нейрон? А потому, что это отсылка к биологическому прототипу нейрону в коре головного мозга. Слои это тоже вполне нейробиологический термин, унаследованный от слоёв клеток коры головного мозга, поэтому у Ивахненко перцептрон многорядный.

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

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

Почему сейчас, когда вспоминают о нейронных сетях, никто не говорит про кибернетиков? Почему на западе компьютерные учёные перестали называть себя таковыми? Почему это название ушло в небытие, хотя наука продолжала развиваться? Ведь к области кибернетики можно отнести и первые нейросетевые эксперименты: Мак-Каллок и Питтс были регулярными участниками конференций Мейси и долгое время работали вместе с Норбертом Винером. Кибернетик У. Росс Эшби изучал мозг, и на основе своих наблюдений выдвинул концепцию суперстабильности систем, а также создал гомеостат одно из первых устройств, которое можно, хотя и с натяжкой, отнести к аппаратному воплощению искусственной нейронной сети. Аналогия с нейронным сетями прослеживается и в устройстве машин Грея Уолтера. Возможно, дело в том, что компания кибернетиков со временем стала относительно малочисленной (после ссоры Винера с Мак-Каллоком и Питтсом круг кибернетиков значительно поредел) и разбросанной по миру: Эшби, Грей Уолтер и Стаффорд Бир работали в Великобритании (какое-то время Бир провёл в Чили, где в годы президентства Сальвадора Альенде предпринял попытку создания кибернетической системы для управления экономикой, но её реализация была прервана приходом к власти хунты Пиночета). Американские поствинеровские кибернетики занимались в основном биологическими системами. Математиков и людей, занимающихся вычислительной техникой, среди них было мало. Естественным путём пальма первенства перешла к тем, кто тогда работал над решением прикладных задач при помощи компьютеров. А решались они главным образом при помощи символьных методов. Коннекционисты второго поколения в 70-е также по разным причинам отошли от дел. Бернард Уидроу занялся бизнесом, Розенблатт погиб, исследователи из Стэнфордского исследовательского института (SRI) переключились на символьные методы. Провал на Западе длился порядка пяти лет и закончился в 1978-ом году, когда темой занялись Дэвид Румельхарт, Джеффри Хинтон и их коллеги. Таким образом на время пальму первенства в мире ИИ перехватил Марвин Минский и Массачусетский институт технологий. Научная традиция пресеклась, и признанные специалисты в области IT были уже не из круга кибернетиков.

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

Из-за того, что советские коннекционисты работали с маленькими бюджетами, им не удалось достичь быстрого прогресса, как на Западе в 1980-е. Несмотря на то, что советские учёные благодаря работам Галушкина были знакомы с методом обратного распространения ошибки уже в 1970-е годы, это не привело к появлению крупных прикладных проектов в области нейронных сетей, подобных созданию системы распознавания рукописных почтовых индексов в США. С другой стороны, не было и периода полной приостановки исследований в области искусственных нейронных сетей.

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

Шахматные алгоритмы, которые думают почти так же, как человек, только лучше

26.02.2021 16:11:32 | Автор: admin

Когда создавались первые вычислительные машины, их воспринимали только как дополнение к человеческому разуму. И до недавнего времени так и было. Программисты учили компьютеры играть в шахматы с 1960-х годов. И тогда победа у игрока-новичка уже считалась большим прогрессом. О серьёзных матчах даже не задумывались.

В 1980-х программа Belle достигла рейтинга Эло в 2250 пунктов, что примерно соответствует рейтингу мастера спорта. И с того времени развитие компьютерных шахмат вышло на совершенно новый уровень.

Сначала честь человечества не смог защитить Гарри Каспаров в 1996 году, а сегодня уже создана нейросеть с рейтингом около 5000 Эло, что в разы превосходит даже сильнейших игроков.

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


Как работает шахматный движок: от механического перебора вариантов до умного выбора

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

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

Общее количество уникальных партий в шахматы составляет примерно 10120, что на 1040 превышает количество атомов во Вселенной.

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

В шахматной теории у каждой фигуры есть своя ценность, которая измеряется в пешках:

  • Конь 3 пешки;

  • Слон 3 пешки;

  • Ладья 5 пешек;

  • Ферзь 9 пешек;

  • Пешка 1 пешка.

Король бесценен, потому что его потеря означает проигрыш партии.

Анализ современных машин подтверждает истинность такой оценки. Так, в зависимости от позиции на доске компьютер оценивает ферзя в 912 пешек, ладью в 56, коня и слона в 35. Короля же машина оценивает в 300 пешек. Это задаёт максимальную границу оценки.

Анализ современных машин подтверждает истинность такой оценки. Так, в зависимости от позиции на доске компьютер оценивает ферзя в 912 пешек, ладью в 56, коня и слона в 35. Короля же машина оценивает в 300 пешек. Это задаёт максимальную границу оценки.

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

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

Минимакс, или прямой перебор вариантов, в таком случае не работает. Даже КМС без проблем найдёт на доске мат в 3 хода в миттельшпиле, когда на доске ещё много фигур. А программе для этого нужно будет перебрать свыше 750 млн. полуходов.

Даже если программа перебирает 1 млн вариантов в секунду, чтобы найти мат в 3 хода, ей понадобится до 750 секунд, или 12,5 минут.

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

Поэтому для анализа позиции используется алгоритм под названием альфа-бета-отсечение.

Система анализирует начальные варианты ходов и сразу отсекает те из них, которые ведут к мгновенному ухудшению оценки.

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

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

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

function alphabeta(node, depth, , , maximizingPlayer) is    if depth = 0 or node is a terminal node then        return the heuristic value of node    if maximizingPlayer then        value :=         for each child of node do            value := max(value, alphabeta(child, depth  1, , , FALSE))             := max(, value)            if    then                break (*  cutoff *)        return value    else        value := +        for each child of node do            value := min(value, alphabeta(child, depth  1, , , TRUE))             := min(, value)            if    then                break (*  cutoff *)        return value

За код особо не ругайте.

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

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

После ответа соперника ситуация снова анализируется по тому же алгоритму. Сначала отсекаются заведомо проигрышные линии (таких порядка 95 %), а затем путём более глубокого анализа перспективных вариантов выбирается лучший из них.

Новая эра в шахматных движках: нейросеть Alpha Zero

В 2017 году компания Deep Mind объявила о создании нейросети Alpha Zero. Тестировать её решили на трёх самых популярных стратегических настольных играх: шахматы, го и сёги.

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

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

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

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

В декабре 2018 года Alpha Zero во второй раз сразилась с самой последней версией движка Stockfish.

Исследователи провели 1000 партий с контролем 3 часа на партию плюс 15 секунд на ход.Alpha Zero одержала уверенную победу, выиграв в 155 партиях, сыграв вничью 839 партий и проиграв только 6.

Более того, Alpha Zero одерживала победу даже в партиях с форой по времени на обдумывание. Имея в 10 раз меньше времени, чем у противника, нейросеть всё равно победила в суммарном итоге. Только 30-кратная фора во времени смогла уравнять шансы и дать Stockfish примерно равную игру 3 часа у движка и всего лишь 6 минут у нейросети.

Alpha Zero анализирует лишь 60 000 позиций в секунду, а тестируемая версия Stockfish 60 млн. позиций. Для достижения аналогичных результатов анализа нейросети нужно в 1000 раз меньше ресурсов, чем движку.

Секрет успеха в качественно другом уровне анализа. Нейросеть использует метод Монте-Карло, который высчитывает математическое ожидание комплекса ходов.

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

И, что гораздо более важно, при оценке ситуации Alpha Zero учитывает стратегическую позицию.

Давайте рассмотрим на примере одной из партий.

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

Интересно, что Stockfish в упор не видит стратегических решений Alpha Zero, оценивая позицию как абсолютно ничейную. Но в результате минимальных укреплений позиции к 39-му ходу оказывается, что все фигуры белых активны, а чёрный конь и слон занимают пассивную оборонительную позицию. А после размена ферзей и ладей даже Stockfish оценивает преимущество нейросети в +2,2. Ещё несколько ходов и король черных зажат в углу доски, а конь в одиночку не способен справиться с проходной пешкой. Поэтому программа сдалась.

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

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

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

Многие теоретики считают, что благодаря шахматным компьютерам повысился и средний рейтинг топовых шахматистов. Ведь современные тренировки включают глубокую проработку компьютерных вариантов и разбора партий движками. Средний рейтинг ведущих топ-100 шахматистов в 2000 году составлял 2644 пункта Эло, а в январе 2021 года 2715. За 20 лет среднее значение увеличилось на 71 пункт.

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

Она умеет мыслить позиционно и при этом не допускает зевков и ошибок. И самое интересное в этом ситуации шахматы для Alpha Zero являются только тестировочным полигоном, где система оттачивает навыки работы. Реальные же её цели Google не раскрывает. Поэтому здесь может быть всё что угодно: от анализа изменений климатической ситуации до создания системы идеально персонифицированной рекламы. А как вы считаете, для чего создают настолько мощную нейросеть?

Создать своего гениального цифрового шахматиста или получить Level Up по навыкам и зарплате можно пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодом HABR, который даст еще +10% скидки на обучение. Узнайте подробности.

Другие профессии и курсы
Подробнее..

Краткость сестра таланта Как сделать TransformerSummarizer на Trax

22.02.2021 10:14:04 | Автор: admin

В новой курсеровской специализации NLP от deeplearning.ai в качестве библиотеки глубокого обучения используется Trax. В последнем курсе подробно разбирается механизм внимания и его использование в архитектуре Transformer, в том числе в таких новеллах как BERT и T5. Имея некоторое количество свободного времени специализацию можно пройти за несколько недель, что я собственно и сделал, соблазнившись возможностью построить собственный трансформер. Очень хотелось сделать модель, которая может работать с текстами на русском языке.

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

Trax полнофункциональная библиотека для глубокого обучения с фокусом на понятный код и быстрые вычисления. По синтаксису она в общем похожа на Keras, а модель на Trax можно сконвертировать в модель на Keras. Библиотека активно развивается и поддерживается командой Google Brain. Trax использует Tensorflow и является одной из библиотек в его экосистеме. Она работает на CPU, GPU и TPU, при этом используется одна и та же версия. Не буду говорить неправду, TPU я пока не попробовал.

Transformer - архитектура глубоких нейронных сетей, представленная в 2017 году исследователями из Google Brain. Transformer предназначен для работы с последовательностями, в том числе текстовыми, но в отличие от архитектур на рекуррентных сетях, не требует обрабатывать последовательность по порядку. Сильно упрощая можно сказать, что если из архитектуры Seq2Seq на LSTM с механизмом внимания оставить только механизм внимания и добавить нейронную сеть прямого распространения (Feed Forward), то он и получится. Подробнее про трансформеры с картинками здесь на английском, здесь на русском.

Данные

В качестве набора данных для эксперимента я решил использовать корпус новостей Lenta.Ru, свежую версию которого нашел на Kaggle. Корпус содержит более 800 тыс. новостных статей в формате (url, title, text, topic, tags, date). Если статья это text, то summary для моей модели title. Это законченное предложение, содержащее основную мысль новостной статьи. Конечно это не полное summary как, например, в англоязычном корпусе cnn_dailymail, но я подумал, что так даже интереснее.

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

Для начала я отфильтровал аномально короткие и аномально длинные статьи. Затем выделил из набора тексты и заголовки, преобразовал всё к нижнему регистру, сохранил в виде списка кортежей и в виде полного текста. Список кортежей разбил на две части для обучения (train) и оценки (eval). Далее написал бесконечный генератор, который дойдя до конца списка, перемешивает его и начинает сначала. Неприятно же, когда генератор заканчивается где-то в середине эпохи. Это важно прежде всего для оценочного набора, я взял всего 5% от общего количества статей, примерно 36 тысяч пар.

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

Для такой сегментации существует несколько сравнительно честных способов, познакомиться с ними можно например здесь. Я выбрал модель на основе Byte Pair Encoding (BPE), реализованную в библиотеке sentencepiece. BPE способ кодирования текста со сжатием. Для кодирования часто повторяющейся последовательности символов используется символ, которого нет в исходной последовательности. Всё тоже самое и при сегментации, только последовательность часто встречающихся символов становится новым токеном, и так пока не будет достигнут заданный размер словаря. Мой словарь содержит 16000 токенов.

Пример сегментированного текста

['ученые', 'придума', 'ли', 'новый', 'способ', 'взаимо', 'действия', 'с', 'граф', 'ен', 'ом', ',', 'который', 'позволяет', 'избавиться', 'от', '"', 'сли', 'па', 'ющихся', '"', 'ли', 'стов', '.', 'статья', 'ученых', 'появилась', 'в', 'журнале', 'ac', 's', 'n', 'an', 'o', ',', 'а', 'ее', 'крат', 'кое', 'из', 'ложение', 'приво', 'дится', 'на', 'сайте', 'северо', '-', 'запа', 'дного', 'университета', ',', 'сотрудники', 'которого', 'принимали', 'участие', 'в', 'работе', '.']

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

Обучается модель благодаря вот такой нехитрой конструкции:

import sentencepiece as spmspm.SentencePieceTrainer.train('--input=full_text.txt \                                --pad_id=0 --bos_id=-1 --eos_id=1 --unk_id=2 \                                --model_prefix=bpe --vocab_size=16000 --model_type=bpe')

Результат два файла: словарь для контроля и модель, которую можно загрузить в обертку токенайзера. Для выбранной мной модели статья и заголовок должны быть преобразованы в последовательности целых чисел и объединены с разделением служебными токенами EOS :1 и PAD :0 (конец последовательности и заполнитель).

После преобразования последовательность помещается в корзину фиксированной длинны. У меня их три: 256, 512 и 1024. Последовательности в корзине автоматически дополняются заполнителями до фиксированной длинны и собираются в пакеты (batches). Количество последовательностей в пакете зависит от корзины, соответственно 16, 8, 4.

Рефлексия по поводу последовательностей длиннее 512 токенов

Трудно представить, что 2000 символов могут дать что-то длиннее 512 токенов, но на всякий случай сделал три корзины. А длиннее 1024 не может быть в принципе из-за фильтра в пайплайне.

Сегментация и конкатенация выполняются в пайплайне trax:

input_pipeline = trax.data.Serial(    trax.data.Tokenize(vocab_type='sentencepiece',                       vocab_dir='/content/drive/MyDrive/',                       vocab_file='bpe.model'),    preprocessing,    trax.data.FilterByLength(1024))train_stream = input_pipeline(train_data_stream())eval_stream = input_pipeline(eval_data_stream())

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

boundaries =  [256, 512]batch_sizes = [16, 8, 4]train_batch_stream = trax.data.BucketByLength(    boundaries, batch_sizes)(train_stream)eval_batch_stream = trax.data.BucketByLength(    boundaries, batch_sizes)(eval_stream)

Модель

Transformer, работающий с двумя последовательностями, например при машинном переводе, включает два блока энкодер и декодер, но для саммаризации достаточно только декодера. Такая архитектура в общем реализует языковую модель, где вероятность следующего слова определяется по предыдущим. Еще её называют Decoder-only Transformer и она похожа на GPT (Generative Pre-trained Transformer). Разобраться в деталях архитектур можно здесь.

Для моего случая в библиотеке Trax есть отдельный класс моделей trax.models.transformer.TransformerLM(...), то есть создать модель можно одной строчкой кода. В упомянутой специализации модель строится from scratch. Я же выбрал нечто среднее построил модель из готовых блоков, используя примеры кода.

Схема модели показана на рисунке:

PositionlEncoder() это блок, обеспечивающий построение векторного пространства и кодирование позиции токена во входной последовательности. Код:

from trax import layers as tldef PositionalEncoder(vocab_size, d_model, dropout, max_len, mode):    return [         tl.Embedding(vocab_size, d_model),          tl.Dropout(rate=dropout, mode=mode),         tl.PositionalEncoding(max_len=max_len, mode=mode)] 

Аргументы:
vocab_size (int): размер словаря
d_model (int): количество признаков векторного пространства
dropout (float): степень использования dropout
max_len (int): максимальная длина последовательности для позиционного кодирования
mode (str): 'train' или 'eval' для dropout и поз. кодирования.

FeedForward формирует блок прямого распространения с выбранной функций активации:

def FeedForward(d_model, d_ff, dropout, mode, ff_activation):    return [         tl.LayerNorm(),         tl.Dense(d_ff),         ff_activation(),        tl.Dropout(rate=dropout, mode=mode),         tl.Dense(d_model),         tl.Dropout(rate=dropout, mode=mode)     ]

Аргументы:
d_model (int): количество признаков векторного пространства
d_ff (int): ширина блока или количество юнитов в выходном плотном слое
dropout (float): степень использования dropout
mode (str): 'train' или 'eval' чтобы не использовать dropout при оценке качества модели
ff_activation (function): функция активации, в моей модели ReLU

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

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

def DecoderBlock(d_model, d_ff, n_heads, dropout, mode, ff_activation):            return [      tl.Residual(          tl.LayerNorm(),           tl.CausalAttention(d_model, n_heads=n_heads, dropout=dropout, mode=mode)         ),      tl.Residual(          FeedForward(d_model, d_ff, dropout, mode, ff_activation)        ),      ]

Из неизвестных аргументов только n_heads (int) количество головок внимания, надеюсь это удачный термин для attention heads. Каждая головка учится обращать внимание на что-то своё.

Собираю все части вместе и задаю параметры модели. У меня шесть декодеров, в каждом из которых по восемь головок внимания. Общее количество обучаемых параметров 37 412 480.

Из неизвестных мне уровней пожалуй только ShiftRight. Он сдвигает входную последовательность вправо, заполняя освободившееся место нулями, по умолчанию на одну позицию. Это нужно для teacher forcing, специальной техники, упрощающей обучение языковой модели, особенно на ранних этапах. Идея здесь в следующем: когда модель учится прогнозировать следующее слово по предыдущим, вместо прогноза модели, возможно неверного, в качестве этих предыдущих слов используются правильные ответы (ground truth). Коротко это можно описать формулой:
y(t) = x(t+1). Здесь подробное объяснение для RNN.

def SumTransformer(vocab_size=vocab_size,                  d_model=512,                  d_ff=2048,                  n_layers=6,                  n_heads=8,                  dropout=0.1,                  max_len=4096,                  mode='train',                  ff_activation=tl.Relu):    decoder_blocks = [DecoderBlock(d_model, d_ff, n_heads, dropout, mode,                       ff_activation) for _ in range(n_layers)]     return tl.Serial(        tl.ShiftRight(mode=mode),         PositionalEncoder(vocab_size, d_model, dropout, max_len, mode),        decoder_blocks,         tl.LayerNorm(),         tl.Dense(vocab_size),         tl.LogSoftmax()     )

Обучение

По моему опыту Google Colab не очень любит длительное использование своих GPU и не всегда их выделяет, особенно во второй половине дня. Поэтому я обучал модель отдельными эпохами по 20 000 шагов, где шаг соответствует одному пакету (batch). Получалось сделать 1-2 эпохи в день. 100 шагов это примерно минута, а эпоха около трех часов.

Первая эпоха показала, что модель учится только несколько тысяч шагов, дальше никаких улучшений не происходит. Оказалось, что я выбрал слишком большой шаг обучения (learning_rate). Для моей модели он должен быть 0.0002 первые несколько эпох, затем 0.0001 и 0.00005 в конце. Если бы я учил модель за один проход, то можно было бы использовать lr_schedules из trax.supervised. Там есть разные удобные варианты и с прогревом и с постепенным уменьшением шага.

В качестве метрик я использовал CrossEntropyLoss и Accuracy. За 12 эпох на оценочном наборе loss упал с 10 до 2, а доля правильных ответов возросла почти до 60%. Этого оказалось достаточно, чтобы генерировать почти приемлемые заголовки.

Цикл обучения выглядит следующим образом:

from trax.supervised import trainingdef training_loop(SumTransformer, train_gen, eval_gen, output_dir = "~/model"):    output_dir = os.path.expanduser(output_dir)    train_task = training.TrainTask(         labeled_data=train_gen,        loss_layer=tl.CrossEntropyLoss(),        optimizer=trax.optimizers.Adam(0.0001),        n_steps_per_checkpoint=100    )    eval_task = training.EvalTask(         labeled_data=eval_gen,         metrics=[tl.CrossEntropyLoss(), tl.Accuracy()]     )    loop = training.Loop(SumTransformer(),                         train_task,                         eval_tasks=[eval_task],                         output_dir=output_dir)        return loop

Аргументы:
SumTransformer (trax.layers.combinators.Serial): модель
train_gen (generator): поток данных для обучения
eval_gen (generator): поток данных для оценки качества.
output_dir (str): папка для файла модели, откуда её можно скопировать на Google Drive перед выключением виртуальной машины.

Дальше всё просто:

loop = training_loop(SumTransformer, train_batch_stream, eval_batch_stream)loop.run(20000)

и три часа ожидания...

Оценка результатов

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

Примеры из оценочного набора:
(Исходный текст сокращен)

Тест #1: швейцарская часовая компания audemars piguet представила новую модель из коллекции royal oak. как сообщает luxurylaunches, речь идет о часах с вечным календарем. официальная презентация пройдет в рамках международного салона высокого часового искусства sihh, который проходит в женеве...
Образец: дом audemars piguet оснастил часы вечным календарем
Модель: audemars piguet представила новую модель из коллекции royal oak

Тест #2: на ежегодном фестивале в городе грэхэмстаун, юар, фокусник случайно выстрелил в голову своему напарнику во время представления. об этом сообщает местная газета the daily dispatch. инцидент произошел 30 июня. брендон пил (brendon peel) и его ассистент ли лау (li lau) выполняли магический трюк перед многочисленной аудиторией, когда пил по неосторожности пустил в затылок напарника стрелу...
Образец: фокусник случайно подстрелил ассистента наглазах узрителей
Модель: на фестивале в грэлково напали с ножом
(И не в грэлково, и не напали, и не с ножом, но спасибо, что это было холодное оружие, а не пистолет)

Еще примеры

Тест #3: международный валютный фонд (мвф) в среду, 15 мая, утвердил выделение кипру кредита в размере 1,33 миллиарда долларов (миллиард евро). как сообщает agence france-presse, в качестве первого транша кипрское правительство получит 110,7 миллиона долларов. утвержденный 15 мая кредит является частью плана помощи...
Образец: мвф выделил кипру миллиард евро
Модель: мвф утвердил кредит на кипрский кредит

Тест #4: автопортрет энди уорхола, выполненный в 1965 году и ранее не выставлявшийся, продадут с аукциона, пишет the new york times. автопортрет более 40 лет хранила бывшая секретарша уорхола кэти нейсо (cathy naso), которая получила картину от художника в оплату ее работы. нейсо работала в студии уорхола...
Образец: неизвестный автопортрет энди уорхола выставят наторги
Модель: энди уорхола продадут с аукциона

Тест #5: sony решила выпустить файтинг, который станет "ответом на игру super smash bros" от nintendo, пишет vg24/7 со ссылкой на paul gale network и neogaf. в новом проекте, в настоящее время известном под названием title fight, герои из нескольких игр издательства сразятся между собой...
Образец: sony приписали разработку нового файтинга
Модель: sony выпустит файтинг от nintendo

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

Ссылки

  • Мой репозитарий с кодом эксперимента)

  • Репозитарий trax

  • Математика механизма внимания в знаменитой статье Attention Is All You Need. Кстати один из авторов статьи, Lukasz Kaiser, штатный исследователь Google Brain, является также автором и инструктором специализации.

Примечания

Я использовал trax 1.3.7, он инсталлируется через pip, но не работает под Windows. На форумах пишут что можно под WSL. А еще там нет beam_search, который есть в документации и который я очень хотел попробовать.

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

В упомянутой модели TransformerLM выход не нормализован (нет уровня softmax).

Подробнее..

Нейродайджест главное из области машинного обучения за февраль 2021

01.03.2021 20:22:05 | Автор: admin

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


Papers with Datasets and Libraries


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

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

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

Google Model Search


Доступность: страница проекта, репозиторий

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

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

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

ZenML


Доступность: сайт проекта / репозиторий

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

TensorFlow 3D


Доступность: Статья / репозиторий

С распространением устройств, захватывающих 3D-данные, вроде лидаров и камер измерения глубины, обострилась потребность в технологии обработки этих данных и понимания трехмерной сцены. Это нужно для навигации и работы в реальном мире самоуправляемых автомобилей и роботов, а также для совершенствования AR-технологий.

Google представили модульную библиотеку для применения глубокого обучения на 3D-данных в TensorFlow. Она содержит пайплайны обучения и оценки для трехмерной семантической сегментации, классификации сцены, обнаружения трехмерных объектов и т.д.

MeInGame



Доступность: статья / репозиторий

В компьютерных играх часто есть редактор персонажа, который позволяет с помощью настроек разных параметров менять внешность игрока. Алгоритм MeInGame позволяет создать кастомного персонажа всего по одной фотографии. Нейросеть предсказывает форму лица и его текстуру. Хотя методы, основанные на 3D Morphable Face Model (3DMM), могут генерировать 3D-портрет из отдельных изображений, топология сетки обычно отличается от тех, что используются в большинстве игр. Авторы этого алгоритма заявляют, что эту проблему решили.

SAM



Доступность: статья / репозиторий

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

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

VOGUE



Доступность: страница проекта / интерактивное демо

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

NeRViS



Доступность: страница проекта / репозиторий

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

Stable View Synthesis


Доступность: статья / репозиторий

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

Статью опубликовали еще в ноябре прошлого года, но код стал доступен только сейчас.

JigsawGan


Доступность: статья

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

CharacterGAN



Доступность: статья / репозиторий

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

Discrete VAE


Доступность: репозиторий

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

Deep Nostalgia


Доступность: онлайн-сервис

Ну и напоследок, всегда приятно когда на основе моделей делают простой и понятный продукт. Так вот, компания MyHeritage, которая занимается вопросами генеалогии и родословных, судя по всему, взяла алгоритм First Order Model, прикрутила удобный пользовательский интерфейс и сделала на его основе сервис по оживлению фотографий.

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

На этом все, спасибо за внимание и до встречи через месяц!
Подробнее..

DeepPavlov 3 года обзор и итоги 2020 года

01.03.2021 12:17:20 | Автор: admin

Уже февраль 2021 года, а значит пришло время подводить итоги! В это время, 3 года назад, состоялся первый альфа релиз библиотеки. Библиотека DeepPavlov v0.0.1 содержала несколько предварительно обученных моделей и конфигураций JSON. А сегодня у нас есть несколько продуктов, множество пользователей и сценариев использования, достижения на всемирно известных конкурсах и конференциях, и всего через несколько месяцев библиотека DeepPavlov совершит скачок до версии v1.

И несмотря на обстоятельства пандемии, в 2020 году у нас было много задач и поводов для гордости. Как минимум, мы обновили наш веб-сайт, выпустили новый продукт DP Dream, выиграли Про/Чтение, а также повторно участвуем в Alexa Prize Challenge. Об этих и других достижениях мы рады поделиться с вами в обзоре нашего 2020 года.

P.S. 5 марта в честь 3х летия состоится встреча пользователей и разработчиков открытой библиотеки DeepPavlov. Посмотреть детали и зарегистрироваться можно на сайте.

DeepPavlov в достижениях и цифрах

Прежде чем мы перейдем к деталям, давайте вспомним о тех огромных скачках, которые мы сделали за этот год:

  • Скачивания DeepPavlov Library возросли на 40% по сравнению с прошлым годом. Сейчас всего более 200 тысяч pip установок и более полумиллиона установок контейнеров. Наши технологии используют в 80 странах!

  • DeepPavlov Library достигла 5 тысяч звезд на Github.

  • Выпустили 7 релизов DeepPavlov Library, с которыми выкатили новые модели и стали писать release notes о них.

  • Первый релиз DP Dream и нашего открытого multiskill AI Assistant-а Deepy.

  • Количество активных участников сообщества возросло в 5 раз. Сотрудниками проводятся ежемесячные звонки DeepPavlov Community Calls, в рамках которых разбираются основы использования компонент библиотеки DeepPavlov, проводятся обучающие туториалы, а также приглашаются внешние спикеры для проведения мастер-классов.

  • Наша команда студентов и аспирантов дошла до полуфинала конкурса Alexa Prize Socialbot Grand Challenge 3 by Amazon.

  • Наша команда студентов и аспирантов была повторно отобрана для участия в Alexa Prize Socialbot Grand Challenge 4 by Amazon.

  • Команда DeepPavlov заняла 1 место в номинации Грамматика.Eng в рамках первого этапа конкурса по разработке системы искусственного интеллекта для выявления смысловых, логических и фактических ошибок в текстах Up Great ПРО//ЧТЕНИЕ.

  • Сотрудниками было опубликовано 12 научных публикаций.

  • Три наших аспиранта успешно защитили диссертации на соискание ученой степени кандидата наук.

  • Мы провели более 20 лекций и воркшопов на таких конференциях, как: NVIDIA GTC Fall 2020, ODSC WEST 2020, MLConf EU 2020, Conversations AI, AI Journey 2020, Innopolis NLP MeetUp.

  • Выпустили 10 постов-туториалов по использованию компонент библиотеки DeepPavlov.

  • Приняли 10 студентов-стажеров в команду по направлениям NLP и Conversational AI.

  • Мы провели 2 учебных курса в весеннем семестре: Глубокое обучение в обработке естественного языка и Advanced Topics in Deep Reinforcement learning.

  • 1 место на либерборде DialoGLUE.

Ключевые направления и программные решения DeepPavlov

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

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

  • Развитие открытой платформы DeepPavlov Agent, которая представляет собой многофункциональный оркестратор, использующий декларативный подход для формирования конвейеров и построения диалогового ИИ в виде модульной системы.

  • Развитие открытой платформы DeepPavlov Dream, необходимой для разработки масштабируемых и многофункциональных виртуальных помощников, и опирающейся на технологии DP Library и Agent.

  • Развитие репозитория открытого multiskill AI Assistant-а Deepy, в настоящее время представляющего собой очень простую демонстрацию многофункционального AI Assistant, обладающего всего двумя навыками (целенаправленным и болталкой), а также несколькими аннотаторами. Это фундамент, который позволит любому желающему создать своего ассистента.

  • Расширение функционала демо-версии работы компонент библиотеки и открытой версии ИИ ассистента на базе Deepy.

  • Развитие NLP сообщества и контрибьюторов, а также участие в конкурсах для преодоления технологических барьеров.

Вместе с продуктами DeepPavlov, мы обновили наш сайт. Пройдемся детальнее по основному функционалу, о котором вы могли не знать:

  • Появился свой собственный блог как на русском, так и на английском языке, где мы делимся новостями.

  • Появился раздел Challenges, где мы рассказываем о конкурсах, в которых участвуем или проводим сами.

  • Для раздела DeepPavlov Library появилась вкладка Releases, где подробно описаны изменения в библиотеке.

  • Теперь вы можете узнать о наших научных проектах, а также про текущие стажировки в разделе Research.

  • Другие улучшения разделов: о проекте, о команде, о выпускниках, и т.д.

DeepPavlov Library: что нового

У нас отличные новости начиная с v0.12.0 релиза DeepPavlov Library поддерживает и TensorFlow, и PyTorch. В дополнение к этому DP поддерживает Трансформеры от Hugging Face, позволяющие разработчикам использовать широкий выбор моделей на основе Трансформеров и сотни датасетов для ваших моделей.

В 2020 году в DeepPavlov Library также появились следующие модели:

  • Speech recognition and synthesis (ASR and TTS)

  • Knowledge Base Question Answering model for WikiData

  • Entity Linking

  • Intent Catcher

  • Обновления Go-Bot

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

  • Переход на PyTorch всех моделей

  • Изменение документации

  • Прекращение поддержки старых моделей и кода

  • Контроль версий моделей

  • Обновления конфигураций

Вы можете заполнить эту форму, чтобы сообщить нам, как вы используете DeepPavlov Library, и что бы вы предложили добавить или улучшить!

Контейнеры библиотеки DeepPavlov теперь доступны в облаке NVIDIA GPU Cloud (NGC)

В 2020 DeepPavlov стал партнёром программы NVIDIA GPU Cloud (NGC) контейнерного реестра для работы с искусственным интеллектом, машинным обучением, нейронными сетями и высокопроизводительными вычислениями. Теперь контейнеры библиотеки DeepPavlov доступны в облаке NGC.

Контейнеры DeepPavlov состоят из предварительно обученных моделей, которые используют современные модели глубокого обучения типа BERT для задач классификации, распознавания именованных сущностей, вопросов-ответов и других задач области NLP. Использование GPU позволяет ускорить работу библиотеки DeepPavlov до 20 раз (для примера был взят запуск конвейеров модуля ASR / TTS на V100 GPU в сравнении с CPU).

DeepPavlov для построения AI Assistants

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

Для достижения этой цели мы создали целое семейство ИИ-помощников и сейчас подробнее о них расскажем.

DeepPavlov Dream

DeepPavlov Dream это ИИ-помощник, основанный на социальном боте, созданном командой лаборатории для участия в конкурсе Alexa Prize Socialbot Grand Challenge 3 от Amazon. Подробнее о нем мы недавно писали в статье.

Но что важно знать, сейчас этот помощник отключен от каналов и не работает. По причине того, что организаторы Alexa Prize попросили выключить его на время нашего участия в конкурсе. Сейчас доступны мини-версии под названием Deepy.

Deepy

Deepy это репозиторий нашего простого многофункционального ИИ-помощника, обладающего всего двумя навыками (целенаправленным, написанным с использованием нашей инфраструктуры Go-Bot, и болталкой, написанной с использованием AIML), а также несколькими аннотаторами.

Архитектура Deepy та же, что и у DeepPavlov Dream: мы используем тот же DeepPavlov Agent в качестве механизма для оркестрации и тот же конвейер. Однако количество используемых компонентов значительно сокращено по сравнению Dream.

В настоящий момент доступны 3 конфигурации дистрибутивов:

  • deepy_base базовый дистрибутив Deepy, состоящий из двух навыков: простого целенаправленного навыка и навыка общения в чате, а также классификаций эмоций и аннотаторов проверки орфографии,

  • deepy_gobot_base дистрибутив Deepy на основе Go-Bot, состоящий также из двух навыков: целенаправленного на основе Go-Bot и болталки, а также классификаций эмоций и аннотаторов проверки орфографии,

  • deepy_adv более продвинутый дистрибутив Deepy, который в дополнение к компонентам deepy_gobot_base также включает еще несколько аннотаторов, включая Entity Linking, Intent Catcher и Sentence Segmentation.

Третий дистрибутив, deepy_adv, в настоящее время работает на нашем демо.

Почитать подробнее про DeepPavlov Deepy можно в статье.

DeepPavlov Challenges

Мы не только проводим конкурсы, но и сами участвуем в них.

Так, в марте 2020 наш исследователь и преподаватель курса по NLP Алексей Сорокин занял второе место на конкурсе GramEval-2020. Он разработал модель для автоматического определения морфологических и синтаксических характеристик слов в предложениях на русском языке.

А в декабре 2020 исследователь проекта DeepPavlov и капитан команды Dream Alexa Prize Диляра Баймурзина взяла третье место на конкурсе AI 4 Humanities: ruGPT-3 от AIJourney. Она разработала модель AI CopyWriter для перефразирования и/или распространения текста на основе ruGPT3Large.

Alexa Prize Socialbot Grand Challenge

Каждый год американская компания Amazon проводит конкурс на создание разговорного искусственного интеллекта для своего голосового помощника Alexa Prize Socialbot Grand Challenge. Задача научить Alexa поддерживать естественный диалог с пользователем на свободные темы. 10 командам выдают грант на разработку в $250 тыс. В 2019 году в эту десятку впервые попала наша команда DREAM DeepPavlov. Команда год работала над проектом и достигла полуфинала конкурса. Узнать подробности созданной архитектуры DREAM socialbot можно ознакомившись с техническим отчетом команды DREAM для конкурса Alexa Prize 3 .

В 2020 команда проекта повторно прошла отбор на участие в конкурсе Alexa Prize Socialbot Grand Challenge 4. На данный момент команда активно работает над созданием бота и проходит необходимые стадии тестирования системы на ресурсах Amazon. Всю актуальную информацию можно найти на официальной странице проекта.

Технологический конкурс Up Great ПРО//ЧТЕНИЕ

В декабре 2020 года завершился первый цикл конкурса Up Great ПРО//ЧТЕНИЕ по созданию ИИ-системы для проверки сочинений школьников. В соревновании приняли участие более 180 команд, из которых до испытаний были допущены 11 коллективов разработчиков, показавших эффективные решения. В рамках испытаний ИИ-ассистенты проверили по 500 эссе и сочинений ЕГЭ на русском языке и столько же на английском. Автоматизированная платформа сопоставила тексты, проверенные ИИ-ассистентами и реальными педагогами, и выявила наиболее качественные решения.

Наша команда НейроЧтение заняла первое место в номинации Грамматика.Eng. В работе над решением сложной составной задачи по проверке эссе на английском языке мы использовали фреймворк DeepPavlov Agent.

Наша команда на удаленке!Наша команда на удаленке!

DeepPavlov Community

Для удобства общения с пользователями ранее был создан форум разработчиков, позволяющий найти и задать вопросы по теме фреймворка. Так что если у вас есть вопросы, то сразу пишите туда. А для тех, кто использует DeepPavlov Dream, дополнительно создана группа в Telegram.

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

А если вы просто хотите быть на волне DeepPavlov, то подключайтесь к каналу новостей в Telegram.

DeepPavlov Workshops

На 2х летии библиотеки мы обещали вам больше туториалов и воркшопов. За прошедший год мы постарались исполнить обещание, и по итогу мы участвовали:

И это только часть, но самая весомая.

Учебные курсы

Немаловажную часть нашего Community занимают образовательные программы. Так в весеннем семестре 2020 года мы организовали курс Глубокое обучение в обработке естественного языка. Темами занятий являлись построение диалоговых систем, способы оценки диалоговой системы с возможностью генерации ответа, различные фреймворки диалоговых систем и др. В курсе суммарно приняли участие более 800 человек с разных регионов России, а защитили успешно проекты порядка 100 (самый масштабный для нас курс выдался). Кстати, подробную информацию об итоговых проектах можно найти в статье на Хабр.

Также в весеннем семестре совместно с АНО ВО Школа анализа данных был проведен курс Advanced Topics in Deep Reinforcement learning, проходивший в формате reading group. В рамках курса было проведено 13 занятий, где исследовались и анализировались современные подходы по теме обучения с подкреплением. Для успешного завершения курса участникам также необходимо было выполнить итоговый проект. Если вам интересна тема RL, обязательно посмотрите запись курса.

Call to Action

Также мы хотели бы поблагодарить всех контрибьюторов за ценный вклад в код в этом году. В 2020 году более 20 человек впервые добавили код в DeepPavlov. Большое спасибо всем вам!

Ну а если вы ML/NLP инженер и разделяете нашу внутреннюю страсть к расширению границ разговорного ИИ или просто хотите упростить использование инструментов NLP/NLU сообществом, мы приглашаем присоединиться к нашему Open Source Community.

Узнать больше про community можно в статье блога, а если хочется внести свой вклад прямо сейчас, то вам сюда.

DeepPavlov 3 года

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

В программе вечера 10 крутых докладов от сотрудников проекта DeepPavlov и приглашенные специалисты:

  • Булат Замалиев, Уполномоченный по технологиям ИИ в Татарстане расскажет, как DeepPavlov помогает в решении государственных задач

  • Диляра Баймурзина, исследователь и капитан Dream Team Alexa, расскажет как русской команде участвовать в международном конкурсе Alexa Prize Socialbot Grand Challenge

  • Дмитрий Сошников, Microsoft, поделится туториалом о том, как адаптировать DeepPavlov отвечать на вопросы по COVID-19

  • Роман Смирнов, Системный архитектор DeepPavlov, поделится, как можно организовать свою личную жизнь с помощью ИИ

  • Щекин Роман и Тедеев Алан, исследователи НКО АО НРД, в своем докладе расскажут о том, как им удалось свести обработку новостей о корпоративных действиях к очень специфичному NER'у с помощью DeepPavlov

  • Фёдор Игнатов, старший инженер-разработчик, расскажет о том, какой станет версия 1.0 библиотеки DeepPavlov Library.

  • В докладе Александра Янчина, CTO в Leroma B2B-Platform, речь пойдет об использовании библиотеки DeepPavlov для определения сущностей из текста, и дальнейшей структуризации этой информации.

  • Данила Корнев, CPO проекта DeepPavlov, расскажет о том, как и почему лаборатория DeepPavlov пришла к открытому решению для создания Multiskill AI Assistants.

  • Татьяна Шаврина, AGI NLP Team lead в управлении экспериментальных систем машинного обучения в Sberdevices, поговорит с вами на Всю эту BERTологию

  • Ну и герой вечера Михаил Бурцев, руководитель проекта, держит в секрете тему выступления, чтобы удивить вас!

Всех ждем! Присоединяйтесь!


Спасибо всем, кто дочитал этот текст. Мы ждем ваши комментарии и вопросы по продуктам, которые мы создаем в проекте. Ждем ваши пулреквесты и предложения. И конечно же, ждем на нашем праздновании!

Подробнее..

Вредоносный ИИ реальность угрозы

20.02.2021 16:15:19 | Автор: admin

Привет, Хабр! Сегодня мы бы хотели обсудить такую щекотливую тему как вредоносный ИИ.

Вокруг искусственного интеллекта в целом витает очень много хайпа - и ещё больше алармизма, особенно в последнее время. Не последнюю роль в этом, конечно, сыграла поп-культура: достаточно вспомнить такие телесериалы последних десяти лет, как: В поле зрения (Person of Interest), Мир Дикого Запада (Westworld) и недавнее детище Ридли Скотта Взращённые волками (Raised by Wolves).

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

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

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

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

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

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

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

В 2018 и 2020 годах были опубликованы два разных аналитических документа, в которых рассматриваются возможности применения ИИ с вредоносными целями. Первый был главным образом теоретический. Исследование The Malicious Use of Artificial Intelligence: Forecasting, Prevention, and Mitigation" ("Злонамеренное использование искусственного интеллекта: прогнозы, предотвращение и минимизация ущерба") явилась плодом усилий сразу 26 экспертов из 14 европейских и американских организаций, в том числе, Оксфордского, Кэмбриджского, Стэнфордского университетов, Фонда Электронного Фронтира и OpenAI.

Источником второго исследования стал Европол, и в нём описаны несколько случаев вредоносного применения ИИ на практике. И это о многом говорит.

Робот-хакер

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

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

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

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

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

Что, если такие атаки удастся автоматизировать и продвинутые атаки будут производиться машинами самостоятельно - так же, как это делают люди, только быстрее и эффективнее?

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

В каком-то смысле программный хакер уже реальность. в том же 2018 году IBM представила концептуальную модель вредоносного ПО, использующего технологии ИИ, - DeepLocker.

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

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

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

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

Подменить человека, обмануть человека

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

По данным Европола, на подпольных киберфорумах вовсю обсуждаются, тестируются и даже сдаются в аренду инструменты для взлома паролей и CAPTCHA на базе нейросети, такие как XEvil 4.0 (используется для взлома CAPTCHA на сайтах Яндекса), или PWnagotchi 1.0.0 (используется для взлома сетей Wi-Fi посредством атак деаутентификации).

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

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

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

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

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

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

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

Отдельная история - дипфейки. Убедительное поддельное видео с помощью ИИ уже вполне можно создать. Это не очень просто сделать, но реально.

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

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

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

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

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

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

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

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

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

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

Обмануть ИИ

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

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

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

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

Можно ли что-то с этим сделать?

Как ни странно это может прозвучать, но в конечном счёте всё упирается в этику. Пусть и в самом широком смысле.

В уже упомянутом сериале "В поле зрения" сквозной сюжетной линией трёх из пяти сезонов оказывается противостояние двух искусственных интеллектов (настоящих) и их создателей - людей.

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

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

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

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

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

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

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

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

Но кроме этого, поменяться должна и этика разработчиков любого ПО. На сегодняшний день существует консенсус, в рамках которого создатели программного обеспечения не несут ответственности за кибератаки, оказавшиеся возможными из-за ошибок в коде - соответствующие положения прописываются в пользовательских соглашениях. А почему возникают ошибки? - В частности потому, что программисты работают в условиях "внезапных дедлайнов" и неожиданных идей начальства, которые имеют большее значение, чем best practices безопасной разработки. Ниоткуда не следует, что подходы к разработке в области ИИ окажутся значительно совершеннее. А вот риски куда выше.

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


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

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

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

И, действительно, чем дальше, тем меньше оснований остаётся думать как-то иначе.

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

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

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

Тем более, что основной мишенью вредоносного применения ИИ является именно человек. А не технологические устройства.

Подробнее..

Перевод Как скоро цифровые люди захватят приложения для знакомств?

22.02.2021 12:20:43 | Автор: admin
Одежда, волосы, модная борода всё как у людейОдежда, волосы, модная борода всё как у людей

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

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

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

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

Эта пугающая перспектива не так уж далека, как мы думаем.

Просто зайдите в MetaHumans. С помощью нового инструмента от Epic Games the MetaHuman Creator можно создавать высококачественных цифровых людей за считанные минуты. MetaHuman Creator обещает дать каждому возможность создатьна заказ фотореалистичного цифрового человека.

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

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

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

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

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

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

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

Подробнее..

Как умные тележки покоряют супермаркеты в США

22.02.2021 18:08:55 | Автор: admin

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

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

В чем преимущества

Тележки выступают в роли умных весов. Вы кладете все свои личные вещи (например, кошелек) в корзинку спереди, под экран. А пакеты и сумки в основное отделение, и взвешиваете их. Дальше можно ехать и класть любой товар (скажем, овощи или фрукты) в тележку, можно прямо к себе в сумку. И продолжаете свой маршрут. За счет компьютерного зрения она понимает, что именно вы положили, и заодно взвешивает новый товар. Вам остается только подтвердить на экране, что это именно то, что вы купили. По задумке Kroger и Caper, так получается намного быстрее, чем подходить с фруктами к весам, а потом ещё и стоять там в очереди.

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

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

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

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

Умные тележки повышают доход магазина, потому что покупать становится быстрее и удобнее, а людям дают ещё больше рекламы, причем в неожиданном месте, к которому они еще не привыкли. К тому же, Caper использует ИИ не только для определения товаров, но и для подбора рекомендаций. Например, если вы положите хлопья, вам порекомендуют молоко, которое только сегодня привезли. Купите спагетти посоветуют соус. Положите вино выдадут сыр, который замечательно раскрывает его вкус. Устоять перед всеми этими предложениями невероятно сложно. Фирма заявляет, что средняя сумма покупок с её тележками увеличивается на 18 процентов.

Как это работает

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

Умные тележки обходятся супермаркетам недешево. Один такой аппарат стоит от $5000 до $10000 (обычная тележка меньше $100). Также они требуют более аккуратного обращения, и техников, которые периодически следят за обновлением ПО и чинят баги на местах.

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

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

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

По словам Caper, Kroger третья сеть супермаркетов за последний год, которая заключила с ними контракт на умные тележки. Стартап, основанный в 2017 году, пока не раскрывает названия других компаний-партнеров.

Альтернативы

Умная тележка в Amazon FreshУмная тележка в Amazon Fresh

Свои умные тележки для покупок развивают также стартапы Veeve из Сиэтла и Tracxpoint из Тель-Авива. Некоторые похожие системы уже работают в Китае и Японии. Ну а самым известным примером, конечно, является Amazon. Летом 2020-го она представила свои умные тележки Dash Carts в одном из своих магазинов в Вудленд-Хиллз (Калифорния). Отзывы клиентов были настолько положительными, что Amazon добавила их еще в пять магазинов в Калифорнии и в магазин Amazon Fresh в Иллинойсе.

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

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

Подробнее..

Hadoop мертв, да зравствует Hadoop! Или что новенького в Cloudera?

22.02.2021 18:08:55 | Автор: admin

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

Что новенького в Cloudera?

Пожалуй, начнём немного издалека для тех, кто не так активно следит за развитием проектов экосистемы Hadoop: компании Hortonworks и Cloudera объединились в 2019 году под общим названием Cloudera. С этого момента началась новая ветка в истории развития дистрибутива Hadoop, так как усилиями уже общей команды стартовала работа над новой сборкой, которая включила в себя всё лучшее из обоих миров. В 2019 году состоялся первый релиз нового дистрибутива Cloudera Data Platform (дальше - CDP), в который вошло более 50 лучших в своем классе инструментов с открытым исходным кодом для работы с большими данными.

Так что же такого интересного предлагает Cloudera Data Platform? В рамках платформы мы предоставляем корпоративное облако данных для данных любого типа, в любой инстраструктуре, от периферии до ИИ. CDP работает в различных средах: локальной, в частном и публичном облаке, или в гибридном варианте архитектуры.

Теперь более подробно о названиях всех вариантах дистрибутива. Версия для традиционной локальной инсталляции на железо называется CDP Private Cloud Base. Она является фундаментом для расширения локальной архитектуры до частного облака (поэтому и имеет такое название). Полноценная же архитектура частного облака, куда входит часть Base (уровень хранилища) и аналитические приложения на Kubernetes (уровень вычислений), называется CDP Private Cloud Plus/Max. С версией для публичных облаков всё проще - CDP Public Cloud. При этом это полноценный PaaS, тесно интегрированный с нативными сервисами большой тройки: AWS, Azure и GCP.

Благодаря единой панели управления, фреймворку Cloudera SDX (Shared Data Experience) и неизменному набору сервисов, работа с платформой выглядит одинаково, независимо от среды развёртывания, что позволяет реализовать полноценную гибридную архитектуру. При этом набор доступных сервисов позволяет работать сданными любого типа от периферии до ИИ с обеспечением безопасности корпоративного уровня (шифрование данных в пути и покое, полная керберизация кластера) и data governance:

Также в самом наборе инструментов появились интересные новинки:
- С декабря 2020 года для всех пользователей CDP стал доступен Spark 3.0, а добавление 3.1 запланировано на первую половину 2021.
- В конце лета прошлого года в дистрибутив был добавлен доработанный и готовый к работе в продуктиве Apache Ozone - S3 совместимое объектное хранилище, своего рода преемник HDFS, который закрывает многие из его слабых мест и позволяет делать гораздо более плотные конфигурации узлов (мы тестировали 350TB на узел - стабильная работа всех нагрузок).
- После приобретения компании Arcadia Data в стеке появился полноценный BI компонент Cloudera Data Visualization, работающий со всеми основными движками аналитики данных: Hive/Impala, Solr, Druid.
- Приобретение компании Eventador в 2020 году позволило добавить функционал аналитики потоковых данных с помощью SQL на базе Flink - теперь с потоками данных из Кафка можно работать как со стандартными таблицами в СУБД и создавать материализованные представления для, например, передачи трансформированных потоков обратно в Кафку.
- В начале этого года Cloudera объявила о включении проекта Apache Iceberg в дистрибутив, что позволит ещё более гибкоработать с огромными наборами данных благодаря снапшотам, поддержке эволюции схемы и возможностям откатов к предыдущим версиям по времени.

Изначально архитектура частного облака поддерживалась только на базе платформы Red Hat OpenShift, но в ближайшее времявыходит CDP Private Cloud Plus с поддержкой встроенного ванильного кубернетеса, что значительно упростит инсталляцию и ускорит внедрение гибридной архитектуры. Пользователи смогут быстрее начинать работу с данными, получат все преимущества облачной инфраструктуры, и при этом данные будут храниться в локальном ЦОДе.

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

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

А что же со всеми любимыми сборками HDP/CDH?

Эти дистрибутивы не будут обновляться и постепенно заканчивают свой жизненный цикл поддержки (HDP2x/CDH5x уже закончили с концом 2020 года, такая же судьба настигнет HDP3/CDH6в скором времени). Более того, репозитории даже этих версий уже не доступны для публичного доступа - для этого теперь также требуется лицензия.

В тексте упоминался ИИ, что платформа предлагает для работы с моделями МО кроме Zeppelin?

В дистрибутиве есть дополнительный компонент - Cloudera Machine Learning (также известный как Cloudera Data Science Workbench), отвечающий за организацию полного цикла работы над моделями МО. Это полноценная MLOps платформа на кубере с центральным репозиторием метаданных, версионированием моделей, возможностью совместной работы в любом IDE (Jupyter Lab/Notebook включён по умолчанию) и любыми библиотеками, безопасным соединением с основным кластером и возможностью внедрения готовых моделей как функций в бизнес-процессы через REST API.


Пожалуйста, оставляйте свои комментарии к статье, какие еще вопросы о наших продуктах и технологиях вам было бы интересно обсудить?

Подробнее..

Machine Learning news

22.02.2021 20:07:34 | Автор: admin

Дисклеймер:

Здесь я собираю новости абсолютно субъективно.

Часть новостей - новости только для меня и могли появиться довольно давно. Просто я заметил их только сейчас.

Я сопровождаю новость своим комментарием, в котором излагаю причину, почему эта новость интересна мне.

  1. Похоже, что Transformers from Hugginface

Подробнее..

Специфические задачи Data Science в Банке

28.02.2021 18:05:07 | Автор: admin
image

В течение последних пяти лет я проработал в Центральном Аппарате Сбербанка в Управлении Валидации моделей машинного обучения (machine learning, ML) и видел много узких мест, которые возникают при разработке и валидации моделей машинного обучения.

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



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

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

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

Устоявшаяся банковская практика



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

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

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

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

EL = PD*EAD*LGD

где EL expected loss, ожидаемые потери;
PD probability at default, вероятность того, что заемщику будет присвоен статус дефолта в течение следующего года, начиная с даты оценки;
EAD exposure at default, все те денежные средства, которые клиент на дату выхода в дефолт должен вернуть Банку, включая как выданную денежную сумму, так и проценты, штрафы и комиссии;
LGD loss given default, доля от общей задолженности заемщика перед банком, которую Банк себе уже не вернет. То есть это чистая потеря для Банка;

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

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

Нет никакой истинной метки класса


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

Мы плохо знаем своего клиента


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

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

Регулятор требует делать модели интерпретируемыми


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

Прокрустово ложе устоявшейся банковской практики


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

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

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

На чем спотыкаются продвинутые data-driven альтернативы



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

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

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

Почему бы для решения вышеописанной задачи не применить мощную нейросеть (то есть пресловутый искусственный интеллект)? Перечислю несколько мешающих этому обстоятельств:
Центробанк требует, чтобы модели участвующие в расчете достаточности капитала применялись в живом кредитном процессе. То есть именно эти модели должны применяться в принятии решений о выдаче кредитов, быть интерпретируемыми и проходить ряд обязательных валидационных тестов;
базы клиентских данных постоянно расширяются и дополняются. Например, относительно новыми видами данных является биометрия, веб-аналитика, аналитика мобильных приложений, скоринг социальных сетей. Добавление новых атрибутов происходит в динамике, а соответственно исторических данных по ним у нас практически нет;
продукты и процессы Банка постоянно видоизменяются и требуется перерасчет CLTV по клиентам и расчет NPV (net present value) по новым продуктам. А для того, чтобы построить модель приемлемого качества надо подождать несколько лет, накопить исторические данные и вычислить фактические значения CLTV или NPV на выборке из реальных заемщиков;

Итог:


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

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

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

Категории

Последние комментарии

© 2006-2021, personeltest.ru