Модель joblib не загружается: где хранить собственные функции

Pipeline сохранился и загрузился в той же сессии ноутбука. В отдельном скрипте загрузка падает: собственная функция осталась в __main__ или модуль недоступен. Проверка внутри обучающего процесса не проверила переносимость кода.

Артефакт может ссылаться на код по имени

При обычной pickle-сериализации пользовательские функции и классы часто восстанавливаются через имя модуля и объекта. Поэтому принимающему процессу нужен импортируемый модуль с совместимой реализацией. Сохранённый объект не является самостоятельной копией всего проекта.

У FunctionTransformer особенно легко оставить lambda или функцию из ячейки. Для передачи через стандартный joblib путь с именованной функцией в установленном пакете понятнее. Если выбираете формат, который встраивает больше Python-кода, отдельно учитывайте его требования к совместимости. Формат не отменяет фиксации зависимостей.

Проверить восстановление без обучающей сессии

Пример создаёт маленький модуль feature_rules.py в временном каталоге. Pipeline использует его функцию, обучается, сохраняется и загружается уже другим процессом Python. Дочерний процесс получает путь к модулю через PYTHONPATH. В обычном проекте вместо этого устанавливают версию вашего пакета.

Функция действительно входит в Pipeline и вызывается при прогнозе. Новый процесс проверяет численный результат, а не только успешный load. Так тест обнаружил бы и отсутствие модуля, и несовместимый новый вариант преобразования.

import importlib
import os
from pathlib import Path
import subprocess
import sys
from tempfile import TemporaryDirectory
import joblib
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer

with TemporaryDirectory() as directory:
    root = Path(directory)
    (root / "feature_rules.py").write_text(
        "def double_column(X):\n"
        "    return [[row[0] * 2] for row in X]\n", encoding="utf-8")
    sys.path.insert(0, str(root))
    try:
        rules = importlib.import_module("feature_rules")
        model = make_pipeline(FunctionTransformer(rules.double_column),
                              LinearRegression()).fit([[0], [1], [2]], [0, 2, 4])
        path = root / "model.joblib"
        joblib.dump(model, path)
        env = os.environ.copy()
        env["PYTHONPATH"] = str(root) + os.pathsep + env.get("PYTHONPATH", "")
        check = ("import joblib,sys; m=joblib.load(sys.argv[1]); "
                 "p=float(m.predict([[4]])[0]); assert abs(p-8)<1e-9; print(p)")
        result = subprocess.run([sys.executable, "-c", check, str(path)],
                                env=env, capture_output=True, text=True, check=True)
        assert abs(float(result.stdout.strip()) - 8.0) < 1e-9
        print("fresh process prediction:", result.stdout.strip())
    finally:
        sys.path.remove(str(root))
        sys.modules.pop("feature_rules", None)

Версионировать функцию вместе с моделью

Если сохранить старую модель и затем изменить feature_rules.double_column под тем же именем, load может выполниться, но прогноз будет относиться уже к новому преобразованию. Поэтому артефакт должен ссылаться на конкретную версию пакета, а не просто на название файла. Контрольные сырые входы помогают проверить весь путь после установки.

При приёмке запускайте загрузку в чистом процессе с теми зависимостями, которые получит исполнитель. Наличие нужного объекта в globals обучающего ноутбука больше не должно помогать тесту. Для ошибки ModuleNotFoundError сначала восстановите правильную версию пакета, затем повторите контрольный прогноз.

Закрепить базовую операцию

В открытом уроке joblib вы сохраните и восстановите обученную модель. Следующим шагом выполните загрузку из отдельного процесса, как в статье, если в модели есть ваш Python-код. Откройте урок «Сохранение обученной модели через joblib». Затем вернитесь к сценарию статьи и проверьте его на своём небольшом наборе. Посмотрите программу курса первой ML-модели, чтобы пройти путь от обучения к использованию результата.

Продолжить на практике