# Другие рекомендации (Прогнозируй и рекомендуй). Часть 2 ## Интро Уже достаточно продолжительное время различные команды занимаются разработкой и внедрением алгоритмов, "под капотом" которых находятся модели машинного обучения и оптимизационные алгоритмы, в промышленности: черная и цветная металлургия, нефтяная отрасль и др. Не буду сравнивать сложность внедрение решений на базе ML в промышленности и, например, в ритейле или банкинге - об этом можно послушать и почитать в других материалах. Но, очевидно, что любое промышленное производство - это очень сложная система со всех точек зрения: технология, IT, оборудование, логистика и пр. Далее предлагаю рассмотреть кейс из черной металлургии: **"Оптимизация расхода ферросплавов"**, для которого необходимо разработать рекомендательную систему. **Глоссарий** - плавка - конвертер - отдача - сталевар - доводка **Описание процесса (поверхностно)** В конвертерном цехе на сталелитейном производстве выполняется часть обработки плавки следующих агрегатов: конвертер, установка печь ковш (УПК) и установка ковшевого вакуумирования (УКВ). В плавку, состоящую из чугуна, лома, извести и флюсов добавляют ферросплавы для получения стали с требуемым содержанием химических элементов: $Si, Mn, C, N, Al$ и др. Разные требования по химическому составу соответствуют разным маркам стали, т.е. в некоторых видах изделий требуется высокое содержание $Si$, а в некоторых - нет. !добавить ссылку на описание процесса **Пролематика** Стоимость ферросплавов высокая, в процессе производства стали в конвертерном цехе требуется минимизировать расходы на ферросплавы при достижении требуемого химического состава. Избыточное использование ферросплавов может быть вызвано: - сложностью процесса и, как следствие, проблематичностью учета всевозможных факторов; - опытом сталеваров; - требованиями по производительности. **Задача** Сформировать рекомендации об объемах отдачи различных ферросплавов на разных этапах/агрегатах конвертерного цеха. **Решение** Разработка рекомендательного сервиса с использованием моделей машинного обучения, базирующихся на исторических данных об использовании ферросплавов, технологических режимах обработки плавки и результатах химического анализа. ## Моделирование **Исходные данные** Чуть подробнее рассмотрим ключевые данные: - добавляемые материалы: $ferro_i$ -- **управляемые параметры**, ранее обозначенные как $u_i, i=\overline{0, m-1}$ Например: $ferro_0$ - $FeMn\; 78\%$ - технологические параметры: $feature_i$ -- **фиксированные параметры**, ранее обозначенные как $x_i, i=\overline{0, n-1}$. Например: $feature_2$ -- температура плавки на агрегате, например, на УКП - целевые параметры: $target_i$ - $target_1$ - изменение между текущим и предыдущим химическим анализ (далее ХА) по $Mn$ - $target_2$ - изменение между текущим и предыдущим ХА по $Si$ - дополнительные параметры - $target\_min\_bound_i, i\in\{1, 2\}$ - нижняя граница по требованиями ХА для $Mn$ и $Si$ - $target\_max\_bound_i, i\in\{1, 2\}$ - верхняя граница по требованиями ХА для $Mn$ и $Si$ - $initial\_val\_target_1$ - предыдущий ХА плавки по $Mn$ - $initial\_val\_target_2$ - предыдущий ХА плавки по $Si$ все вышеприведенные параметры имеют размерность $\mathrm{R}^n$, где $n$ -- количество наблюдений в данных. **Прогнозные модели** Строим следующие прогнозные модели \begin{equation} pred\_target_i^k = f_i(feature_0^k, \dots, feature_{n-1}^k, ferro_0^k, \dots, ferro_{m-1}^k), \; i \in \{1, 2\}, \end{equation} где $k$ -- номер наблюдения, $k=\overline{1, n},\; pred\_target_i^k\in \mathrm{R}$ -- прогноз целевой переменной для $k$-го наблюдения при заданных фиксированных и управляемых параметрах. **Оптимизационная задача** Перед нами стоит задача минимизировать затраты за счет более экономного использования ферросплавов, но при этом ни в коем случае это не должно влиять на качество изготавливаемой продукции. Следовательно, из этого вытекает следующая критериальная функция: \begin{equation} \sum_{i=\overline{0, m-1}}c_i\cdot ferro_i^k \to \min\limits_{ferro_i^k \in U} \end{equation} где $c_i$ - цена за 1 тонну $i$-го ферросплава. При минимизации стоимости плавки требуется соблюдать требования по химии, т.е. могут быть наложены следующие ограничения на $U$ \begin{gather*} target\_min\_bound_i \leq pred\_target_i^k \leq target\_max\_bound_i, \; i \in \{1, 2\}, \\ g_j(pred\_target_1^k,\; pred\_target_2^k) \leq 0, \; j=\overline{1, l} \end{gather*} через ограничения $g_j$ могут устанавливаться различные дополнительные технологические ограничения, например, ограничения на соотношение между ключевыми элементами или более узкие диапазоны для $pred\_target_i^k$, для того, чтобы попадать в определенную цель и стабилизировать процесс доводки стали. Альтернативным примером критериальной функции может быть \begin{equation} |pred\_target_i^k - goal\_target_i^k| \to \min\limits_{ferro_i \in U}, \end{equation}где $goal\_target_i^k$ является некоторой целью по содержанию химического элемента, в которую необходимо попасть. Чаще всего люди с производства имеют регламент с заданными целями или владеют этим ценным знанием в своей голове. **Заметка** - Критериальные функции можно комбинировать, но нужно быть аккуратным, чтобы не складывать тонны с рублями; - Выше приведена одна из возможных постановок задачи. Может меняться практически каждый компонент в зависимости от ваших предпочтений и требований людей, которые в дальнейшем будут эксплуатировать систему; - Далее по ходу статьи постановка задачи будет незначительно модифицироваться в зависимости от подхода к решению. ## Подходы При решении задачи оптимизации мы используем внутреннюю библиотеку, которая поддерживает различные методы и постановки задачи. Для демонстрации части подходов будут использоваться методы из библиотеки. ### Перебор по сетке В данном подходе используется подход декомпозиции исходной задачи, который в данном случае заключается в следующем: 1. Строится модель для прогноза $Mn$ на части ферросплавов, которые используются для доводки $Mn$ 2. Формируется рекомендация ферросплавов для доводки $Mn$ 3. Строится модель для прогноза $Si$ на ферросплавах, которые используются для доводки $Si$ и $Mn$ 4. Формируется рекомендация ферросплавов для доводки $Si$, но с учетом того, что в п.2 при рекомендации ферросплавы также содержат $Si$ необходимо учесть, сколько из уже добавленных ферросплавов будет прирост по $Si$. Для решения задачи необходимо определить следующие пункты: 1. Сетка для поиска ``` search_space = { 'ferro_0': np.linspace(ferro_0_min, ferro_0_max, 100), 'ferro_1': np.linspace(ferro_1_min, ferro_1_max, 100) } ``` 2. Критериальная функция - абсолютное отклонение от цели (здесь и далее префикс $gs$ расшифровывается как $GridSearch$) ``` def gs_opt_obj(x, models, target_point): # Target variable prediction by model target_pred = models['target_1']['model'].predict(x[models['target_1']['features']].values) # Diff between true and predicted target variable value target_loss = abs(target_point['target_1_point'] - target_pred) return target_loss ``` 3. Прогнозная модель - градиентный бустинг ``` gs_optim = gs_opt.GSOpt( models= { 'target_1': { 'model': gb_models['target_1']['model'], 'features': gb_models['target_1']['features'] } } ) ``` 4. Запуск поиска по сетке ``` # set goal_target gs_obj_params = [{'target_point': data.loc[idx, ['target_1_point']].to_dict()}] opt_res, opt_hist=gs_optim.run_gs_opt( x=data.loc[idx], # set point for optimization objective_params=gs_obj_params, # set params objectives=[gs_opt_obj], # set objective search_space=search_space, # set search space opt_direction=[0, 'minimize'] # set direction ) ``` После выполнения поиска результат выглядит так: ``` ferro_0 0.378000 ferro_1 0.000000 ferro_2 0.000000 feature_0 0.180000 feature_1 -0.007000 feature_2 1560.000000 feature_3 70.100000 feature_4 295.763000 feature_5 4.219158 initial_val_target_1 1.360000 target_1_min_bound 1.450000 target_1_max_bound 1.600000 target_1 0.080000 target_1_point 0.100000 ferro_0_opt 0.104848 ferro_1_opt 0.468808 target_1_opt 0.099997 dtype: float64 ``` В данном случае эксперимент проводился на исторических данных, поэтому у нас есть возможность сравнить рекомендуемые результаты с фактическими: - **Факт** - Добавляемые материалы: - Ферросплав №0: 378 кг. - Ферросплав №1 и №2: 0 кг. - Прирост по $Mn$: 0.08% - **Рекомендации** - Цель (target_1_point) прирост по $Mn$: 0.1% - Оценка прироста моделью (target_1_opt): 0.099997% - Добавляемые материалы: - Ферросплав №0: 104 кг. - Ферросплав №1: 468 кг. - Ферросплав №2: 0 кг. - **Резюме по рекомендациям** - по сетке удается найти точку, которая будет близка к установленной цели; - в используемой критериальной функции не учитывается стоимость, поэтому полученное перераспределение не говорит об экономии. Анализ вида критериальной функции возможно выполнить по графикам зависимости целевой функции от агрументов Рисунок 2 (добавить подпись картинке) и от номера итерации Рисунок 3 (добавить подпись картинке), по которому заметно, что задача имеет не единственное решение, и в качестве прогнозной моделеи используется градиентный бустинг. ![Рисунок 2](https://i.imgur.com/ny78AyK.png) ![Рисунок 3](https://i.imgur.com/YgRLCr8.png) ### Линейная модель на ферросплавах Для разработки линейной прогнозной модели используем робастные к выбросам алгоритмы, такие как $HuberRegressor$ из библиотеки $sklearn$. Важно, что линейная модель строится только признаках, содержащих в себе информацию о добавленных ферроспалавах, т.е. в данном случае на основании исторических данных мы пробуем подобрать оптимальные коэффициенты усвояемости материалов. Возможные подходы к подбору оптимальных коэффициентов - вопрос отдельной статьи, здесь предложен наиболее простой вариант. Задачу оптимизации определим следующим образом: \begin{equation} 1880*ferro_0^k+2000*ferro_1^k+3600*ferro_2^k \to \min\limits_{ferro_0^k, ferro_1^k, ferro_2^k} \end{equation} ограничения на прогнозы моделей \begin{equation} target\_min\_bound_i \leq pred\_target_i^k \leq target\_max\_bound_i, \; i \in \{1, 2\}, \end{equation} допустимые границы ферросплавов \begin{equation} min\_ferro_j^k \leq ferro_j^k \leq min\_ferro_j^k, \; j \in \{0, 1, 2\}, \end{equation} дополнительные ограничения на ферросплавы \begin{gather*} ferro_0^k + ferro_1^k \leq 0.4 \\ ferro_0^k + ferro_2^k \leq 0.5 \\ ferro_1^k + ferro_2^k \leq 0.4 \\ \end{gather*} После формализации постановки задачи основной челлендж, с которым необходимо справиться в линейной постановке задачи -- это не ошибиться при составлении матрицы ограничений $A$ и вектора $b$, затем для решения задачи необходимо определить следующие пункты: 1. Вектор стоимостей ферросплавов ``` ferro_cost = { 'ferro_0': 1880, 'ferro_1': 2000, 'ferro_2': 3600 } ``` 2. Ограничения на ферросплавы ``` upper_bound = data.loc[idx,['tar_delta_1_upper_bound', 'tar_delta_2_upper_bound']] lower_bound = data.loc[idx, ['tar_delta_1_lower_bound', 'tar_delta_2_lower_bound']] A = np.array([[1, 1, 0], [1, 0, 1], [0, 1, 1]]) b=np.array([0.4, 0.5, 0.4]) ``` 3. Установить коэффициенты прогнозной модели и критериальной функции ``` lin_optim = lin_opt.LinOpt( objective_coefs=ferro_cost, model_coefs = model_coefs ) ``` 5. Запустить оптимизатор ``` opt_res, opt_hist = lin_optim.run_linprog_opt( data=data.loc[idx], lower_bound=lower_bound, upper_bound=upper_bound, feat_bounds=ferro_bounds, A_ub=A, b_ub=b ) ``` **Заметка** Очевидно, что линейной моделью проблематично описать влияние различных факторов технологического процесса. Но, с другой стороны, линейная модель очень удобна в оптимизации и хорошо интерпретируема. По этой причине мы используем комбинированный подход, состоящий из следующих шагов 1. Обучение линейной модели на добавках $\to lin\_pred\_target_i^k$ 2. Расчет ошибки линейной модели 3. Обучение градиентного бустинга на ошибку линейной модели $\to error\_pred\_target_i^k$, причем из обучения исключаются управляемые параметры, т.е. итоговый прогноз модели состоит из двух компонент \begin{equation} pred\_target_i^k = lin\_pred\_target_i^k + error\_pred\_target_i^k \end{equation} Данный подход позволяет: - сохранить возможность использовать методы для задачи линейного программирования; - повышает точность прогноза; - интерпритируемый прогноз. ## Байесовская оптимизация ### Смешивание критериев/ограничений Как ранее говорилось, при использовании байесовской оптимизации нельзя в явном виде учитывать ограничения на прогноз модели и одновременно ставить задачу о снижении стоимости рассходуемых материалов. Решением данной задачи может быть учет нескольких критериев и ограничений через критериальную функцию, но это сразу же усложняет настройку алгоритма, а найденная точка необязательно будет удовлетворять всем ограничениям. \begin{gather*} L_1 = \frac{1}{C_{cost}}(1880*ferro_0^k+2000*ferro_1^k+3600*ferro_2^k), \\ L_2 = \sum_{i \in \{1, 2\}}\frac{1}{C_{target}^i}|pred\_target_i^k - goal\_target_i^k|, \\ L = L_1 + k \cdot L_2 \to \min\limits_{ferro_0^k, ferro_1^k, ferro_2^k} \end{gather*} допустимые границы ферросплавов \begin{equation} min\_ferro_j^k \leq ferro_j^k \leq min\_ferro_j^k, \; j \in \{0, 1, 2\}, \end{equation}где - $C_{target}^i$ -- нормировочный коэффициент для $i$-го таргета; - $C_{cost}$ -- нормировочный коэффициент для стоимостной добавки; - $k$ -- коэффициент взвешивания компонент критериальной функции. Для применения данного подхода необходимо выполнить следующие шаги: 1. Ограничения на ферросплавы ``` space = { 'ferro_0': hp.quniform('ferro_0', ferro_0_min, ferro_0_max, 0.01), 'ferro_1': hp.quniform('ferro_1', ferro_1_min, ferro_1_max, 0.01), 'ferro_2': hp.quniform('ferro_2', ferro_2_min, ferro_2_max, 0.01) } ``` 2. Расчет нормировочных коэффициентов ``` # Normalization constant for target variables targets_norm = { 'target_1': data['target_1'].abs().mean(), 'target_2': data['target_2'].abs().mean() } # Normalization constant for summary cost of additives median_cost = ( data['ferro_0'].mean() * ferro_cost['ferro_0'] + data['ferro_1'].mean() * ferro_cost['ferro_1'] + data['ferro_2'].mean() * ferro_cost['ferro_2'] ) # Coefficient for part of summary cost of additives in total loss cost_coeff = 0.25 ``` 3. Определить критериальную функцию ``` def hyper_opt_obj(x, models, target_point, targets_norm=targets_norm, ferro_cost=ferro_cost, median_cost=median_cost): total_loss = 0 cost = 0 for target_name in models.keys(): # Target variable prediction by model target_pred = models[target_name]['model'].predict(x[models[target_name]['features']].values.reshape(1, -1))[0] # Diff between true and predicted target variable value target_loss = abs(target_point[f'{target_name}_point'] - target_pred) / targets_norm[target_name] # Total loss (sum of loss for each target variable) total_loss += target_loss # Loss by cost of additives for ferro in ferro_cost.keys(): cost += x[ferro] * ferro_cost[ferro] cost_loss = cost / median_cost total_loss += cost_loss * cost_coeff return total_loss ``` 4. Запустить оптимизатор ``` hyper_optim = hyp_opt.HypOpt( models = gb_models ) hyp_obj_params = {'target_point': data.loc[idx, ['target_1_point', 'target_2_point']].to_dict()} opt_res, opt_hist = hyper_optim.run_hyper_opt( x=data.loc[idx], objective_params=hyp_obj_params, objective=hyper_opt_obj, search_space=space, max_evals=500, ) ``` ### Optuna Интресной фичей библиотеки Optuna является возможность решить задачу многокритериальной оптимизации, т.е. нет необходимости изобретать комбинированную целевую функцию, как это было в предыдущем разделе. Без дополнительных преобразований задача может быть записана следующим образом: \begin{gather*} L_1 \to \min\limits_{ferro_0^k, ferro_1^k, ferro_2^k}, \;L_2 \to \min\limits_{ferro_0^k, ferro_1^k, ferro_2^k} \end{gather*} допустимые границы ферросплавов \begin{equation} min\_ferro_j^k \leq ferro_j^k \leq min\_ferro_j^k, \; j \in \{0, 1, 2\}. \end{equation} ## Общие советы По результатам прочтения статьи у вас должно было сложиться впечатление (но не точно), что при создании рекомендательного сервиса есть очень много нюансов практичных и математических, которые не сразу же бросаются в глаза. Несколько советов, о которых стоит помнить: - выбранные параметры должны быть физически реализуемы - например, на производстве редко есть возможность регулировать подачу ферросплавов с точностью до 1 кг. - управляемый параметр, который вычисляется через какие-то статистики достаточно использовать в реальной жизни; - выбранные исходные данные должны быть доступны в момент формирования рекомендации, этому может помешать: - ручной ввод данных - задержки во время передачи данных - и много других уникальных ситуаций. - данные в CSV и данные с прода редко совпадают; - технические специалисты, которые в дальнейшем будут эксплуатировать систему, должны понимать и доверять ей, т.е. важна точность и интерпритация моделей. - прогнозную модель на базе градиентного бустинга из коробки сложно сделать физичной, т.е. в случаях, где должна явно наблюдаться монотонная линейная зависимость, а бустинг ее не обеспечивает. В данном случае может помочь переход к комбинированной линейной модели и модели градиентного бустинга; - в некоторых случаях можно предложить совсем иной подход к рекомендательному сервису. Например, не строить прогнозную модель и не решать задчу оптимизации, а искать похожие плавки в истории и рекомендовать аналогичный исторический режим обработки. Причем это применимо во многих задачах и не только ферросплавов. - и это только верхушка айсберга # Итоги **Ссылки на статьи**