Учебное пособие по использованию библиотеки Python LightGBM с примерами кода

Обнаружение мошенничества - один из главных приоритетов для банков и финансовых учреждений, с которым можно бороться с помощью машинного обучения. Согласно отчету, опубликованному Nilson, в 2017 году мировые убытки от мошенничества с картами достигли 22,8 миллиарда долларов. По прогнозам, проблема усугубится в следующие годы: к 2021 году счет за мошенничество с картами, как ожидается, составит 32,96 миллиарда долларов.

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

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

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

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

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

Чтобы установить зависимости в Linux:

$ sudo apt-get update
$ sudo apt-get install cmake build-essential libboost-all-dev -y
$ conda env create -n fraud -f conda.yaml
$ source activate fraud
(fraud)$ python -m ipykernel install --user --name fraud --display-name "Python (fraud)"

Вот используемые библиотеки и их версии:

import numpy as np
import sys
import os
import json
import pandas as pd
from collections import Counter
import requests
from IPython.core.display import display, HTML
import lightgbm as lgb
import sklearn
import aiohttp
import asyncio
from utils import (split_train_test, classification_metrics_binary, classification_metrics_binary_prob, binarize_prediction, plot_confusion_matrix, run_load_test, read_from_sqlite)
from utils import BASELINE_MODEL, PORT, TABLE_FRAUD, TABLE_LOCATIONS, DATABASE_FILE
print("System version: {}".format(sys.version))
print("Numpy version: {}".format(np.__version__))
print("Pandas version: {}".format(pd.__version__))
print("LightGBM version: {}".format(lgb.__version__))
print("Sklearn version: {}".format(sklearn.__version__))
%load_ext autoreload
%autoreload 2
System version: 3.6.0 |Continuum Analytics, Inc.| (default, Dec 23 2016, 13:19:00) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)]
Numpy version: 1.13.3
Pandas version: 0.22.0
LightGBM version: 2.1.1
Sklearn version: 0.19.1

Набор данных

Первый шаг - загрузить набор данных и проанализировать его.

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

query = 'SELECT * FROM ' + TABLE_FRAUD
df = read_from_sqlite(DATABASE_FILE, query)
print("Shape: {}".format(df.shape))
df.head()
Shape: (284807, 31)

Как видим, набор данных крайне несбалансирован. На долю меньшинства приходится около 0,002% примеров.

df['Class'].value_counts()
0    284315
1       492
Name: Class, dtype: int64
df['Class'].value_counts(normalize=True)
0    0.998273
1    0.001727
Name: Class, dtype: float64

Следующим шагом является разделение набора данных на обучение и тестирование.

X_train, X_test, y_train, y_test = split_train_test(df.drop('Class', axis=1), df['Class'], test_size=0.2)
print(X_train.shape)
print(X_test.shape)
print(y_train.shape)
print(y_test.shape)
(227845, 30)
(56962, 30)
(227845,)
(56962,)
print(y_train.value_counts())
print(y_train.value_counts(normalize=True))
print(y_test.value_counts())
print(y_test.value_counts(normalize=True))
0    227451
1       394
Name: Class, dtype: int64
0    0.998271
1    0.001729
Name: Class, dtype: float64
0    56864
1       98
Name: Class, dtype: int64
0    0.99828
1    0.00172
Name: Class, dtype: float64

Обучение с LightGBM - Базовый уровень

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

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

lgb_train = lgb.Dataset(X_train, y_train, free_raw_data=False)
lgb_test = lgb.Dataset(X_test, y_test, reference=lgb_train, free_raw_data=False)
parameters = {'num_leaves': 2**8,
              'learning_rate': 0.1,
              'is_unbalance': True,
              'min_split_gain': 0.1,
              'min_child_weight': 1,
              'reg_lambda': 1,
              'subsample': 1,
              'objective':'binary',
              #'device': 'gpu', #comment if you're not using GPU
              'task': 'train'
              }
num_rounds = 300
%%time
clf = lgb.train(parameters, lgb_train, num_boost_round=num_rounds)
CPU times: user 45.1 s, sys: 7.68 s, total: 52.8 s
Wall time: 11.9 s

Получив обученную модель, мы можем получить некоторые показатели.

