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

Графы

Перевод Руководство по анализу Sysmon-угроз, часть 2. Использование данных из Sysmon-событий для выявления угроз

08.07.2020 16:06:56 | Автор: admin


Эта статья является первой частью серии по анализу Sysmon-угроз. Все остальные части серии:
Часть 1. Знакомство с анализом логов Sysmon
Часть 2. Использование данных из Sysmon-событий для выявления угроз (мы тут)
Часть 3. Углубленный анализ Sysmon-угроз с помощью графов

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

  1. Использование PowerShell для прямого доступа к гранулированной информации о процессах;
  2. Построение и визуализация иерархии процессов первый важный шаг в поиске угроз;
  3. Использование метаданных Sysmon для формирования важных метрик, полезных при углублённом расследовании угроз, таких как подсчёт частоты, с которой запускаются конкретные процессы.

Использование Get-Sysmonlogs


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

$events = Get-WinEvent  -LogName "Microsoft-Windows-Sysmon/Operational" | where { $_.id -eq 1 } foreach ($event in $events)  {    $ev = $event.Message -split "`r`n"    $jsons="{ "    foreach ($line in $ev) {        $line=$line -replace "\\","\\" `               -replace "\{"," " `               -replace "\}"," " `               -replace '"','\"' `               -replace "`n"," "         $line=$line -replace '(\s*[\w\s]+):\s*(.*)', '"$1":"$2",'        $jsons = $jsons + $line }         $jsons =$jsons + '"blah" : "blah" }'         ConvertFrom-Json -InputObject $jsons     }}

Весь код сейчас выложен на GitHub и вы можете его скачать и импортировать как Sysmon-модуль для собственного проекта. Единственная нестабильность связана с удалением нескольких неприятных символов скобки, бекслэши, символы конца строки, кавычки чтобы сделать вывод приближённым к JSON.
Итак, классическим сигналом нарушителя, копошащимся вокруг системы, является использование команды whoami, и зачастую следующей после hostname. Хакер (или, возможно, инсайдер), заполучивший чью-то учётную запись, хочет убедиться, что имперсонализация работает, поэтому он часто набирает вышеуказанные команды, как только оказывается на сервере жертвы. Для остальных же whoami и hostname это не те слова, которые они будут вводить в консоли собственной системы, даже если они когда-либо пользуются командной строкой.

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

Обычно, когда хакер проникает в сеть и получает доступ к командой строке, она представляет из себя устаревшую cmd кстати, именно так и происходит в случае взлома при помощи psexec или smbexec. Используя вывод get-symonlogs, можно отловить процессы whoami, которые были порождены этими устаревшими шеллами, и это будет хорошим доказательством угрозы.

Внимание: Whoami запустился через устаревший шелл cmd


Внимание: Whoami запустился через устаревший шелл cmd


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

Азы структур данных: списки и графы


Логи Sysmon не только предоставляют нам командную строку родительского процесса, но и идентификатор этого процесса!

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

Сначала я думал, что мне придётся сдувать пыль с моей копии Структуры данных для поэтов и су-шефов, но тут меня выручили интернеты. Я наткнулся на шикарную коллекцию базовых алгоритмов Дага Финке (Doug Finke) на Gihub, написанную на PowerShell. Спасибо тебе, Даг!
После преодоления некоторой кривой обучения, я смог использовать его алгоритмы для структуризации моих событий Sysmon. Я построил структуры данных в виде списка и графа, а затем, с использованием API, написал PowerShell-функцию поиска команды и вывода иерархии процесса. Круто.

Я назвал её show-threat-path. Она осуществляет поиск в глубину по иерархии процесса и выводит имена приложений и ассоциированные с ними команды для корневого приложения, указанного в качестве входного параметра. В качестве моего первого теста я поискал по whoami.exe. И вот что увидел:

Иерархия процессов: процесс 2452 выглядит подозрительным!


Иерархия процессов: процесс 2452 выглядит подозрительным!


Дополнительный бонус тому, кто заметил на выводе выше, что whoami, ассоциированный с процессом 2452, был вызван через устаревший шелл cmd, который уже в свою очередь был запущен exe-файлом со странным именем в папке Windows.

Хммм. Если вы знакомы с механиками удалённых вызовов psexec, описанными здесь , то мысленно должны уже бить в колокола. Но я расскажу вам маленький секрет: играя роль хакера, я предварительно запустил данный whoami с удалённого сервера Linux с помощью python-скриптов Impacket.

Целью является демонстрация того, что с помощью обогащённых Sysmon логов и небольшой порции PowerShell, можно приготовить вполне практичную утилиту по выявлению уязвимостей, как я это только что проделал с show-threat-path.

Охота на угрозы с помощью направленных графов


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

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

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

#Let's graph it!!!$gv = New-Graph -Type BiDirectionalGraph # PSQuickGraphforeach ($e in $g.getAllEdges() )  { $g from Doug Fink's functions    $vs= $e.startvertex   $ve= $e.endvertex    PSQuickGraph\Add-Edge -From $vs.value.Key -To $ve.value.Key -Graph $gv |Out-Null}Show-GraphLayout -Graph $gv

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

GraphViz:Библиотека PowerShell для визуализации иерархий процессов


GraphViz: Библиотека PowerShell для визуализации иерархий процессов


Что это даёт? По сути это графический способ выявлять угрозы. Вместо того, чтобы искать определённую сигнатуру текста, как мы это делали раньше с командой show-threat-path, теперь мы можем попытаться найти аномалии на графе.

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

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

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

Волновой анализ как метод ведения споров

17.06.2020 02:23:57 | Автор: admin

О чем эта статья


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



Есть ли смысл в спорах


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


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


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


Терминология, используемая в статье

Здесь краткое описание терминов, вводимых и поясняемых в статье.


  • Волновой анализ метод нахождения ответа на вопрос, является ли утверждение истинным или ложным с использованием логических цепочек, основанных на операциях опровержения и дополнения (см. ниже).
  • Волновой граф. Логические цепочки волнового анализа могут быть представлены в виде графа. Вершины графа это утверждения. Ребра графа это связи между утверждениями. Существует 3 основных вида связи между вершинами волнового графа:
    • прямое опровержение
    • косвенное опровержение
    • дополнение
      все остальные связи, необходимые в волновом анализе, могут быть представлены через эти три.
  • Корневое утверждение (корневая вершина) анализируемое основное утверждение, является корнем волнового графа. В рамках описанного подхода мы ожидаем, что это утверждение может быть только строго истинным или строго ложным.
  • Прямое опровержение. Предположим у нас есть два утверждения A и B. Если истинность утверждения B приводит к тому, что утверждение A ложно, то это называется прямым опровержением и обозначается как A --п-> B. Символ --п-> значит "ложно потому, что". На графе прямое опровержение обозначается как сплошная линия со стрелкой от A к B.
  • Косвенное опровержение. Отличается от прямого лишь силой связи. Если в случае прямого опровержения истинность B однозначно приводит к ложности A, то в случае косвенного опровержения истинность B лишь увеличивает вероятность истинности A. Обозначается как A --к-> B. Символ --к-> значит "с некоторой вероятностью ложно потому, что". На графе обозначается как пунктирная линия со стрелкой от A к B.
  • Дополнение (или отрицание). Это обычное логическое отрицание, то есть если B истинно, то A ложно и, наоборот, если B ложно, то A истинно. Будем обозначать это как A<->B. Легко показать, что если A<->B, то B<->A. На графике будем обозначать это сплошной линией с двумя разнонаправленными стрелками.
  • Прямое подтверждение. Если B истинно, то А тоже истинно. Легко увидеть, что это может быть представлено как A<->C--п->B. (C просто дополнение А, и может быть выражено как "не А").
  • Косвенное подтверждение. Если B истинно, то вероятность того, что А тоже истинно увеличивается. Легко увидеть, что это может быть представлено в виде A<->C--к->B.
  • Волновые логические цепочки (волны). Если следовать стрелкам волнового графа, то мы получим "волну" утверждений, последовательно опровергающих предыдущее в цепочке утверждение и, соответственно, поочередно опровергая и подтверждая корневое утверждение.
  • Сходимость (разрешимость) волнового графа. Считается, что волновой граф сходится (или является разрешимым), если его анализ приводит к однозначному выводу о ложности или истинности корневого утверждения.
  • Тупиковые вершины. Это утверждения, которые не опровергаются ни одним другим утверждением. На графе из таких вершин не исходит ни одной стрелки (в том числе и двунаправленных).
  • Циклы. Это любые циклы в волновых логических цепочках. Самые простые и фундаментальные циклы это циклы, состоящие из двух элементов: дополнение и взаимное опровержение. Наличие неразрешимых циклов является следствием скрытой информации.
  • Опорные точки. Это минимальный набор самосогласованных утверждений (в идеале одно), в которые человек верит, что позволяет графу сойтись. В зависимости от того, с каким знаком эта вера (верит ли он, что эти/это утверждение истинно или ложно) граф сходится либо с положительным, либо с отрицательным результатом. На полном волновом графе (см. определение ниже) эти вершины являются частью неразрешимых циклов (скрытая информация).
  • Причинный анализ. Анализ основанный на причинно-следственных связях.
  • Причинный граф. Этот граф можно получить из волнового, если следовать по волновым логическим цепочкам от опорного утверждения в сторону корневого, но только по утверждениям, находящимся в той же фазе, что и опорное.
  • Абсолютно полный волновой граф. Волновой граф, учитывающий все волновые зависимости. Этот граф скорее теоретический, потому что на практике нет необходимости рассматривать все "волны".
  • Полный волновой граф. Волновой граф, включающий все волновые логические цепочки с "разумной" (соответствующей контексту дискуссии) степенью глубины анализа.
  • Основная аксиома волнового анализа: "всегда есть скрытая информация, не позволяющая однозначно разрешить полный волной граф".

Волновой анализ


Связи между утверждениями


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


  • Независимость (А не зависит от В). Истинность или ложность утверждения A никак не зависит от истинности или ложности утверждения B.
  • Прямое опровержение. Если утверждение B истинно, то утверждение A ложно. Будем обозначать такую связь, как A --п-> B. Суть в том, что стрелка --п-> фактически заменяет выражение "ложно потому, что", то есть A --п-> B это краткая запись выражения "A ложно потому, что B (истинно)". На графе эту связь мы будем обозначать сплошной линией со стрелкой от А к B:


    Прямое опровержение
    Замечание

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

    Пример

    Утверждение А: "Вчера в 7 часов вечера Алиса была дома".
    Утверждение B: "Вчера в 7 часов вечера Алиса была в театре".
    A --п->B: "Вчера в семь часов вечера Алиса не была дома, потому что она была в театре."

  • Дополнение (логическое отрицание). Если утверждение B истинно, то утверждение A ложно, и если утверждение B ложно, то утверждение A истинно. Будем обозначать это как А <-> B. Легко показать, что если А <-> B, то B <-> A. На графе эту связь мы будем обозначать сплошной линией с двунаправленной стрелкой:
    Дополнение
    Пример

    Утверждение А: "Вчера в 7 часов вечера Алиса была дома".
    Утверждение B: "Вчера в 7 часов вечера Алисы НЕ было дома".
  • Прямое подтверждение. Если утверждение B истинно, то утверждение А тоже истинно. Будем называть это прямым подтверждением. Легко показать, что такая зависимость составляется из предыдущих зависимостей: A<->C--п-> B. В этом случае если B истинно, то C ложно, а значит A истинно. На графе это может быть представлено в виде
    Прямое подтверждение
    Пример

    Утверждение А: "Вчера в 7 часов вечера Алиса была дома".
    Утверждение B: "Вчера в 7 часов вечера Боб был в гостях у Алисы, и видел её дома".
    A <->C--п->B: "Вчера в семь часов вечера Алиса была дома, потому что Боб в это время был у нее в гостях и видел её там."
    Дословно же эту запись можно представить, как "Вчера в семь часов вечера Алиса не была вне дома, потому что Боб был у нее в гостях и видел её дома".
  • Косвенное опровержение. Если утверждение B истинно, то вероятность того, что утверждение А ложно увеличивается. Будем называть это косвенным опровержением и обозначать как A --к-> B. На графе будем обозначать это пунктирной линией со стрелкой от A к B:


    Косвенное опровержение
    Замечание

    При этом, если утверждение B ложно, то это может ничего не значить для А.
    Отличие от прямого опровержения в силе связи. Если В истинно, то это еще не значит, что А ложно, но говорит нам о том, что вероятность того, что А ложно увеличивается.

    Пример

    Утверждение А: Вчера в 7 часов вечера Алиса была дома".
    Утверждение B: "Вчера в 7 вечера к Алисе приходил Боб, звонил в дверь, но никто не открыл".
    A --п->B: "Вчера в семь часов вечера Алисы скорее всего не было дома, потому что в это время приходил Боб и звонил в дверь, но никто не открыл". Но в действительности Алиса могла быть дома, но по каким-либо причинам не могла или не хотела открывать.

  • Косвенное подтверждение. Если утверждение B истинно, то вероятность того, что утверждение А тоже истинно увеличивается. Будем называть это косвенным подтверждением. Легко показать, что такая зависимость составляется из предыдущих зависимостей: A<->C--к-> B. В этом случае, если B истинно, то вероятность того, что C ложно увеличивается, а значит вероятность того, что A истинно тоже увеличивается. Отличие от прямого подтверждения опять-таки в силе связи (как и для косвенного опровержения). На графе это может быть представлено как
    Косвенное подтверждение
    Пример

    Утверждение А: "Вчера в 7 часов вечера Алиса была дома".
    Утверждение B: "Вчера в 7 часов вечера соседи слышали звуки пианино из квартиры Алисы".
    A <-> C --к->B: "Вчера в семь часов вечера Алиса скорее всего была дома, потому что соседи слышали звуки пианино из ее квартиры". Но, в действительности, это ведь могла быть просто запись.

