Привет, Хабр.
Маркетинговая оптимизация, установка лимитов по портфелю кредитных продуктов, логистика и товарная аналитика, оптимизация производственных процессов, … — список применения методов математической оптимизации далеко не ограничивается перечисленными задачами, а методы оптимизации начали решать задачи бизнеса задолго до того, как науки о данных стали называться науками о данных.
С развитием адаптации технологий ML/DS можно ожидать рост популярности оптимизационных методов прежде всего за счет того, что решения бизнес задач становятся более комплексными. То есть, вместо того, чтобы сделать одну-две модели, которые выдают почти финальные решения, процесс принятия решения декомпозируется на отдельные составляющие компоненты, в которых есть место прогнозным моделям, а для самого принятия решения с учетом всех этих компонент и ограничений работает уже оптимизационная модель.
В статье поговорим о возможной постановке задачи оптимизации в банковской сфере и методах ее решения.
В команде GlowByte Advanced Analytics мы активно продвигаем подход, согласно которому проекты по ML лучше изначально формулировать как задачи оптимизации, то есть как систему поддержки принятия решений с измеримыми бизнес-показателями.
Существует много открытых фреймворков для решения оптимизационных задач таких как Gekko, Pyomo, Python-mip, а так же различное проприетарное ПО типа IBM ILOG CPLEX Optimization Studio.
План статьи
Задача оптимизации
Задача оптимизации заключается в нахождении экстремума целевой функции в области пространства, ограниченной некоторым набором условий (равенств и/или неравенств). В зависимости от типа целевой функции и типа ограничений задачи оптимизации делятся на задачи линейного и нелинейного программирования.
В общем виде математическая постановка задачи может выглядеть следующим образом
Т.е. среди всех векторов множества , которое ограничено условиями и необходимо найти такой вектор , при котором значение будет минимальным на всем множестве .
При линейных ограничениях и линейной целевой функции задача (1) относится к классу задач линейного программирования и может быть решена симплекс-методом.
Маркетинговая оптимизация в банке
Представим себе современный банк, работающий с физическими лицами и продающий им свои основные продукты: кредитные карты, кредиты наличными и т.д. Каждому клиенту банк может предложить один из продуктов в одном из доступных для коммуникации каналов (звонок, смс и т.д.). При этом количество доступных для отправки в неделю/месяц коммуникаций в каждом канале (объем канала) ограничено
Предположим, что в банке имеется модель (или несколько моделей), которая с хорошей точностью предсказывает вероятность отклика клиента на определенный банковский продукт в определенном канале. Оценить качество такой модели с точки зрения того, как ее оценки соотносятся с реальными вероятностями, можно, построив на отложенной выборке распределение прогнозной вероятности и реального отлика по бакетам скора модели.
Рис. 1
Теперь, имея
При постановке задачи важно понимать, какую именно метрику мы хотим максимизировать на выходе. Например, если в результате коммуникаций мы хотим получить максимальный отклик, то соответствующая задача оптимизации может быть поставлена следующим образом
Это классическая задача линейного программирования, которая легко решается с помощью открытых фреймворков, упомянутых выше.
В случае, если целью коммуникаций будет максимизация будущей доходности, то целевую функцию в задаче (3) можно записать в виде
где — доходность от k-го продукта на i-м клиенте. Значения могут быть получены с помощью прогнозных моделей или оценены каким-то другим способом.
Замечания
- Описанные выше подходы предполагают, что мы имеем достаточно хорошие прогнозы/оценки для и
- Если скоры для различных продуктов получены от разных моделей и при этом они (скоры) плохо согласуются с реальной вероятностью отклика (это можно увидеть, например, по графику как на Рис. 1), то перед оптимизацией их необходимо откалибровать. Про различные способы калибровки можно почитать по ссылке.
- Предполагается также, что количество коммуникаций, на которые банк готов потратить средства, меньше, чем количество клиентов, которым банк готов предложить свои продукты. В противном случае оптимизировать будет нечего.
Немного кода
Попробуем решить задачу маркетинговой оптимизации, поставленную в виде (3), с помощью библиотеки MIP, упомянутой выше. Возьмем случайным образом сгенерированный датасет объемом в 6000 строк, в котором содержится 1000 клиентов, каждому из которых можно предложить один из 3-х продуктов в двух каналах — SMS и звонок.
Код
import pandas as pd
from mip import Model, MAXIMIZE, CBC, BINARY, OptimizationStatus, xsum
frame = pd.read_csv('table_for_optimization.csv')
frame.head()
Предположим, что у нас есть ограничение на объем коммуникаций: 500 SMS и 200 звонков. Напишем функцию для решения задачи оптимизации.
Код
def optimize(frame: pd.DataFrame, channel_limits: dict) -> list:
"""
Возвращает массив оптимальных предложений
"""
df = frame.copy()
#создание модели
model = Model(sense=MAXIMIZE, solver_name=CBC)
#вектор бинарных переменных задачи
x = [model.add_var(var_type=BINARY) for i in range(df.shape[0])]
df['x'] = x
#целевая функция
#функция xsum значительно ускоряет суммирование https://docs.python-mip.com/en/latest/classes.html#mip.xsum
model.objective = xsum(df['score'] * df['x'])
#ограничения на количество коммуникаций в каждом канале
for channel in df.channel.unique():
model += (xsum(df[df.channel==channel]['x']) <= channel_limits[channel])
#ограничения на количество продуктов для каждого клиента (не более одного продукта на клиента)
for product_cnt in df.groupby(['client_id'])['x'].apply(xsum):
model += (product_cnt <= 1)
status = model.optimize(max_seconds=300)
del df
if status == OptimizationStatus.OPTIMAL or status == OptimizationStatus.FEASIBLE:
return [var.x for var in model.vars]
elif status == OptimizationStatus.NO_SOLUTION_FOUND:
print('No feasible solution found')
Зададим ограничения на объем коммуникаций в каналах, запустим решение задачи оптимизации и посмотрим как в итоге распределятся оптимальные предложения по каналам и продуктам.
Код
#объем доступных коммуникаций в каналах
CHANNELS_LIMITS = {
'call': 200,
'sms': 500
}
optimal_decisions = optimize(frame=frame, channel_limits=CHANNELS_LIMITS)
frame['optimal_decision'] = optimal_decisions
#распределение продуктов в каналах
frame[frame['optimal_decision']==1].groupby(['channel', 'product']). agg({'client_id': 'count'}). rename(columns={'client_id': 'client_cnt'})
Весь код и данные доступны по ссылке.
P.S.
В зависимости от типа прогнозных моделей мы можем располагать не просто средней оценкой вероятности отклика а также иметь распределение этого значения для каждого клиента и продукта. В таком случае задача оптимизации (3) может быть дополнена условием
Более того, если в нашем распоряжении есть распределение для каждой вероятности , то мы можем также решать и обратную задачу: минимизировать количество коммуникаций при условях типа (5) с учетом определенных ограничений, задаваемых бизнесом.
Благодарю коллег из команды GlowByte Advanced Analytics за помощь и консультации при подготовке этой статьи.