y_prob = clf.predict(X_test)
y_pred = binarize_prediction(y_prob, threshold=0.5)
metrics = classification_metrics_binary(y_test, y_pred)
metrics2 = classification_metrics_binary_prob(y_test, y_prob)
metrics.update(metrics2)
cm = metrics['Confusion Matrix']
metrics.pop('Confusion Matrix', None)
array([[55773,  1091],
       [   11,    87]])
print(json.dumps(metrics, indent=4, sort_keys=True))
plot_confusion_matrix(cm, ['no fraud (negative class)', 'fraud (positive class)'])
{
    "AUC": 0.9322482105532139,
    "Accuracy": 0.980653769179453,
    "F1": 0.13636363636363638,
    "Log loss": 0.6375216445628125,
    "Precision": 0.07385398981324279,
    "Recall": 0.8877551020408163
}

С точки зрения бизнеса, если система классифицирует честную транзакцию как мошенничество (ложное срабатывание), банк расследует проблему, вероятно, с участием человека. Согласно отчету Javelin Strategy за 2015 год, у 15% всех держателей карт в прошлом году по крайней мере одна транзакция была неправильно отклонена, что представляет собой ежегодное снижение почти на 118 миллиардов долларов. Почти 4 из 10 держателей карт, отказавшихся от оплаты, сообщают, что они отказались от своей карты после ложного отказа.

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

Обычный способ использования бизнес-правил в этих прогнозах - контролировать пороговое значение или рабочую точку прогнозирования. Этим можно управлять, изменяя пороговое значение в binarize_prediction(y_prob, threshold=0.5). Обычно делают цикл от 0,1 до 0,9 и оценивают различные бизнес-результаты.

clf.save_model(BASELINE_MODEL)

O16N с Flask и веб-сокетами

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

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

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

Чтобы запустить api, выполните (fraud)$ python api.py в среде conda. Вы также можете запустить api из записной книжки (хотя мне это труднее отлаживать). Для этого просто раскомментируйте следующие две строки:

#%%bash --bg --proc bg_proc
#python api.py

Сначала убеждаемся, что API включен

#server_name = 'http://the-name-of-your-server'
server_name = 'http://localhost'
root_url = '{}:{}'.format(server_name, PORT)
res = requests.get(root_url)
display(HTML(res.text))

Полиция по мошенничеству наблюдает за вами !!!

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

vals = y_test[y_test == 1].index.values
X_target = X_test.loc[vals[0]]
dict_query = X_target.to_dict()
print(dict_query)
{'Time': 57007.0, 'V1': -1.2712441917143702, 'V2': 2.46267526851135, 'V3': -2.85139500331783, 'V4': 2.3244800653477995, 'V5': -1.37224488981369, 'V6': -0.948195686538643, 'V7': -3.06523436172054, 'V8': 1.1669269478721105, 'V9': -2.2687705884481297, 'V10': -4.88114292689057, 'V11': 2.2551474887046297, 'V12': -4.68638689759229, 'V13': 0.652374668512965, 'V14': -6.17428834800643, 'V15': 0.594379608016446, 'V16': -4.8496923870965185, 'V17': -6.53652073527011, 'V18': -3.11909388163881, 'V19': 1.71549441975915, 'V20': 0.560478075726644, 'V21': 0.652941051330455, 'V22': 0.0819309763507574, 'V23': -0.22134783119833895, 'V24': -0.5235821592333061, 'V25': 0.224228161862968, 'V26': 0.756334522703558, 'V27': 0.632800477330469, 'V28': 0.25018709275719697, 'Amount': 0.01}
headers = {'Content-type':'application/json'}
end_point = root_url + '/predict'
res = requests.post(end_point, data=json.dumps(dict_query), headers=headers)
print(res.ok)
print(json.dumps(res.json(), indent=2))
True
{
  "fraud": 1.0
}

Визуализация мошеннических транзакций

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

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

В нашем случае, когда пользователь делает запрос к конечной точке /predict_map, модель машинного обучения оценивает детали транзакции и делает прогноз. Если прогноз классифицируется как мошеннический, сервер отправляет сигнал с использованием socketio.emit('map_update', location). Этот сигнал просто содержит словарь, называемый location, с смоделированным именем и местом, где произошла мошенническая транзакция. Сигнал показан в index.html, который просто отображает некоторый код javascript, на который имеется ссылка через id="chartdiv".

