Сохранение и использование модели

Сохранение обученной модели через joblib

Содержание курса

В прошлом уроке вы обучили LogisticRegression на данных о задержках доставки и сравнили её метрики с DummyClassifier. Объект lr с подобранными весами существовал в памяти — но только до конца той сессии. В новом сеансе переменные не сохраняются, поэтому здесь мы сначала воспроизведём обучение, а затем зафиксируем результат на диске через joblib, чтобы не повторять этот шаг каждый раз заново.

joblib.dump: сериализация обученной модели в бинарный файл

joblib превращает любой Python-объект в последовательность байт и записывает её в файл. Для scikit-learn это означает, что все внутренние атрибуты модели — веса coef_, смещение intercept_, параметры вроде C и max_iter — упакованы в один бинарный файл. Файл можно открыть в следующей сессии или скопировать на другую машину, однако scikit-learn не поддерживает загрузку между разными версиями как официальный контракт: файл, сохранённый в одном окружении, может не загрузиться в другом. Даже при совпадающих версиях Python, scikit-learn и joblib корректный результат — это воспроизводимое окружение, а не безусловная гарантия.

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

Переменные не сохраняются между сессиями, поэтому начнём с полного воспроизводимого блока обучения — он запускается здесь целиком и ни на какие переменные из предыдущего урока не рассчитывает:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
import joblib

df = pd.read_csv('delivery_risk.csv')

features = ['distance_km', 'items_count', 'warehouse_load', 'forecast_rain_mm', 'is_weekend']
X = df[features]
y = df['delayed']

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

lr = LogisticRegression(max_iter=1000, random_state=42)
lr.fit(X_train, y_train)

Теперь lr — обученный объект в памяти. Стоит перезапустить ядро, и все веса исчезнут.

Критический момент: порядок вызовов. joblib.dump не знает, обучена ли модель. Если вызвать его до lr.fit(X_train, y_train), файл всё равно создастся — без ошибки и без предупреждения. Внутри окажется объект с параметрами, заданными при создании (max_iter=1000, random_state=42), но без обученных коэффициентов — атрибут coef_ будет отсутствовать. При последующей попытке предсказания такой объект выбросит NotFittedError: scikit-learn проверяет состояние fit перед predict и не допускает работы необученной модели.

Чтобы зафиксировать результат на диске, сначала убеждаемся в fitted-состоянии, а затем делаем единственный вызов joblib.dump:

assert hasattr(lr, 'coef_'), 'модель не обучена — fit не вызван'

saved = joblib.dump(lr, 'model.joblib')
print(saved)  # ['model.joblib']

coef_ появляется у LogisticRegression только после успешного fit, поэтому его наличие — надёжный индикатор fitted-состояния. Это особенно важно в длинных ноутбуках, где ячейки легко запустить не в том порядке. joblib.dump возвращает список путей записанных файлов — обычно из одного элемента. Сам файл бинарный: открывать его текстовым редактором бессмысленно.

После вызова joblib.dump файл model.joblib содержит полный снимок обученной модели.