Тесты данных и моделей

В ML мало тестировать функции — нужно тестировать данные на входе и поведение модели на выходе.

Тесты данных и моделей — автоматические проверки, которые ловят битые данные до обучения и недостаточно качественную модель до деплоя.

Зачем отдельный вид тестов

Unit-тесты проверяют, что код делает то, что написано. Но в ML код может быть идеален, а результат — мусором, если данные битые или модель деградировала. Поэтому нужны два дополнительных класса проверок: на входе (данные) и на выходе (модель).

Тесты данных

  • Схема. Есть ли все нужные колонки, правильные ли типы.
  • Диапазоны. Возраст в [0, 120], доля положительного класса в ожидаемых пределах.
  • Пропуски. Доля null не превышает порог.
  • Категории. Нет новых, невиданных категорий.

Реализуем простой валидатор данных на чистом Python.

def validate_batch(rows):
    errors = []
    n = len(rows)
    # 1. доля пропусков по age
    missing = sum(1 for r in rows if r.get("age") is None)
    if missing / n > 0.05:
        errors.append(f"too many missing age: {missing}/{n}")
    # 2. диапазон age
    bad = [r["age"] for r in rows
           if r.get("age") is not None and not (0 <= r["age"] <= 120)]
    if bad:
        errors.append(f"age out of range: {bad}")
    # 3. допустимые категории
    allowed = {"new", "active", "churned"}
    unknown = {r["status"] for r in rows if r.get("status") not in allowed}
    if unknown:
        errors.append(f"unknown status: {sorted(unknown)}")
    return errors

batch = [
    {"age": 25, "status": "active"},
    {"age": 200, "status": "new"},
    {"age": None, "status": "ghost"},
]
problems = validate_batch(batch)
print("OK" if not problems else "FAIL")
for p in problems:
    print(" -", p)

Вывод:

FAIL
 - too many missing age: 1/3
 - age out of range: [200]
 - unknown status: ['ghost']

В реальных проектах для этого используют Great Expectations, pandera или встроенную валидацию TFX, но логика та же: декларируете ожидания, батч их нарушает — пайплайн останавливается.

Тесты модели

  • Порог метрики. f1 на валидации не ниже заданного.
  • Срезы. Модель не проваливается на важном сегменте (например, новых пользователях).
  • Инварианты (behavioral tests). Логичные свойства: при росте дохода вероятность одобрения кредита не должна падать.
  • Не хуже baseline. Кандидат сравнивается с текущей прод-моделью.
def test_min_f1(model, X_val, y_val):
    assert f1_score(y_val, model.predict(X_val)) >= 0.90

def test_no_regression(candidate, baseline, X_val, y_val):
    f1_new = f1_score(y_val, candidate.predict(X_val))
    f1_old = f1_score(y_val, baseline.predict(X_val))
    assert f1_new >= f1_old - 0.005  # допуск на шум

Как работает под капотом

Тесты данных встают в пайплайн перед обучением: если батч не прошёл — обучение не стартует, и битые данные не попадают в модель. Тесты модели встают после обучения, перед регистрацией: модель, не прошедшая гейт, не регистрируется как кандидат. Так конвейер сам отсекает мусор на входе и регрессии на выходе, не полагаясь на бдительность человека.

Частые ошибки

  • Проверять только среднюю метрику. Модель может быть хороша в среднем и провальна на важном срезе.
  • Жёсткие пороги без допуска на шум. Маленькие колебания метрики — норма; сравнивайте с допуском.
  • Тестировать модель, но не данные. Тогда обучение «успешно» съест мусор.

Итог

  • В ML к unit-тестам кода добавляются тесты данных (на входе) и тесты модели (на выходе).
  • Тесты данных проверяют схему, диапазоны, пропуски и категории до обучения.
  • Тесты модели проверяют пороги, срезы, инварианты и отсутствие регрессии до деплоя.
Проверьте себя
1. Зачем в ML тесты данных, если код уже покрыт unit-тестами?
AЧтобы дублировать unit-тесты
BКод может быть верен, но при битых данных модель получится мусорной
CЭто требование PEP8
DЧтобы ускорить CI
2. Почему проверять только среднюю метрику недостаточно?
AСреднее всегда врёт
BМодель может быть хороша в среднем, но проваливаться на важном срезе
CСреднее нельзя посчитать
DТак быстрее
3. Что такое поведенческий тест (инвариант) модели?
AПроверка скорости инференса
BПроверка логичного свойства, например: при росте дохода вероятность одобрения не падает
CПроверка размера файла модели
DПроверка версии Python