- BrainTools - https://www.braintools.ru -
Вот когда каждый грамм действительно имеет значение: если вам нужно спрогнозировать вес птицы перед продажей, чтобы экономить на кормах и оптимизировать производство. Меня зовут Михаил Чирков, я data scientist в R-Style Softlab и сегодня хочу поделиться с вами кейсом прогнозирования с помощью XGBoost, этот проект мы делали в рамках внедрения BI-системы для птицефабрики.
Передо мной стояла задача создать модель машинного обучения [1], которая позволяет предсказывать финальный вес птицы при ежедневном внесении данных и динамически корректировать прогноз с учетом новой информации.
Для решения был выбран алгоритм XGBoost — одна из наиболее мощных и популярных реализаций градиентного бустинга. Технический стек проекта включает в себя Python 3.12, библиотеки для машинного обучения (Scikit-learn, XGBoost, CatBoost), инструменты для работы с данными (pandas, Seaborn) и оптимизации гиперпараметров (Optuna).
Пока изучал датасет из 265 214 строк и 36 колонок с данными за почти четыре года, много узнал о курицах, количестве потребляемого корма и воды, температурах в помещениях, влажности, вентиляции и других параметрах.
Как часто бывает в реальных проектах, данные оказались далеки от идеала: пропущенные значения, несоответствия типов данных, аномальные показатели. Например, встречались записи с нулевой влажностью и температурой (привет с Марса?). Некоторые колонки, относящиеся к убою и перемещению птицы, были заполнены менее, чем на 20%.
Предобработка данных в итоге включала:
Очистку от пропущенных значений
df = df.dropna()
Колонки с критически высоким процентом пропущенных значений (более 80%) были удалены из анализа:
df = df.drop(['УбойПродажаГоловы', 'ЗаселениеВес', 'ЗаселениеГоловы', 'УбойВес', 'УбойГоловы', 'ПлановыйУбойВес', 'ПлановыйУбойГоловы', 'РаннийУбойВес', 'РаннийУбойГоловы', 'УбойПродажаВес'], axis=1)
Также была произведена конвертация колонки «Дата» в соответствующий формат:
df['Дата'] = pd.to_datetime(df['Дата'])
Построение корреляционной матрицы
preview = df.loc[:, ~df.columns.isin(['IDСерия', 'Дата'])]
corr_matrix = preview.corr()
plt.figure(figsize=(15, 10))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', fmt='.2f')
plt.title('Корреляционная матрица')
plt.show()
Обработку аномальных значений. Нулевые и аномально высокие значения рабочей температуры, влажности и вентиляции были заменены на медианные значения (медиана выбрана в связи с тем, что более устойчива к выбросам, чем среднее).
median_value_humid = df['ВлажностьРаб'].median()
median_value_temp = df['ТемператураРаб'].median()
median_value_vent = df['Вентиляция'].median()
df.loc[df['ВлажностьРаб'] == 0, 'ВлажностьРаб'] = median_value_humid
df.loc[df['ТемператураРаб'] == 0, 'ТемператураРаб'] = median_value_temp
df.loc[df['Вентиляция'] == 0, 'Вентиляция'] = median_value_vent
df.loc[df['ВлажностьРаб'] > 100, 'ВлажностьРаб'] = median_value_humid
Корреляционная матрица позволяет выявить взаимосвязи между признаками и целевой переменной, в нашем случае целевой переменной была «ВесНарастающийИтог».
Вот что получилось при построении:
Как можно увидеть, большинство признаков слабо коррелируют с целевой переменной, что может говорить о комплексных и нелинейных зависимостях. Кроме того, матрица выявила сильную корреляцию между температурными показателями («ТемператураMax», «ТемператураMin», «ТемператураРаб»). Это неудивительно, но создавало риск мультиколлинеарности, которая может негативно сказаться на работе модели — в итоге за наиболее репрезентативный показатель я взял «ТемператураРаб». Сильная корреляция между «СреднийВесНорма» и «ДеньВыращивания» объясняется тем, что показатель веса, по сути, является производной от дня выращивания. Включение обоих признаков в модель создало бы «утечку данных» (data leakage), что привело бы к переобучению модели и искусственно высоким показателям на тестовых данных, но плохой работе на реальных. Это классическая ловушка в машинном обучении, которую важно было избежать.
В результате анализа матрицы я сократил количество признаков, оставив только наиболее информативные и некоррелирующие между собой:
ID
Дата
ДеньВыращивания
ТемператураРаб
ВлажностьРаб
Вентиляция
Корм
Вода
ГоловыНарастающийИтог
Реальный вес (она же — «ВесНарастающийИтог»)
После предобработки датасет был разделен на обучающую (80%) и тестовую (20%) выборки:
features = ['ДеньВыращивания', 'ТемператураРаб', 'ВлажностьРаб', 'Вентиляция','Корм', 'Вода', 'ГоловыНарастающийИтог']
target = ['Реальный вес']
X = df[features]
y = df[target]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
Поскольку перед нами стоит задача регрессии (предсказание веса птиц), в качестве базовой модели (baseline) я выбрал XGBoost со стандартными параметрами. Этот алгоритм отлично справляется с большими массивами данных (в отличие от линейной регрессии или случайного леса), находит сложные нелинейные закономерности между признаками и целевой переменной, а также поддерживает параллельные вычисления — что значительно ускоряет работу.
Для сравнения решил протестировать еще два алгоритма: CatBoostRegressor и стекинговую модель, где в качестве базовой модели выступил Random Forest Regressor, а в роли мета-модели — уже знакомый нам XGBoostRegressor.
Градиентный бустинг — это метод ансамблевого обучения, который строит последовательность слабых моделей (обычно деревьев решений), где каждое последующее дерево «учится» предсказывать градиент ошибки [2] предыдущих деревьев, тем самым уменьшая её. Представьте себе компанию разработчиков, где каждый следующий исправляет баги предыдущего 🙃 — настоящая командная работа!
Основной принцип работы
Первая модель (обычно дерево решений) обучается на исходных данных и делает предсказания.
Ошибка первой модели вычисляется как разница между истинными значениями и предсказанными.
Вторая модель обучается на этих ошибках, пытаясь минимизировать остатки.
Процесс продолжается, и каждая новая модель корректирует ошибки предыдущих.
Итоговое предсказание – сумма предсказаний всех моделей
Теперь коротко о каждом типе моделей.
XGBoost (Extreme Gradient Boosting). Это оптимизированная версия градиентного бустинга с высокой производительностью. Основные его преимущества по сравнению с обычным градиентным бустингом: использует L1 (Lasso) и L2 (Ridge) регуляризацию, что предотвращает переобучение. Также XGBoost строит деревья решений по одному на каждой итерации, а затем суммирует их прогнозы.
Catboost. Работает по схожему принципу, но отличается следующими моментами: не требует явного кодирования категориальных признаков (one-hot, label encoding) – он сам их преобразует; также использует особый способ построения деревьев, который предотвращает утечку данных и переобучение.
Случайный лес. Данная модель является ансамблем решающих деревьев, строит несколько деревьев на разных подвыборках данных и усредняет их предсказания (для регрессии).
Принцип работы:
1. Бутстрэпинг (Bootstrap sampling). Каждое дерево обучается на случайной подвыборке данных с заменой, то есть один и тот же объект может попасть в выборку несколько раз, а другой – не попасть вовсе.
2. Случайное подмножество признаков (Random feature selection). Для каждой вершины дерева случайно выбирается только часть признаков (feature bagging). Это снижает корреляцию между деревьями и повышает устойчивость модели.
3. Обучение множества деревьев (Ensemble of decision trees). Каждое дерево строится самостоятельно, без обрезки глубины (deep trees). Благодаря бутстрэпингу и случайному выбору признаков, деревья получаются разными.
4. Объединение предсказаний (Ensemble learning). Усредняются предсказания всех деревьев.
Инициализируем стоковую модель XGBRegressor:
XGB_model = XGBRegressor(random_state=42, n_jobs=-1, eval_metric='rmse')
где random_state=42 – задает фиксированное начальное состояние для воспроизводимости, n_jobs=-1 – включает многопоточное обучение (использует все процессорные ядра), eval_metric=’rmse’ – метрика качества (RMSE – корень из средней квадратичной ошибки).
Обучим модель и оценим ее качество работы на тестовой выборке
XGB_model.fit(X_train, y_train)
y_pred = XGB_model predict(X_test)
rmse = mean_squared_error(y_test, y_pred, squared=False)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"R^2: {r2:.2f}")
print(f"RMSE: {rmse:.2f}")
print(f"MAE: {mae:.2f}")
Результаты XGBoost
R²: 0.99
RMSE: 2945.97
MAE: 1498.84
cb_model = CatBoostRegressor(silent=True, random_state=22)
cb_model.fit(X_train, y_train)
y_pred = cb_model.predict(X_test)
rmse = mean_squared_error(y_test, y_pred, squared=False)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"R^2: {r2:.2f}")
print(f"RMSE: {rmse:.2f}")
print(f"MAE: {mae:.2f}")
Результаты CatBoost
R²: 0.99
RMSE: 2787.50
MAE: 1404.66
Инициализируем базовую модель
base_models = [('rf', RandomForestRegressor(random_state=42, n_jobs=-1))]
Инициализируем мета-модель и сам StackingRegressor
final_model = XGBRegressor(random_state=42, n_jobs=-1, eval_metric='rmse')
stacking = StackingRegressor(estimators=base_models, final_estimator=final_model)
Собираем пайплайн, обучаем и тестируем модель
pipeline = Pipeline(steps=[('stacking', stacking)])
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
rmse = mean_squared_error(y_test, y_pred, squared=False)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"R^2: {r2:.2f}")
print(f"RMSE: {rmse:.2f}")
print(f"MAE: {mae:.2f}")
Результаты стекинговой модели
R²: 0.99
RMSE: 3037.86
MAE: 1379.53
Для автоматизации процесса поиска оптимальных параметров и минимизации времени использовалась библиотека Optuna. Задача — снизить MSE за счет подбора ключевых параметров каждой модели.
Optuna позволила автоматически искать значения ключевых гиперпараметров, таких как:
n_estimators (число деревьев в модели),
learning_rate (шаг обучения),
max_depth (глубина деревьев),
min_child_weight (минимальный вес узла),
subsample (доля данных для построения каждого дерева),
colsample_bytree (доля признаков для работы каждого дерева).
Весь процесс работы Optuna строился на базовом принципе: случайная генерация гиперпараметров и их последовательное улучшение через байесовский подход. В результате оптимизации значения итоговых ошибок всех моделей относительно их средней ошибки составили:
XGBRegressor — 9,7%,
CatBoost — 9,3%,
StackingRegressor — 10,2%.
Среднее значение веса в нашем датасете составляет 27 860 граммов. Соответственно, даже отклонения до 9-10% находятся в приемлемых пределах и говорят о высоком качестве моделей.
Одной из задач проекта было также выявление признаков, наиболее значимо влияющих на вес птицы. Анализ диаграммы важности признаков показал, что корм в сочетании с водой — ключевой показатель ресурсоемкости выращивания. Дальнейшее развитие моделей на этих данных может помочь оптимизировать режим кормления и добиться максимального веса.
Для теста моделей заказчик предоставил таблицу с партиями, которые на момент формирования базы данных не были закрыты. На них и сделаем прогноз и сравним с фактом. Проделав такие же манипуляции с данными (как с исходным датасетом), сделаем прогноз моделью Catboost:
x = data[features]
y_pred = cb.predict(x)
data['Прогнозный вес'] = y_pred
data['Ошибка'] = abs(data['Прогнозный вес'] - data['Реальный вес'])/data['Прогнозный вес'] * 100
Далее сгруппируем полученный результат и вычислим среднюю ошибку по партии:
dz = data.groupby('ID')['Ошибка'].mean().reset_index()
ID. Ошибка
589 4.376792
1027 4.554168
2499 3.376872
6395 6.476118
6407 7.115308
6410 4.380329
6411 3.547634
Как видим, ошибка варьируется от 3 до 7%, что довольно неплохо.
База данных охватывает период с 09.12.2020 по 23.07.2024. Для подготовки набора данных я отфильтровал партии, актуальные на дату 23.07.2024. Используя фильтр по дню выращивания, выбрал 17 партий, где возраст птицы был менее 40 дней, и далее выделил несколько из них для демонстрации прогноза.
Для демонстрации возможностей модели выполнили предварительный расчёт ключевых показателей, включая:
Слияние (конкатенацию) данных партий с данными по птичникам, чтобы связать каждую партию с соответствующим птичником.
Расчёт падежа по партиям: определение дневных изменений количества голов и среднего значения по птичнику.
Заполнение данных смоделированными показателями, учитывающими день выращивания (например, динамика потребления корма и воды, температуры и влажности).
Формирование итогового датасета и выполнение прогноза модели по подготовленным данным.
Для каждой выбранной партии производились расчёты:
Сортировка данных по ID партии и дню выращивания, а также расчёт дневного падежа (убыль количества голов) с использованием группировки.
Расчёт среднего значения падежа для каждой партии.
Далее на основе текущих данных был проведён расчёт:
Средних темпов прироста корма и воды.
Динамического уменьшения количества голов с фиксированным шагом.
Итоговые данные были дополнены необходимыми категориальными признаками, после чего выполнен прогноз веса. Прогнозируемый вес для каждой партии был рассчитан моделью, а полученные данные дополнены колонкой с прогнозной датой.
На основе известных данных последнего дня (2024-07-22) был сформирован полный датасет, содержащий как актуальные данные, так и прогнозы на следующие несколько дней. Итоговый набор данных сформирован путём объединения фактических и прогнозных данных, что позволяет продемонстрировать результативность модели на выбранных партиях.
Проект достиг своей основной цели – модель довольно точно прогнозирует вес, о чем свидетельствуют солидные показатели R² и низкий RMSE на тестовой выборке. Существующие ограничения можно преодолеть при поступлении дополнительных данных от заказчика. Перспективным направлением для улучшения точности может стать разработка стекинговой модели, объединяющей предсказания CatBoost, XGBoost и Случайного леса в один ансамбль.
Модель была сохранена с помощью joblib в формат .pkl, в дальнейшем она может быть интегрирована в рабочий процесс через FastAPI. Данный подход дает возможность развернуть решение в облаке с помощью Docker и настроить автоматизацию, при которой данные будут поступать раз в сутки для моментального прогнозирования веса.
В дальнейшем проект может быть расширен до прогнозирования не только веса птицы, но и расхода пищи и воды, что сделает решение еще более значимым с точки зрения [3] оптимизации бизнес-процессов.
Автор: R_STYLE_SOFTLAB
Источник [4]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/13566
URLs in this post:
[1] обучения: http://www.braintools.ru/article/5125
[2] ошибки: http://www.braintools.ru/article/4192
[3] зрения: http://www.braintools.ru/article/6238
[4] Источник: https://habr.com/ru/companies/rshb/articles/894260/?utm_campaign=894260&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.