Minimal – chatgpz.de – KI-Tools & Praxiswissen https://chatgpz.de Unabhängige Tests, Anleitungen und Hintergründe zu KI-Tools. Fri, 12 Jun 2026 03:10:37 +0000 de-DE hourly 1 https://wordpress.org/?v=6.4.2 234900186 Characteristic Shops von Grund auf: Eine minimal funktionierende Implementierung https://chatgpz.de/data-analytics/characteristic-shops-von-grund-auf-eine-minimal-funktionierende-implementierung/ https://chatgpz.de/data-analytics/characteristic-shops-von-grund-auf-eine-minimal-funktionierende-implementierung/#respond Fri, 12 Jun 2026 03:10:36 +0000 http://chatgpz.de/data-analytics/feature-stores-von-grund-auf-eine-minimal-funktionierende-implementierung/ [ad_1]

Feature-Stores

# Einführung

Die meisten Groups stellen fest, dass sie eine benötigen Characteristic-Retailer auf die harte Tour. Ein Betrugsmodell arbeitet im Pocket book und bringt nonetheless und leise die Produktion zum Erliegen. Ein Supportmitarbeiter gibt eine allgemeine Antwort, da er keine Ahnung hat, wer der Benutzer ist. Eine Empfehlungspipeline dupliziert dieselbe Berechnung der „30-Tage-Ausgaben“ für drei Jobs, und zwei davon sind anderer Meinung.

Ein Characteristic Retailer ist das Teil davon Infrastruktur das behebt diese Probleme. Es definiert Funktionen einmal, speichert sie in zwei Formen (eine zum Coaching, eine zum Servieren) und hält beide synchron. Wir werden ein Minimalmodell von Grund auf neu bauen Pythonverwenden DuckDB, Parkett, RedisUnd FastAPI. Dann schauen wir uns an, wie KI-Anwendungen das verändern, wofür wir sie tatsächlich nutzen.

Der vollständige Code ist kurz genug, dass wir jede Komponente durchgehen können.

Feature-Stores

# Was ein Characteristic Retailer tatsächlich löst

Der klassische Pitch ist der Trainings-Serving-Skew: Das SQL, das Ihren Trainingssatz erstellt hat, ist nicht derselbe Codepfad, der bei der Inferenz ausgeführt wird, sodass die Werte abweichen. Dieses Downside ist actual und die Trennung von Offline und On-line ist die Standardlösung.

Das moderne Spielfeld ist breiter. Giant Language Mannequin (LLM)-Agenten und Retrieval-Augmented Era (RAG)-Pipelines benötigen einen strukturierten Benutzerkontext zur Inferenzzeit, bei jeder Anfrage, in weniger als 10 ms. Ein LLM hat keine Erinnerung daran, wer der Benutzer ist. Wenn wir eine personalisierte Ausgabe wünschen, müssen wir die Planstufe des Benutzers, die letzte Aktivität und den Kontostatus in die Eingabeaufforderung einfügen, und wir brauchen ein System, das diese Werte schnell und konsistent zurückgeben kann. Genau das bieten uns der On-line-Store und die Abruf-API eines Characteristic-Shops.

Deshalb bauen wir für beides. Die gleichen fünf Komponenten behandeln den Anwendungsfall des prädiktiven maschinellen Lernens und den LLM-Kontext-Anwendungsfall.

# Die fünf Komponenten

  • Eine Characteristic-Registrierung, die Options als Code definiert.
  • Ein Offline-Store auf Parquet, abgefragt mit DuckDB, für Schulungen und Auffüllungen.
  • Ein On-line-Store auf Redis für Nachschlagevorgänge mit geringer Latenz.
  • Eine Materialisierungspipeline, die die neuesten Werte von offline nach on-line überträgt.
  • Ein FastAPI-Dienst, der eine typisierte Abruf-API verfügbar macht.

Feature-Stores

# Laufbeispiel: Ein personalisierter LLM-Empfehler

Wir betreiben einen Streamingdienst. Wenn ein Benutzer die App öffnet, generiert ein LLM eine kurze, personalisierte Nachricht „Was Sie als Nächstes ansehen sollten“. Das LLM benötigt drei Dinge über den Benutzer:

Besonderheit Typ Frische
user_segment Zeichenfolge täglich
watch_count_30d int stündlich
last_genre Zeichenfolge professional Veranstaltung

Das Wesen ist user_id. Wir werden diese drei Funktionen registrieren, materialisieren und sie dem LLM auf Anfrage zur Verfügung stellen.

// 1. Definieren der Characteristic Registry