Таким образом мы ввели 3 основных вида зависимости утверждения A от утверждения B:


  • прямое опровержение
  • косвенное опровержение
  • дополнение (логическое отрицание)

Все остальные необходимые для анализа комбинации можно получить из этих 3х.


  • в случае нескольких опровержений одного утверждения, они объединяются логическим "или". На данном графе показано, что N ложно потому что M1 или M2 или Mk (истинно)


    опровержение
    Замечание

    Возможна комбинация прямых и косвенных опровержений.

  • если мы хотим показать аргументы, поддерживающие наше утверждение, то, как уже было замечено выше, это можно сделать через опровержение дополнения. Так, например, если мы имеем утверждение N1 и аргументы M1, M2, .., Mk, где M1 косвенное подтверждение, а M2 и Mk прямые, то это будет выглядеть следующим образом



Отелло

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


Волновой граф


Предположим, что происходит обсуждение истинности/ложности утверждения A. Утверждение А должно быть сформулировано таким образом, что возможен один и только один вариант А истинно или А ложно.


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


  • утверждение А будет корнем нашего графа
  • вершинами графа являются утверждения, поддерживающие или опровергающие корневое утверждение. Для удобства мы будем нумеровать вершины
    Замечание

    В данной статье мы будем использовать положительные числа, если утверждение является подтверждающим наше корневое утверждение, и отрицательные числа если опровергает, но это, конечно, необязательно (обязательным является однозначность нумерации). При этом номером +0 будет обозначена корневая вершина, а -0 её дополнение. Конечно, это всего лишь обозначение, поэтому тот факт, что +0 = -0 ничему не мешает.
  • ребра графа отражают перечисленные виды зависимости и, соответственно, у нас всего лишь 3 вида ребер

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


Пример волнового графа. Отелло

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



  • корнем этого графа является утверждение "Дездемона изменила Отелло"
  • далее вниз мы можем наблюдать волновые логические цепочки (волны). Цвет ближе к голубому (мы будем называть его голубым) является подтверждением того, что "Дездемона НЕ изменяла Отелло", а цвет ближе к желтому (мы будем называть его желтым), является подтверждением противоположного, а именно, что "Дездемона изменила Отелло". Мы видим чередование голубых и желтых вершин
  • мы знаем, что истинными являются все голубые утверждения, Отелло же на момент убийства был уверен в том, что истинными являются все "желтые утверждения"
  • рассмотрим примеры всех возможных зависимостей:
    • прямое опровержение
      +0 --п-> -2: утверждение, что "Дездемона изменила Отелло" ложно потому, что "Дездемона говорит, что она не изменяла". Если то, что говорит Дездемона правда, то, конечно же, это прямое опровержение того, что Дездемона изменила
    • косвенное опровержение
      +0 --к-> -3: утверждение, что "Дездемона изменяла Отелло" ложно потому, что "Дездемона любит Отелло". Действительно, если она действительно любит Отелло, то это еще не значит, что она ему не изменяла, но этот факт увеличивает вероятность того, что Дездемона верна Отелло
    • дополнение
      +0 <-> -0: "Дездемона изменила Отелло" и "Дездемона не изменяла Отелло".
    • прямое подтверждение
      +0 <-> -0 --п-> +3: "Дездемона изменила Отелло" потому, что "Кассио сам рассказал об этом". Очевидно, что, если Кассио рассказал правду, то это прямое подтверждение
    • косвенное подтверждение.
      +0 <-> -0 --к-> +5: "Дездемона изменила Отелло" потому, что "Кассио говорил о связи с Дездемоной во сне". Понятно, что даже если это и правда, и Кассио говорил об этом во сне, то это еще не значит, что Дездемона изменила. Однако, это все же увеличивает вероятность этого
  • мы можем рассмотреть отдельные волновые логические цепочки (волны). Для этого нужно просто следовать стрелкам. Помним, что каждая однонаправленная стрелка от вершины N к вершине M значит, что "N ложно потому, что M (истинно)". Так же у нас может быть конструкция с подтверждением (описана выше). Например, рассмотрим логическую цепочку +0 <->-0 п-> +3 п-> -6 п-> +13 п-> -14 к-> +20 п-> -15 <-> + 21 --к-> -19 <->+25:
    • +0 <->-0 п-> +3: Дездемона изменила Отелло, и доказательством является то, что Отелло слышал разговор Кассио с Яго, где Кассио говорил о связи с Дездемоной
    • +3 п-> -6: но это не так, потому что Отелло слышал только часть разговора и неправильно интерпретировал его
    • -6 п-> +13: интерпретация верна, Яго подтвердил верность интерпретации
    • +13 п-> -14: Яго лжёт
    • -14 к-> +20: У Яго нет мотива обманывать Отелло
    • +20 п-> -15: Нет, у Яго есть мотив. Он обижен на то, что Отелло назначил не его заместителем, а Кассио
    • -15 <-> + 21 --к->-19: Яго обижен на Отелло и держит на него зло потому, что он злой и порочный человек
    • -19 <->+25: Это не так, потому что Яго хороший и честный человек
      Зачем мы закончили --19 <-> + 25? Потому, что мы не можем точно знать (Отелло не может), каким человеком является Яго, злым или хорошим. У нас нет аргументов. Такая же ситуация бывает и в споре, когда оппоненты просто упираются в "да нет" без какой-либо аргументации. Это и есть одна из опорных точек.
      Замечание

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

Анализ волнового графа


Сходимость графа


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


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


Тогда утверждение M, для которого M --п-> N будем называть утверждением, опровергнутым N.


Ниже приведен алгоритм разрешения волнового графа.


  • найдем (если есть) тупиковую вершину N
  • найдем все утверждения, опровергнутые N. Пометим эти вершины и тупиковую вершину N как "удаленные". Все стрелки, ведущие к ним и от них, также пометим как "удаленные". Мы не учитываем их в дальнейшем анализе
  • все вершины, которые не входят в волновые логические цепочки с началом в корне (потеряна логическая связь с корневым утверждением) помечаем как "удаленные". Все стрелки, ведущие к ним и от них, также пометим как "удаленные". Мы не учитываем их в дальнейшем анализе.
  • если для двух вершин N и M таких что N<->M вершина M помечена как удаленная, то вершина N становится тупиковой (доказанной), и все стрелки ведущие из нее (все опровержения) помечаются как удаленные
  • повторяем этот процесс до тех пор, пока не кончатся тупиковые вершины
  • когда тупиковые вершины закончатся у нас возможны 3 варианта:
    • корневое утверждение будет помечено как "удаленное". Тогда данный граф считается решенным. Наше корневое утверждение оказалось ложным
    • осталось только корневое утверждение. В этом случает данный граф считается решенным. Наше корневое утверждение оказалось истинным
    • все остальные варианты. В этом случае мы можем утверждать, что существует скрытая информация, которая не позволяет нам определить, является утверждение ложным или истинным
      Замечание

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

Вернемся к примеру с Отелло.


Пример сходимости. Дездемона виновна

Мы видим, что в том виде, как представлен волновой граф, он неразрешим. У нас только 5 тупиковых вершин: +19, +18, +17, +11 и +8. Вершины +8, +18 и +19 отражают косвенные связи, поэтому не интересны. Но утверждение +17 ("Дездемона умеет лгать, потому что она уже лгала отцу, когда убежала к Отелло") является действительно тупиковым. Это приводит к тому, что вершины +17 и -11 в соответствии с нашим алгоритмом мы помечаем как удаленные. Также вершину +11 разумно признать тупиковой. Нельзя полагаться на показания Эмилии о том, изменяла ли Дездемона. Она принципиально не может знать правды. Может предполагать, догадываться, но она не знает. Поэтому вершины +11, -1 можно удалить.


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


Так почему же восприятие Отелло сложилось таким образом, что он был уверен, что Дездемона виновна?


Давайте посмотрим, что произойдет, если вершину +23 ("Яго говорит правду") или +25 ("Яго хороший и честный человек") считать истинной? Как только мы принимаем это, то


  • вершина +23 (или +25) становится тупиковой (нет опровержений)
  • это приводит к тому, что ее и вершину -14 (со всеми связями) мы помечаем как удаленные
  • вся ветка с вершинами +20, -16, -15, +21, +25, -19 теряется связь с корнем и поэтому также удаляется со всеми связями
  • далее, вершины +15, +14, +13 становятся тупиковыми
  • таким образом, удаляются они (+15, +14, +13), а также те вершины, которые были опровергнуты ими, а именно, -8, -7, -6 и те вершины, которые потеряли "связь" c корневым утверждением: +22, +26
  • теперь тупиковой вершиной является вершина +6. Важная вершина, потому что она говорит о том, что Дездемона подарила платок Кассио. Это приводит к тому, что эта вершина, а также вершины -7, +7, +8, +9 вместе со связями могут быть удалены из графа
  • тогда вершина +2 становится тупиковой, что позволяет нам удалить вершины +2, -3, +1, -5, -26
  • следующим шагом мы можем удалить ветку, которая потеряла связь с вершиной: +12, -13, +19, +18, и мы получаем следующий граф:




И этот граф формально опять-таки является неразрешимым. Причина в циклах:


  • -4 <-> +4: Кассио лжёт или говорит правду? Отелло не знает ответа. Да, он слышал разговор, да, "честный Яго" подтвердил, что он не ослышался и правильно понял суть, но, возможно, Кассио просто бахвалился
  • -10 <-> +16: Дездемона порочна или добрая и чистая душе? Это та дилемма, которую Отелло и пытается решить. Но этот цикл не так уж важен, потому что вершина -10 является лишь косвенным опровержением вершины +10
  • +10 <-> -12: Дездемона лжет или говорит правду?

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


Этот анализ показывает, что, как только Отелло принимает, что Яго говорит правду, и это перевешивает его доверие Дездемоне (соответственно, перестает ей доверять), так сразу граф формально сходится.


Пример сходимости. Дездемона невинна

Итак, Отелло убил Дездемону в полной уверенности в ее порочности. Но сразу после ее смерти его видение ситуации развернулось на противоположное. Что же произошло, и как это объясняет волновой анализ?


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


Теперь к нашему графу мы можем добавить вершины -20, +24, -21, -17, -18 (здесь представлена только правая часть графа):



При этом -20<->+24 --п->-17 и -20<->+24 --п->-18 мы можем отнести к прямым доказательствам:


  • -20<->+24 --п->-17: "Эмилия рассказала правду про платок, что показывает, что Яго лгал", и это правда потому, что "Яго так напуган словами Эмилии, что убивает её"
  • -20<->+24 --п->-18: "Эмилия рассказала правду про платок, что показывает, что Яго лгал", и это правда потому, что "Эмилия находится под сильным впечатление от смерти Дездемоны и не способна лгать"

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


Проанализируем наш граф теперь.


  • вершины -17 и -18 на нашем графе тупиковые. Значит, эти вершины, а также вершины +24 и +25 и все их связи мы помечаем как удаленные. Также мы удаляем вершину -21 вместе со связями, т.к. она потеряла связь с корнем графа
  • теперь вершины -19 и -20 становятся тупиковыми. Значит, эти вершины, а также вершины +6, +23 и +20 и все их связи можно удалить. Также удаляем и вершины, не имеющие пути к корню, то есть -16, -15, +21, -8, +15, + 22
  • теперь тупиковые вершины -14, -7. Удаляем их и вершины, которые они прямо опровергли: +13, +5

Получаем граф (только правое крыло):



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


На следующем шаге это приводит к тому, что вершина +3 и с ней вершины -4 и +4 со всеми связями также удаляются, и вершина -0 остается без прямых и косвенных опровержений. Это значит, что все аргументы, как прямые, так и косвенные, подтверждающие, что Дездемона изменила были опровергнуты.


Что мы имеем в данной точке нашего анализа?


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

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


Почему же граф сошелся для Отелло? Для этого давайте взглянем на его левую часть. Теперь все просто, вершины -13 ("Эмилия говорит правду") и -12 ( "Дездемона говорит правду") становятся тупиковыми (доказанными) в восприятии Отелло, и после этого граф легко разрешается.


Итак, что же произошло? Теперь, когда Отелло перестал верить Яго, он автоматически стал верить Дездемоне и Эмилии (что, в принципе, иррационально), и это привело к тому, что граф сошелся, но уже с противоположным результатом. Фаза восприятия мавра сдвинулась на 180 градусов.


Иррациональные элементы и опорные точки


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


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


Что же произошло? Отелло получил от Эмилии некоторую дополнительную информацию, но он даже не проверил, правдива ли она, а ведь Эмилия могла тоже обманывать, и холодный анализ требует построения "волны" доказательств/опровержений и для фактов, предоставленных Эмилией. Но мавр опять поверил. Да, в соответствии с той реальностью, которую предоставил нам Шекспир, в этот раз он не ошибся. Но, в жизни могло быть все не так, и на самом деле именно Яго мог бы быть истинным положительным героем, преданным и честным, попавшем в паутину интриги.


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


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


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


Пример 1

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

Замечание

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

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


Пример 2

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

Причинный анализ

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


Пример

Вернемся к нашему примеру с Отелло.


