Доброго времени суток.
Пару лет назад перед нашей командой (compliance в швейцарском банке) встала очень интересная задача — нужно было сгенерировать большой граф транзакций между клиентами, компаниями и банкоматами, добавить в этот граф паттерны, похожие на паттерны отмывания денег и другой преступной деятельности, а также добавить минимальную информацию об узлах этого графа — имена, адреса, время, и т.д. Разумеется, все данные должны былии быть сгенерированны с нуля, без использования существующих данных клиентов.
Для решения данной задачи был написан генератор, которым мне бы хотелось с вами поделиться. Под катом вы найдете историю, объясняющую зачем нам это было нужно, и описание работы генератора. Для нетерпеливых — вот тут лежит код. Буду рад, если кому-нибудь будет полезен наш опыт.
Зачем мы занялись такой ерундой?
Наша команда решила поучаствовать спонсорами на хакатоне LauzHack
. Одним из условий участия в формате спонсора было предоставление реальной бизнес задачи для участников. Как раз в то время у нас был очень интересный проект, связанный с автоматизацией поиска финансовых преступлений и отмывания денег среди транзакций наших клиентов, и мы, не долго думая, решили предложить эту же задачу участникам хакатона.По очевидным причинам мы не могли использовать реальные данные, по-этому пришлось их создавать. Чтобы задача была максимально приближена к реальности, мы посмотрели на статистику реальных данных и пытались, как могли, приблизить сгенерированные данные к реальным распределениям, а так же не скупились на количество и сложность данных — нам не нужно было решение, работающие на графе из 100 узлов и 200 соединений, мы искали решение, способное обрабатывать графы размером в миллионы узлов и миллиарды соединений, и учитывающие всю доступную информацию об узлах и соединениях.
Что у нас получилось
А получился у нас вполне себе быстрый (с поправкой на количество данных), интересный и конфигурируемый генератор! Давайте разбираться детально
Типы данных
Мы хотим иметь граф финансовых транзакций, соответственно возможные участники этого графа это:
- Клиент — можно сказать, аккаунт абстрактного клиента банка. Описывается именем, имэилом, возрастом, рабочей деятельность, политическими взглядами, национальностью, образованием и адресом проживания
- Компания — бизнес сущность в финансовой системе. Определяется типом компании, названием и страной.
- Банкомат — грубо говоря, точки выхода денег из подконтрольного нам графа. Определены географическими координатами.
- Транзакция — Факт передачи денег от одного узла графа к другому. Определены начальным и конечным узлом, суммой, валютой и временем.
Для создания этих данных мы используем Mimesis, отличная библиотека для создания фейковых данных.
Создание графа: базовые сущности
Для начала надо создать все базовые сущности — клиентов, компании и банкоматы. Скрипт принимает количество клиентов, которое требуется создать, и на основе этого высчитывает количество компаний и банкоматов. По нашим данным, количество компаний, имеющих какое-либо крупное количество транзакций с клиентами, равно приблизительно 2.5% от количества клиентов, а количество банкоматов — 0.05% от количества клиентов. Эти значения очень обобщены и неконфигурируемы (зашиты в коде генератора).
Вся информация сохраняется в .csv файлы. Запись в эти файлы происходит пакетно, по k строк за раз. Это значение настраивается аргументами скрипта. Также три типа узлов генерируются паралельно.
Создание графа: соединения между сущностями
После создания базовых сущностей мы начинаем соединять их между собой. На этом этапе мы еще не генирируем сами транзакции, а просто сам факт наличия связи между узлами. Сделано это для ускорения процесса генерации всего графа и работает примерно следующим образом: если два узла соединены, то мы генерируем какое-то количество транзакций между ними, раскиданное по времени. Если не соединены, но транзакций между данными узлами не существует.
Вероятность наличия связи между двумя узлами настраивается через аргументы, стандартные значения указаны ниже.
Возможные типы соединений:
- Клиент -> Клиент (p = 0.4%)
- Клиент -> Компания (p = 1%)
- Клиент -> Банкомат (p = 3%)
- Компания -> Клиент (p = 0.5%)
Как и узлы, все типы соединений генерируются паралельно и записываются в свои файлы пакетно.
Создания графа: транзакции
Имея узлы графа и соединения между ними, попадающие под желаемое распределение, мы можем начать генерировать транзакции. Процесс достаточно простой сам по себе, но распаралелить его достаточно сложно. Поэтому на этом этапе есть только два независимых потока — транзакции, исходящие от клиента, и транзакции, исходящии от компании.
Ничего особо интересного на этом этапе не происходит: скрипт пробегает по списку соединений и для каждого соединения генерирует случайное количество транзакций. Пишется это все точно также — в .csv файлы по пакетам.
Создания графа: паттерны
А вот тут есть интересные моменты. Типы паттернов поведения, которые мы хотели получить в конечном графе:
- Flow — большая сумма уходит из одного узла к m другим, каждый из этих m узлов переправляет деньги следующему уровню из n узлов, и так далее, пока последний уровень не отправляет все деньги одному получателю.
- Circular — сумма денег идет по кругу и возвращается к источнику.
- Time — определенная сумма денег переходит от отдого узла к другому с какой-то фиксированной частотой.
Давайте рассмотрим каждый из этих паттернов подробней:
Flow
Для начала выбирается количество уровней, через которые деньги должны будут пройти. В нашей реализации это случайное число между 2 и 6, не конфигурируемо и зашито в коде. Далее выбираются два узла графа — отправитель и получатель. Также выбирается случайная сумма, которую отправитель отправит получателю (по учень хитрой формуле 50000 * random() + 50000 * random()
).
Каждый участник этой сети берет какую то плату за свои услуги. В нашей реализации максимальная цена за пропуск денег через сеть будет 10% от суммы, передаваемой отправителем.
Сгенерированные транзакции имеют сдвиг по времени относительного транзакций предыдущего уровня сети — то есть деньги сначала приходят на уровень n-1, а только потом уходят на уровень n. Задержки выбираются случайно в пределах 4-5 дней. Также сгенерированные транзакции имеют псевдо-случайные суммы (ограниченные изначальной суммой и с учетом платы каждому узлу)
Circular
Генерируется по схожему принципу, что и Flow, только вместо разных отправителя и получателя и нескольких уровней в данном паттерне деньги идут по кругу и возвращаются в изначальному узел. Все промежуточные узлы берут плату, как и в случае с Flow, и транзакции также имеют сдвиг по времени.
Time
Самый простой паттерн. Определенная сумма отправляется от отправителя получателю случайное количество раз (от 5 до 50, не настраивается) с псевдо-случайными сдвигами во времени.
Все новые транзакции точно так же пишутся в .csv файлы пакетами.
Рандомизация графа и сбор всех транзакций в один файл
На данном этапе у нас есть несколько .csv файлов:
- 3 файла с узлами (клиенты, компании и банкоматы)
- 4 файла с транзакциями: один для обычных транзакций и 3 содержащих паттерны.
Дополнительный скрипт смешивает транзакции паттернов вместе с обычными транзакциями, чтобы не было возможности увидеть паттерны в графе по порядку записи транзакций в файле.
И что со всем этим делать?
В конце концов у нас есть 4 красивых файла с узлами графа и транзакциями между ними. Можно импортировать в Neo4J, можно раздавать через REST, да всё что душе угодно можно с ними делать.
А что касается нас — мы получили очень положительный фидбек со стороны участников хакатона, и несколько очень интересных решений по поиску паттернов в массивных графах.
Комментарии (9)
Stas911
11.04.2019 04:24А как вы собирали клиентов для «преступного сообщества»? Они были объединены какими-то общими признаками?
Mgrin Автор
11.04.2019 13:50В нашей реализации нет, мы просто выбирали узлы случайно. Но в принципе нет ничего сложного объеденить их по стране, общей компании или же даже по одному банкомату, в котором они снимают деньги одновременно раз в месяц с разницей в 2 минуты :)
Hile
11.04.2019 15:17> pip3 install mimesis
Could not install packages due to an EnvironmentError: [Errno 13] Permission denied: '/usr/local/LICENSE'
Consider using the `--user` option or check the permissions.
MMik
Mgrin Автор
Почему? Эта информация теоретически может быть использована алгоритмами. В нашем же случае это просто почти полный список того, что библиотека Mimesis могла генерировать :)
Daddy_Cool
Увы, зато логично. «Этичные» данные не позволяют сделать выводы о тайных незаконных мотивах, в этом как бы и суть — то почему мы считаем какие-то данные неэтичными.
Скажем (по данным РИА) «в Москве 75% изнасилований совершают приезжие из стран Азии», соответственно в Москве есть много невиновных азиатов, и даже пусть эти 75% совершает 0.0001% азиатов, но для предиктивной аналитики это ценная информация.
Voila2000
Этично/неэтично всего лишь принятые в обществе, устоявшиеся номы взаимоотношений.
На практике (пока еще) поля с информацией о политических взглядах, национальности, образовании клиента выглядят диковато :)
А вот параметров у сущности «Компания», как мне кажется, маловато.