Eine Registrierung ist lediglich ein Ort, an dem Options einmalig mit ihrer Entität, ihrem Dtype und ihrer Quelle deklariert werden. Wir verwenden eine Datenklasse.

from dataclasses import dataclass
from typing import Literal

@dataclass(frozen=True)
class Characteristic:
    title: str
    entity: str
    dtype: Literal("int", "float", "str")
    supply: str  # path to a Parquet file or a SQL view

REGISTRY: dict(str, Characteristic) = {
    "user_segment": Characteristic("user_segment", "user_id", "str", "information/user_segment.parquet"),
    "watch_count_30d": Characteristic("watch_count_30d", "user_id", "int", "information/watch_count_30d.parquet"),
    "last_genre": Characteristic("last_genre", "user_id", "str", "information/last_genre.parquet"),
}

Den vollständigen Code finden Sie hier Hier.

Wenn Sie es ausführen, wird in der Ausgabe Folgendes angezeigt:

Registered options:
user_segment  entity=user_id  dtype=str  supply=information/user_segment.parquet
watch_count_30d  entity=user_id  dtype=int  supply=information/watch_count_30d.parquet
last_genre  entity=user_id  dtype=str  supply=information/last_genre.parquet

Das ist der Vertrag. Jede andere Komponente liest aus REGISTRYsodass das Umbenennen eines Options, das Ändern seines D-Typs oder das Verweisen auf eine neue Quelle an einer Stelle erfolgt. In Produktionssystemen wäre dies der Fall YAML oder ein in ein Git-Repository eingechecktes Python-Modul mit Codeüberprüfung bei jeder Änderung.

// 2. Aufbau des Offline-Retailers mit DuckDB und Parquet

Der Offline-Store speichert den vollständigen Verlauf jedes Merkmalswerts. Wir verwenden Parquet-Dateien als Speicherschicht und DuckDB als Abfrage-Engine. DuckDB liest Parquet direkt, was bedeutet, dass keine separate Datenbank ausgeführt werden muss.

Hier ist ein Beispiel des Codes:

import duckdb
import pandas as pd

def get_historical_features(
    entity_df: pd.DataFrame, options: record(str)
) -> pd.DataFrame:
    con = duckdb.join()
    con.register("entities", entity_df)
    base = "SELECT * FROM entities"
    for fname in options:
        f = REGISTRY(fname)
        src = f.supply.change("'", "''")
        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()

Den vollständigen Code finden Sie hier Hier.

Wenn Sie es ausführen, wird in der Ausgabe Folgendes angezeigt:

Benutzer-ID event_timestamp user_segment watch_count_30d last_genre
8a2f 05.05.2026 12:00:00 lässig 22 NaN
b13c 07.05.2026 20:00:00 lässig 5 Thriller
8a2f 07.05.2026 22:00:00 power_user 47 Dokumentarfilm

Der AsOf beitreten ist der Level-in-Time-Be part of. Für jede Entitätszeile wird der aktuellste Featurewert ausgewählt, bei dem der Zeitstempel des Options beim oder vor dem Ereigniszeitstempel liegt. Dadurch wird Leckage verhindert – wenn eine Trainingszeile mit einem Merkmalswert erstellt wird, der zu dem Zeitpunkt, für den wir die Vorhersage treffen, noch nicht vorhanden conflict.

Level-in-Time-Joins sind immer noch die richtige Antwort für jedes Modell, das wir trainieren oder verfeinern möchten. Für einen reinen Inferenzzeit-LLM-Anwendungsfall rufen wir diese Funktion möglicherweise nie auf. Wir wollen weiterhin den Offline-Retailer, da dort Backfills, Bewertungsdatensätze und Audits herkommen.

// 3. Einrichten des On-line-Retailers auf Redis

Der On-line-Store speichert nur den neuesten Wert professional Entität. Redis ist die Standardwahl, da Hash-Suchvorgänge weniger als eine Millisekunde dauern.

import json
import fakeredis  # use redis.Redis() in opposition to an actual server in manufacturing

r = fakeredis.FakeRedis(decode_responses=True)

def write_online(entity: str, entity_id: str, values: dict) -> None:
    r.hset(
        f"{entity}:{entity_id}",
        mapping={okay: json.dumps(v) for okay, v in values.gadgets()},
    )

def read_online(entity: str, entity_id: str, options: record(str)) -> dict:
    uncooked = r.hmget(f"{entity}:{entity_id}", options)
    return {f: json.masses(v) if v else None for f, v in zip(options, uncooked)}

Den vollständigen Code finden Sie hier Hier.

Wenn Sie es ausführen, wird in der Ausgabe Folgendes angezeigt:

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