Рассмотрим два утверждения -14 ("Яго лжет") и его дополнение +23 ("Яго говорит правду").

Если утверждение +23 становится опорным (Отелло верит, что Яго говорит правду), то это опорное утверждение становится корнем для причинных логических цепочек, приводящих к тому, что Отелло начинает считать, что "Дездемона изменила Отелло". Например, цепочка +23 -> +13 ->+3 ->+0 (обратите внимание, что все вершины желтого цвета), выделенная желтыми стрелками на графе выше, означает, что

"Яго говорит правду, поэтому правда то, что Кассио сам лично рассказывал о связи с Дездемоной, поэтому это правда, что Дездемона изменила Отелло".

В случае же если -14 является опорным ("Яго лжет"), то фаза меняется на противоположную. Рассмотрим цепочку -14 -> -6 ->0 (все вершины голубые):

"Яго лжет, поэтому правда то, что мавр слышал лишь часть диалога и неверно интерпретировал этот диалог, поэтому Дездемона не изменяла Отелло".

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


Аксиома волнового анализа


Введем понятия полного и абсолютно полного волновых графов.


Абсолютно полный волновой граф это граф, на котором учтены все возможные волны.


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


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


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


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


Замечание

Возникает вопрос, где же проходит эта граница контекста беседы? На каком уровне глубины анализа разумно остановиться? Что мы готовы принять за аксиомы (и, следовательно, на веру) в нашем анализе?

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


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

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


  • всегда есть скрытая информация, не позволяющая однозначно разрешить полный волной граф

Следствие:


  • если произошло разрешение волнового графа, то это говорит о наличии иррационального элемента, ошибке логики или о неполноте волнового графа

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


Пояснение на примерах

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


Пример 1. Заседание суда. Алиса утверждает, что была 1-го апреля 2020 года в 6 вечера дома. И суду надо понять не обманывает ли Алиса. В контексте данного анализа нас интересует информация свидетелей. Например, Боб и Ева были в этот день у нее гостях и подтверждают это. Здесь полный граф предполагает, как минимум, следующие волны


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

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


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


Говорит ли введенная аксиома о том, что нельзя докопаться до истины?


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


Просто к логике, к знанию, к информации, нужно добавить что-то еще.


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


Замечание

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

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

Типичные ошибки при ведении дискуссии


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


Приведем список типичных ошибок ведения споров:


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

Применение волнового анализа на практике


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


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


Так что же может дать нам волновой анализ на практике?


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


    Замечание

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

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

Принципы волнового анализа


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


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


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

Подробнее..

Как нарисовать холдинг, цепочки владения и посчитать доли КИК

28.10.2020 18:06:09 | Автор: admin
В юридической практике корпоративных юристов относительно недавно (несколько лет назад) появилась необходимость составлять и подавать уведомления о контролируемых иностранных компаниях (КИК) в рамках ст. 25.13 НК РФ. Суть этой обязанности составить и подать документ, в котором будут отражены все связи общества в холдинге по цепочкам от текущего ООО (АО) в РФ до владельца- налогового резидента РФ КИК. Говоря проще, если офшором владеет россиянин (налоговый резидент РФ), а офшор российским ООО (даже через забор промежуточных ООО) более 25 % уведомлению быть. Изюминка в том, что подавать необходимо всем ООО (АО) в которых эта ситуация наблюдается и подавать как сведения о владении более 25%, так и последующие изменения доли владения своевременно, иначе штрафы (100 000 рублей по каждой компании в цепочке ст. 129.6 НК РФ). Так как холдинг (совокупность юр. лиц) организм живой и постоянные изменения долей владения неизбежны, за всем этим надо как-то следить, чтобы не насобирать штрафов. Как упростить работу в данном направлении, автоматизировать ее, посвящена данная статья. Статья также будет интересна с точки зрения графического представления связанных структур, например соц. сетей.


В данной статье не будем останавливаться на юридических аспектах подаваемых уведомлений о КИК, об участии в КИК, рассмотрим техническую сторону вопроса.
Бесспорно, если холдинг, о котором идет речь представляет себя простые структуры вида ООО->КИК->россиянин, то, что-то строить здесь с привлечением машины нецелесообразно, другое дело, если структура ветвится, двоится и нет числа этим сплетениям.
Рассмотрим несколько существующих графических решений, которые упростят работу.
Для удобства визуализации будет использоваться среда jupyter notebook и python.

Networkx.


Данное решение самое древнее из представленных и не может похвастаться своей интерактивностью. О данном пакете есть такая же древняя статья на Хабре.
Однако старое не значит плохое, и данный вариант один из наиболее удачных как в плане рисования, так и в вычислительном.
Установим и импортируем модуль через jupyter:
!pip install networkximport networkx as nx

Также импортируем иные доп. модули, которые помогут нарисовать фигуры:
from matplotlib import pyplot as plt%matplotlib inlineplt.rcParams.update({    'figure.figsize': (7.5, 7.5),    'axes.spines.right': False,    'axes.spines.left': False,    'axes.spines.top': False,    'axes.spines.bottom': False})

Построим с помощью networkx первую сеть:
from pathlib import Pathdata_dir = Path('.') / 'data'# Read edge listG = nx.read_edgelist('example.edgelist')# Draw network#pos = nx.spring_layout(G)pos = nx.spectral_layout(G)#pos = nx.planar_layout(G)nx.draw_networkx(G, pos)plt.gca().margins(0.15, 0.15)

Вот, что получилось:

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

Разберем код выше.
Импортировали модуль и указали откуда будем считывать данные на диске:
from pathlib import Pathdata_dir = Path('.') / 'data'

В текущей директории считали 'example.edgelist':
G = nx.read_edgelist('example.edgelist')

*example.edgelist это обычный текстовый файл вида:
# source targetИванов КИК1Иванов КИК2КИК1 КИК2КИК1 Ромашка_ОООКИК2 Ведро_АО

Значения записаны кто-кем владеет с пробелом между ними.
Далее определили как будет выглядеть сеть:
pos = nx.spectral_layout(G)

Если поменять на pos = nx.spring_layout(G), то она примет вид:

И это расположение, как ни странно, наиболее подходящее для более масштабных структур.
Наконец, нарисовали сеть, обозначив отступы:
nx.draw_networkx(G, pos)plt.gca().margins(0.15, 0.15)

Сохранить картинку просто:
plt.savefig('plot.png')


Как нарисовать сегмент в networkx.


#подграфикH = G.subgraph(['Иванов', 'КИК1', 'Ромашка_ООО'])plt.subplot(212) print("Подграфик:") nx.draw_networkx(H)

Здесь мы отступы не сделали, и названия уехали:


Networkx оперирует понятиями нод(nodes) и связей(edges) между ними. В нашей ситуации ноды это Иванов, КИК1, КИК2, Ромашка_ООО, Ведро_АО. А связи то, что находится в файле example.edgelist.
Посмотреть и то и другое можно просто, обратившись к методам G.nodes и G.edges:


Направленный график в networkx (Directed edge list).


Проясним немного построенную сеть, добавим стрелочки:
# Read edge listG = nx.read_edgelist(    str('example.edgelist'),    create_using=nx.DiGraph)pos = nx.spectral_layout(G)# Draw networknx.draw_networkx(G, pos, arrowsize=20)plt.gca().margins(0.15, 0.15)

Небольшие изменения позволили нарисовать более внятную картину кто кем владеет:

В коде, как можно заметить, изменения минимальны.

Следующий этап построение графика, где будут видны размеры пакетов владения.
Для этого надо познакомиться с понятием веса (weight) это третий основной параметр, с которым может работать networkx. Чтобы его включить в работу, в текстовый файл надо добавить эти самые веса, например так:
# source targetИванов КИК1 100Иванов КИК2 100КИК1 КИК2 50КИК1 Ромашка_ООО 100КИК2 Ведро_АО 100

Теперь заново построим сеть, используя уже веса и обозначим их на графике:
# Read edge listG = nx.read_weighted_edgelist(    str('example2.edgelist'))# Extract weightsweights = [d['weight'] for s, t, d in G.edges(data=True)]nx.draw_networkx(G,pos)labels = nx.get_edge_attributes(G,'weight')nx.draw_networkx_edge_labels(G,pos,edge_labels=labels)plt.gca().margins(0.15, 0.15)

*example2.edgelist это файл, который сформирован выше с весами.
Получим вот такую картину:


Кто кем и как владеет, networkx.


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

С помощью networkx сделать это можно следующим образом:
list(nx.all_simple_paths(G,'Иванов', 'Ведро_АО'))

В качестве первого аргумента идет нода-владелец, вторым нода, до которой мы будем строить пути.
Используя данный метод можно увидеть, что Ведром_АО Иванов владеет по следующим цепочкам:
[['Иванов', 'КИК1', 'КИК2', 'Ведро_АО'], ['Иванов', 'КИК2', 'Ведро_АО']]

Графически это подтверждается.
Узнать долю владения можно перемножив веса между соответствующими нодами: 1*0,5*1=0,5, 1*1=1. Доля более 25%, уведомление необходимо подавать.
В коде перемножение делается следующими костылями (более изящный метод пока не найден):
x=0b=0c=[]for i in list(nx.all_simple_paths(G,'Иванов', 'Ведро_АО')):        for a in i:                if x>len(i)-2:            pass                                else:                        b=int(nx.bidirectional_dijkstra(G, i[x],i[x+1])[0])#доля владения                                   x+=1            c.append(b/100)              print(c)import numpy as npprint(np.prod(c))

x=0b=0c=[]for i in list(nx.all_shortest_paths(G,'Иванов', 'Ведро_АО')):    for a in i:                if x>len(i)-2:            pass                              else:                        b=int(nx.bidirectional_dijkstra(G, i[x],i[x+1])[0])#доля владения                                   x+=1            c.append(b/100)              print(c)import numpy as npprint(np.prod(c))

В первом случае выведет долю 0,5, во втором 1.

Какие еще есть доступные варианты визуализации? Например, Netwulf.

Netwulf


Документация находится здесь.

Сама сеть интерактивна, в этом ее основной плюс. После установки python пакета, построим сеть:
import netwulf as nwplt.figure(figsize=(200,200))G = nx.read_weighted_edgelist(str('example2.edgelist'),create_using=nx.DiGraph)pos = nx.spring_layout(G)nw.visualize(G)

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

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


Еще один неплохой вариант подобной визуализации для python молодой проект webweb.

Webweb.


Документация здесь.
Строится сеть схожим образом:
from webweb import Webweb = Web(title='kitchen_sink')web.display.networkName = 'tree'web.display.networkLayer = 2web.display.colorBy = 'ring'web.display.sizeBy = 'degree'web.display.gravity = .3web.display.charge = 30web.display.linkLength = 15web.display.colorPalette = 'Greens'web.display.scaleLinkOpacity = Falseweb.display.scaleLinkWidth = Truefrom pathlib import Pathdata_dir = Path('.') / 'data'# Read edge listG = nx.read_edgelist('example.edgelist',create_using=nx.DiGraph)plt.figure(figsize=(200,200))# Draw networkpos = nx.spring_layout(G)Web(list(G.edges)).show()



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


Резюмируя, можно сказать, что развивающиеся потомки networkx netwulf и webweb хороши для построения быстрой картинки структуры небольшого холдинга. У обоих модулей есть режим freeze, чтобы заморозить ноды, которые слипаются в одну кучу в силу интерактивности графика. Однако даже используя их непросто работать с масштабными структурами, где количество нод больше 200.

Подножка от Минфина, перекрестное и кольцевое владение.


Все было бы совсем хорошо при построении подобных структур, если бы не одно но, которое портит всю картину. Это но заключается в том, что в холдингах общества владеют сами собой через другие юр. лица и это называется либо перекрестное либо кольцевое владение.
На картинках в письмах от Минфина (например от 02.07.2013 ОА-4-13/11912) это выглядит так.
Перекрестное владение:

Кольцевое:


Посмотрим, как определит связи networkx для схемы перекрестного владения участия D в B.
Создадим edgelist со связями:
# source targetD B 45B A 40A B 55E A 60

Построив сеть с весами, можно увидеть, что обратная связь между A и B не отражена:

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


Что с расчетами? Какова совокупная доля D в B?
Тут кажется все прозрачно, 45%
И networkx выдает при команде list(nx.all_simple_paths(G,'D', 'B')):
[['D', 'B']]
Но не все так просто.
Минфин говорит, совокупная доля D в B определяется по формуле:

И составит 57,69%.

Что делать? networkx бессильна?
Вовсе нет, networkx позволит выявить подобные ситуации, но вот формула расчета будет другой, согласно букве Закона.
Частично проблему можно снять, добавив в edgelist записи
A A
B B
Далее командой list(nx.nodes_with_selfloops(G)) можно посмотреть ноды с участием в самих себе, но при определении путей из D в B это все равно не учитывается.

jupyter тетрадка скачать здесь.
Подробнее..

Учиться, учиться, и ещё раз учиться?

01.06.2021 14:09:01 | Автор: admin

TLDR: крохотные модельки обошли модные графовые нейронки в предсказании свойств молекул.
Код: здесь. Берегите Природу.


image
ФОТО: Андерс Хеллберг для Wikimedia Commons, модель Грета Тунберг


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


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


