Тесты данных и моделей
В 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-тестам кода добавляются тесты данных (на входе) и тесты модели (на выходе).
- Тесты данных проверяют схему, диапазоны, пропуски и категории до обучения.
- Тесты модели проверяют пороги, срезы, инварианты и отсутствие регрессии до деплоя.