Die Schlüsselform ist entity:entity_id. Der Wert ist ein Hash mit einem Feld professional Characteristic. Eine Single HMGET gibt alle von uns gewünschten Funktionen in einem Rundlauf zurück. Auf einer lokalen Redis-Instanz mit drei Funktionen ist dies in deutlich unter 1 ms erledigt.

// 4. Ausführen der Materialisierungspipeline

Durch die Materialisierung werden Werte von offline nach on-line verschoben. In einem realen System läuft dies nach einem Zeitplan (Airflow, Cron, ein Streaming-Job). Hier ist es eine Funktion.

def materialize(options: record(str)) -> None:
    by_entity: dict(str, dict) = {}
    for fname in options:
        f = REGISTRY(fname)
        src = f.supply.change("'", "''")
        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.gadgets():
        write_online("user_id", entity_id, values)

Den vollständigen Code finden Sie hier Hier.

Wenn Sie es ausführen, wird in der Ausgabe Folgendes angezeigt:

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

Der QUALIFY Die Klausel behält die neueste Zeile professional Entität bei. Wir gruppieren alle Funktionen für denselben Benutzer in einem Redis-Schreibvorgang, um Roundtrips zu vermeiden. Führen Sie dies mit der Kadenz aus, die jede Funktion benötigt: stündlich für watch_count_30dnahezu in Echtzeit für last_genretäglich für user_segment. Die Registrierung ist der richtige Ort, um diesen Rhythmus in einer realen Implementierung zu kodieren.

// 5. Offenlegung des FastAPI Retrieval Service

Der Abrufdienst ist die Produktionsfläche. Es ist das, was die LLM-Anwendung aufruft.

f = resp.json()("options")
print("nPrompt the LLM would obtain:")
print(
    f"  System: You advocate reveals for a streaming service.n"
    f"  Person context: section={f('user_segment')}, "
    f"watched {f('watch_count_30d')} titles in final 30 days, "
    f"final style watched: {f('last_genre')}.n"
    f"  Activity: recommend 3 titles in a pleasant, brief message."
)

Den vollständigen Code finden Sie hier Hier.

Wenn Sie es ausführen, wird in der Ausgabe Folgendes angezeigt:

POST /get-online-features -> 200
physique: {'user_id': '8a2f', 'options': {'user_segment': 'power_user', 'watch_count_30d': 47, 'last_genre': 'documentary'}}
Immediate the LLM would obtain:
  System: You advocate reveals for a streaming service.
  Person context: section=power_user, watched 47 titles in final 30 days, final style watched: documentary.
  Activity: recommend 3 titles in a pleasant, brief message.

Der Characteristic Retailer ist der Teil, der „Benutzer 8a2f“ in einen strukturierten Kontext umwandelt, den das LLM verwenden kann.

# Wo der Characteristic Retailer endet und die Vektordatenbank beginnt

Eine Vektordatenbank (Tannenzapfen, Weben, pgvector) ist kein Characteristic Retailer, auch wenn beide bei der Schlussfolgerung vor einem Modell stehen. Sie lösen verschiedene Retrieval-Probleme.

Feature-Stores

Ein echter LLM-Stack nutzt beides. Die Vektordatenbank gibt die drei ähnlichsten vergangenen Betrachtungssitzungen zurück. Der Characteristic Retailer gibt das Phase des Benutzers und die letzten Zählungen zurück. Die Eingabeaufforderung kombiniert sie.

# Gängige Anti-Patterns

Einige Muster, die wir immer wieder beobachten, scheitern:

  • Rechenfunktionen innerhalb des Modelldienstes. Die gleiche Logik landet im Trainingsnotizbuch und in der API, und die beiden Definitionen weichen innerhalb eines Quartals voneinander ab.
  • Den On-line-Store als Quelle der Wahrheit betrachten. Redis verliert Daten bei einem fehlerhaften Neustart. Der Offline-Store ist kanonisch; Der On-line-Store ist ein Cache.
  • Überspringen der Registrierung. Drei Groups definieren unabhängig voneinander active_user und die Dashboards stimmen nicht mehr mit dem Modell überein.
  • Eine Vektordatenbank als Characteristic Retailer bezeichnen. Es können keine strukturierten Suchvorgänge mit Entitätsschlüssel durchgeführt werden, und eine Eingabeaufforderung, die beides benötigt, wird ohnehin mit zwei Systemen verbunden sein.
  • Auffüllen ohne Level-in-Time-Joins. Das Trainingsset sieht großartig aus, das Serienmodell sieht kaputt aus und die Lücke ist das Leck.

# Vergleich mit Feast, Tecton und Databricks

