Генератор больших графов транзакций с паттернами преступной деятельности
- пятница, 12 апреля 2019 г. в 00:21:28
Доброго времени суток.
Для решения данной задачи был написан генератор, которым мне бы хотелось с вами поделиться. Под катом вы найдете историю, объясняющую зачем нам это было нужно, и описание работы генератора. Для нетерпеливых — вот тут лежит код. Буду рад, если кому-нибудь будет полезен наш опыт.
Наша команда решила поучаствовать спонсорами на хакатоне LauzHack
. Одним из условий участия в формате спонсора было предоставление реальной бизнес задачи для участников. Как раз в то время у нас был очень интересный проект, связанный с автоматизацией поиска финансовых преступлений и отмывания денег среди транзакций наших клиентов, и мы, не долго думая, решили предложить эту же задачу участникам хакатона.По очевидным причинам мы не могли использовать реальные данные, по-этому пришлось их создавать. Чтобы задача была максимально приближена к реальности, мы посмотрели на статистику реальных данных и пытались, как могли, приблизить сгенерированные данные к реальным распределениям, а так же не скупились на количество и сложность данных — нам не нужно было решение, работающие на графе из 100 узлов и 200 соединений, мы искали решение, способное обрабатывать графы размером в миллионы узлов и миллиарды соединений, и учитывающие всю доступную информацию об узлах и соединениях.
А получился у нас вполне себе быстрый (с поправкой на количество данных), интересный и конфигурируемый генератор! Давайте разбираться детально
Мы хотим иметь граф финансовых транзакций, соответственно возможные участники этого графа это:
Для создания этих данных мы используем Mimesis, отличная библиотека для создания фейковых данных.
Для начала надо создать все базовые сущности — клиентов, компании и банкоматы. Скрипт принимает количество клиентов, которое требуется создать, и на основе этого высчитывает количество компаний и банкоматов. По нашим данным, количество компаний, имеющих какое-либо крупное количество транзакций с клиентами, равно приблизительно 2.5% от количества клиентов, а количество банкоматов — 0.05% от количества клиентов. Эти значения очень обобщены и неконфигурируемы (зашиты в коде генератора).
Вся информация сохраняется в .csv файлы. Запись в эти файлы происходит пакетно, по k строк за раз. Это значение настраивается аргументами скрипта. Также три типа узлов генерируются паралельно.
После создания базовых сущностей мы начинаем соединять их между собой. На этом этапе мы еще не генирируем сами транзакции, а просто сам факт наличия связи между узлами. Сделано это для ускорения процесса генерации всего графа и работает примерно следующим образом: если два узла соединены, то мы генерируем какое-то количество транзакций между ними, раскиданное по времени. Если не соединены, но транзакций между данными узлами не существует.
Вероятность наличия связи между двумя узлами настраивается через аргументы, стандартные значения указаны ниже.
Возможные типы соединений:
Как и узлы, все типы соединений генерируются паралельно и записываются в свои файлы пакетно.
Имея узлы графа и соединения между ними, попадающие под желаемое распределение, мы можем начать генерировать транзакции. Процесс достаточно простой сам по себе, но распаралелить его достаточно сложно. Поэтому на этом этапе есть только два независимых потока — транзакции, исходящие от клиента, и транзакции, исходящии от компании.
Ничего особо интересного на этом этапе не происходит: скрипт пробегает по списку соединений и для каждого соединения генерирует случайное количество транзакций. Пишется это все точно также — в .csv файлы по пакетам.
А вот тут есть интересные моменты. Типы паттернов поведения, которые мы хотели получить в конечном графе:
Давайте рассмотрим каждый из этих паттернов подробней:
Для начала выбирается количество уровней, через которые деньги должны будут пройти. В нашей реализации это случайное число между 2 и 6, не конфигурируемо и зашито в коде. Далее выбираются два узла графа — отправитель и получатель. Также выбирается случайная сумма, которую отправитель отправит получателю (по учень хитрой формуле 50000 * random() + 50000 * random()
).
Каждый участник этой сети берет какую то плату за свои услуги. В нашей реализации максимальная цена за пропуск денег через сеть будет 10% от суммы, передаваемой отправителем.
Сгенерированные транзакции имеют сдвиг по времени относительного транзакций предыдущего уровня сети — то есть деньги сначала приходят на уровень n-1, а только потом уходят на уровень n. Задержки выбираются случайно в пределах 4-5 дней. Также сгенерированные транзакции имеют псевдо-случайные суммы (ограниченные изначальной суммой и с учетом платы каждому узлу)
Генерируется по схожему принципу, что и Flow, только вместо разных отправителя и получателя и нескольких уровней в данном паттерне деньги идут по кругу и возвращаются в изначальному узел. Все промежуточные узлы берут плату, как и в случае с Flow, и транзакции также имеют сдвиг по времени.
Самый простой паттерн. Определенная сумма отправляется от отправителя получателю случайное количество раз (от 5 до 50, не настраивается) с псевдо-случайными сдвигами во времени.
Все новые транзакции точно так же пишутся в .csv файлы пакетами.
На данном этапе у нас есть несколько .csv файлов:
Дополнительный скрипт смешивает транзакции паттернов вместе с обычными транзакциями, чтобы не было возможности увидеть паттерны в графе по порядку записи транзакций в файле.
В конце концов у нас есть 4 красивых файла с узлами графа и транзакциями между ними. Можно импортировать в Neo4J, можно раздавать через REST, да всё что душе угодно можно с ними делать.
А что касается нас — мы получили очень положительный фидбек со стороны участников хакатона, и несколько очень интересных решений по поиску паттернов в массивных графах.