Бывала у вас такая ситуация, когда хотели снять деньги в банкомате, а их там не оказалось, и приходилось искать банкомат рядом, про себя ругаясь на банк: «Неужели так сложно сделать, чтобы деньги в аппарате были всегда?» Да, это возможно, но есть нюанс.
Меня зовут Мария, я работаю в Альфа-Банке на позиции Middle Data Scientist, и сейчас я вам про этот нюанс расскажу — почему же эта задача не такая тривиальная, как кажется на первый взгляд.
Ликбез по банкоматам
У Альфа-Банка достаточно много банкоматов — в открытом доступе около 3500. В банковской сфере банкоматы еще называют АТМ — Automated Teller Machine. Их можно классифицировать на следующие типы:
Банкоматы общего доступа. Они стоят в ТЦ, БЦ и любых местах, доступных для обычного клиента.
Банкоматы в отделениях. Здесь всё просто, не надо объяснять.
Самоинкассация — АТМ для бизнеса. Специально установленные банкоматы, куда наши клиенты (юрлица) могут приносить свою прибыль за какой-то период. Таким образом они инкассируют сами себя.
Зарплатные банкоматы (ЗП): могут стоять как в открытом доступе, так и в закрытом, но они всегда ставятся под какой-то зарплатный проект.
Также есть другой вид разделения:
Cash IN — банкоматы, которые только принимают деньги.
Cash OUT — банкоматы, которые только выдают деньги.
FULL — банкоматы, которые и принимают и выдают деньги. Однако, они не могут выдать те купюры, которые люди внесли перед вами.
RCL или recycling, ресайклинги — банкоматы, которые могут и принимать и выдавать деньги. При этом всё это делается из одних и тех же кассет (о них позже), в отличие от АТМ типа FULL. То есть когда клиент перед вами внес в банкомат 1000 рублей, вы можете снять эти же 1000 рублей.
Место расположения банкомата подбирается как раз в зависимости от его типа. Например, банкоматы Cash OUT могут ставить под зарплатные проекты или рядом с рынками, а RCL ставят в местах более оживленных, типа торговых центров или отделений. Наиболее интересными и важными для банка являются именно RCL банкоматы, поэтому при решении задачи мы сосредоточились на них.
В данной статье не будем останавливаться на устройстве банкоматов, тем более, что на эту тему есть много статей. Здесь я расскажу о том, как мы разработали алгоритм, подбирающий сумму денег и момент инкассации. Естественно, что до нас подобный алгоритм уже существовал и очень хорошо работал, иначе банкоматы были бы бесполезны. Но у него была пара моментов, которые требовали улучшения.
«Всё просчитано до нас»
В процессе инкассации одного банкомата задействовано много людей и производится много расчетов.
Раз в месяц коллеги на основе исторических снятий и внесений рассчитывают сумму загрузки во все банкоматы России по своему алгоритму. Так как процесс пересчета достаточно трудоемкий, в течение месяца используется только эта сумма. Очень редко, когда в середине месяца её пересчитывают.
Изначально, с этой проблемой бизнес к нам и пришел: необходимо было оптимизировать и автоматизировать процесс пересчета сумм загрузки.
На этом этапе отсутствовала гибкость и скорость реагирования на изменения во внешнем мире. Например, рядом с банкоматом открылся ресторан, в котором владелец принимает только наличные деньги. Вследствие этого суммы снятий из этого банкомата сильно выросли, денег стало не хватать, и банкомат большую часть времени стоит недоступный и пустой. В результате много клиентов расстраивается, а это, в свою очередь, влияет на имидж банка.
В процессе знакомства с задачей мы узнали, что в регионах присутствует своя система прогнозирования. На основе исторических снятий и внесений она может предлагать банкомат к инкассации. Система учитывает достаточно объёмный набор дополнительных гиперпараметров, что позволяет инкассировать банкомат по ряду разных причин:
Прогнозируется маленький остаток в банкомате через несколько часов.
Прогнозируется отсутствие какого-то номинала в банкомате.
Банкоматы могут инкассировать в паре или в тройке.
К сожалению, Москву система не поддерживает. В Москве коллегам приходится каждый день вручную вычислять, уйдёт ли банкомат сегодня в простой или нет, что является достаточно трудоемким процессом.
После того как сотрудник решает, что банкомат необходимо поставить на инкассацию, он заполняет заявку и отправляет её в кассовый центр.
В кассовом центре подготавливают деньги для банкомата и отдают их инкассаторам, которые объезжают банкоматы по определённому маршруту и инкассируют их.
При инкассации они загружают заранее подготовленную сумму в банкомат, а те деньги, которые остались с прошлой инкассации, забирают обратно в кассовый центр.
В кассовом центре кассеты опустошают и подготавливают для следующей инкассации.
Итого, основной запрос у бизнеса был в оптимизации подбора суммы загрузки в банкомат. Задача сводится к оптимизационной, так как подбор нужно осуществить таким образом, чтобы банк нёс наименьшие финансовые потери. Так давайте же теперь разберёмся с основными категориями расходов, которые нужно оптимизировать.
Категории расходов
Что очень важно в контексте рассматриваемой задачи для банка и для наших коллег, так это то, что необходимо всегда обеспечивать максимально возможную доступность каждого банкомата для клиентов.
Но решение, которое дает 100% доступность в любой момент времени — самое неоптимальное. Оно будет приносить много финансовых потерь.
Ведь можно каждый день закладывать в банкомат много миллионов денег, тогда доступность АТМ всегда будет на высоте, а клиенты останутся довольны. Только вот банк не очень будет рад такой большой сумме трат.
Первое, что нам нужно было понять, — на основе чего банкомат генерирует доходы и расходы? Доходы в основном складываются из комиссий различных типов, а вот расходов много разных, начиная с рекламы и мойки и заканчивая расходными материалами и сопровождением. Для нас же представляли интерес три основные категории расходов:
на инкассацию,
на пересчёт.
на фондирование.
Давайте разберём каждую категорию чуть подробнее.
Затраты на пересчёт
Внутри банкомата, помимо сложной электроники, находятся кассеты с деньгами.
Кассеты — это специальные коробки, в которых лежат купюры.
Каждая кассета отвечает за один номинал (100, 200, 500 рублей и т.д.) и имеет свою максимальную вместимость. Также есть одна специальная кассета, которая называется кассетой BNA или reject-кассетой. В неё попадают нераспознанные купюры или дефективные (порванные, резаные, стёртые и т.д.). Если кассета одного из номиналов переполнится, все купюры этого номинала также попадают в кассету BNA.
Чтобы подготовить кассеты для инкассации, необходимо подсчитать (пересчитать) необходимое количество купюр и заложить их в кассету. Аналогично, после разгрузки АТМ из старых кассет нужно достать оставшиеся купюры и пересчитать их. Этим занимаются специально обученные люди в кассовых центрах. Чтобы вычислить затраты банка на этом этапе, необходимо умножить время, потраченное на пересчёт кассеты на среднюю стоимость минуты работы сотрудника.
Затраты на инкассацию
Чтобы привезти и увезти кассеты, необходима сторонняя служба инкассации, которая стоит денег. Можно и свою организовывать, но здесь возникают затраты в виде зарплат инкассаторам, покупка специальных машин, оплаты бензина и пр. Расходы на инкассацию рассчитываются на один банкомат и сильно зависят от удалённости от кассового центра.
Затраты на фондирование
Возможно, самая неочевидная категория.
В банкоматах на конец дня остаётся какая-то сумма денег, которую банк не может использовать: нельзя положить эти деньги под процент или дать кредит. Получается, что деньги простаивают.
Чтобы посчитать, сколько было потеряно за день на одном банкомате, необходимо сумму остатка умножить на ставку фондирования (ставку ЦБ) и разделить на 365. Так как банкоматов у Альфа-Банка достаточно много, а суммы остатка на конец дня могут быть крупные, то и общая сумма потерь по всей стране набирается достаточно большая.
С основными категориями расходов мы разобрались, теперь можно переходить к обсуждению данных и технической постановке задачи.
Постановка ML-задачи
Как мы уже говорили выше, бизнес-заказчик пришёл к нам с запросом оптимизации подбора сумм загрузки. Мы перебирали и обдумывали много разных вариантов решения, но пришли к выводу, что для оптимального решения всё же будет необходимо построить планы инкассаций.
Верхнеуровнево мы видели задачу так:
Спрогнозировать снятия и внесения.
Подобрать моменты инкассаций.
Заложить в АТМ сумму, которая будет снята за период между двумя инкассациями.
Если наши прогнозы будут достаточно точны и оптимизатор будет корректно работать, то мы получим наиболее оптимальное решение из возможных. Чтобы понять его реализуемость, необходимо проанализировать данные, которые нам доступны.
Изучение данных
Большая часть необходимых нам данных была в базе и регулярно обновлялась. Для работы мы использовали следующую информацию:
Посуточные снятия и внесения по АТМ в разрезе кассет/номиналов.
Срезы остатков на каждые 45 минут.
Остатки в АТМ на конец дня с рассчитанным фондированием.
Заявки на инкассацию, которые одобряют сотрудники.
Фактические инкассации.
Все необходимые словари: таблица с активными АТМ, усреднённые траты на пересчет+инкассацию в разрезе АТМ.
Сначала мы анализировали все типы АТМ, чтобы понять, какие есть различия и какие потенциальные сложности могут возникнуть при разработке ML-моделей.
Самая первая и основная сложность, которая бросалась в глаза, — это то, как себя ведут целевые АТМ (RCL общего доступа). Достаточно часто графики снятий и внесений были похожи на белый шум, с невыраженной сезонностью и неповторяющимися аномалиями (рис. 1). Иногда могли присутствовать простои, информация о которых никак не логировалась. В то же время банкоматы Cash IN и Cash OUT имели более предсказуемые графики.
У нас в процессе возникла гипотеза о том, что несколько АТМ, которые стоят в локации вместе, должны вести себя одинаково. Это же логично — они ведь стоят в одной локации?
Гипотеза не подтвердилась. Посмотрите на рисунок ниже — на графики трёх разных АТМ. Они стоят в одном и том же отделении, но их поведение очень сильно отличается, как и частотность инкассаций.
Похожи? Нет.
Оказывается, что большую роль может играть положение банкомата в пространстве, которое также не логируется — притом в достаточно небольшом пространстве. А ещё на популярность банкомата влияет его модель — более новый АТМ привлекает больше внимания.
Отдельно изучалась связь между АТМ самоинкассации и ресайклингами. Оказалось, что некоторые ресайклинги заменяют банкоматы самоинкассации. Малый и микробизнес может пользоваться любыми банкоматами, чтобы перевести суточный заработок на карту. Конечно же, им проще найти ближайший АТМ. Но для модели такие большие внесения сильно усложняют работу.
Поняв основные нюансы в данных, мы приступили к построению бейзлайна.
Бейзлайн и первые модели
Самый простой бейзлайн, который можно построить — это усреднение исторических данных. Усреднять можно за разные периоды: пару месяцев, полгода, год. И усреднять нужно по дням недели.
После построения бейзлайна мы начали тестировать базовые модели, которые используются для решения Time Series задач — это Arima/Sarima и Prophet. Ввиду всех особенностей наших данных эти модели не справлялись с предсказаниями. Даже при условии тюнинга доступных параметров они предсказывали грубое среднее, что приводило к слишком большой ошибке.
Большой минус также состоял в том, что модели способны работать только с одним банкоматом за раз, что осложнило бы масштабирование. Даже распараллеливание не давало нам нужной скорости работы модели.
По итогу тестирования базовых моделей мы пришли к выводу, что надо попробовать использовать наш любимый CatBoostRegressor. Ведь он умеет работать с мультирегрессионным таргетом, а нам не нужны очень глубокие по времени предсказания. Поэтому мы начали генерировать признаки.
Генерация признаков
Глубина данных у нас была достаточно большая — с 2020 года. Однако, много данных пришлось обрезать, так как банкоматы за пару лет очень сильно поменяли своё поведение и графики двухлетней давности уже не отражали текущее поведение.
Признаки генерировались достаточно стандартные:
Лаги — сколько денег сняли/внесли вчера, позавчера и т.д.
Дни недели, месяц, выходной/не выходной.
Усредненные данные в разных разрезах: средняя сумма снятий/внесений в этот день недели, предыдущий, следующий, средние суммы снятий/внесений по сегментам клиента, средняя сумма снятий/внесений за n дней.
Дней до ближайшего праздника.
И т.п.
В основном для модели использовались данные о посуточных снятиях и внесениях.
После получения таблицы с признаками мы начали подготавливать её к обучению.
Обработка данных
После аналитики и визуального просмотра графиков ещё на этапе изучения данных мы поняли, что было необходимо обрабатывать в данных в первую очередь:
Простои, когда банкомат не работал по любым причинам (сломался, замёрз, не успели проинкассировать).
Слишком большие выбросы, возникающие по большим праздникам или политическим событиям.
На этапе построения бейзлайна обе эти проблемы решались заглушками снизу и сверху на одинаковое значение для всех банкоматов. В процессе разработки заглушки мы заменяли чем-то более гибким, например, брали среднее для конкретного АТМ за последнюю неделю. Но прироста это не дало.
Поэтому для улучшения результата мы решили эти дни убрать. Если убирать выбросы до момента генерации признаков, то это вызывает дырки во временном ряде в фичах с лагами. Поэтому было принято решение сначала сделать признаки, а далее убрать строчки, где таргет является выбросом.
Избавлялись от них следующим образом:
№1. Убирали те банкоматы, которые работали меньше 180 дней или за период полугода имели более 60 дней с нулевыми снятиями. Убирали дни, если банкомат простаивал 5 и более дней.
№2. Выбросы фильтровались с помощью модели линейной регрессии. На примере выдачи:
Строится простая модель на признаках: дня недели, выходной, сколько дней осталось до выплаты ЗП.
Считается предсказание выдачи на каждый день у каждого банкомата.
Проверка сверху: если истинное значение выдачи в конкретный день больше 99% персентиля и при этом значение предсказания в этот день меньше, чем среднее предсказание всей выдачи, то значение считается выбросом. Аналогичная проверка делается снизу.
Если выброс найден в выходной день — он убирается.
После обработки данных самое время приступать к разработке модели.
Модель
Ранее мы писали про построение бейзлайна и использование стандартных алгоритмов для задач Time Series для построения моделей. Помимо этих попыток мы также пробовали использовать модель линейной регрессии, вейвлеты, LSTM, Dlinear.
По итогу тестирования всех этих моделей мы не получили желаемого результата, поэтому решили сделали модель с использованием CatBoostRegressor. У этой модели есть несколько плюсов:
Основное плюс в том, что CatBoost может обрабатывать мультирегрессионный таргет. Нам нужно предсказывать снятия и внесения на несколько дней вперёд, и эта опция позволяет нам уйти от разработки нескольких моделей для каждого дня и сделать одну общую модель. В результате у нас получается одна модель, которая для каждого банкомата предсказывает его снятия/внесения на 14 дней вперёд.
Интерпретируемость результатов: бизнесу легче понять модель, которая анализирует знакомые для них параметры (например, средние снятия), чем разобраться в чёрном ящике.
Скорость работы модели достаточно высокая. В основном из-за того, что она обрабатывает весь массив фичей за раз, и нам не нужно делать перебор всех банкоматов.
Проще масштабировать, так как сразу начинаем делать предсказания на все банкоматы
Выбросы обработать проще.
Конечно, были и минусы, например, количество признаков. При тестировании обнаружилось, что для каждого дня запуска важен свой лаг (сколько снимали день, два, пять назад). Поэтому таких признаков приходится добавлять достаточно много.
Модель справлялась лучше, чем все предыдущие, но качество модели всё ещё далеко от идеала. В процессе разработки также тестировалось несколько гипотез. Были попытки объединять банкоматы по похожести, по городам, и обучать несколько моделей.
В итоге мы всё же остановились на одной модели для всех банкоматов, так как разделение не всегда давало прирост к качеству, а иногда и делало хуже.
В процессе разработки мы продолжали сталкиваться с тем, что модель недопредсказывает. Особенно критично это в дни выплаты зарплат.
По-разному пытались решить проблему: и с помощью фильтрации данных, и поиском аномалий в рядах. В финальное решение вошло использование штрафа за недопредсказание. За основу этого штрафа была взята функция ошибок MultiRMSE, но для элементов, в которых предсказание модели было меньше, чем фактическое значение, функция ошибки умножалась на alpha.
С помощью этого штрафа получилось улучшить результаты предсказания, что в свою очередь улучшило работу оптимизатора.
Оптимизатор
После получения результатов модели возникает естественный вопрос — а денег то сколько в банкомат надо положить?
Модель не даёт ответ на этот вопрос, а вот оптимизатор может.
Написать оптимизатор можно по-разному.
Можно фиксировать дни и инкассировать, например, каждые два дня.
На этапе тестирования модели Prophet мы также пробовали сделать оптимизатор, который будет предсказывать момент инкассации по часам. Расчёт оказался слишком затратным.
Можно решать «систему уравнений» и получать момент инкассации. Но это тоже оказалось очень затратно.
В итоге остановились на обычном переборе вариантов. Это работает адекватное количество времени и дает наиболее оптимальный результат.
Как же работает оптимизатор?
На вход подаем актуальные остатки, предсказания модели на 14 дней и оценку на исторических данных, и сколько денег должно попасть в кассету BNA. Количество дней было выбрано эмпирически.
После этого внутри оптимизатора происходит перебор всех возможных комбинаций дней с инкассациями — примерно 16 000 вариантов. Также учитываем сколько денег к моменту запуска оптимизатора уже могли снять. При переборе на каждый вариант рассчитываются траты на инкассацию, пересчёт и фондирование. В результате мы получаем много планов инкассаций на 14 дней и выбираем тот, у которого траты минимальные и не прогнозируется переполнение кассет.
Далее результат необходимо обработать.
Во-первых, необходимо разбить их по номиналам и разложить по кассетам. Сколько и в какую кассету положить рассчитывается на основе исторических данных.
Во-вторых, количество листов в кассетах не должно быть слишком большим или слишком маленьким. Если где-то есть переполнение кассеты, необходимо делать ребалансировку. При ребалансировке из переполненной кассеты лишняя сумма раскладывается по непереполненным кассетам. Если же в кассете денег не хватает, то деньги забираются из самой заполненной кассеты.
В-третьих, количество листов в кассетах необходимо округлить до кратности 100, так как это требование кассового центра.
После всех обработок получаем готовый план — в какой день и с какой суммой необходимо проинкассировать банкомат.
Планы мы отдаем уже на 7 дней, так как бОльшая глубина уже не несет особого смысла. В основном коллеги пользуются только планом на завтра, так как поведение банкоматы меняют очень быстро и резко.
На самом деле данный алгоритм запускается три раза. Первый запуск выдает план инкассаций без каких-либо ограничений. Во втором и третьем запуске мы говорим оптимизатору, что инкассировать необходимо сегодня или завтра. Это делается с целью получения сумм инкассаций, которые сотрудники могут использовать в случае экстренной инкассации, если банкомат резко опустеет, или появится заявка на тех обслуживание.
Когда первая версия оптимизатора была написана, необходимо было понять, сколько денег мы экономим банку, поэтому мы приступили к расчёту эффекта.
Расчёт эффекта
В самом начале мы допускали много ошибок при расчете эффекта. Например, у нас было много попыток запустить оптимизатор на фактических данных, потом запустить его на прогнозных данных, а после этого сделать вывод об эффекте. Также запускали оптимизатор на одной неделе в месяце несколько месяцев подряд и оценивали простои. Все эти подходы не отражали реальный способ применения оптимизатора, поэтому мы решили попробовать реализовать ретротест.
Ретротест имел свои сложности и минусы. В цепочке инкассации банкоматов есть очень много человеческого фактора. В какой момент сотрудник заметит, что банкомат нужно поставить на инкассацию? Какой лимит для какого банкомата является граничным? В какой момент в кассовом центре подготовят кассеты? Когда довезут кассеты до банкомата? На все эти вопросы нельзя найти точный ответ, как и нельзя их замоделировать.
Тот ретротест, который нам получилось реализовать, был с усреднениями и оговорками на все вышеперечисленные вопросы. Но этот способ оценки был уже явно лучше. Мы поняли, что со своим оптимизатором точно не тратим больше, чем тратят сейчас.
Так мы пришли к понимаю, что пилот нам необходим.
Подготовка к пилоту
На самом деле пилотов у нас было несколько. В первый пилот очень много времени было уделено тому, как бизнес будет забирать результат модели. Нужно было сделать так, чтобы планы выгружались быстро и без падений. Специально для этого мы делали интерфейс, который по кнопке выгружал данные из PostgreSQL.
Параллельно с подготовкой интеграции занимались подбором двух похожих групп банкоматов: контрольной и пилотной. На этом этапе возникла проблема, так как подобрать похожие группы оказалось нетривиальной задачей. Поведение RCL-банкоматов достаточно шумное и непредсказуемое, корреляция оборотов и трат между банкоматами низкая, а дисперсия большая. Было несложно найти один похожий банкомат, но вот две похожие группы — это уже задача со звездочкой. Мы пытались кластеризовать АТМ по тратам, по оборотам и использовали алгоритм DTW. Но банкоматы слишком часто меняют свое поведение и, как следствие, прыгают между группами.
Зафиксировать группы мы смогли только при использовании разбиения по городу и функциональности и сравнивая банкоматы по тратам. Только в таком подходе мы получили стабильные группы адекватного размера. С помощью ретротеста оценили, что ожидаем финансовый эффект в районе 4%, который можно зафиксировать в течение 11 недель пилота.
Результаты пилота
На первом пилоте мы получили эффект сильно выше, чем оценили ранее. Он был равен 12%. По прошествии ещё нескольких пилотов мы поняли, что эффект также очень сильно зависит от сезона, политической ситуации, а самое главное, от ставки ЦБ.
На графике привожу пример одного из пилотов. Размер группы 66 банкоматов, длительность 11 недель.
В какие-то периоды эффект достигал 4-6%, но даже этого достаточно, чтобы экономить банку миллионы рублей в год.
Итоги
Итого, полный алгоритм выглядит следующим образом:
В качестве вывода от себя могу добавить, что задача оказалась очень сложной. Когда мы начинали её решать, не могли представить, что поддержание сети банкоматов настолько нетривиальная задача. Что есть множество нюансов, которые контролируют коллеги из бизнеса и что не все из них в реальности можно учесть в оптимизаторе.
В данный момент алгоритм используется в Москве на RCL-банкоматах и уже облегчает жизнь коллегам и экономит деньги банку. Он позволяет ускорить время реагирования на внешние ситуации и не загружать людей лишней работой.
А чтобы быть в курсе новых статей и реализованных проектов, познакомиться поближе с командой Центра продвинутой аналитики и получать инфо об актуальных вакансиях — присоединяйтесь к нашему TG-каналу Alfa Advanced Analytics.
askv
Кто-нибудь рассчитывал, как размеры комиссионного вознаграждения влияют на объём операций с наличными? Например, до введения бесплатного перевода через СБП 30 млн рублей, чтобы перевести деньги из банка в банк у клиента-физлица было два варианта: перевести безналично (обычно с комиссией), или снять бесплатно наличные и бесплатно положить их в другом банке на счёт. В итоге в последнем варианте банк несёт расходы по выдаче наличных, вместо того, чтобы достаточно дёшево перевести те же средства безналом.
vikarti
Мне вот больше вспоминается другой пример похожий :). Много лет назад.
Зарплата приходят на карту Payoneer (насколько помню еще даже не чиповая).
Задача - сделать чтобы деньги были на обычном российском банковском счете.
Оптимальный способ решения как выяснилось - найти банкомат который и принимает и выдает, выдоить его, вставить его родную карту и скормить ему эти же деньги.
Насколько помню проблема была что Card2Card Payoneer поддерживал но вот российские банки его нормально не поддерживали. У некоторых опция такая была...в офисе и с кучей проблем.