Полученные результаты занимательны. В худшем случае простые модели (uGCN + degree kernel + random forest) показали счёт 54:90 против полноценно обученных GCN, в то время как реалистичный сценарий закончился разгромным реваншем 93:51, указывающим на то, что мы можем позволить себе почти бесплатные эмбеддинги, которые превосходят или показывают результаты на уровне полноценно обученных GCN в задаче предсказания свойств графа (например эффекта медикаментов: яд или лекарство) за долю стоимости. Простые модели обучались ~10 минут в то время как весь эксперимент продлился ~4 часа. Перейдём же к деталям и разберёмся с тем, что произошло!


Основные понятия


Многие из важных наборов данных об окружающем нас мире имеют связный характер: социальные сети, графы знаний, взаимодействия белков, всемирная паутина WWW и т.д. (просто несколько примеров) [1].


Граф, обыкновенно записываемый как G=(V, E) это математическая модель, множество множеств, состоящее из набора вершин V и множества рёбер E попарных связей e(i, j) между вершинами i и j. Расширением Графа является модель Граф со Свойствами (Labeled Property Graph), позволяющий задать вектор признаков xi для вершины i (мы также можем определять свойства для рёбер, однако это выходит за рамки сегодняшнего эксперимента). Графовая нейронная сеть [3] (GNN) это модель машинного обучения (параметрическая функция, которая подбирает, другими словами выучивает, параметры из данных), расширяющая возможности хорошо известного семейства алгоритмов, вдохновлённых биологией, до работы с неструктурированными данными в виде графов. На мой взгляд, передача сообщений это самая простая интуиция для понимания механики работы GNN и вполне оправдано обратиться к мнемоническому правилу 'скажи мне, кто твой друг и я скажу тебе кто ты'. Графовые свёрточные нейронные сети (GCN) очень подробно описал их изобретатель здесь (https://tkipf.github.io/graph-convolutional-networks/) и мне, право, непросто что-то ещё добавить к этой замечательной истории.


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


image


Многослойная GCN с фильтрами первого порядка.


Данные


Проведём серию экспериментов на общедоступных данных. Мы обратимся к (i) коллекции TUDatasets [4] и (ii) ограничим наше упражнение задачей бинарной классификации (предсказанием свойств) небольших молекул. Ещё одним условием нашего мероприятия будет (iii) использование графов с признаками вершин.


Заданные ограничения оставляют нам несколько наборов данных, широко используемых для сравнения современных алгоритмов. Вот наш итоговый список: AIDS, BZR, COX2, DHFR, MUTAG и PROTEINS. Все обозначенные наборы данных доступны как часть Pytorch Geometric [5] (библиотека для глубокого обучения на графах) в двух версиях: оригинальной и очищенной от дубликатов [6]. Итого у нас будет 12 датасетов.


AIDS Antiviral Screen Data [7]


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


Benzodiazepine receptor (BZR) ligands [8]


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


Cyclooxygenase-2 (COX-2) inhibitors [8]


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


Dihydrofolate reductase (DHFR) inhibitors [8]


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


MUTAG [9]


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


PROTEINS [10]


Энзимы и не-энзимы. В оригинальном наборе содержится 1113 молекул, по 3 признака на вершину. Очищенная версия 975 структур.


Дизайн Эксперимента


Мы устроим турнир!


Для каждого набора данных проведём 12 раундов обучения и тестирования.


В каждом раунде:


(1) псевдослучайным образом разделим данные в пропорции 80/20 в Pytorch Geometric (начиная со стартового параметра генератора random seed = 42 и увеличивая его на единицу в каждом последующем раунде), таким образом 80% точек данных (графов) будут использованы в качестве обучающей выборки, а оставшиеся 20% будут тестовой выборкой;


(2) обучим модели и оценим долю верных ответов (accuracy) на тесте.


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


Для GCN мы проводим 200 эпох обучения и тестирования со скоростью обучения learning rate = 0.01 и принимаем во внимание:
(А) среднее значение доли верных ответов для 10 финальных эпох обучения реалистичный сценарий;
(В) наибольшее значение доли верных ответов, достигнутое в процессе обучения (как если бы мы сохраняли промежуточное состояние для того, чтобы выбрать наилучшую модель впоследствии) наилучший сценарий для GCN (и наихудший для простых моделей);


(3) лучшей модели присуждается 1 балл;


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


Всего будет распределено 288 баллов: 12 датасетов 12 раундов 2 сценария.


Модели


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


import networkx as nximport numpy as np from scipy.sparse import csgraph# g - граф формате популярной библиотеки NetworkXnumNodes = len(g.nodes)degreeHist = nx.degree_histogram(g)# нормализуемdegreeHist = [x/numNodes for x in degreeHist]

Необученная графовая свёрточная нейронная сеть (uGCN) со случайной инициализацией весов 3 слоя с промежуточной нелинейной активацией (ReLU, т.е. f(x) = max(x, 0)). Аггрегация усреднением полученных после прямого прохода 64-разрядных векторов (эмбеддинги вершин) позволяет получить компактное представление графа. Это на самом деле очень просто.


A = nx.convert_matrix.to_scipy_sparse_matrix(g)

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


# A - матрица связности графа# X - матрица признаков вершин (np.array)D = sparse.csgraph.laplacian(A, normed=True)shape1 = X.shape[1]X = np.hstack((X, (D @ X[:, -shape1:])))

(код здесь приводится чтобы подчеркнуть очаровательный минимализм реализации метода)


Разберём его на части и пересоберём заново.


Использованная реализация uGCN выглядит так:


# A - матрица связности графа# X - матрица признаков вершин (np.array)# W0, W1, W2 - случайным образом инициализированные весаD = sparse.csgraph.laplacian(A, normed=True)# слой 0Xc = D @ X @ W0# ReLUXc = Xc * (Xc>0)# конкатенация признаков вершин с аггрегированной информацией соседейXn = np.hstack((X, Xc))# слой 1Xc = D @ Xn @ W1# ReLUXc = Xc * (Xc>0)Xn = np.hstack((Xn, Xc))# слой 2 - эмбеддинги вершинXc = D @ Xn @ W2# аггрегация усреднением - эмбеддинг графаembedding = Xc.sum(axis=0) / Xc.shape[0]

Комбинация DK и uGCN (Mix) конкатенацией представлений графа, полученных с помощью моделей DK и uGCN.


mix = degreeHist + list(embedding)

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


Графовая свёрточная нейронная сеть (GCN) полноценно обученный классификатор, состоящий из 3 свёрточных слоёв размерностью 64 с промежуточной нелинейной активацией (ReLU), агрегацией усреднением (до этого момента архитектура GCN очень похожа на uGCN), за которой следует слой регуляризации дропаутом (произвольным обнулением разрядов с вероятностью 50%) и линейный классификатор. Мы будем обозначать результаты модели, отобранные в наилучшем для GCN сценарии (B) как GCN-B, а модели в реалистичном сценарии (А) как GCN-A.


Результаты


После 144 раундов (12 датасетов * 12 раундов) сравнения качества предсказаний на отложенной выборке между простыми моделями и полноценно обученными графовыми свёрточными сетями 288 баллов распределились как:


147:141


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


image


Наборы данных, в которых простые модели побеждают: AIDS, DHFR(A) и MUTAG.


Например, DK собрала все 48 баллов для набора данных AIDS, демонстрируя отрыв более чем на 10% (абсолютное значение) от доли верных ответов полноценно обученной GCN.


image


Здесь побеждают GCN: BZR, COX2 и PROTEINS.


Индивидуальный зачёт:
90 GCN-B;
71 DK;
55 Mix (uGCN + DK);
51 GCN-A;
21 uGCN.


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


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


Выводы


Как видим, проведенный эксперимент подтверждает предположение о том, что в задаче предсказания свойств молекул мы можем позволить себе использовать почти бесплатные эмбеддинги, которые превосходят или показывают результаты на уровне полноценно обученных нейронных сетей. Наблюдения согласуются с вдохновляющими этот эксперимент результатами [2] в том, что концептуально метод Label Propagation очень похож на передачу сообщений в графовой свёрточной нейронной сети. Объяснение эффективности скорее всего следует искать в том, что на самом деле мощнее подбирать параметры фильтров для того, чтобы внутренние представления, выученные сетью стали линейно разделимыми, либо же просто использовать классификатор помощнее, как это сделано в рассмотренном примере.


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


image


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


Послесловие


В лекции открытого курса по графам знаний GCN названа Королевской Лазейкой Через Пространство Фурье, этот ярлык приклеился с тех пор, когда впервые выступил на публике с рассказом о силе графов и провёл первые эксперименты с классификацией картинок (как графов) для того, чтобы продемонстрировать мощь спектральных фильтров одной юной леди, запускавшей стартап в милой моему сердцу аэрокосмической области. Данная заметка появилась в результате того, что пару недель назад в реальной задаче на закрытых данных uGCN, вместе с простенькими моделями показали результат, который полноценно обученные GCN смогли превзойти всего на 2% (96 против 98) и мне вздумалось проверить вопрос о том, кто кого заборет ещё на каких-нибудь данных.


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


Перед тем, как ступать на очаровательный путь машинного обучения на графах, пожалуйста ознакомьтесь с основами этого дела. Значительные усилия прилагаются к тому, чтобы сделать новейшие достижения (да и классические методы тоже) доступными широкой аудитории совершенно бесплатно. Упомяну лишь несколько из таких инициатив: материалы и лекции стенфордского cs224w, площадку для тестирования качества алгоритмов Open Graph Benchmark [14] и недавнюю работу об основах геометрического глубокого обучения [15] методологию разработки новых архитектур нейронных сетей. Напоследок, ещё раз напомню о том, что начинать проекты машинного обучения стоит с простых методов, вроде ядер и необученных графовых свёрточных сетей достаточно часто эти модельки показывают неприлично хороший уровень.


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


image


Литература


[1] Kipf & Welling, Semi-Supervised Classification with Graph Convolutional Networks (2017), International Conference on Learning Representations;
[2] Huang et al., Combining Label Propagation and Simple Models out-performs Graph Neural Networks (2021), International Conference on Learning Representations;
[3] Scarselli et al., The Graph Neural Network Model (2009), IEEE Transactions on Neural Networks ( Volume: 20, Issue: 1, Jan. 2009);
[4] Morris et al.,TUDataset: A collection of benchmark datasets for learning with graphs (2020), ICML 2020 Workshop on Graph Representation Learning and Beyond;
[5] Fey & Lenssen, Fast Graph Representation Learning with PyTorch Geometric (2019), ICLR Workshop on Representation Learning on Graphs and Manifolds;
[6] Ivanov, Sviridov & Burnaev, Understanding isomorphism bias in graph data sets (2019), arXiv preprint arXiv:1910.12091;
[7] Riesen & Bunke, IAM Graph Database Repository for Graph Based Pattern Recognition and Machine Learning (2008), In: da Vitora Lobo, N. et al. (Eds.), SSPR&SPR 2008, LNCS, vol. 5342, pp. 287-297;
[8] Sutherland et al., Spline-fitting with a genetic algorithm: a method for developing classification structure-activity relationships (2003), J. Chem. Inf. Comput. Sci., 43, 1906-1915;
[9] Debnath et al., Structure-activity relationship of mutagenic aromatic and heteroaromatic nitro compounds (1991), J. Med. Chem. 34(2):786-797;
[10] Dobson & Doig, Distinguishing enzyme structures from non-enzymes without alignments (2003), J. Mol. Biol., 330(4):771783;
[11] Pedregosa et al., Scikit-learn: Machine Learning in Python (2011), JMLR 12, pp. 2825-2830;
[12] Waskom, seaborn: statistical data visualization (2021), Journal of Open Source Software, 6(60), 3021;
[13] Hunter, Matplotlib: A 2D Graphics Environment (2007), Computing in Science & Engineering, vol. 9, no. 3, pp. 90-95;
[14] Hu et al., Open Graph Benchmark: Datasets for Machine Learning on Graphs (2020), arXiv preprint arXiv:2005.00687;
[15] Bronstein et al., Geometric Deep Learning: Grids, Groups, Graphs, Geodesics, and Gauges (2021), arXiv preprint arXiv:2104.13478.

Подробнее..

Thank you points сетевой анализ социальных связей внутри DataArt

07.12.2020 20:14:08 | Автор: admin


Святослав Зборовский из BI-команды DataArt изучил, кого из коллег чаще всего благодарят с помощью корпоративной системы. В статье для Хабр он рассказал, как быстро построить и оптимизировать граф и какие кластеры ему удалось на нем выделить.

Святослав Зборовский, Data Analyst, DataArt

DataArt достаточно крупная IT-компания, в 20 наших офисах в десяти странах работает более 3000 человек. Многие проектные команды распределены по разным городам, взаимодействие между сотрудниками и до начала пандемии COVID-19 чаще всего происходило онлайн. Еще девять лет назад в компании придумали способ дистанционно поблагодарить коллегу с помощью TYPs Thank you points. Типсы аналог внутренней валюты, никак не привязанный к бонусам, зарплатам или стажу. Их можно посылать тем, кто вам помог, получать от тех, для кого что-то хорошее сделали вы сами, и время от времени менять на сувениры: кружки, рюкзаки, пауэрбанки, резиновых уточек и т. д. Если интересно, подробнее о том, как работает система TYPs, можно почитать здесь, но, в общем, это действительно такое спасибо онлайн.


Иллюстрация из статьи о том, как работает типсовая система в DataArt, выходившей осенью 2019 года

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

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

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

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



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

Первоначальный датасет имел формат таблицы связей и состоял из 46 896 строк отдельных фактов дарения типсов. С 2011 года именно столько раз коллеги официально сказали друг другу спасибо внутри системы учета рабочего времени. Выглядит это примерно вот так:



Поэтому для облегчения графа я применил фильтр degree > 50, другими словами, оставил на нем только коллег, у которых выявлено более 50 связей (фактов дарения или получения типсов).

Поэкспериментировав с укладкой, я остановился на Fruchterman Reingold. Выглядело это вот так:



К отфильтрованному графу я добавил статистику модулярности для выявления кластеров. Их оказалось восемь.

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

Финальная визуализация выглядит так:



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

Голубым цветом (20,61 % всех наблюдений) представлены коллеги из небольшого центра разработки, расположенного в относительно небольшом городе. DataArt там очень сильный игрок на рынке труда, при этом профессиональное сообщество в целом совсем немногочисленно. В этих условиях местный офис занимает в жизни коллег значительное место, и между собой они общаются очень тесно, даже будучи занятыми в разных проектах. Это наглядно отображается в частых спасибо. Лидеры HR-менеджеры, сисадмины, бухгалтеры и самых опытные из инженеров, выступающие менторами для практикантов и активно участвующие в жизни локального центра разработки. Т. е. они проводят вебинары, представляют в офисе результаты своей работы и интересные кейсы, выступают на конференциях с докладами, которые нравятся коллегам. Центральный большой узел инженер хелпдеска.

Зеленый (18.88 %) напротив, коллеги из самого большого (удивительно!) офиса, расположенного в нестоличном городе среднего размера. Однако здесь картина иная: в целом люди реже отправляют друг другу типсы, а я ярко выраженных любимчиков у них попросту нет. Скорее всего, у коллег просто хорошие горизонтальные отношения.[SZ13]

Фиолетовый (18,88 %) менеджеры, которые помогают планировать командировки и считать бюджеты, и участники внутренней BI-команды. У них есть и выраженная категория поклонников, в которую входят менеджеры проектов, деливери менеджеры, тимлиды и синьорные разработчики, которые чаще других выезжают в офисы клиентов.

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

Оранжевый (11,59 %) объединяет высший менеджмент, HR-менеджеров и тех, кто занимается продвижением компании на рынках труда. Все эти люди развивают бренд DataArt как работодателя и, хотя работают они в разных командах и департаментах, регулярно пересекаются и благодарят друг друга. Эта тенденция прослеживается на протяжении всех девяти лет работы системы, поэтому объединить таких коллег в один кластер вполне логично.

Красным цветом (6,87 %) обозначена еще одна небольшая локация. Самые большие точки два системных администратора и главный HR, действительно много времени уделяющий общению с коллегами и во многом объединяющий их между собой.

Темно-зеленый (3,86%) опять сисадмины, однако не привязанные к определенному офису. Это те, кто помогает настраивать виртуальные окружения, налаживает работу корпоративных систем и консультирует коллег из разных городов и стран. Поэтому и определенной группы, представители которой благодарили бы их чаще других, выявить невозможно. Им в равной степени благодарны все сотрудники компании за это им и полагается отдельный кластер.

Желтый (3,86 %) разработчики внутренних систем Project Manager и EDU, в которых мы ведем учет рабочего времени, следим за динамикой активности в проектах, аккумулируем обучающие курсы и общаемся между собой. Одним словом, в них отражена вся жизнь компании, поэтому тех, кто над ними работает, благодарят достаточно часто, причем коллеги из разных проектов и стран.

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

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

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

Перевод Компьютерный поиск помог разобраться с 90-летней математической задачей

31.08.2020 18:16:08 | Автор: admin

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




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

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

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

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

Авторы новой работы, Джошуа Брейкенсик из Стэнфорда, Марийн Хиюл и Джон Мэки из университета Карнеги-Меллона, и Дэвид Нарваез из Рочестерского технологического института, решили эту задачу при помощи 40 компьютеров. Всего за 30 минут машины выдали односложный ответ: да, в семи измерениях гипотеза верна. И нам даже не надо принимать это заключение на веру.

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

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

Загадочное седьмое измерение


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

Ранние результаты поддержали предсказание Келлера. В 1940-м Оскар Перрон доказал, что гипотеза верна для измерений с первого по шестое. Однако более 50 лет спустя новое поколение математиков нашло первый контрпример для этой гипотезы: в 1992 году Джеффри Лагариас и Питер Шор доказали, что гипотеза не работает в 10-м измерении.


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


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

Открытым осталось только седьмое измерение это было либо самое большое измерение, для которого эта гипотеза верна, или самое малое, в котором гипотеза неверна.

Никто точно не знает, что там происходит, сказал Хиюл.

Соединяем точки


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

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


Марийн Хиюл из университета Карнеги-Меллона

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

Вот, как это работает.

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

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

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


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


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

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

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

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

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

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

Они должны соприкасаться, но не полностью, сказал Хиюл.


Одинаковая раскраска одинаковое расположение.
Разные цвета, нет пар частичное наложение.
Два парных цвета и пара одинаковых общая сторона.
Два парных цвета и два разных частичное соприкосновение сторонами.


Масштабирование


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

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

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

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

Мэки опроверг гипотезу Келлера в 8-м измерении, найдя клику из 256 кубиков (28), поэтому оставалось разобраться с гипотезой в 7-м измерении, найдя клику из 128 кубиков (27). Найдя эту клику, вы опровергнете гипотезу Келлера в седьмом измерении. Докажите, что она не существует и вы докажете истинность гипотезы.

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

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

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

Язык логики


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

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

В терминах информатики эта задача называется задачей на приемлемость. Решить её можно, описав условие в пропозициональной формуле. В данном случае она имеет следующий вид, а А, К и В обозначают потенциальных гостей: (A OR NOT K) AND (K OR B) AND (NOT A OR NOT B).

Компьютер просчитывает её, подставляя в каждую переменную 0 или 1. 0 значение переменной ложь, или выключено, а 1 правда или включено. Подставив 0 вместо А, мы говорим о том, что Алексея не пригласили, а 1 что пригласили. В эту простую формулу 0 и 1 можно подставить (составляя список гостей) множеством способов, и, возможно, что после перебора их всех компьютер заключит, что всем интересам удовлетворить невозможно. Однако в данном случае есть два способа подставить 1 и 0 так, чтобы всем угодить: A = 1, K = 1, B = 0 (пригласить Алексея и Колю) и A = 0, K = 0, B = 1 (пригласить одного Влада).

Компьютерная программа, решающая такие утверждения, называется SAT solver, где SAT сокращение от satisfiability, удовлетворяемость. Она исследует все комбинации переменных и выдаёт односложный ответ либо ДА, есть способ удовлетворить требованиям формулы, либо НЕТ, его нет.


Джон Маки из университета Карнеги-Меллона

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

Вопрос поиска клики из 128 кубиков задача сходного толка. Её тоже можно переписать в виде пропозициональной формулы и задать SAT-солверу. Начните с большого количества кубиков с 7 точками на каждом и шестью возможными цветами. Можно ли раскрасить точки так, чтобы 128 кубиков были связаны друг с другом по определённым правилам? Иначе говоря, можно ли назначить цвета так, чтобы клика появилась?

Пропозициональная формула, описывающая вопрос с кликами, получилась довольно длинной и содержит 39 000 переменных. Каждой можно назначить одно из двух значений, 0 или 1. В итоге количество возможных вариантов расстановки значений, или способов назначения цветов, составило 239 000 что очень, очень много.

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

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

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

Скрытые эффективности


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

В тот день в моём офисе реально работал человеческий гений, сказал Мэки. Я будто наблюдал за Уэйном Гретцки, или за Леброном Джеймсом в финале НБА. У меня мурашки по телу от одного только воспоминания об этом.

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

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

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

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

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

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

В итоге они настолько улучшили поиски клики размера 128, что вместо проверки 239 000 конфигураций их программе пришлось проверять всего порядка миллиарда (230). Это превратило поиск, на который могла уйти вечность, в задачку на одно утро. Наконец, всего через полчаса вычислений они получили ответ.

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

Компьютер выдал не просто односложный ответ. Он присовокупил к нему длинное доказательство на 200 ГБ, поддерживающее это заключение.

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

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

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

24.11.2020 16:19:42 | Автор: admin

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




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

В опубликованном в сентябре четырёхстраничном доказательстве Давид Конлон и Асаф Фербер дали наиболее точную оценку многоцветным числам Рамсея. Эти числа описывают размер графов, до которого они могут вырасти перед тем, как в них начнут неизбежно проявляться определённые закономерности.

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

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

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

Числа Рамсея относятся к определённой последовательности, монохромной клике. Это набор вершин, которые будут соединены друг с другом рёбрами одного и того же цвета после определенной процедуры раскрашивания.


Асаф Фербер из Калифорнийского университета в Ирвине

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

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

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

Это потрясающий результат, сказала Аксенович. Мне он очень понравился.

Цветные связи


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

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

Числа Рамсея меняются в зависимости от количества цветов и размера искомой монохромной клики. Но в целом их сложно вычислять точные значения известны математикам только в небольшом числе случаев. Даже для небольших клик размером 5 и двух цветов лучше, что могут сказать математики по поводу числа Рамсея что оно находится в промежутке от 43 до 48.

Неловкая ситуация, сказал Ювал Уигдерсон, аспирант из Стэнфорда. Мы работаем над этой задачей уже почти 100 лет, и так ничего и не узнали.


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


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

Приходится проверять слишком много, сказала Аксенович.

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

Если они попросят у нас число Рамсея для [клик размера] 6, то лучше сразу атаковать, сказала Аксенович.

Изучая случайность


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

В 1935 году Пал Эрдёш и Дьёрдь Секереш установили первую такую границу. Они применили короткое доказательство того, что числа Рамсея для двух цветов должны быть меньше, чем 4t, где t размер интересующей вас монохромной клики. Они также обнаружили, что числа Рамсея для трёх цветов должны быть меньше 27t. Через 10 лет, в 1947 году, Эрдёш подсчитал первую нижнюю границу для этих чисел: для двух цветов это (2)t вершин, а для трёх (3)t.

Между (2)t и 4t есть большая разница, особенно при очень больших значениях t. Этот разрыв отражает нечёткое понимание чисел Рамсея математиками. Однако форма границ то, как размер нужного графа выражается через размер нужной клики намекает на то, что больше всего интересно математикам.

Что мне очень хотелось бы понять это то, как растут эти числа Рамсея с ростом размера клики, сказала Лиза Сауэрман, постдок в Институте передовых исследований в Принстоне.

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

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

Можно начать, как сделал Эрдёш, со случайного раскрашивания рёбер. Для каждого ребра бросим что-то вроде трёхгранного кубика и раскрасим тем цветом, который выпадет случайно. Эрдёш знал, что легко подсчитать вероятность того, что любое подмножество из 10 рёбер окажется одного цвета. Нужно просто перемножить вероятность того, что одно из рёбер будет, допустим, красным, с вероятностью того, что другое ребро будет красным, и так далее для всех десяти рёбер (то есть, 1/310). Затем он умножил то число на три, чтобы учесть тот факт, что нужную монохромную клику может породить любой из трёх разных цветов.

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

Пока граница объединения остаётся меньше 1, вы знаете, что метод случайной раскраски не гарантирует появления заданной монохромной клики. В нашем примере граница равна 0,0128. Это очень далеко от гарантированного появления монохромной клики в 5 вершин, а значит, для данного примера число Рамсея больше 10 вершин.

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


Пал Эрдёш изобрёл вероятностный метод подсчёта чисел Рамсея.
1) Начинаем с полного графа из 10 вершин. При раскраске рёбер тремя цветами всегда ли можно найти 5 вершин, соединённых 10 рёбрами одного цвета?
2) Вероятность того, что конкретное ребро красное (а не синее или жёлтое) равна 1/3
3) Вероятность того, что 10 рёбер красные, равна (1/3)10
4) Всего монохромную клику могут породить три цвета.
5) Общее количество разных клик из 5 вершин равно 252.
(1/3)10 * 3 * 252 = 0,0128 вероятность получить монохромную клику при случайной раскраске рёбер.


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

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

