Архив рубрики ~Лента новостей~

Создание хранилищ признаков с нуля: минимальная рабочая реализация

Создание хранилищ признаков с нуля: минимальная рабочая реализация

Создайте пять компонентов, необходимых для любого хранилища характеристик, а затем посмотрите, как искусственный интеллект изменит дизайн.

Магазины-концепции

# Введение

Большинство команд на собственном горьком опыте убеждаются в необходимости хранилища функций . Модель обнаружения мошенничества работает в ноутбуке, но незаметно дает сбой в рабочей среде. Агент службы поддержки дает общий ответ, потому что не знает, кто пользователь. Конвейер рекомендаций дублирует один и тот же расчет «затрат за 30 дней» в трех заданиях, и два из них не совпадают.

Хранилище признаков — это часть инфраструктуры , которая решает эти проблемы. Оно определяет признаки один раз, хранит их в двух форматах (один для обучения, другой для развертывания) и поддерживает их синхронизацию. Мы собираемся создать минимальное хранилище с нуля на Python , используя DuckDB , Parquet , Redis и FastAPI . Затем мы рассмотрим, как приложения ИИ меняют то, для чего мы его на самом деле используем.

Полный код достаточно короткий, чтобы мы могли рассмотреть каждый компонент.

Магазины-концепции

# Что на самом деле решает магазин функций

Классический аргумент заключается в искажении результатов при подаче данных на обучающую выборку: SQL-запросы, использованные для создания обучающего набора данных, не совпадают с кодом, выполняемым при выводе результатов, поэтому значения расходятся. Эта проблема реальна, и стандартным решением является разделение данных на офлайн и онлайн этапы.

Современный подход шире. Агенты на основе больших языковых моделей (LLM) и конвейеры генерации с расширенным поиском (RAG) нуждаются в структурированном контексте пользователя во время вывода, при каждом запросе, менее чем за 10 мс. LLM не помнит, кто пользователь. Если нам нужен персонализированный результат, мы должны ввести в запрос уровень тарифного плана пользователя, недавнюю активность и состояние учетной записи, и нам нужна система, которая может быстро и стабильно возвращать эти значения. Именно это нам и предоставляет онлайн-хранилище и API поиска данных в хранилище признаков.

Поэтому мы разрабатываем решения для обоих случаев. Одни и те же пять компонентов обрабатывают как сценарий использования прогнозного машинного обучения, так и сценарий использования контекста LLM.

# Пять компонентов

  • Реестр функций, определяющий функции в виде кода.
  • Офлайн-магазин на Parquet, данные о котором запрашиваются из DuckDB, предназначен для обучения и замещения вакансий.
  • Интернет-магазин на Redis для поиска информации с низкой задержкой при выполнении инференции.
  • Конвейер материализации, который передает последние значения из офлайн-режима в онлайн.
  • Сервис FastAPI, предоставляющий типизированный API для получения данных.

Магазины-концепции

# Пример работы: Персонализированная система рекомендаций для получения степени магистра права

Мы управляем стриминговым сервисом. Когда пользователь открывает приложение, LLM генерирует короткое персонализированное сообщение «что посмотреть дальше». Для этого LLM необходимы три вещи о пользователе:

Особенность Тип Свежесть
пользовательский_сегмент нить ежедневно
watch_count_30d инт почасово
последний_жанр нить за событие

Сущность называется user_id. Мы зарегистрируем эти три функции, материализуем их и предоставим LLM по запросу.

// 1. Определение реестра функций

Реестр — это просто место, где функции объявляются один раз, с указанием их сущности, типа данных и источника. Мы используем класс данных.

from dataclasses import dataclass from typing import Literal @dataclass(frozen=True) class Feature: name: str entity: str dtype: Literal[«int», «float», «str»] source: str # путь к файлу Parquet или представлению SQL REGISTRY: dict[str, Feature] = { «user_segment»: Feature(«user_segment», «user_id», «str», «data/user_segment.parquet»), «watch_count_30d»: Feature(«watch_count_30d», «user_id», «int», «data/watch_count_30d.parquet»), «last_genre»: Feature(«last_genre», «user_id», «str», «data/last_genre.parquet»), }

Полный код можно найти здесь.

При запуске программы отображается следующий вывод:

Зарегистрированные признаки: user_segment entity=user_id dtype=str source=data/user_segment.parquet watch_count_30d entity=user_id dtype=int source=data/watch_count_30d.parquet last_genre entity=user_id dtype=str source=data/last_genre.parquet