Unsere ca. 200 Linien erledigen die gleiche Aufgabe im Miniaturformat.

Feature-Stores

Fest ist der naheliegendste Vergleich, wenn wir nach dem gleichen Muster weitergehen wollen: selbst gehostet. Tecton Und Datenbausteine sind die verwalteten Pfade und verfügen über explizite LLM-Funktionen (Tectons Characteristic Retrieval API für LLMs, Databricks Characteristic Serving für zusammengesetzte generative KI-Systeme). Die Wahl zwischen ihnen hängt hauptsächlich davon ab, wie viel wir selbst betreiben möchten und ob der Relaxation unseres Stacks bereits in Databricks lebt.

# Abschluss

Ein funktionierender Characteristic Retailer besteht aus fünf Komponenten: einer Registrierung, einem Offline-Retailer, einem On-line-Retailer, einem Materialisierungsschritt und einer Abruf-API. Wenn wir es einmal bauen, erfahren wir, warum die Produktionssysteme so aussehen, wie sie aussehen. Es zeigt auch, wo sich das Design für KI ändert: Der On-line-Abrufpfad ist die Oberfläche, auf die das LLM trifft, Level-in-Time-Joins sind wichtig, wenn wir trainieren oder auswerten, und die Vektordatenbank befindet sich neben dem Characteristic Retailer und nicht darin.

Sobald wir diese Teile haben, ist der Austausch unserer Minimalversion gegen Feast, Tecton oder Databricks größtenteils eine Migration der Registrierung. Die Kind des Methods bleibt gleich.

Nate Rosidi ist Datenwissenschaftler und in der Produktstrategie tätig. Er ist außerdem außerordentlicher Professor für Analytik und Gründer von StrataScratch, einer Plattform, die Datenwissenschaftlern hilft, sich mit echten Interviewfragen von High-Unternehmen auf ihre Interviews vorzubereiten. Nate schreibt über die neuesten Developments auf dem Karrieremarkt, gibt Ratschläge zu Vorstellungsgesprächen, stellt Knowledge-Science-Projekte vor und behandelt alles rund um SQL.



[ad_2]

]]>
https://chatgpz.de/data-analytics/characteristic-shops-von-grund-auf-eine-minimal-funktionierende-implementierung/feed/ 0 19200
Minimal lebensfähiges MLE. Aufbau einer minimalen produktionsreifen… | von Lenix Carter | Okt. 2024 https://chatgpz.de/machine-learning/minimal-lebensfaehiges-mle-aufbau-einer-minimalen-produktionsreifen-von-lenix-carter-okt-2024/ https://chatgpz.de/machine-learning/minimal-lebensfaehiges-mle-aufbau-einer-minimalen-produktionsreifen-von-lenix-carter-okt-2024/#respond Thu, 31 Oct 2024 10:27:17 +0000 https://chatgpz.de/machine-learning/minimal-lebensfaehiges-mle-aufbau-einer-minimalen-produktionsreifen-von-lenix-carter-okt-2024/ [ad_1]

Verwendung eines benutzerdefinierten Modells

Obwohl der Prozess der Erstellung und Feinabstimmung eines Modells nicht die Absicht dieses Projekts ist, ist es wichtig zu verstehen, wie ein Modell zu diesem Prozess hinzugefügt werden kann.

# Filename: practice.py

import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from datasets import load_dataset
from torch.utils.knowledge import DataLoader

def train_model():
# Load dataset
full_dataset = load_dataset("stanfordnlp/imdb", cut up="practice")
dataset = full_dataset.shuffle(seed=42).choose(vary(10000))

model_name = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
mannequin = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)

optimizer = torch.optim.AdamW(mannequin.parameters(), lr=2e-5)

# Use GPU if accessible
system = torch.system("cuda" if torch.cuda.is_available() else "cpu")
mannequin.to(system)

mannequin.practice()

# Create a DataLoader for batching
dataloader = DataLoader(dataset, batch_size=8, shuffle=True)

# Coaching loop
num_epochs = 3 # Set the variety of epochs
for epoch in vary(num_epochs):
total_loss = 0
for batch in dataloader:
inputs = tokenizer(batch("textual content"), truncation=True, padding=True, return_tensors="pt", max_length=512).to(system)
labels = torch.tensor(batch("label")).to(system)

optimizer.zero_grad()
outputs = mannequin(**inputs, labels=labels)
loss = outputs.loss

loss.backward()
optimizer.step()
total_loss += loss.merchandise()

avg_loss = total_loss / len(dataloader)
print(f"Epoch {epoch + 1}/{num_epochs}, Loss: {avg_loss:.4f}")