Код javascript определен в файле frauddetection.js. Часть веб-сокета следующая:

var mapLocations = [];
// Location updated emitted by the server via websockets
socket.on("map_update", function (msg) {
    var message = "New event in " + msg.title + " (" + msg.latitude
        + "," + msg.longitude + ")";
    console.log(message);
    var newLocation = new Location(msg.title, msg.latitude, msg.longitude);
    mapLocations.push(newLocation);
    clear the markers before redrawing
    mapLocations.forEach(function (location) {
      if (location.externalElement) {
        location.externalElement = undefined;
      }
    });
    map.dataProvider.images = mapLocations;
    map.validateData(); //call to redraw the map with new data
});

Когда новый сигнал испускается с сервера в python, код javascript получает его и обрабатывает. Он создает новую переменную с именем newLocation, содержащую информацию о местоположении, которая будет сохранена в глобальном массиве с именем mapLocations. Эта переменная содержит все мошеннические местоположения, появившиеся с момента начала сеанса. Затем выполняется процесс очистки для amCharts, чтобы иметь возможность рисовать новую информацию на карте, и, наконец, массив сохраняется в map.dataProvider.images, который фактически обновляет карту с новой точкой. Переменная map установлена ​​ранее в коде, и это объект amCharts, отвечающий за определение карты.

Чтобы сделать запрос к конечной точке визуализации:

headers = {'Content-type':'application/json'}
end_point_map = root_url + '/predict_map'
res = requests.post(end_point_map, data=json.dumps(dict_query), headers=headers)
print(res.text)
True
{
  "fraud": 1.0
}

Теперь вы можете перейти по URL-адресу карты (локально это будет http: // localhost: 5000 / map) и посмотреть, как карта обновляется с новым мошенническим местоположением каждый раз, когда вы выполняете предыдущую ячейку. Вы должны увидеть карту, подобную следующей:

Нагрузочный тест

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

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

Время ответа 10 запросов составляет около 300 мс, поэтому один запрос будет 30 мс.

num = 10
concurrent = 2
verbose = True
payload_list = [dict_query]*num

Создаем постоянное соединение

%%time
with aiohttp.ClientSession() as session:  
    loop = asyncio.get_event_loop()
    calc_routes = loop.run_until_complete(run_load_test(end_point, payload_list, session, concurrent, verbose))
ERROR:asyncio:Creating a client session outside of coroutine
client_session: aiohttp.client.ClientSession object at 0x7f16847333c8
Response status: 200
{'fraud': 7.284115783035928e-06}
Response status: 200
{'fraud': 7.284115783035928e-06}
Response status: 200
{'fraud': 7.284115783035928e-06}
Response status: 200
{'fraud': 7.284115783035928e-06}
Response status: 200
Response status: 200
{'fraud': 7.284115783035928e-06}
{'fraud': 7.284115783035928e-06}
Response status: 200
Response status: 200
{'fraud': 7.284115783035928e-06}
{'fraud': 7.284115783035928e-06}
Response status: 200
{'fraud': 7.284115783035928e-06}
Response status: 200
{'fraud': 7.284115783035928e-06}
CPU times: user 14.8 ms, sys: 15.8 ms, total: 30.6 ms
Wall time: 296 ms

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

#%%bash
#ps aux | grep 'api.py' | grep -v 'grep' | awk '{print $2}' | xargs kill

Эталонная архитектура корпоративного уровня для обнаружения мошенничества

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

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

1) Два общих источника данных для клиента: данные в реальном времени и статическая информация.

2) Общая часть базы данных для хранения данных. Поскольку это эталонная архитектура и без дополнительных данных, я объединил несколько вариантов (База данных SQL, CosmosDB, Хранилище данных SQL и т. Д.) В облаке или локально.

3) Моделирование экспериментов с использованием Azure ML, опять же с использованием общих вычислительных целей, таких как DSVM, BatchAI, Databricks или HDInsight.

4) Переобучение модели с использованием новых данных и модели, полученной из Управление моделями.

5) Уровень эксплуатации с кластером Kubernetes, который берет лучшую модель и запускает ее в производство.

6) Слой отчетности для отображения результатов.

Ссылки