پیادهسازی پایپلاین استخراج ویژگی و Feature Store با Feast و ردیس
معماری Feature Store برای ریشهکن کردن خطای Training-Serving Skew، تضمین صحت زمانی با Point-in-Time Joins و سرویسدهی آنلاین ویژگیها زیر ۱۰ میلیثانیه به مدلهای یادگیری ماشین.
کابوس پنهان در پروداکشن: چرا مدلهای با دقت بالا پس از استقرار شکست میخورند؟
یکی از رایجترین شکستها در سیستمهای یادگیری ماشین صنعتی (مانند تشخیص تقلب بانکی یا سیستمهای توصیهگر بلادرنگ)، شکاف فاحش بین دقت مدل در نوتبوکهای آزمایشگاهی (Offline Evaluation) و عملکرد واقعی آن در محیط پروداکشن است. این پدیده مخرب از دو خطای معماری ناشی میشود:
- واگرایی منطق محاسباتی (Training-Serving Skew): دانشمند داده ویژگیهایی مانند «میانگین مبلغ تراکنش کاربر در ۲۴ ساعت گذشته» را با کوئریهای SQL یا Pandas روی دادههای تاریخی محاسبه میکند، اما تیم بکاند هنگام استقرار سرویس استنتاج (Inference Service)، همان ویژگی را با منطق متفاوتی در کد میکروسرویس بازنویسی مینماید.
- سفر در زمان و نشت داده (Data Leakage via Naive Joins): هنگام ساخت دیتاست آموزشی، اگر جدول برچسبها (Labels) را با یک
JOINمعمولی به جدول ویژگیها متصل کنید، مقادیر آینده ویژگیها وارد رکوردهای گذشته میشوند و مدل در محیط آزمایشگاه دقت کاذب ۹۹ درصدی نشان میدهد.
معماری دوگانه (Dual-Storage Architecture) در Feast
چارچوب متنباز Feast (Feature Store) با ایجاد یک لایه انتزاعی واحد برای تعریف ویژگیها، ذخیرهسازی داده را به دو مسیر همگام تفکیک میکند:
- مخزن آفلاین (Offline Store): مبتنی بر موتورهای ستونی مقیاسپذیر (مانند فایلهای Parquet روی S3، DuckDB، ClickHouse یا BigQuery) که تاریخچه کامل تغییرات ویژگیها را برای ساخت دیتاست آموزشی و اجرای Time-Travel / ASOF Joins نگهداری میکند.
- مخزن آنلاین (Online Store): مبتنی بر دیتابیسهای کلید-مقدار درونحافظهای فوق سریع (مانند Redis Cluster) که صرفاً آخرین وضعیت (Latest State) هر ویژگی را به ازای هر کلید موجودیت (
entity_id) ذخیره میکند تا در زمان استنتاج آنلاین با تاخیر کمتر از ۵ تا ۱۰ میلیثانیه در اختیار مدل قرار گیرد.
مکانیسم Point-in-Time Correctness و پارامتر TTL
هنگام فراخوانی متد get_historical_features در Feast، شما یک دیتافریم از رویدادهای هدف (شامل user_id و event_timestamp) به سیستم میدهید. موتور Feast برای هر رویداد در زمان ، به عقب برگشته و آخرین مقدار ثبتشده ویژگی را در بازه استخراج میکند؛ بدون آنکه حتی یک میلیثانیه از دادههای پس از زمان به درون دیتاست آموزشی نشت کند.
پیادهسازی تعریف ویژگیها، Materialization به Redis و استنتاج زیر ۱۰ میلیثانیه
در کد زیر، ابتدا ساختار موجودیت و نمای ویژگی (FeatureView) برای سیستم تشخیص تقلب تعریف شده و سپس فرآیند استخراج دیتاست آموزشی بدون نشت زمانی، همگامسازی ویژگیها به درون Redis (materialize_incremental) و واکشی بلادرنگ برای سرویس مدل پیادهسازی شده است:
from datetime import datetime, timedelta, timezone
from typing import Dict, Any, List
import pandas as pd
from feast import Entity, FeatureView, Field, FileSource, FeatureStore, ValueType
from feast.types import Float32, Int64
# 1. Define Entity and Offline Parquet Source
user_entity = Entity(
name='user_id',
join_keys=['user_id'],
value_type=ValueType.INT64,
description='Unique identifier for banking customer'
)
transaction_stats_source = FileSource(
name='user_tx_aggregates_source',
path='s3://ml-lake/features/user_tx_rolling_stats.parquet',
timestamp_field='event_timestamp',
created_timestamp_column='created_timestamp'
)
# 2. Define Unified FeatureView with strict TTL
user_risk_fv = FeatureView(
name='user_risk_features',
entities=[user_entity],
ttl=timedelta(hours=48),
schema=[
Field(name='tx_count_24h', dtype=Int64),
Field(name='avg_amount_24h', dtype=Float32),
Field(name='failed_pin_attempts_1h', dtype=Int64),
Field(name='velocity_score', dtype=Float32),
],
online=True,
source=transaction_stats_source
)
class RealTimeFeatureService:
def __init__(self, repo_path: str = './feature_repo'):
self.store = FeatureStore(repo_path=repo_path)
self.feature_refs = [
'user_risk_features:tx_count_24h',
'user_risk_features:avg_amount_24h',
'user_risk_features:failed_pin_attempts_1h',
'user_risk_features:velocity_score'
]
def build_point_in_time_training_set(self, entity_df: pd.DataFrame) -> pd.DataFrame:
"""
Performs an ASOF Point-in-Time Join to prevent future data leakage.
entity_df must contain ['user_id', 'event_timestamp', 'is_fraud'].
"""
training_data = self.store.get_historical_features(
entity_df=entity_df,
features=self.feature_refs
).to_df()
return training_data
def sync_offline_to_redis(self) -> None:
"""Materializes latest feature values from Parquet into Redis Online Store."""
now_utc = datetime.now(timezone.utc)
self.store.materialize_incremental(end_date=now_utc)
def fetch_online_vector(self, user_ids: List[int]) -> Dict[str, Any]:
"""Sub-10ms Redis lookup via pipelined HGETALL/HMGET under the hood."""
entity_rows = [{'user_id': uid} for uid in user_ids]
response = self.store.get_online_features(
features=self.feature_refs,
entity_rows=entity_rows
)
return response.to_dict()نکته کلیدی مهندسی: برای نگه داشتن تاخیر واکشی از Redis در زیر ۵ میلیثانیه برای صدها کاربر همزمان، ویژگیهایی که همیشه با هم توسط یک مدل فراخوانی میشوند را در یک FeatureView واحد گروهبندی کنید. Feast دادههای هر FeatureView را در قالب یک ساختار Hash در ردیس ذخیره میکند و تجمیع ویژگیهای مرتبط مانع از ارسال دهها دستور شبکه مجزا به کلاستر ردیس میشود.