# Save the mannequin
mannequin.save_pretrained("./mannequin/")
tokenizer.save_pretrained("./mannequin/")

# Check the mannequin with pattern sentences
test_sentences = (
"This film was improbable!",
"I completely hated this movie.",
"It was simply okay, not nice.",
"An absolute masterpiece!",
"Waste of time!",
"A phenomenal story and nicely acted.",
"Not my kind of film.",
"It may have been higher.",
"An exhilarating journey from begin to end!",
"Very disappointing."
)

# Change mannequin to analysis mode
mannequin.eval()

# Put together tokenizer for check inputs
inputs = tokenizer(test_sentences, truncation=True, padding=True, return_tensors="pt", max_length=512).to(system)

with torch.no_grad():
outputs = mannequin(**inputs)
predictions = torch.argmax(outputs.logits, dim=1)

# Print predictions
for sentence, prediction in zip(test_sentences, predictions):
sentiment = "optimistic" if prediction.merchandise() == 1 else "unfavourable"
print(f"Enter: "{sentence}" -> Predicted sentiment: {sentiment}")

# Name the perform to coach the mannequin and check it
train_model()

Um sicherzustellen, dass wir unser neues, trainiertes Modell abfragen können, müssen wir einige unserer vorhandenen Dateien aktualisieren. Zum Beispiel in essential.py Wir verwenden jetzt das Modell von ./mannequin und laden Sie es als vorab trainiertes Modell. Zum Vergleich fügen wir außerdem hinzu, dass jetzt zwei Endpunkte verwendet werden müssen: /predict/naive Und predict/skilled.

# Filename: essential.py

from fastapi import FastAPI
from pydantic import BaseModel
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from transformers import pipeline
from prometheus_client import Counter, Histogram, start_http_server
import time

# Begin prometheus metrics server on port 8001
start_http_server(8001)

app = FastAPI()

# Load the skilled mannequin and tokenizer from the native listing
model_path = "./mannequin" # Path to your saved mannequin
tokenizer = AutoTokenizer.from_pretrained(model_path)
trained_model = AutoModelForSequenceClassification.from_pretrained(model_path)

# Create pipelines
naive_classifier = pipeline("sentiment-analysis", system=-1)
trained_classifier = pipeline("sentiment-analysis", mannequin=trained_model, tokenizer=tokenizer, system=-1)

# Metrics
PREDICTION_TIME = Histogram('prediction_duration_seconds', 'Time spent processing prediction')
REQUESTS = Counter('prediction_requests_total', 'Whole requests')
SENTIMENT_SCORE = Histogram('sentiment_score', 'Histogram of sentiment scores', buckets=(0.0, 0.25, 0.5, 0.75, 1.0))

class TextInput(BaseModel):
textual content: str

class SentimentOutput(BaseModel):
textual content: str
sentiment: str
rating: float

@app.publish("/predict/naive", response_model=SentimentOutput)
async def predict_naive_sentiment(input_data: TextInput):
REQUESTS.inc()
start_time = time.time()

end result = naive_classifier(input_data.textual content)(0)

rating = end result("rating")
SENTIMENT_SCORE.observe(rating) # File the sentiment rating

PREDICTION_TIME.observe(time.time() - start_time)

return SentimentOutput(
textual content=input_data.textual content,
sentiment=end result("label"),
rating=rating
)

@app.publish("/predict/skilled", response_model=SentimentOutput)
async def predict_trained_sentiment(input_data: TextInput):
REQUESTS.inc()
start_time = time.time()

end result = trained_classifier(input_data.textual content)(0)

rating = end result("rating")
SENTIMENT_SCORE.observe(rating) # File the sentiment rating

Wir müssen auch unsere Docker-Datei aktualisieren, um unsere Modelldateien einzuschließen.

# Filename: Dockerfile
FROM python:3.9-slim

WORKDIR /app

COPY necessities.txt .
RUN pip set up -r necessities.txt

COPY essential.py .
COPY ./mannequin ./mannequin

CMD ("uvicorn", "essential:app", "--host", "0.0.0.0", "--port", "8000")

Wichtig: Wenn Sie Git verwenden, stellen Sie sicher, dass Sie Folgendes hinzufügen pytorch_model.bin Datei an git lfsdamit Sie auf GitHub pushen können. Mit git lfs können Sie die Versionskontrolle für sehr große Dateien verwenden.

[ad_2]

]]>
https://chatgpz.de/machine-learning/minimal-lebensfaehiges-mle-aufbau-einer-minimalen-produktionsreifen-von-lenix-carter-okt-2024/feed/ 0 4892