Именно это и удалось сделать Конлону и Ферберу этой осенью.

Включение порядка


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

До этой работы нижняя граница для трёх цветов равнялась (3)t (примерно 1,73 t). Конлон и Фербер улучшили её до 1,834t. Для четырёх цветов они подняли нижнюю границу с 2t до 2,135t. Оба эти результата гигантские шаги вперёд. Увеличивая основание, возводимое в степень t, Конлон и Фербер доказали существование экспоненциально больших графов, раскрашенных в три и четыре цвета, у которых нет монохромных клик. Иначе говоря, показали, что беспорядок встречается в более крупных графах, чем считалось ранее.

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


Давид Конлон из Калифорнийского технологического института

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

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

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

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

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

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

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

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

Выделение бесхордовых циклов из ненаправленного графа

14.11.2020 02:11:06 | Автор: admin
Несколько лет назад мне пришлось погрузиться в данную тему по работе. Погуглив, я, к удивлению своему, не нашёл каких-то готовых решений. Да и до сих пор, в общем-то, ничего не видно. Поэтому пришлось разрабатывать тему с нуля.

Чтобы было понятно о чем речь, приведу простейший пример.

image

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

Текстовое описание:

  1. Для каждой вершины графа находим все смежные вершины и начинаем формировать цикл движением в сторону каждой смежной вершины поочередно.
  2. Если число смежных вершин (исключая ту, с которой вошли) =0, то идём обратно, формируя цикл ---> п.5
  3. Если число смежных вершин (исключая ту, с которой вошли) равно 1, то идём по ней, формируя цикл ---> п.5
  4. Если число смежных вершин (исключая ту, с которой вошли) >=2, то ищем самое правое ответвление (путем нормализации векторов и их перемножения), и идём по нему, формируя цикл ---> п.5
  5. Анализируем вернулись в начальную точку? Если нет, то ---> п.2
  6. Если да, то завершаем формирование цикла и осуществляем проверки.
  7. Ищем в цикле повторяющиеся вершины, если такие есть, то удаляем все вершины между ними, оставляя только одну повторяющуюся.
  8. Ищем в числе уже сформированных циклов совпадающие с данным циклом, и если есть, то прерываем формирование цикла и переходим на ---> п.1 (проверять надо с учетом всех сдвигов и направлений)
  9. Ещё раз проходим по циклу и смотрим на левые ответвления от него. Найдя такие ответвления, для каждого организуем поиск в ширину (или в глубину, неважно). Если при этом мы попадаем в конце концов на вершину сформированного цикла (кроме текущего), то прерываем формирование цикла и переходим на ---> п.1
  10. Записываем цикл, и переходим на поиск нового ---> п.1