Это контракт. Все остальные компоненты считывают данные из REGISTRY, поэтому переименование функции, изменение её типа данных или указание на новый источник происходят в одном месте. В производственных системах это может быть YAML-файл или модуль Python, добавленный в репозиторий Git, с проверкой кода при каждом изменении.

// 2. Создание офлайн-хранилища с использованием DuckDB и Parquet

В офлайн-хранилище хранится полная история значений каждого параметра. В качестве уровня хранения мы используем файлы Parquet, а в качестве механизма запросов — DuckDB. DuckDB считывает файлы Parquet напрямую, что означает отсутствие необходимости в отдельной базе данных.

Вот пример кода:

import duckdb import pandas as pd def get_historical_features( entity_df: pd.DataFrame, features: list[str] ) -> pd.DataFrame: con = duckdb.connect() con.register(«entities», entity_df) base = «SELECT * FROM entities» for fname in features: f = REGISTRY[fname] src = f.source.replace(«'», «''») con.execute(f»CREATE VIEW {fname}_src AS SELECT * FROM '{src}'») base = f»»» SELECT t.*, s.{fname} FROM ({base}) t ASOF LEFT JOIN {fname}_src s ON t.user_id = s.user_id AND t.event_timestamp >= s.event_timestamp «»» return con.execute(base).df()

Полный код можно найти здесь.

При запуске программы отображается следующий вывод:

ID пользователя event_timestamp пользовательский_сегмент watch_count_30d последний_жанр
8a2f 2026-05-05 12:00:00 повседневный 22 NaN
б13с 2026-05-07 20:00:00 повседневный 5 триллер
8a2f 2026-05-07 22:00:00 power_user 47 документальный

Соединение AsOf — это соединение по моменту времени. Для каждой строки сущности оно выбирает самое последнее значение признака, где временная метка признака совпадает или предшествует временной метке события. Именно это предотвращает утечку — ситуацию, когда обучающая строка создается со значением признака, которого еще не существовало в момент прогнозирования.

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

// 3. Настройка интернет-магазина на Redis

Интернет-магазин хранит только самое актуальное значение для каждой сущности. Redis является стандартным выбором, поскольку поиск хеша занимает менее миллисекунды.

import json import fakeredis # использовать redis.Redis() для проверки реального сервера в продакшене r = fakeredis.FakeRedis(decode_responses=True) def write_online(entity: str, entity_id: str, values: dict) -> None: r.hset( f»{entity}:{entity_id}», mapping={k: json.dumps(v) for k, v in values.items()}, ) def read_online(entity: str, entity_id: str, features: list[str]) -> dict: raw = r.hmget(f»{entity}:{entity_id}», features) return {f: json.loads(v) if v else None for f, v in zip(features, raw)}

Полный код можно найти здесь.

При запуске программы отображается следующий вывод:

read_online -> {'user_segment': 'power_user', 'watch_count_30d': 47, 'last_genre': 'documentary'} missing key -> {'user_segment': None}

Ключевая структура — entity:entity_id. Значение — хеш, по одному полю на каждый объект. Один запрос HMGET возвращает все запрошенные объекты за один цикл. На локальном экземпляре Redis с тремя объектами это занимает менее 1 мс.

// 4. Запуск конвейера материализации

Материализация перемещает значения из офлайн-режима в онлайн. В реальной системе это происходит по расписанию (Airflow, cron, потоковая обработка данных). Здесь же это функция.

def materialize(features: list[str]) -> None: by_entity: dict[str, dict] = {} for fname in features: f = REGISTRY[fname] src = f.source.replace(«'», «''») df = duckdb.sql(f»»» SELECT {f.entity}, {fname} FROM '{src}' QUALIFY ROW_NUMBER() OVER ( PARTITION BY {f.entity} ORDER BY event_timestamp DESC ) = 1 «»»).df() for _, row in df.iterrows(): by_entity.setdefault(row[f.entity], {})[fname] = row[fname] for entity_id, values in by_entity.items(): write_online(«user_id», entity_id, values)

Полный код можно найти здесь.

При запуске программы отображается следующий вывод:

user_id:8a2f -> {'user_segment': 'power_user', 'watch_count_30d': 47, 'last_genre': 'documentary'} user_id:b13c -> {'user_segment': 'casual', 'watch_count_30d': 5, 'last_genre': 'thriller'}

В предложении QUALIFY хранится последняя строка для каждой сущности. Мы объединяем все характеристики одного пользователя в одну запись в Redis, чтобы сократить количество обращений к серверу. Запускайте это с той периодичностью, которая необходима для каждой характеристики: ежечасно для watch_count_30d, почти в реальном времени для last_genre, ежедневно для user_segment. В реальной реализации реестр — подходящее место для кодирования этой периодичности.

// 5. Предоставление доступа к службе получения данных FastAPI

Сервис извлечения данных представляет собой производственную поверхность. Именно это используется в приложении LLM.

f = resp.json()[«features»] print(«nЗапрос, который получит LLM:») print( f» Система: Вы рекомендуете сериалы для потокового сервиса.n» f» Контекст пользователя: segment={f['user_segment']}, » f»просмотрел {f['watch_count_30d']} фильмов за последние 30 дней, » f»последний просмотренный жанр: {f['last_genre']}.n» f» Задача: предложить 3 фильма в дружелюбном, коротком сообщении.» )

Полный код можно найти здесь.

При запуске программы отображается следующий вывод:

POST /get-online-features -> 200 body: {'user_id': '8a2f', 'features': {'user_segment': 'power_user', 'watch_count_30d': 47, 'last_genre': 'documentary'}} Запрос, который получит LLM: Система: Вы рекомендуете сериалы для стримингового сервиса. Контекст пользователя: сегмент=power_user, посмотрел 47 фильмов за последние 30 дней, последний просмотренный жанр: документальный. Задача: предложить 3 фильма в дружелюбном, коротком сообщении.

Хранилище функций — это тот элемент, который превращает «пользователя 8a2f» в структурированный контекст, который может использовать LLM.

# Где заканчивается хранилище функций и начинается векторная база данных

Векторная база данных ( Pinecone , Weaviate , pgvector ) не является хранилищем признаков, хотя обе они используются в качестве основы для вывода модели. Они решают разные задачи поиска.

Магазины-концепции

В реальной системе LLM используются оба подхода. Векторная база данных возвращает три наиболее похожих предыдущих сеанса просмотра. Хранилище признаков возвращает сегмент пользователя и количество недавних просмотров. Запрос объединяет их.

# Распространенные антипаттерны

Вот несколько шаблонов, которые, как мы постоянно видим, не работают:

  • Вычисление характеристик внутри сервиса модели. Та же логика в итоге оказывается в обучающем блокноте и API, и эти два определения расходятся в течение квартала.
  • Рассматривайте интернет-магазин как источник достоверной информации. Redis теряет данные при некорректном перезапуске. Офлайн-магазин является каноническим; интернет-магазин — это кэш.
  • Пропуск реестра. Три команды независимо друг от друга определяют active_user, и панели мониторинга перестают соответствовать модели.
  • Называть векторную базу данных хранилищем признаков — это неправильно. Она не может выполнять структурированный поиск по ключу сущности, а запрос, требующий и того, и другого, в любом случае будет подключен к двум системам.
  • Заполнение пробелов без соединения данных в определенный момент времени. Тренировочный набор данных выглядит отлично, производственная модель выглядит некорректно, а пробел — это утечка.

# Сравнение с Feast, Tecton и Databricks

Наши примерно 200 линий выполняют ту же работу в миниатюре.

Магазины-концепции

Если мы хотим продолжить работу по той же схеме — с самостоятельным размещением — то Feast является наиболее близким аналогом. Tecton и Databricks — это управляемые решения с явными функциями LLM (API Tecton для получения признаков для LLM, Feature Serving от Databricks для сложных генеративных систем ИИ). Выбор между ними в основном сводится к вопросу о том, насколько мы хотим управлять ими самостоятельно и размещена ли остальная часть нашего стека уже в Databricks.

# Заключение

Рабочее хранилище признаков состоит из пяти компонентов: реестра, офлайн-хранилища, онлайн-хранилища, этапа материализации и API для извлечения данных. Создание такого хранилища один раз позволяет понять, почему производственные системы выглядят именно так. Это также показывает, где меняется дизайн для ИИ: онлайн-путь извлечения данных — это поверхность, с которой взаимодействует LLM, объединения данных в определенный момент времени имеют значение при обучении или оценке, а векторная база данных располагается рядом с хранилищем признаков, а не внутри него.

Как только у нас появятся эти компоненты, замена нашей минимальной версии на Feast, Tecton или Databricks будет в основном заключаться в миграции реестра. Структура системы останется прежней.

Нейт Розиди — специалист по анализу данных и продуктовой стратегии. Он также является адъюнкт-профессором, преподающим аналитику, и основателем StrataScratch, платформы, помогающей специалистам по анализу данных готовиться к собеседованиям с помощью реальных вопросов от ведущих компаний. Нейт пишет о последних тенденциях на рынке труда, дает советы по прохождению собеседований, делится проектами по анализу данных и освещает все аспекты SQL.

Источник: www.kdnuggets.com

✅ Найденные теги: Минимальная, новости, Нуля, Признаков, Создание, Хранилищ
Читайте также
Архив рубрики ~Обо всем~ Определение автономии для оздоровительных роботов в учреждениях по уходу за пожилыми людьми Архив рубрики ~Обо всем~ В видеоролике, посвященном игре Fable, демонстрируется захватывающая система симулятора жизни в этой ролевой игре. Архив рубрики ~Обо всем~ В июньском обновлении Microsoft исправила 198 ошибок Windows, 3 из которых являются уязвимостями нулевого дня. Архив рубрики ~Обо всем~ NuCS против Choco: решатель ограничений на чистом Python встречается с ветераном JVM. Архив рубрики ~Обо всем~ Почему создание орбитальных центров обработки данных сложнее, чем считают в Кремниевой долине Архив рубрики ~Обо всем~ Подкаст Engadget: Мысли о WWDC 2026 из Apple Park Архив рубрики ~Обо всем~ Я протестировал множество настольных программ для работы с ИИ, но Hermes с Ollama — мой новый фаворит, и вот почему. Архив рубрики ~Обо всем~ Теперь пользователи Pinterest смогут совершать покупки напрямую в магазинах Amazon. Архив рубрики ~Обо всем~ Как рефакторить код с помощью Claude Code Архив рубрики ~Обо всем~ В следующем месяце Microsoft Office 2019 для Mac станет доступен только для чтения. Архив рубрики ~Коротко из Telegram~ Госдума приняла нормы, предусматривающие штрафы за нарушение новых требований к… Архив рубрики ~Обо всем~ Лучшие предложения на роботы-пылесосы в рамках Prime Day, которые я бы купил сейчас, после тестирования десятков вариантов. Архив рубрики ~Обо всем~ Мы профессионально отслеживаем выгодные предложения: вот лучшие предложения, которые нашли наши эксперты CNET на этой неделе. Архив рубрики ~Обо всем~ Как обучить модель оценки в эпоху искусственного интеллекта Архив рубрики ~Обо всем~ Определение автономии для оздоровительных роботов в учреждениях по уходу за пожилыми людьми Архив рубрики ~Обо всем~ В видеоролике, посвященном игре Fable, демонстрируется захватывающая система симулятора жизни в этой ролевой игре. Архив рубрики ~Обо всем~ В июньском обновлении Microsoft исправила 198 ошибок Windows, 3 из которых являются уязвимостями нулевого дня. Архив рубрики ~Обо всем~ NuCS против Choco: решатель ограничений на чистом Python встречается с ветераном JVM. Архив рубрики ~Обо всем~ Почему создание орбитальных центров обработки данных сложнее, чем считают в Кремниевой долине Архив рубрики ~Обо всем~ Подкаст Engadget: Мысли о WWDC 2026 из Apple Park Архив рубрики ~Обо всем~ Я протестировал множество настольных программ для работы с ИИ, но Hermes с Ollama — мой новый фаворит, и вот почему. Архив рубрики ~Обо всем~ Теперь пользователи Pinterest смогут совершать покупки напрямую в магазинах Amazon. Архив рубрики ~Обо всем~ Как рефакторить код с помощью Claude Code Архив рубрики ~Обо всем~ В следующем месяце Microsoft Office 2019 для Mac станет доступен только для чтения. Архив рубрики ~Коротко из Telegram~ Госдума приняла нормы, предусматривающие штрафы за нарушение новых требований к… Архив рубрики ~Обо всем~ Лучшие предложения на роботы-пылесосы в рамках Prime Day, которые я бы купил сейчас, после тестирования десятков вариантов. Архив рубрики ~Обо всем~ Мы профессионально отслеживаем выгодные предложения: вот лучшие предложения, которые нашли наши эксперты CNET на этой неделе. Архив рубрики ~Обо всем~ Как обучить модель оценки в эпоху искусственного интеллекта

Оставить комментарий

Подписка на рассылку

Получайте свежие новости и идеи на почту. Без спама — только самое интересное.

Нажимая «Подписаться», вы соглашаетесь с политикой конфиденциальности.