Антифрод: аналитическая система распознания мошеннических платежей
Статья из цикла «Antifraud Insights». Часть 4
В заключительной четвертой части статьи подробно обсудим наиболее сложную с технической точки зрения часть antifraud-сервиса – аналитическую систему распознания мошеннических платежей по банковским картам.
Выявление различного рода мошенничеств является типичным кейсом для задач обучения с учителем (supervised learning), поэтому аналитическая часть антифрод-сервиса будет построена с использованием алгоритмов машинного обучения.
Для стоящей перед нами задачи воспользуемся Azure Machine Learning – облачным сервисом выполнения задач прогнозной аналитики (predictive analytics).
Для понимания статьи будут необходимы базовые знания в области машинного обучения и знакомство с сервисом Azure Machine Learning.
Что уже было сделано? (для тех не читал предыдущие 3 части, но интересуется)
В первой части статьи мы обсудили, почему вопрос мошеннических платежей (fraud) стоит так остро для всех участников рынка электронных платежей – от интернет-магазинов до банков – и в чем основные сложности, из-за которых стоимость разработки таких систем подчас является слишком высокой для многих участников ecommerce-рынка.
Во 2-ой части были описаны требования технического и нетехнического характера, которые предъявляются к таким системам, и то, как я собираюсь снизить стоимость разработки и владения antifraud-системы на порядок(и).
В 3-ей части была рассмотрена программная архитектура сервиса, его модульная структура и ключевые детали реализации.
В заключительной четвертой части у нас следующая цель…
Цель
В этой части я опишу проект, на первом шаге которого мы обучим четыре модели, используя следующие алгоритмы машинного обучения – логистическую регрессию, персептрон, метод опорных векторов и дерево решений. Из обученных моделей выберем ту, которая дает большую точность на тестовой выборке и опубликуем ее в виде REST/JSON-сервиса.
Далее для полученного сервиса напишем программного клиента и проведем нагрузочное тестирование на сервис.
Начнем…
Создание модели
Создадим новый эксперимент в Azure ML Studio. В конечном виде он будет выглядеть так, как показано на иллюстрации ниже. Соотнесем каждый элемент эксперимента с этапом последовательности, который среднестатистический data scientist проделывает в процессе обучения модели.
![Azure ML experiment](http://static.0xcode.in/images/azure_ml_experiment_(v2).png)
Рассмотрим каждый из этапов создания модели распознания мошеннических платежей, принимая во внимания технические детали, описанные в прошлой 3-ей части статьи.
Гипотеза
Основные концепции и предположения, полезные для создания модели, были обсуждены в первых 2-ух частях статьи. Повторяться не буду, лишь отмечу, что создание хорошей гипотезы – это итеративный процесс проб и ошибок, фундаментом для которого являются знания как в исследуемой предметной области, так и в области Data Science.
Получение данных
Набором данных для модели распознания мошеннических платежей будет являться лог транзакций, который состоит из 2-ух таблиц в NoSQL-хранилище (Azure Table): таблицы фактов о транзакциях TransactionsInfo и таблицы с предварительно рассчитанными статистическими метриками TransactionsStatistics.
На этапе получения данных загрузим эти 2 таблицы через элемент управления Reader.
Подготовка и исследование данных
Сделаем Inner Join загруженных таблиц по полю TransactionId. C помощь элемента управления Metadata Editor укажем типы данных (string, integer, timestamp), отметим столбец с ответами (label) и столбцы с предикторами (features), а также тип шкалы у этих данных: номинальные, абсолютные.
Не стоит недооценивать важность подготовки для создания адекватной модели: приведу простой пример с валютой платежа, которая храниться в виде ISO-кодов (целочисленное значение). ISO-коды – имеет номинальную (классификационную) шкалу. Но вряд ли стоит надеяться, что система автоматически определит, что в столбце «Currency» хранится не целочисленное значение с абсолютной шкалой (т.е. возможны такие операции как + или >). Потому что это слишком неочевидное правило, знаниями о котором система не обладает.
Набор данных может содержать пропущенные значения. В нашем случае, страну или IP-адрес плательщика не всегда есть возможность определить, такие поля могут содержать пустые значения. Проверив имеющийся набор данных, заменим пустые значения стран на «undefined» с помощью элемента управления Clean Missing Data. С помощью этого же элемента управления удалим строки, где в поле держатель карты, сумма платежа или валюта не содержатся значения, как строки, содержащие заведомо некорректные данные, то есть вносящие шум в модель.
На следующем этапе избавимся от неиспользуемых в модели полей: адрес (нас интересует только совпала ли страна плательщика со страной, откуда пришел запрос), хэш имени держателя карта (т.к. не имеет никакого влияния на результат платежа), RowId и PartitionId (служебные данные, попавшие к нам из Azure Table).
В заключении с помощью элемента управления Normalize Data проведем ZScore-нормализацию данных, содержащих большие числовые значения, такие как сумма платежа (столбец TransactionAmount).
Деление данных
Поделим получившийся набор данных на обучающую и тестовую выборку. Выберем оптимальное соотношение данных в обучающей выборки и в тестовой. Для наших целей с помощью элемента управления Split «отправим» 70% всех имеющихся данных в обучающую выборку, дополнительно включив произвольное смешивание данных (флаг Randomized split) при делении на поднаборы данных. Смешивание данных при делении позволит избежать «перекосов» в обучающей выборке, связанных с большими утечками номеров пластиковых карт (и, как следствие, аномальной активностью фрод-роботов в этой период).
Построение и оценка модели
Инициализируем несколько алгоритмов классификации и сравним какой из них дает лучший результат (точность) на тестовой выборке. Важно отметить, что совсем не факт, что на реальных данных будет достигнута та же производительность, что и тестовых данных. Поэтому очень важно понять, что в модели было не учтено, почему один из алгоритмов дает существенно худший или лучший результат, исправить ошибки и запустить алгоритм обучения заново. Этот процесс, заканчивается тогда, когда исследователь получает приемлемую по точности модель.
Azure ML позволяет нам подключать в одном эксперименте неограниченное количество алгоритмов машинного обучения. Это дает возможность на этапе исследования сравнить производительность нескольких алгоритмов с целью выявления того, который из них наилучшим образом подходит для нашей задачи. В нашем эксперименте мы используем несколько алгоритмов двуклассовой классификации: Two-Class Logistic Regression (логистическая регрессия), Two-Class Boosted Decision Tree (дерево решений, построенное методом градиентного роста), Two-Class Support Vector Machine (метод опорных векторов), Two-Class Neural Network (нейросеть).
Еще одна возможность получить лучшую производительность модели – настроить алгоритм машинного обучения, используя большое число параметров доступных для настройки алгоритма. Так для алгоритма Two-Class Boosted Decision Tree было указано количество деревьев, которое необходимо построить, а также минимальное/максимальное количество листьев на каждом дереве; для алгоритма Two-Class Neural Network количество скрытых узлов, итераций обучения и начальные веса.
На заключительном этапе просмотрим выходные данные элемента управления Evaluate Model (команда Visualize из контекстного меню элемента) для каждого из алгоритмов.
![antifraud evaluate model](http://static.0xcode.in/images/antifraud_evaluate_model.png)
Элемент управления Evaluate Model содержит матрицу неточностей (confusion matrix), а также рассчитанные показатели Accuracy, Precision, Recall, F1 Score.
Кроме того, тут мы можем посмотреть на изменение AUC в зависимости от устанавливаемого значения Threshold. В случае с фродом – это важно, так как стоимость нераспознанных мошеннической платежей (False Positive) намного выше, чем стоимость платежей, ошибочно принятых за фрод (False Negative).
В таких случаях необходимо выбирать значение Threshold отличное от значения по умолчанию 0,5.
При выборе наиболее подходящего алгоритма для получения оптимальной модели распознания фрода, кроме уровня Threshold, следует также учитывать и тот факт, что логику принятия решения для некоторых алгоритмов (например, дерево решений) возможно воспроизвести, а для некоторых нет (персептрон). Наличие такой возможности может быть критичным, если важно знать, почему по определенному прецеденту система приняла конкретное решение.
Публикация модели как веб-сервиса
После того, как была получена модель, работающая с требуемой точностью, опубликуем наш эксперимент как веб-сервис. Операция публикации проходит по нажатию кнопки «Publish Web Service» в Azure ML Studio. Процесс создания веб-сервиса из эксперимента тривиален и его описание я пропущу.
В результате Azure ML развернет масштабируемый отказоустойчивый (SLA 99,95%) web-сервис. После публикации сервиса станет доступна страница документации по API сервиса – API help, которая кроме общего описания сервиса, описания форматов ожидаемых входных и выходных сообщений, содержит еще и примеры вызова сервиса на C#, Python и R.
Принцип вызова сервиса программным клиентом можно изобразить так.
![Azure ML services.png](http://static.0xcode.in/images/azure_ml_services_(v2).png)
Подключение к Azure ML web-сервису
Возьмем пример на C# из API help и, немного изменив его, вызовем web-сервис Azure ML.
Листинг 1. Вызов web-сервиса Azure ML
private async Task<RequestStatistics> InvokePredictorService(TransactionInfo transactionInfo, TransactionStatistics transactionStatistics) { Contract.Requires<ArgumentNullException>(transactionInfo != null); Contract.Requires<ArgumentNullException>(transactionStatistics != null); var statistics = new RequestStatistics(); var watch = new Stopwatch(); using (var client = new HttpClient()) { var scoreRequest = new { Inputs = new Dictionary<string, StringTable>() { { "transactionInfo", new StringTable() { ColumnNames = new [] { #region Column name list }, Values = new [,] { { #region Column value list } } } }, }, GlobalParameters = new Dictionary<string, string>() }; client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ConfigurationManager.AppSettings["FraudPredictorML:ServiceApiKey"]); client.BaseAddress = new Uri("https://ussouthcentral.services.azureml.net/workspaces/<workspace_id>/services/<service_id>/execute?api-version=2.0&details=true"); watch.Start(); HttpResponseMessage response = await client.PostAsJsonAsync("", scoreRequest); if (response.IsSuccessStatusCode) await response.Content.ReadAsStringAsync(); statistics.TimeToResponse = watch.Elapsed; statistics.ResponseStatusCode = response.StatusCode; watch.Stop(); } return statistics; }
Получим следующие запрос/ответ:
Листинг 2. Ответ web-сервиса Azure ML
Request:
POST https://ussouthcentral.services.azureml.net/workspaces/<workspace_id>/services/<service_id>/execute?api-version=2.0&details=true HTTP/1.1 Authorization: Bearer <api key> Content-Type: application/json; charset=utf-8 Host: ussouthcentral.services.azureml.net /* другие заголовки */ { "Inputs": { "transactionInfo": { "ColumnNames": [ "PartitionKey", "RowKey", "Timestamp", "CardId", "CrmAccountId", "MCC", "MerchantId", "TransactionAmount", "TransactionCreatedTime", "TransactionCurrency", "TransactionId", "TransactionResult", "CardExpirationDate", "CardholderName", "CrmAccountFullName", "TransactionRequestHost", "PartitionKey (2)", "RowKey (2)", "Timestamp (2)", "CardsCountFromThisCrmAccount1D", "CardsCountFromThisCrmAccount1H", "CardsCountFromThisCrmAccount1M", "CardsCountFromThisCrmAccount1S", "CardsCountFromThisHost1D", "CrmAccountsCountFromThisCard1D", "FailedPaymentsCountByThisCard1D", "SecondsPassedFromPreviousPaymentByThisCard1D", "PaymentsCountByThisCard1D", "HostsCountFromThisCard1D", "HasHumanEmail", "HasHumanPhone", "IsCardholderNameIsTheSameAsCrmAccountName", "IsRequestCountryIsTheSameAsCrmAccountCountry", "TransactionDayOfWeek", "TransactionLocalTimeOfDay" /* значения прочие предикторы */ ], "Values": [ [ "990", "f31f64f367644b1cb173a48a34817fbc", "2015-03-15T20:54:28.6508575Z", "349567471", "10145", "32", "990", "136.69", "2015-03-15T20:54:28.6508575Z", "840", "f31f64f367644b1cb173a48a34817fbc", null, "2015-04-15T23:44:28.6508575+03:00", "640ab2bae07bedc4c163f679a746f7ab7fb5d1fa", "640ab2bae07bedc4c163f679a746f7ab7fb5d1fa", "20.30.30.40", "990", "f31f64f367644b1cb173a48a34817fbc", "2015-03-15T20:54:28.6508575Z", "2", "1", "0", "0", "0", "0", "1", "2", "0", "0", "true", null, "true", "true", "Monday", "Morning" /* значения прочих предикторов */ ] ] } }, "GlobalParameters": { } }
Response:
HTTP/1.1 200 OK Content-Length: 1619 Content-Type: application/json; charset=utf-8 Server: Microsoft-HTTPAPI/2.0 x-ms-request-id: f8cb48b8-6bb5-4813-a8e9-5baffaf49e15 Date: Sun, 15 Mar 2015 20:44:31 GMT { "Results": { "transactionPrediction": { "type": "table", "value": { "ColumnNames": [ "PartitionKey", "RowKey", "Timestamp", "CardId", "CrmAccountId", "MCC", "MerchantId", "TransactionAmount", "TransactionCreatedTime", "TransactionCurrency", "TransactionId", /* значения прочие предикторы */ "Scored Labels", "Scored Probabilities" ], "Values": [ [ "990", "f31f64f367644b1cb173a48a34817fbc", "2015-03-15T20:54:28.6508575Z", "349567471", "10145", "32", "990", "136.69", "2015-03-15T20:54:28.6508575Z", "840", "f31f64f367644b1cb173a48a34817fbc", /* значения прочих предикторов */ "Success", "0.779961256980896" ] ] } } } }
Нагрузочное тестирование
Для целей нагрузочного тестирования воспользуемся IaaS-возможностями Azure – поднимем виртуальную машину (Instance A8: 8x CPU, 56Gb RAM, 40Gbit/s InfiniBand, Windows Server 2012 R2, $2.45/hr) в том же регионе (US Central South), в котором находиться наш Azure ML web-сервис. Запустим на VM задачу на ~20K запросов и посмотрим на результаты.
Листинг 3. Код задачи
public static void Main() { var client = new FraudPredictorMLClient(); RequestsStatistics statistics = client.Invoke(123456).Result; Func<double, string> format = d => d.ToString("F3"); Log.Info("Min: {0} ms", format(statistics.Min)); Log.Info("Average: {0} ms", format(statistics.Average)); Log.Info("Max: {0} ms", format(statistics.Max)); Log.Info("Count of failed requests: {0}", statistics.FailedRequestsCount); } public class FraudPredictorMLClient { public async Task<RequestsStatistics> Invoke(int merchantId) { Contract.Requires<ArgumentOutOfRangeException>(merchantId > 0); // upload data IEnumerable<TransactionInfo> tis = null; IEnumerabletss = null; Parallel.Invoke( () => tis = new TransactionsInfoRepository().Get(merchantId), () => tss = new TransactionsStatisticsRepository().Get(merchantId) ); var inputs = tis .Join(tss, ti => ti.TransactionId, ts => ts.TransactionId, (ti, ts) => new { TransactionInfo = ti, TransactionStatistics = ts }) .ToList(); // send requests var statistics = new List<RequestStatistics>(inputs.Count); foreach (var input in inputs) { RequestStatistics stats = await InvokePredictorService(input.TransactionInfo, input.TransactionStatistics).ConfigureAwait(false); statistics.Add(stats); } return new RequestsStatistics(statistics); } /* other */ }
Результат:
Лучшее время ответа: 421.683 ms
Худшее время: 1355.516 ms
Среднее время: 652.935 ms
Количество успешных запросов: 21017
Количество отказов: 0
Ограничения (потенциальные)
Бутылочным горлышком разрабатываемой системы, на первый взгляд, будет являться Azure ML. Поэтому крайне важно понимать ограничения Azure ML в общем и web-сервисов Azure ML, в частности. Но по данному вопросу очень мало как официальной документации, так и результатов, полученных от community.
Так остается открытым вопрос с throttled policy конечных точек web-сервиса Azure ML: не ясно максимальное значение параллельных запросов, которое может обработать сервис, а также максимальный размер принимаемого сообщения (актуального для пакетного режима работы сервиса).
Менее актуально, но стоит вопрос с максимальным размером входных данных, максимальным количеством предикторов и прецедентов, которые можно отдать на вход алгоритму машинного обучения в Azure ML.
Критически важно сократить время ответа веб-сервиса FraudPredictorML, а также время переобучения модели до минимальных значений, но пока нет никаких официальных рекомендаций по тому, как это возможно сделать (и возможно ли вообще).
Рекомендации клиентам
Антифрод-сервис никак не ограничивает клиентов как в предварительной проверке платежей, так и в последующей интерпретации результатов предсказания. Предварительные специфичные для бизнес-процесса проверки, а также окончательное принятие решения о принятии/отклонение платежа – это задачи, которые явно выходят из зоны ответственности антифрод-сервиса.
В независимости о роли клиента – интернет-магазин, платежная система или банк – для клиентов существуют следующие рекомендации:
- выполняйте предварительную проверку платежей, как используя технологии, принятые в отрасли (fingerprint и т.п.), так и использую собственные знания о клиенте (историю заказов и т.п.);
- интерпретируйте результат, применяя следующую практику: вероятность фрода ниже 0,35 – принимать оплату без 3D-Secure, вероятность от 0,35 до 0,85 – принимайте оплату с включенным 3DS, вероятность фрода – более отказывайте;
- выбирайте уровни, предложенные в предыдущем пункте, на основе собственной аналитики и регулярно пересматривайте их (минимизируйте упущенные выгоды и штрафы за фрод).
Рекомендации для комментирующих (off-topic)
В рамках этого цикла статей мы касались проблематики вопроса, юридической и технической стороны проблемы. Это техническая статья, она не преследует собой цели создать бизнес-план,
сравнить с решениями конкурентов, вычислить дисконтированную стоимость проекта. Со всеми этими вопросами на РБК – не ко мне, не в это хаб, и, есть подозрение, даже не на этот сайт.
Заключение
В этом цикле, состоящем из 4-ех статей, мы провели эксперимент по проектированию и разработке высокомасштабируемого отказоустойчивого надежного antifraud-сервиса, работающего в near real-time режиме и открытого внешним программным клиентам как REST/JSON-сервис.
Применение алгоритмов машинного обучения (дерево решений, нейросети) позволили создать аналитическую систему, способную к самообучению как на накопленной истории, так и на новых платежах. Благодаря использованию PaaS-/IaaS-сервисов удалось сократить первоначальные финансовые затраты на инфраструктуру и ПО практически до нуля. Наличие у разработчика компетенций в предметной области, data science, архитектуре распределенных систем помогло драматически снизить количество участников команды разработки.
В результате менее чем за 50 человеко-часов и с начальными затратами на инфраструктуру около 2,5 тыс. руб. (которые были покрыт из подписки MSDN) удалось создать ядро антифрод-системы.
Получившийся сервис, конечно, требует еще тщательной проверки (и последующего исправления) основных модулей, более тонкой настройки работы классификатора(ов), разработки серии вспомогательных подсистем, интереса и (что тут греха таить) инвестиций.
Но уже по созданному функционалу видно, что на современном уровне развития IT, имея только идею и знания, достаточные для реализации этой идеи, коллектив единомышленников может создавать программные продукты и сервисы, разработку которых еще недавно могли позволить себе только крупные компании.
Другие части статьи
Если Вам осталось неясно в чем проблема (часть 1).
Если Вы не пропустили, почему проблему фрода долго и дорого сложно решить (часть 2).
Если Вам интересно, как это выглядит с точки зрения дизайна приложения (часть 3).
Дмитрий Петухов,
Software Architect & Developer, Big Data Enthusiast, Microsoft Certified Professional
архитектор, разработчик, энтузиаст, неутомимый исследователь и кофеман
Комментариев нет:
Отправить комментарий