Укрупненный псевдокод:
image

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

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

Как связать несвязанное

24.12.2020 16:21:15 | Автор: admin
Явное лучше неявного.


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

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


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

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

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

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

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

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

Люди и фото. Чем чаще люди встречаются на общей фотографии, тем они ближе.

Думаю, примеров достаточно. Во всех примерах присутствуют элементы двух множеств разного типа. При этом элементы каждого множества можно считать независимыми (несвязанными друг с другом). Сотрудники явно не связаны с другими сотрудниками, документы явно не связаны с другими документами. Это определенное упрощение, но оно не критично.

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

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

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

Пространства и подпространства графов (*)


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

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

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

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

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

Таким образом можно преобразовать расстояния между элементами одной доли в значения связей между ними. Логика такая:
Исходный двудольный граф -> (задает) -> Расстояния между элементами доли -> (по которым можно рассчитать) -> Искомый однодольный граф.

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

Матрицы и тензоры


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

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

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

$ \begin{matrix} [p] & [x] & Weight \\ P1 & X1 & 1 \\ P1 & X2 & 1 \\ P2 & X1 & 1 \\ P2 & X2 & 1 \\ P2 & X3 & 1 \\ P3 & X2 & 1 \\ P3 & X3 & 1 \\ P3 & X4 & 1 \end{matrix} $

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

Элементы множества, связи между которыми хотим определить, будем обозначать как $p$$q$), а все множество как $[p]$.

Для элементов дополнительного к нему множества (доли) используем символы $x$$y$). Это могут быть документы, в которых сотрудники оставляют комментарии, или товарные накладные, если ищем связи между товарами.

Тензор связей (матрица бисмежности) между множествами $C^{px}$ считается заданным.

Для каждого объекта (вершины графа) можно определить его степень. Это количество связей данного объекта. Например, для документов степень это количество ссылок на него (сколько в нем всего комментариев или товаров), для слова его длина (количество букв). Степени дополнительной доли обозначим $h^x$, значения степеней равны сумме по колонкам матрицы смежности $C^{px}$:

$ h^x = \sum_p C^{px} = 1_p C^{px} \qquad (1) $

Здесь $\sum_p C^{px}$ свертка тензора по измерению $p$. Эквивалентна произведению матрицы $C^{px}$ на кортеж из единиц $1_p$.

Формула преобразования


В общем случае элементы доли $[p]$ могут быть тоже связаны (на рисунке такие связи обозначены пунктиром). Учтем это введением матрицы связей (смежности) $Ci^{pq}$. Если граф двудольный, то данные связи (и их матрица) равны нулю.
Искомую матрицу результирующих связей между элементами множества (доли) $[p]$ обозначим как $Cr^{pq}$.
Тогда справедливо следующее утверждение (лемма):
Величина результирующих связей между элементами множества $[p]$ равна сумме исходных $Ci^{pq}$ и наведенных $Cx^{pq}$ связей:

$ Cr^{pq} = Ci^{pq} + Cx^{pq} \quad (2) $

При этом матрица наведенных связей представляет собой следующую квадратичную форму (это и есть основная формула статьи):

$ Cx^{pq} = C^{px} F_{xy} C^{yq} \quad (3) $

Здесь $F_{xy}$ фундаментальная матрица, которая определяется как обращение минора матрицы-лапласиана:

$ F_{xy} = (L^{xy})^{-1} \quad (4) $

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

Фишка двудольных графов заключается в том, что минор лапласиана $L^{xy}$ для доли графа представляет собой диагональную матрицу, составленную из степеней элементов $h^x$ (поскольку нет связей между элементами). Поэтому обращение данного минора сводится просто к обратным значениям степеней $h^x$:

$ F_{xy} = (L^{xy})^{-1} = 1/h^x \quad (5) $

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

Подставляя обратные степени в (3), получаем формулу преобразования матрицы би-смежности в матрицу смежности заданной доли:

$ Cx^{pq} = C^{px} /h^x C^{xq} \quad (6) $

Если тензор бисмежности задан в виде реляционной таблицы, то выполнить преобразование (6) можно одним sql-запросом. Ну а в библиотеках типа python pandas данное преобразование может быть записано одной строкой действий над объектом dataframe.

Пример расчета


Для приведенного выше тензора бисмежности степени элементов $[x]$ будут такими: $h^x = [2, 3, 2, 1]$. Соответственно, диагональ фундаментальной матрицы будет равна обратным значениям: $f_x = 1/h^x = [3, 2, 3, 6]/6$. Подставляя ее в формулу (6), получаем тензор наведенных связей (для удобства приводим целочисленные значения):

$ \begin{matrix} [p] & [q] & Weight \\ P1 & P1 & 5 \\ P1 & P2 & 5 \\ P1 & P3 & 2 \\ P2 & P1 & 5 \\ P2 & P2 & 8 \\ P2 & P3 & 5 \\ P3 & P1 & 2 \\ P3 & P2 & 5 \\ P3 & P3 & 11 \end{matrix} $

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

Более подробная математика преобразования пространства графа в подпространство приведена здесь.
Подробнее..

Рекомендации Друзей ВКонтакте ML на эго-графах

13.04.2021 14:10:30 | Автор: admin

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

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

Обзор

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

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

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

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

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

EGOML

Общая схема нашего метода продолжает идеи числа общих друзей и Adamic/Adar. Финальная мера релевантности E(u, v), с помощью которой мы будем отбирать кандидатов, всё так же раскладывается в сумму по общим друзьям u и v. Ключевое отличие в форме слагаемого под суммой: в нашем случае это мера ez_c(u, v).

Сначала попробуем понять физический смысл меры ez_c(u, v). Представим, что мы взяли пользователя c и спросили у него: Насколько вероятно, что два твоих друга, u и v, подружатся? Чем больше информации для оценки он учтёт, тем точнее будет его предсказание. Например, если c сможет вспомнить только число своих друзей, его рассуждения могут выглядеть следующим образом: Чем больше у меня друзей, тем менее вероятно, что случайные двое из них знакомы. Тогда оценка вероятность дружбы u и v (с точки зрения c) может выглядеть как 1/log(n), где n число друзей. Именно так устроен Adamic/Adar. Но что если c возьмёт больше контекста?

Прежде чем отвечать на этот вопрос, разберёмся, почему ez_c(u, v) важно определять через пользователя c. Дело в том, что в таком виде очень удобно решать задачу распределённо. Представим, что теперь мы разослали всем пользователям платформы анкету с просьбой оценить вероятность дружбы в каждой паре их друзей. Получив все ответы, мы можем подставить значения в формулу E(u, v). Именно так выглядит вычисление E(u, v) с помощью MapReduce:

  • Подготовка. Для каждого c выделяется тот контекст, который он будет учитывать для вынесения оценок. Например, в Adamic/Adar это будет просто список друзей.

  • Map. Спрашиваем у каждого c, что он думает про возможность дружбы в каждой паре его друзей. По сути, вычисляем ez_c(u, v) и сохраняем в виде (u, v) ez_c(u, v) для всех u, v in N(c). В случае Adamic/Adar: (u, v) 1/log|N(c)|.

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

Таким образом мы получаем все ненулевые значения E(u, v). Заметим: необходимое условие того, что E(u, v) > 0, существование хотя бы одного общего друга у u и v.

Эго-граф ХоппераЭго-граф Хоппера

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

Ключевая идея меры ez_c в том, что её можно сделать обучаемой. Для каждого пользователя c, его эго-графа и всех пар пользователей u, v внутри него мы можем посчитать много разных признаков, например:

  • число общих друзей u и v внутри эго-графа c;

  • число общих друзей u и c;

  • интенсивность взаимодействий между v и c;

  • время, прошедшее с последней дружбы между u и кем-либо из эго-графа c;

  • плотность эго-графа c;

  • и другие.

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

В конечном счёте мы получаем датасет следующего вида:

  • для каждой пары пользователей u и v, а также их общего друга c, посчитаны признаки по эго-графу c;

  • пара пользователей u и v встречается в датасете ровно столько раз, сколько у них общих друзей;

  • все пары пользователей в датасете не являются друзьями на момент времени T;

  • для каждой пары u и v проставлена метка подружились ли они в течение определённого промежутка времени начиная с T.

По такому датасету мы и будем обучать нашу меру ez_c. В качестве модели выбрали градиентный бустинг с pairwise функцией потерь, где идентификатором группы выступает пользователь u.
По сути, мера ez_c(u, v) определяется как предсказание описанной выше модели. Но есть один нюанс: при pairwise-обучении распределение предсказаний модели похоже на нормальное. Поэтому, если в качестве определения меры ez_c(u, v) взять сырое предсказание, может возникнуть ситуация, когда мы будем штрафовать финальную меру E(u, v) за общих друзей, так как значения предсказаний бывают отрицательными. Это выглядит не совсем логично хочется, чтобы с ростом числа общих друзей мера E(u, v) не убывала. Так что поверх предсказания модели мы решили взять экспоненту:

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

Имея такую модель, мы могли бы получить предсказания для всех пар пользователей одного эго-графа быстрее. Достаточно применить модели A и B для каждого пользователя, а затем сложить соответствующие парам предсказания. Таким образом, для эго-графа из n вершин мы могли бы сократить число применений модели с O(n^2) до O(n). Но как получить такую модель, каждое дерево которой зависит только от одного пользователя? Для этого сделаем следующее:

  1. Исключим из датасета все признаки, которые одновременно зависят и от u и от v. Например, от признака число общих друзей u и v внутри эго-графа c придётся отказаться.

  2. Обучим модель A, используя только признаки на базе u, c и эго-графа c.

  3. Для обучения модели B оставим только признаки на базе v, c и эго-графа c. Также в качестве базовых предсказаний передадим предсказания модели A.

Если объединим модели A и B, получим то что нужно: первая часть использует признаки u, вторая признаки v. Совокупность моделей осмысленна, поскольку B была обучена корректировать предсказания A. Эта оптимизация позволяет ускорить вычисления в сотни раз и делает подход применимым на практике. Финальный вид ez_c(u, v) и E(u, v) выглядит так:

Вычисление меры E в онлайне

Заметим, что E(u, v) можно представить в виде:

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

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

Итог

В результате система EGOML позволяет:

  1. Распределённо отбирать кандидатов для каждого пользователя в офлайне. Асимптотическая сложность оптимизированного алгоритма составляет O(|E|) вычислений признаков и применений модели, где |E| число связей в графе. На кластере из 250 воркеров время работы алгоритма составляет около двух часов.

  2. Быстро вычислять меру релевантности E(u, v) для любой пары пользователей в онлайне. Асимптотическая сложность операции O(|N(u)| + |N(v)|).

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

В конечном счёте мы перешли со способа отбора кандидатов с использованием Adamic/Adar к системе EGOML и внедрили в модель второй уровень признаков на основе меры E(u, v). И это позволило увеличить количество подтверждённых дружб со всей платформы на несколько десятков процентов.

Благодарность

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

Подробнее..

Перевод Может ли геймпад заменить клавиатуру? Пробуем программировать на стиках

10.08.2020 10:05:17 | Автор: admin
image

Введение


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

Из-за малого количества кнопок на геймпаде никто не рассматривал их как средство ввода объёмных текстов, например, в программировании.

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

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


Экранный ввод текста в Legend of Zelda

В Legend of Zelda игрок должен по очереди выбирать буквы при помощи крестовины со стрелками и каждый раз нажимать кнопку подтверждения для добавления буквы в поле ввода текста.

С тех пор были разработаны более эффективные способы ввода. Если вас это заинтересовало, то прочитайте статью на Gamasutra.

К сожалению, все найденные мной способы ввода имеют два серьёзных изъяна, из-за которых они не подходят для серьёзной работы:

  • Они недостаточно быстры
  • Они требуют визуальной обратной связи

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

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

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

Определяемся с жестами


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

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


Визуализация паттернов движения аналоговых стиков

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

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


Левый стик переместился вверх и обратно в центр

Сколько направлений и какие именно направления можно точно выбрать вслепую? Рассмотрим следующий пример.


Левый стик переместился вверх, вниз, в центр, влево и вправо, а правый стик двигался по диагонали

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

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


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


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

Учитывая все придуманные пока жесты, мы получили 4 + 8 + 8 = 20 вариантов ввода на каждом стике.

Разумеется, оба стика можно двигать одновременно, создавая комбинированные жесты ввода.


