Подготовка данных: чтение, очистка, признаки, пайплайны
Зачем в ML так много внимания данным
В предыдущих статьях мы:
разобрали, что такое модель, признаки, датасет и разделение на выборки
настроили базовый C++-инструментарий (CMake, Eigen, зависимости)Теперь переходим к самому частому источнику “почему модель плохая”: подготовка данных. В реальных проектах качество, стабильность и воспроизводимость пайплайна подготовки данных часто важнее выбора конкретного алгоритма.
Типовые цели подготовки данных:
привести данные к формату, удобному для обучения и инференса
устранить пропуски и аномалии
построить признаки так, чтобы модель могла извлекать закономерности
не допустить утечки данных между train/val/test
сделать одинаковую обработку в обучении и на продакшене!Обобщенная карта шагов, которая помогает не потерять важные детали
Форматы данных и чтение в C++
На практике данные для классического ML чаще всего приходят в одном из форматов:
CSV/TSV файлы
логи (текстовые строки)
бинарные форматы (реже на старте, чаще в крупных системах)
данные из БД (но в учебных примерах обычно начинаем с файлов)Минимальный разбор CSV без внешних библиотек
Для учебных проектов полезно уметь прочитать “простой CSV” самостоятельно: без кавычек, без запятых внутри полей. Это не универсальный CSV-парсер, но хороший старт.
Пример: читаем файл, пропускаем заголовок, извлекаем числовые признаки и целевую переменную.
Что важно понимать про этот подход:
он подходит для контролируемых учебных данных
в реальных CSV часто бывают кавычки, экранирование, пропуски в виде пустых полейЕсли вам нужен быстрый “готовый” парсер CSV, можно посмотреть репозиторий fast-cpp-csv-parser. Он популярен, но все равно важно понимать, что именно вы ожидаете от формата.
Представление данных для ML-кода
Для большинства классических моделей удобно хранить:
матрицу признаков размера
вектор ответов размера Где:
— число объектов (строк)
— число признаков (столбцов)В C++ с Eigen типичный выбор:
Eigen::MatrixXd для
Eigen::VectorXd для Плюс такого представления: большинство операций (нормализация, вычисление статистик) получаются короткими и быстрыми.
Очистка данных: пропуски, типы, аномалии
Очистка — это приведение данных к корректному и предсказуемому виду.
Пропуски
Пропуски встречаются почти всегда: не заполнили поле, сломался источник, значение неприменимо.
Основные стратегии:
удалить строки с пропусками (часто плохо, если данных мало)
заполнить константой (например, 0 или -1, но это может исказить смысл)
заполнить статистикой по train (средним/медианой)
добавить бинарный признак “значение было пропущено”Ключевое правило: все параметры заполнения считаются только по обучающей выборке, иначе появится утечка данных.
Приведение типов и единиц измерения
Типовые проблемы:
числа пришли строками (нужно парсить)
разные единицы измерения в разных источниках (например, “рубли” и “тысячи рублей”)
разные часовые пояса и форматы датПрактика: в пайплайне явно фиксируйте все преобразования и покрывайте их тестами.
Выбросы и некорректные значения
Выброс — это значение, которое сильно выбивается из типичного диапазона.
Причины:
реальная редкая ситуация
ошибка ввода/сбоя
другой “режим” данных (например, новый сегмент пользователей)Что можно делать:
клиппинг: ограничивать значения сверху/снизу (winsorization)
лог-преобразования для длинных хвостов
отдельные правила валидации (например, возраст не может быть отрицательным)В продакшене обычно дополнительно вводят “защиту инференса”: если на вход пришли NaN/inf или слишком большие числа, сервис должен обработать это предсказуемо.
Построение признаков: что модель реально “видит”
Модель не понимает “дату”, “город” или “текст” как человек. Она работает с числами. Признаки — это мост между сырой реальностью и числовым представлением.
Масштабирование числовых признаков
Для многих моделей важно, чтобы признаки были в сопоставимых масштабах.
Самый распространенный вариант — стандартизация:
Где:
— исходное значение признака
— среднее значение признака по обучающей выборке
— стандартное отклонение признака по обучающей выборке
— стандартизированное значениеПочему это нельзя считать “на всем датасете сразу”: если вы посчитали и на train+test, то “подсмотрели” распределение test.
Минимальная реализация “скейлера” на Eigen:
Обратите внимание:
fit вычисляет параметры только на train
transform применяет те же параметры к любым данным (val/test/прод)
константный признак не должен ломать кодКатегориальные признаки
Категориальные признаки — это значения вроде “город=Москва”, “тариф=Премиум”. Сами по себе они не числа.
Популярные подходы:
one-hot encoding: отдельный бинарный столбец на каждую категорию
ordinal encoding: сопоставить категории с числами (опасно, если нет естественного порядка)
hashing trick: отправлять категорию в фиксированное число корзин хешемДля C++-инференса важен вопрос что делать с неизвестной категорией, которая не встречалась на train:
завести специальную категорию __UNK__
или игнорировать (в one-hot это “все нули”)Даты и время
Почти никогда нельзя просто “подать дату строкой”. Часто используют:
компоненты: час, день недели, месяц
циклические преобразования для периодичности (например, часы по кругу)
агрегаты по окнам (скользящие суммы/средние)Даже простые вещи вроде “день недели” могут резко поднять качество, если в данных есть сезонность.
Нормализация текста и изображений
В этом курсе упор на классический ML и инженерное внедрение, но важно понимать общий принцип:
текст обычно превращают в числовой вектор (например, bag-of-words, TF-IDF, эмбеддинги)
изображения часто нормализуют (масштабирование, вычитание среднего) и приводят к нужному размеруЕсли вы работаете с изображениями в C++, де-факто стандарт препроцессинга — OpenCV.
Разделение на выборки и борьба с утечкой данных
В первой статье мы обсуждали train/val/test. В подготовке данных эта тема становится практической.
Правильный порядок действий
Частая ошибка новичков: “сначала нормализую весь датасет, потом делю”. Это почти всегда утечка.
Правильный порядок:
Разделить данные на train/val/test.
На train посчитать все параметры препроцессинга: средние, словари категорий, статистики.
Применить полученные параметры к val/test.Особые случаи: временные ряды и группы
Иногда случайное перемешивание запрещено:
временные ряды: train должен быть “раньше”, test “позже”
групповые данные: нельзя, чтобы объекты одного пользователя одновременно попали в train и testИначе вы получите завышенное качество “на бумаге” и провал после внедрения.
Пайплайн в C++: как сделать обработку воспроизводимой
Пайплайн — это последовательность шагов, которая из сырого объекта делает вектор признаков, а затем дает предсказание.
Почему пайплайн важен:
вы не хотите дублировать логику “как готовить признаки” в десяти местах
вы хотите, чтобы обучение и продакшен-инференс делали одно и то же
вы хотите уметь сериализовать не только модель, но и препроцессингМинимальная архитектура: fit/transform
Один из самых практичных паттернов:
fit учится на train и запоминает параметры
transform применяет параметры к даннымНиже упрощенная идея “цепочки преобразований”. Это не библиотека, а каркас, который помогает структурировать код.
Как это использовать концептуально:
добавляете шаги (например, StandardScaler)
вызываете pipeline.fit(X_train)
затем pipeline.transform(X_val) и pipeline.transform(X_test)Сериализация: модель важна, но препроцессинг тоже
Если вы нормализовали признаки или кодировали категории, то на проде вам нужны:
параметры нормализации (средние, стандартные отклонения)
словари категорий
порядок признаков и версия схемыСериализация может быть:
бинарная (быстро и компактно, но сложнее отлаживать)
текстовая (например, JSON) для удобства проверкиДля JSON в C++ часто используют библиотеку nlohmann/json.
Ключевая инженерная идея: артефакт инференса — это почти всегда “модель + препроцессинг + метаданные”.
Практический чек-лист перед обучением модели
Перед тем как обучать первую модель (в следующих темах), полезно пройтись по списку.
Определены входные признаки и целевая переменная.
Зафиксирован формат данных (что означают столбцы, какие единицы измерения).
Есть стратегия обработки пропусков.
Есть стратегия обработки неизвестных категорий.
Разделение на train/val/test сделано корректно для вашей природы данных.
Все параметры препроцессинга считаются только на train.
Реализация препроцессинга покрыта тестами на граничные случаи.
Подготовка данных оформлена как воспроизводимый пайплайн, который можно применить на проде.В следующей части курса мы начнем обучать простые модели на подготовленных данных и обсуждать метрики качества уже “на реальном коде”, где пайплайн подготовки играет решающую роль.