Оба стика одновременно движутся вверх и обратно к центру

При комбинировании жестов в сумме получается 20 * 20 + 20 + 20 = 440 вариантов ввода, чего, по-моему, более чем достаточно.

Кодирование жестов


Я разделил пространство ввода каждого стика на 4 сектора и присвоил каждому сектору число.

Input spaces divided into sectors

Пространства ввода, разделённые на сектора

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


Круговая пороговая область вокруг центра

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

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

Stick movements for input ((0,), (2, 3))

Перемещения стиков для ввода ((0,), (2, 3))

Привязка жестов к действиям


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

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

Самые часто нажимаемые клавиши должны быть привязаны к простейшим (а значит, самым быстрым) жестам. Я оценивал сложность жеста сложением длин вводов каждого стика. Например, показанный выше ввод ((0,), (2, 3)) имеет сложность 1 + 2 = 3.

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

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

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

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

Рассмотрим следующий пример. Первая группа сложности состоит из всех вводов со сложностью 1, то есть ((0,), ()), ((1,), ()), ((2,), ()), ((3,), ()), ((), (0,)), ((), (1,)), ((), (2,)), ((), (3,)).

В этой группе 8 вводов, поэтому мы берём 8 самых частых клавиш из отсортированного списка. Это 'e', 'o', 't', 'a', 'i', 's', 'j', 'r'. Создаём граф с этими клавишами в качестве узлов и назначаем веса рёбрам между этими узлами, соответствующие частоте каждого сочетания клавиш.

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

При удалении слабых рёбер из графа он рано или поздно превращался в несвязанный.
The key j is frequent but isolated.

Клавиша j встречается часто, но она изолирована.

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

Так как граф несвязанный, я продолжаю применять алгоритм к связанным компонентам. Подграф, состоящий только из узла j, уже является двудольным (j + пустое множество). Я рекурсивно применяю алгоритм к другому компоненту.
Component is bipartite after removing the weakest edges

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

Затем компонент можно без проблем разделить на две группы без рёбер между узлами в группе.
Bipartite drawing of the component

Двудольная схема компонента

В конце я объединяю двудольные множества.
Final grouping for the first 8 keys

Группировка, получившаяся для первых 8 клавиш

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

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

Я применил пакет pyautogui языка Python для генерации клавиатурных событий при срабатывании действий.

Практика


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

Practising gamepad touch typing in ktouch

Практикуем аналоговый ввод на геймпаде в ktouch

Наблюдения


  • Хотя процесс Python, в котором запущена эта система ввода, обычно потреблял не больше 10% ресурсов процессора, если бы он должен был постоянно выполняться в фоновом режиме, то я бы реализовал его заново и оптимизировал на языке более низкого уровня, чтобы процессор мог заниматься более затратными задачаи.
  • После покупки геймпада DualShock4 я понял, то довольно точно могу выполнять диагональный ввод. Интеграция диагонального ввода уменьшит количество более сложных вариантов ввода, а значит, увеличит скорость.
  • После недели практики я могу печатать на геймпаде всего в два раза медленнее, чем на клавиатуре. Учитывая мою безумную скорость печати на клавиатуре, это совсем неплохо. Для дополнительной выгоды я использую оставшиеся варианты ввода, привязав их к специальным действиям, например, к горячим клавишам или операциям конкретных программ.
  • Похоже, сильнее всего напрягают большие пальцы круговые движения. Вероятно, стоит заменить их более долгими, но более прямыми вариантами ввода.
  • Напряжение больших пальцев снижается после практики. Однако в начале стоит практиковаться всего по несколько минут за раз.

Заключание


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

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

21.07.2020 14:09:04 | Автор: admin
В статье описаны общие идеи по реализации ориентированного графа в PostgreSQL.
Граф был использован для реализации подчинения между сотрудниками, взамен использованного ранее метода предок-потомок в таблице отделов.
Опыт оказался успешным, может быть кому-то пригодится и поможет сэкономить время. Я в свое время искал реализации именно на pqSQL, но видимо плохо искал. Пришлось реализовывать самому. Что в общем-то даже к лучшему, задача интересная, всегда приятно что-то сделать своими руками, так, что время потрачено не зря.

Входные данные

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

Выходные данные


  • Список подчиненных сотрудников для данного
  • Список начальников для данного сотрудника
  • Является ли сотрудник подчиненным для данного
  • Список подчиненных сотрудников для данного(путь от начальника к сотруднику)


Реализация, используя PL/pgSQL


Хранение графа в виде таблицы ребер


Вершинами являются значения id, целевой таблицы.
CREATE TABLE graph(    vertex integer NOT NULL ,         --id записи в целевой таблице  edge integer NOT NULL ,           --id ребра  vertex_order integer NOT NULL --порядок вершины ( 0 предок, 1 потомок));

Для генерации id ребра используется последовательность
CREATE SEQUENCE enge_seq OWNED BY graph.edge; 


Заполнение таблицы ребер


Для вставки нового ребра(вершины id0, id1) используется две операции INSERT
--Генерация нового id ребраnew_id = nextval('enge_seq');

--Вставка предкаINSERT INTO  graph (vertex  , edge , vertex_order  ) VALUES ( id0 , new_id , 0  );

--Вставка потомкаINSERT INTO  graph (vertex  , edge , vertex_order  ) VALUES ( id1 , new_id , 1  );


Получение списка подчиненных сотрудников для данного current_id


SELECT   id FROM   graphWHERE   vertex_order  = 1 AND   edge IN (SELECT edge FROM graph WHERE vertex  = current_id AND vertex_order = 0  );


Получение списка начальников для данного current_id


SELECT   id FROM   graphWHERE   vertex_order  = 0 AND   edge IN (SELECT edge FROM graph WHERE vertex  = current_id AND vertex_order = 1  );


Генерация матрицы доступности (алгоритм Дейкстры) из заданной вершины start_id


Создание временной таблицы tmp_matrix
CREATE TEMPORARY TABLE tmp_matrixAS(  SELECT     DISTINCT vertex  ,     FALSE AS label ,    999999 AS distance ,    FALSE AS is_finish   FROM    graph);

Первоначальное заполнение таблицы tmp_matrix
UPDATE tmp_matrix SET label = TRUE , distance = 0 WHERE vertex = current_id ; current_id = start_id ;SELECT   distanceINTO   current_distanceFROM   tmp_matrixWHERE   vertex = current_id;SELECT   EXISTS (SELECT 1 FROM tmp_matrix WHERE label = FALSE )INTO  not_visit;

Заполнение матрицы доступности
WHILE not_visit LOOP  FOR v_rec IN    SELECT       *   FROM      tmp_matrix  WHERE     NOT label AND     --Вершина достижима за один шаг     vertex IN ( SELECT                        id                      FROM                       graph                    WHERE                       vertex_order  = 1 AND                       edge IN (SELECT                                      edge                                     FROM                                       graph                                     WHERE                                       vertex  = current_id AND vertex_order = 0  ))  LOOP        --Найдена достижимая вершина    IF v_rec.distance > (current_distance +1)    THEN       UPDATE tmp_matrix SET distance = (current_distance +1) WHERE vertex = v_rec.vertex;    END IF ;       --если закончен обход   IF SELECT         NOT EXISTS         (          SELECT 1 FROM graph WHERE vertex = v_rec.vertex AND vertex_order = 0         )   THEN     UPDATE tmp_matrix SET label = TRUE , is_finish = TRUE WHERE vertex = v_rec.vertex;   END IF ;     END LOOP;    UPDATE tmp_matrix SET label = TRUE WHERE vertex = current_id ;  --Следующая итерация  SELECT     vertex  INTO    current_id   FROM     tmp_matrix   WHERE     NOT label AND    distance < 999999 ;  SELECT     distance  INTO     current_distance  FROM     tmp_matrix   WHERE      vertex = current_id ;  SELECT     EXISTS (SELECT 1 FROM tmp_matrix  WHERE label = FALSE )  INTO   not_visit ;  IF current_id IS NULL   THEN     not_visit = FALSE ;   END IF;END LOOP;

Выдать результирующую матрицу доступности
SELECT   vertex ,   label ,    distance ,   is_finishFROM   tmp_matrix WHERE   distance != 999999 ;


Является ли сотрудник с check_id подчиненным для данного start_id


Получить матрицу доступности для start_id (см. выше).
IF EXISTS   ( SELECT distance FROM tmp_matrix WHERE vertex = check_id  )THEN    RETURN TRUE;ELSE   RETURN FALSE;END IF;


Список подчиненных сотрудников для данного(путь от начальника start_id к сотруднику finish_id)


Получить матрицу доступности для start_id (см. выше).
Заполнить таблицу пути между таблицами start_id и finish_id
current_id = finish_id ;result[1] = finish_id ; WHILE current_id != start_id LOOP  SELECT     par.id   FROM    ( SELECT        id      FROM       graph    WHERE       vertex_order  = 0 AND       edge IN (SELECT                      edge                     FROM                       graph                     WHERE                       vertex  = current_id AND vertex_order = 1  )) par  JOIN  tmp_matrix m ON ( par.id = m.vertex  )  INTO     parent_id  LIMIT 1 ;  SELECT     array_append( result , parent_id )  INTO     result ; --Следующая итерацияcurrent_id = parent_id ;   END LOOP;

В результате в массиве result сформируется путь в виде набора id в обратном порядке. Для удобства просмотра массив можно реверсировать любым способом.
Подробнее..
Категории: Postgresql , Графы , Graphs , Pgsql

Число Рамсея R(5,5) 43

13.02.2021 16:10:43 | Автор: admin

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

Для начала, что изображено на картинке? Однозначного математического ответа на этот вопрос нет: с геометрической точки зрения это ортогональные проекции пятиячейника на плоскости Коксетера A2 и A4, а с точки зрения теории графов диаграммы K4 и K5.

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

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

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

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

Используемые определения:

(Простой неориентированный) граф это пара множеств (V, E) (множество вершин и множество рёбер), причём E {{v1, v2} | v1, v2 V, v v2}.

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

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

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

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

Подграф граф, содержащий некое подмножество вершин исходного графа и некое подмножество инцидентных им рёбер.

Полный подграф подграф, каждая пара вершин которого соединена ребром исходного графа.

Раскраска графа разбиение вершин или рёбер графа на множества (называемые цветами).

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

Таблица значений и оценок чисел Рамсея:

Формальное определение графа это математическая заумь и требует дополнительных разъяснений.

Понятнее всего граф с геометрической точки зрения: достаточно задать на плоскости наборы точек, например A,B,C,D и соединяющих их отрезков, например AB, AC, BC, CD это и будет граф.

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

Ход решения

Числа Рамсея были записаны треугольником и сравнены треугольником Паскаля.

Стала видна область пересечения (красный треугольник на рисунке выше), а также то, что 36 (синий квадрат) это R(3,9), из чего родилась гипотеза: R(5,5)=45, R(4,6)=36, R(4,7)=49.

Предполагаемое решение основывалось на:

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

  2. 10-регулярном гамильтоновом графе с 45 вершинами, из которого будет построен K45. Промежуточный итог его построения:

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

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

Happy end? Нет! За применение полученного 43 года назад результата награду скорее всего не дадут, значит необходимо двигаться дальше. На тот момент я понял природу чисел Рамсея настолько, что поиск алгоритма их нахождения занял всего два дня. Барабанная дробь!

Первый алгоритм нахождения чисел Рамсея

Предельно прост. Это сумма предыдущего числа, его квадрата и единицы: R(9,9)=432+43+1=1893 и так далее, что нетрудно доказать через теорию бифуркаций (делать это я конечно же не буду) или через графы (квадрат это петля).Давайте лучше ещё раз взглянем на таблицу с числами Рамсея.

R(3,3) находится в центре квадрата с длиной стороны 5 ячеек, R(5,5) находится в его правой нижней точке, при этом 43=62+6+1. Аналогично R(5,5) центр квадрата со стороной 9, R(9,9) его правая нижняя точка. Далее по индукции. К сожалению, алгоритм работает только в конкретном, описанном выше случае. К счастью, разложение R(9,9) на простые множители 631 и 3 явно указывает на то, что каждое неизвестное число Рамсея может быть вычислено алгоритмически.

Этих алгоритмов не более двенадцати, включая найденный.

Благодарю Михаила Ивановича Снегирёва за неоценимый вклад.


Использованные материалы:

TAKEUCHI, Kisao. Arithmetic triangle groups. J. Math. Soc. Japan 29 (1977), no. 1, 91--106.


Немного личного

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

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

Так обстоят дела только в России? Нет, так везде. Большинство не желает видеть дальше собственного носа, все озабочены своими гомологиями, гомотетиями, самоцитированием и прочей ерундой. К примеру, кто систематически развивает идеи Джейкоба Лурье? Единицы. Остальные морщат носик: мы заслуженные специалисты, поэтому изучать что-то новое не можем займёт целый год, может пострадать карьера. Тусовка эгоистична настолько, что среди миллионов(!!!) не-англоязычных математиков до сего момента не нашлось ни одного человека, готового потратить час своего драгоценного времени на перевод статьи Википедии о центрах Морли фундаментальном результате планиметрии треугольника с английского на язык которым говорят его дети.

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

Ценный совет специалистам по теории Рамсея

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

Резко поумнев, можно браться за самостоятельное доказательство R(5.5)=43 через графы:

  1. Взять три копии K4и раскрасить их вершины, например цветами CMYK, рёбра раскрасятся естественным образом.

  2. Из 6 рёбер первой копии сделать соответственно раскрашенные вершины K6, 15 рёбер которого снова раскрасятся естественным образом, из них сделать 15 вершин.

  3. Вторую и третью копии исходного K4объединить в K8, получить раскраску 28 его рёбер, сделать из них 28 вершин.

  4. На основе 15 предварительно раскрашенных вершин из п.2 и 28 вершин из п.3 построить K43, все 903 ребра которого вновь окажутся раскрашены оттенками всего 4 исходных.

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

  6. Profit!

Спасибо дочитавшим, на этом на сегодня всё. Всегда ваш, Мальчег-Зайчег.

Зайчик (осторожно, кровь)
Подробнее..

Основные направления работы Big Data МегаФона и задачи на графах

11.11.2020 18:11:34 | Автор: admin

Привет, меня зовут Рома Васильев, я Data Scientist в компании МегаФон. Здесь я и решил поделиться здесь своим докладом с Zero Day ICPC.

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

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

  • Customer profile мы выделяем интересы и потребности абонентов и стараемся строить свои продукты на их основе.

  • Development & Retention персонализируем все, что только можно как продукты, которые предлагаем абонентам, так и каналы и способы коммуникации с абонентом для увеличения NPS и LTV.

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

  • Retail как я уже и говорил, количество салонов МФ Ритейл постоянно растет, и мы строим различные модели по управлению закупками, ценами, предсказанию спроса и клиентопотока салонов.

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

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

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

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

  • Strategy and Marketing выделяем тренды в клиентской базе, помогаем оптимизировать затраты на маркетинг в разных каналах.

  • BigData, Enablers активно развиваем внутреннюю BigData инфраструктуру создаем много различных tool-ов, которые сокращают time-to-market моделей NBA, Real Time Marketing и т.д.

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

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

Что же такое граф коммуникаций?

Это граф, в котором вершины абоненты, а ребра коммуникации между ними.

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

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

Что же такое эмбединг?

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

Как же строить эмбединги?

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

Одним из самых крутых, на наш взгляд, Open Sourse решений в этой области является метод GraphSAGE, который был представлен группой из Cтенфорда в 2017 году. Он порядком расширяет функционал GCN за счет введения новых функций агрегации. На слайде вы можете видеть схему расчета эмбединга для вершины С.

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

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

Еще одним несомненным преимуществом описанного подхода является то, что расчеты можно грамотно переиспользовать. К примеру, если мы уже посчитали эмбединг глубины 2 для вершин A, B и C, то при расчете эмбедингов для вершин D, E и F мы сможем использовать компоненты предыдущих расчетов, что в наших масштабах значительно увеличивает время работы алгоритма.

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

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

Подробнее..

Баланс в настольном геймдизайне строим графы с помощью Google App Script и Gephi

11.12.2020 16:14:18 | Автор: admin

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

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

Поставленные задачи

Основная механика Писем призрака строится на ассоциациях между картами с изображениями различных предметов (картами улик). И на одном из ранних этапов разработки мы задались вопросом: А можно ли просчитать и выстроить баланс в игре на ассоциации?. Собственно, почему бы не попробовать.

Задача балансировки ассоциаций состояла примерно в следующем:

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

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

  • Добиться максимальной равномерности и хаотичности распределения ассоциаций по всему множеству карт.

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

Инструменты

Google Docs

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

  • Автоматизация с помощью Google App Script. При наличии минимальных навыков программирования на JS даёт практически безграничные возможности для анализа и формирования данных, а также интеграции с другими онлайн-сервисами.

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

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

Gephi

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

Позволяет строить даже вот такие масштабные штуки:

Подготовка данных

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

Мы условно разделили ассоциации на 4 уровня по силе:

0 полное отсутствие ассоциаций
1 слабая ассоциация либо сходство по форме, цвету или материалу
2 достаточно крепкая ассоциация
3 однозначная ассоциация

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

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

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

Фрагмент получившейся таблицы:

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

// Обновление изображений в углу таблицыfunction RefreshPictures() {  var ss = SpreadsheetApp.getActiveSpreadsheet();  // Таблица ассоциаций  var sheet_a = ss.getSheetByName("Ассоциации");  var range_a = sheet_a.getDataRange();  // Таблица с картинками  var sheet_p = ss.getSheetByName("Картинки");  var range_p = sheet_p.getDataRange();    // Получаем координаты выделенной ячейки  var row = sheet_a.getActiveCell().getRow();  var col = sheet_a.getActiveCell().getColumn();    // Получаем id карт  var id1 = range_a.getCell(row, 1).getDisplayValue().toString();  var id2 = range_a.getCell(1, col).getDisplayValue().toString();    // Ищем в таблице картинок нужную строчку по id  var pos_pic1 = RowOfId(id1, range_p);  var pos_pic2 = RowOfId(id2, range_p);    // Проверяем, удалось ли найти картинки с нужными id  if (pos_pic1 != -1) {    // Картинки подгружены в таблицу в виде формул,    // содержащих ссылки на файлы в облаке    var pic1_f = range_p.getCell(pos_pic1, 2).getFormula();    range_a.getCell(2, 1).setFormula(pic1_f);  }  else  {    range_a.getCell(2, 1).setValue("X");  }    if (pos_pic2 != -1) {    var pic2_f = range_p.getCell(pos_pic2, 2).getFormula();    range_a.getCell(2, 2).setFormula(pic2_f);  }  else  {    range_a.getCell(2, 2).setValue("X");  }}// Поиск в таблице картинок нужной строчки по idfunction RowOfId(id, rng) {    var height = rng.getHeight();  var data = rng.getValues();    for (var i = 1; i < height; i++) {        if (data[i][0].toString() == id) {      return i + 1;    }  }    return -1;}

С загрузкой картинок в таблицу тоже пришлось повозиться. Загружать 150 картинок по одной совсем не хотелось, а закинуть сразу все функционал Google Sheets пока не позволяет (сама возможность вставлять картинки в ячейки таблицы появилась сравнительно недавно). Я написал отдельный скрипт для загрузки всех изображений из папки на гугл-диске, через Google App Script довольно удобно работать с другими их сервисами.

// Загрузка картинок в таблицу из папки в Google Drivefunction LoadPicturesFromDrive() {  var ss = SpreadsheetApp.getActiveSpreadsheet();  var sheet_p = ss.getSheetByName("Картинки");  var range_p = sheet_p.getDataRange();    var art_folder = DriveApp.getRootFolder().getFoldersByName("Папка с картинками").next()  var files = art_folder.getFiles();    // Перебираем все файлы в папке  var i = 1;  while (files.hasNext()) {    var file = files.next();        var file_name = file.getName();    // Вытаскиваем из имени файла id карты    var id = file_name.slice(0, file_name.indexOf("."));        // Записываем id в таблицу    sheet_p.getRange(i + 1, 1).setValue(id);        // Устанавливаем права доступа к файлу    file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);    var file_id = file.getId();        // Вставляем картинку в ячейку с помощью формулы IMAGE    sheet_p.getRange(i + 1, 2).setFormula("=IMAGE(\"" + "https://drive.google.com/uc?export=download&id=" + file_id + "\")");        i = i + 1;  }}

Но тут выяснилось, что разработчики из гугла не сумели полноценно подружить Google Sheets и Google Drive, из-за чего примерно 10% всех изображений просто не отображались. Пошарив по форумам, я выяснил, что далеко не первый столкнулся с этой проблемой, и этот баг пока не исправлен. Пришлось дополнительно разбираться с API Dropbox, чтобы сделать загрузку изображений уже оттуда. Поскольку Dropbox не является частью гугловской экосистемы, это потребовало гораздо больше манипуляций, зато в итоге всё заработало безотказно.

// Загрузка картинок в таблицу из папки в Dropboxfunction LoadPicturesFromDropbox() {  var ss = SpreadsheetApp.getActiveSpreadsheet();  var sheet_p = ss.getSheetByName("Картинки");  var range_p = sheet_p.getDataRange();    // Задаём параметры для POST-запроса  var data = {    "path": "",    "recursive": false,    "include_media_info": false,    "include_deleted": false,    "include_has_explicit_shared_members": false,    "include_mounted_folders": true,    "include_non_downloadable_files": true  };  var payload = JSON.stringify(data);    var options = {    "method" : "POST",    "contentType" : "application/json",    "headers" : {       "Authorization" : "Bearer [код авторизации]"    },    "payload" : payload,    muteHttpExceptions : true  };    // Отправляем POST-запрос для получения списка файлов в папке  var url = "https://api.dropboxapi.com/2/files/list_folder";  var response = UrlFetchApp.fetch(url, options);  var json = JSON.parse(response.getContentText());    // Обрабатываем полученный список файлов  for (var i = 0; i < json.entries.length; i++) {    var name = json.entries[i].name;    // Создаём публичную ссылку на файл    CreateSharedLink(name);    var sh_link = GetSharedLink(name);        // Вытаскиваем из имени файла id карты    id = name.slice(0, name.indexOf("."))      // Вставляем картинку в ячейку с помощью формулы IMAGE    sheet_p.getRange(i + 2, 1).setValue(id);    sheet_p.getRange(i + 2, 2).setFormula("=IMAGE(\"" + sh_link+"\")");  }}// Создание публичной ссылки на файл function CreateSharedLink(name) {  // Задаём параметры для POST-запроса  var data = {    "path": ("/" + name),    "settings": {        "requested_visibility": "public",        "audience": "public",        "access": "viewer"    }  };  var payload = JSON.stringify(data);    var options = {    "method" : "POST",    "contentType" : "application/json",    "headers" : {       "Authorization" : "Bearer [код авторизации]"    },    "payload" : payload,    muteHttpExceptions : true  };    // Отправляем POST-запрос для создания публичной ссылки на файл  var url = "https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings";  var response = UrlFetchApp.fetch(url, options);}// Получение публичной ссылки на файлfunction GetSharedLink(name) {  // Задаём параметры для POST-запроса  var data = {    "path": ("/" + name)  };  var payload = JSON.stringify(data);    var options = {    "method" : "POST",    "contentType" : "application/json",    "headers" : {       "Authorization" : "Bearer [код авторизации]"    },    "payload" : payload,    muteHttpExceptions : true  };    // Отправляем POST-запрос для получения публичной ссылки на файл  var url = "https://api.dropboxapi.com/2/sharing/list_shared_links";  var response = UrlFetchApp.fetch(url, options);  var json = JSON.parse(response.getContentText());    // Вытаскиваем ссылку на файл из полученного ответа  var urlForDownload = json.links[0].url.slice(0, -1) + '1';    return urlForDownload;}

Получившаяся таблица ассоциаций была преобразована в специальный вид для загрузки в Gephi (опять таки с помощью скриптов) и экспортирована в формате CSV. Нужны две таблицы: таблица меток вершин (столбцы: id, label) и таблица связей между вершинами (столбцы: source, target, weight).

// Преобразование получившейся матрицы в таблицу меток и таблицу связейfunction CreateGraph() {  var ss = SpreadsheetApp.getActiveSpreadsheet();  var sheet_a = ss.getSheetByName("Ассоциации");  var range_a = sheet_a.getDataRange();  var data = range_a.getValues();  var height = range_a.getHeight();    // Таблица меток вершин  var sheet_lbl = ss.getSheetByName("Graph Labels");  // Таблица связей между вершинами  var sheet_edg = ss.getSheetByName("Graph Edges");    // Массив допустимых весов для связей  var weights = new Array("1", "2", "3");  var edg_num = 0;    // Заголовок таблицы меток вершин  var lbl_header = ["Id", "Label"];  // Заголовок таблицы связей между вершинами  var edg_header = ["Source", "Target", "Weight"];    // Очищаем таблицы  sheet_lbl.clear();  sheet_edg.clear();    // Добавляем заголовки в таблицы  sheet_lbl.appendRow(lbl_header);  sheet_edg.appendRow(edg_header);    // Список, в котором мы будем накапливать связи между вершинами  var tmp_arr = [];  var tmp_arr_len = 0;    // Перебираем все ячейки матрицы (без учёта зеркальных)  for (var i = 2; i < height; i++) {    var id1 = data[i][0];    var name1 = data[i][1];        // Добавляем запись в таблицу меток вершин    var lbl_row = [id1, name1];    sheet_lbl.appendRow(lbl_row);        for (var j = i + 1; j < height; j++) {      var wt = data[i][j].toString();            if (weights.includes(wt)) {        var id2 = data[0][j];        edg_num += 1;                var edg_row = [id1, id2, wt];                tmp_arr.push(edg_row);        tmp_arr_len += 1;                // Как только накопим в списке 100 записей, добавляем их в таблицу связей.        // Если добавлять записи по одной сразу в таблицу, программа выходит за        // максимальное допустимое время выполнения Google App Script        if (tmp_arr_len >= 100) {          sheet_edg.getRange(sheet_edg.getLastRow() + 1, 1, tmp_arr_len, 3).setValues(tmp_arr);          tmp_arr = [];          tmp_arr_len = 0;        }      }    }  }    // Добавляем оставшиеся в списке записи в таблицу связей  if (tmp_arr_len > 0) {    sheet_edg.getRange(sheet_edg.getLastRow() + 1, 1, tmp_arr_len, 3).setValues(tmp_arr);    tmp_arr = [];    tmp_arr_len = 0;  }}

Построение графа

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

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

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

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

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


Если вам интересно следить за развитием проекта, подписывайтесь на группу игры ВКонтакте и Instagram. Там я выкладываю заметки о разработке, сюжетные фрагменты и многое другое.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru