Tüm rehberler
Rehber Yapay Zeka Öneri Sistemleri

Öneri Sistemleri —
Two-Tower & Ranking.

Retrieval + Ranking + Reranking mimarisi. Matrix factorization, Two-Tower model, FAISS ile ANN, Wide&Deep, BPR/LambdaRank ve NVIDIA Merlin ile GPU hızlandırmalı üretim pipeline.

00 Öneri Sistemi Mimarisi — Retrieval + Ranking + Reranking

Modern öneri sistemleri üç aşamalı bir huni (funnel) mimarisi kullanır: milyonlarca item'dan başlayarak sırasıyla yüzlerce, onlarca öneriye indirilir.

Brüt katalog milyonlarca item içeriyorken her kullanıcıya gerçek zamanlı olarak ranking modeli çalıştırmak pratik değildir. Bu yüzden aşamalı eleme kullanılır. Retrieval (Geri Çağırma): Approximate Nearest Neighbor (ANN) veya inverted index ile milyonlarca item'dan ~1000 aday seçilir; bu aşama çok hızlı olmalıdır (1-10 ms). Ranking: 1000 aday üzerinde daha zengin özelliklerle ağır model çalıştırılır, top-100 seçilir. Reranking: iş kuralları (çeşitlilik, sponsorlu, yasaklı içerikler) uygulanır ve final liste oluşturulur.

01 Kullanıcı isteği → Retrieval: 10M item → 1000 aday (ANN/Two-Tower)
02 1000 aday → Ranking: 1000 → 100 (Wide&Deep / DLRM / xDeepFM)
03 100 aday → Reranking: 100 → 10-20 (çeşitlilik, iş kuralları)
04 10-20 öneri → kullanıcı arayüzü → feedback → online learning
AşamaInputOutputGecikmeModel
Retrieval10M item~1000 aday<10 msTwo-Tower + FAISS
Ranking1000 aday~100 sıralı<50 msWide&Deep, DLRM
Reranking100 aday~20 final<5 msKural tabanlı + MMR

01 Collaborative Filtering — Matrix Factorization & Implicit Feedback

Collaborative filtering, benzer kullanıcıların benzer item'ları sevdiği varsayımıyla çalışır; matrix factorization latent faktörleri öğrenir.

Explicit feedback (1-5 puan) nadiren mevcuttur; gerçek dünyada genellikle implicit feedback vardır: tıklama, satın alma, izleme süresi. ALS (Alternating Least Squares), implicit feedback için tercih edilen MF algoritmasıdır; kullanıcı ve item matrislerini sırayla optimize eder. BPR (Bayesian Personalized Ranking), gözlemlenen item'ın gözlemlenmeyenden daha yüksek sıralanmasını öğrenir. implicit kütüphanesi CPU/GPU hızlandırmalı ALS sunar.

matrix_factorization.py
import numpy as np
import pandas as pd
import scipy.sparse as sp
import implicit

# ── Veri hazırlama ───────────────────────────────────────────
# Kullanıcı-item etkileşim verisi (implicit)
interactions = pd.read_csv("interactions.csv")
# Columns: user_id, item_id, count (tıklama/satın alma sayısı)

user_enc = {u: i for i, u in enumerate(interactions.user_id.unique())}
item_enc = {v: i for i, v in enumerate(interactions.item_id.unique())}

rows = interactions.user_id.map(user_enc)
cols = interactions.item_id.map(item_enc)
vals = interactions["count"].values.astype(float)

# item × user sparse matrix (implicit kütüphanesi formatı)
ui_matrix = sp.csr_matrix((vals, (cols, rows)),
                             shape=(len(item_enc), len(user_enc)))

# ── ALS modeli ───────────────────────────────────────────────
model = implicit.als.AlternatingLeastSquares(
    factors=128,
    regularization=0.01,
    iterations=50,
    use_gpu=False,
)
model.fit(ui_matrix)

# ── Öneri üretme ─────────────────────────────────────────────
user_id = "u_12345"
user_idx = user_enc[user_id]

item_indices, scores = model.recommend(
    user_idx, ui_matrix.T[user_idx],
    N=10, filter_already_liked_items=True
)
for idx, score in zip(item_indices, scores):
    item_id = [k for k, v in item_enc.items() if v == idx][0]
    print(f"Item: {item_id:20s} Score: {score:.4f}")

# ── Benzer item bulma ─────────────────────────────────────────
item_idx = item_enc["i_678"]
similar_items, sim_scores = model.similar_items(item_idx, N=5)
print("\nBenzer itemlar:")
for idx, s in zip(similar_items, sim_scores):
    print(f"  {idx}: {s:.4f}")

02 Content-Based Filtering — Item Embeddings & Similarity

Content-based filtering, item özelliklerinden embedding üretir ve kullanıcı profilini sevdiği item'ların özelliklerinden oluşturur.

Collaborative filtering, cold-start probleminden muzdariptir: yeni item veya yeni kullanıcı için etkileşim verisi yoktur. Content-based filtering bu sorunu çözer: item özellikleri (metin, kategori, görsel) kullanılarak embedding üretilir ve kullanıcının geçmiş eğilimlerine göre benzer item'lar önerilir. Metin tabanlı item'lar için SentenceTransformer veya TF-IDF, görsel item'lar için CLIP veya ResNet feature extraction kullanılır.

content_based.py
import numpy as np
import pandas as pd
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity

# ── Item embeddingleri ────────────────────────────────────────
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

items_df = pd.DataFrame({
    "item_id": ["i001", "i002", "i003"],
    "description": [
        "Kuzey İtalya turu, 7 gece, 4 yıldızlı otel",
        "Paris gezi paketi, Eyfel Kulesi, müze kartı dahil",
        "İstanbul weekend, Boğaz turu, Topkapı Sarayı",
    ],
})

item_embeddings = model.encode(
    items_df["description"].tolist(),
    normalize_embeddings=True
)
print(f"Item embedding boyutu: {item_embeddings.shape}")

# ── Kullanıcı profili ─────────────────────────────────────────
user_history = ["i001", "i003"]  # kullanıcının sevdiği itemlar
user_item_idx = [items_df[items_df["item_id"] == iid].index[0]
                 for iid in user_history]
user_profile = item_embeddings[user_item_idx].mean(axis=0, keepdims=True)

# ── Benzerlik skorları ────────────────────────────────────────
scores = cosine_similarity(user_profile, item_embeddings)[0]
ranked = np.argsort(scores)[::-1]

for rank, idx in enumerate(ranked, 1):
    item = items_df.iloc[idx]
    print(f"#{rank} {item.item_id}: {item.description[:50]}... ({scores[idx]:.3f})")

# ── Cold-start: yeni kullanıcı tercih anketi ─────────────────
def cold_start_profile(preferences: list, item_embeddings, items_df) -> np.ndarray:
    """Kullanıcı tercihlerinden başlangıç profili oluştur."""
    pref_embs = model.encode(preferences, normalize_embeddings=True)
    return pref_embs.mean(axis=0, keepdims=True)

new_user_prefs = ["tarihî yerler", "müze gezisi", "Avrupa seyahati"]
new_user_profile = cold_start_profile(new_user_prefs, item_embeddings, items_df)
scores_new = cosine_similarity(new_user_profile, item_embeddings)[0]

03 Two-Tower Modeli — User Tower, Item Tower, Dot Product

Two-Tower (Dual Encoder), kullanıcı ve item için ayrı embedding ağları kurar; dot product benzerliğiyle retrieval için optimize edilir.

Two-Tower modeli, retrieval aşamasının baskın mimarisidir. User tower, kullanıcı ID ve özelliklerini embedding vektörüne dönüştürür. Item tower, item ID ve özelliklerini benzer boyutlu embedding'e dönüştürür. Benzerlik skoru iki embedding'in dot product'ı (veya cosine) ile hesaplanır. Eğitim sırasında in-batch negative sampling kullanılır: aynı batch'teki diğer item'lar negatif örnek olur.

two_tower.py
import torch
import torch.nn as nn
import torch.nn.functional as F

class UserTower(nn.Module):
    def __init__(self, n_users, n_cats, embed_dim=128):
        super().__init__()
        self.user_emb  = nn.Embedding(n_users, embed_dim)
        self.cat_emb   = nn.Embedding(n_cats, 32)
        self.fc = nn.Sequential(
            nn.Linear(embed_dim + 32, 256), nn.ReLU(),
            nn.Linear(256, embed_dim),
        )

    def forward(self, user_ids, cat_ids):
        u = self.user_emb(user_ids)
        c = self.cat_emb(cat_ids)
        out = self.fc(torch.cat([u, c], dim=-1))
        return F.normalize(out, dim=-1)

class ItemTower(nn.Module):
    def __init__(self, n_items, n_genres, embed_dim=128):
        super().__init__()
        self.item_emb  = nn.Embedding(n_items, embed_dim)
        self.genre_emb = nn.Embedding(n_genres, 32)
        self.fc = nn.Sequential(
            nn.Linear(embed_dim + 32, 256), nn.ReLU(),
            nn.Linear(256, embed_dim),
        )

    def forward(self, item_ids, genre_ids):
        v = self.item_emb(item_ids)
        g = self.genre_emb(genre_ids)
        out = self.fc(torch.cat([v, g], dim=-1))
        return F.normalize(out, dim=-1)

class TwoTowerModel(nn.Module):
    def __init__(self, n_users, n_items, n_cats, n_genres, embed_dim=128):
        super().__init__()
        self.user_tower = UserTower(n_users, n_cats, embed_dim)
        self.item_tower = ItemTower(n_items, n_genres, embed_dim)
        self.temperature = nn.Parameter(torch.tensor(0.07))

    def forward(self, user_ids, cat_ids, item_ids, genre_ids):
        u = self.user_tower(user_ids, cat_ids)         # (B, D)
        v = self.item_tower(item_ids, genre_ids)        # (B, D)
        # In-batch softmax loss
        logits = (u @ v.T) / self.temperature           # (B, B)
        labels = torch.arange(len(u), device=u.device)
        loss = F.cross_entropy(logits, labels)
        return loss, u, v

# ── Eğitim ───────────────────────────────────────────────────
model = TwoTowerModel(10000, 50000, 50, 20)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

for batch in train_loader:
    loss, _, _ = model(**batch)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

04 ANN Retrieval — FAISS, ScaNN & HNSW ile Milyonlarca Item

Milyonlarca item embedding'ini yönetmek ve milisaniyeler içinde en yakın komşuları bulmak için Approximate Nearest Neighbor (ANN) indexleri gereklidir.

FAISS (Facebook AI Similarity Search), GPU/CPU destekli ANN kütüphanesidir. Index tipleri: IndexFlatL2 (tam arama, küçük setler), IndexIVFFlat (inverted file, büyük setler), IndexHNSWFlat (grafik tabanlı, yüksek recall). HNSW (Hierarchical Navigable Small World), günümüzde en yüksek recall/speed oranı için tercih edilir. ScaNN (Google), quantization ile çok büyük setlerde FAISS'ten hızlı olabilir.

ann_retrieval.py
import faiss, numpy as np, time

# ── Item embedding'lerini oluştur (önceden hesaplanmış) ───────
D = 128           # embedding boyutu
N = 1_000_000   # 1M item

import torch
model = TwoTowerModel(10000, N, 50, 20)
model.eval()
with torch.no_grad():
    all_item_ids  = torch.arange(N)
    all_genre_ids = torch.zeros(N, dtype=torch.long)
    item_embs = model.item_tower(all_item_ids, all_genre_ids)
    item_embs = item_embs.numpy()

# ── FAISS IVF Index ──────────────────────────────────────────
n_centroids = 1024

quantizer = faiss.IndexFlatIP(D)  # inner product (cosine için normalize gerekli)
index_ivf = faiss.IndexIVFFlat(quantizer, D, n_centroids, faiss.METRIC_INNER_PRODUCT)
index_ivf.train(item_embs)
index_ivf.add(item_embs)
index_ivf.nprobe = 50  # daha yüksek recall için

# ── FAISS HNSW Index (üretim tercihi) ────────────────────────
index_hnsw = faiss.IndexHNSWFlat(D, 32, faiss.METRIC_INNER_PRODUCT)
index_hnsw.add(item_embs)

# ── Arama ─────────────────────────────────────────────────────
query_user_emb = np.random.randn(1, D).astype("float32")
query_user_emb /= np.linalg.norm(query_user_emb, axis=1, keepdims=True)

t0 = time.perf_counter()
scores, item_indices = index_hnsw.search(query_user_emb, k=100)
dt = (time.perf_counter() - t0) * 1000
print(f"HNSW search: {dt:.2f} ms | Top item: {item_indices[0][0]}")

# ── Index kaydetme/yükleme ───────────────────────────────────
faiss.write_index(index_hnsw, "item_index.faiss")
loaded_index = faiss.read_index("item_index.faiss")

# ── GPU FAISS ────────────────────────────────────────────────
if faiss.get_num_gpus() > 0:
    res = faiss.StandardGpuResources()
    gpu_index = faiss.index_cpu_to_gpu(res, 0, index_hnsw)
    _, gpu_result = gpu_index.search(query_user_emb, k=100)

05 Feature Crossing & Wide&Deep — Memorization + Generalization

Wide&Deep modeli, geniş (lineer) ve derin (neural) bileşenleri birleştirerek hem pattern ezberlemeyi hem genellemeyi başarır.

Cheng et al. 2016 (Google Play) tarafından önerilen Wide&Deep: Wide kısım (lineer model), yüksek boyutlu sparse özellik etkileşimlerini (kullanıcı × uygulama, vb.) ezberler; Deep kısım (DNN), dense embedding'lerden genel pattern öğrenir. İkisi birlikte joint training yapılır. Bu mimari, büyük e-ticaret platformlarında tıklama tahmininde standart haline gelmiştir.

wide_and_deep.py
import torch, torch.nn as nn, torch.nn.functional as F

class WideAndDeep(nn.Module):
    def __init__(self, n_users, n_items, n_user_cats,
                 n_item_cats, embed_dim=32):
        super().__init__()
        # ── Wide: crossed features ────────────────────────────
        # user_id × item_id hash crossing (sparse)
        self.wide = nn.EmbeddingBag(n_users * n_items, 1, mode="sum")

        # ── Deep: embedding + MLP ─────────────────────────────
        self.user_emb     = nn.Embedding(n_users, embed_dim)
        self.item_emb     = nn.Embedding(n_items, embed_dim)
        self.user_cat_emb = nn.Embedding(n_user_cats, 16)
        self.item_cat_emb = nn.Embedding(n_item_cats, 16)

        deep_input = 2 * embed_dim + 2 * 16
        self.deep = nn.Sequential(
            nn.Linear(deep_input, 256), nn.ReLU(), nn.Dropout(0.3),
            nn.Linear(256, 128), nn.ReLU(), nn.Dropout(0.3),
            nn.Linear(128, 1),
        )

    def forward(self, user_ids, item_ids, user_cat_ids, item_cat_ids):
        # Wide: crossed ID feature
        cross = (user_ids * 50000 + item_ids).unsqueeze(-1)  # hashing trick
        wide_out = self.wide(cross % (self.wide.num_embeddings))

        # Deep: embedding concat
        u = self.user_emb(user_ids)
        v = self.item_emb(item_ids)
        uc = self.user_cat_emb(user_cat_ids)
        ic = self.item_cat_emb(item_cat_ids)
        deep_out = self.deep(torch.cat([u, v, uc, ic], dim=-1))

        return torch.sigmoid(wide_out + deep_out).squeeze(-1)

model = WideAndDeep(100000, 500000, 100, 200)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.BCELoss()

06 Pointwise / Pairwise / Listwise Ranking — BPR & LambdaRank

Ranking öğrenmesinde kayıp fonksiyonu kritiktir: pointwise sınıflandırma gibi, pairwise çift karşılaştırma, listwise liste metriğini doğrudan optimize eder.

Pointwise: her örnek bağımsız, BCEloss veya MSEloss — kolay ama ranking için yetersiz. Pairwise (BPR): Bayesian Personalized Ranking, gözlemlenen item'ın gözlemlenmeyenden daha yüksek skorlanmasını öğrenir: L = -log σ(x_ui - x_uj). Listwise (LambdaRank): NDCG gibi liste metriklerinin gradyanını doğrudan hesaplar; LightGBM'in built-in lambdarank desteği vardır.

bpr_ranking.py
import torch, torch.nn as nn, torch.nn.functional as F

# ── BPR Loss ─────────────────────────────────────────────────
def bpr_loss(pos_scores: torch.Tensor,
             neg_scores: torch.Tensor,
             reg_lambda: float = 1e-4) -> torch.Tensor:
    """
    pos_scores: model skoru (u, i+)
    neg_scores: model skoru (u, i-)
    """
    diff = pos_scores - neg_scores
    loss = -F.logsigmoid(diff).mean()
    return loss

# ── BPR ile MF eğitimi ────────────────────────────────────────
class MF(nn.Module):
    def __init__(self, n_users, n_items, dim=64):
        super().__init__()
        self.user_emb = nn.Embedding(n_users, dim)
        self.item_emb = nn.Embedding(n_items, dim)
        nn.init.normal_(self.user_emb.weight, std=0.01)
        nn.init.normal_(self.item_emb.weight, std=0.01)

    def forward(self, users, items):
        u = self.user_emb(users)
        v = self.item_emb(items)
        return (u * v).sum(dim=-1)

def train_bpr(model, interactions, n_items, epochs=20, batch_size=1024):
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    for epoch in range(epochs):
        total_loss = 0
        for batch_start in range(0, len(interactions), batch_size):
            batch = interactions[batch_start:batch_start + batch_size]
            users    = torch.tensor(batch[:, 0], dtype=torch.long)
            pos_items = torch.tensor(batch[:, 1], dtype=torch.long)
            # Rastgele negatif örnekler
            neg_items = torch.randint(0, n_items, (batch_size,))

            pos_scores = model(users, pos_items)
            neg_scores = model(users, neg_items)
            loss = bpr_loss(pos_scores, neg_scores)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            total_loss += float(loss)
        print(f"Epoch {epoch+1:3d} | Loss: {total_loss:.4f}")

# ── LightGBM LambdaRank ──────────────────────────────────────
import lightgbm as lgb
import numpy as np

# group: her sorgu için kaç item var
train_data = lgb.Dataset(X_train, label=y_relevance,
                          group=group_sizes_train)
valid_data = lgb.Dataset(X_valid, label=y_val_rel,
                          group=group_sizes_valid)

params = {
    "objective": "lambdarank",
    "metric": "ndcg",
    "ndcg_eval_at": [5, 10],
    "num_leaves": 31,
    "learning_rate": 0.05,
    "n_estimators": 500,
}
ranker = lgb.train(params, train_data,
                   valid_sets=[valid_data],
                   callbacks=[lgb.early_stopping(50)])

07 NVIDIA Merlin — GPU Hızlandırmalı Öneri Sistemi

NVIDIA Merlin, end-to-end öneri sistemi için GPU optimize edilmiş veri işleme, model eğitimi ve inference kütüphanesi sunar.

Merlin ekosistemi üç katmandan oluşur: NVTabular — GPU tabanlı ETL ve özellik mühendisliği (categorify, normalize, embedding bag); Merlin Models — DLRM, Two-Tower, Wide&Deep gibi modellerin kuyruktan çıkan implementasyonları; Merlin Systems — Triton Inference Server ile üretim deployment. GPU üzerinde feature preprocessing, CPU'ya göre 10-100x hız artışı sağlar.

merlin_pipeline.py
# pip install merlin-core nvtabular merlin-models
import nvtabular as nvt
from nvtabular.ops import (
    Categorify, Normalize, FillMissing, HashBucket, AddMetadata
)
import merlin.models.tf as mm
from merlin.schema import Tags

# ── NVTabular preprocessing workflow ─────────────────────────
cat_features = ["user_id", "item_id", "category"] >> Categorify()
cont_features = ["price", "rating_count"] >> Normalize()
label = ["click"] >> AddMetadata(tags=[Tags.TARGET])

workflow = nvt.Workflow(cat_features + cont_features + label)
# workflow.fit_transform(dataset).to_parquet("processed/")

# ── Merlin Two-Tower modeli ──────────────────────────────────
from merlin.io import Dataset
from merlin.schema import Schema

schema = Schema().from_parquet("processed/train.parquet")

two_tower = mm.TwoTowerModel(
    schema,
    query_tower=mm.MLPBlock([256, 128]),
    candidate_tower=mm.MLPBlock([256, 128]),
    samplers=[mm.InBatchSampler()],
    embedding_dim_default=64,
)

two_tower.compile(
    optimizer="adam",
    run_eagerly=False,
    metrics=[mm.RecallAt(10), mm.NDCGAt(10)],
)
two_tower.fit(
    Dataset("processed/train.parquet"),
    validation_data=Dataset("processed/valid.parquet"),
    batch_size=4096,
    epochs=10,
)

08 A/B Test & Online Learning — Bandit Algoritmaları

Çevrimiçi ortamda model performansını ölçmek ve sürekli öğrenmek için A/B test istatistiği ve bandit algoritmaları gereklidir.

Öneri sistemi değerlendirmesi çevrimdışı metriklerde (NDCG, Recall@K, MRR) başlar; ancak asıl gerçek ölçüm çevrimiçi A/B test ile yapılır. A/B test: kullanıcılar rastgele bölünür, istatistiksel anlamlılık için yeterli örnek beklenir. Multi-armed bandit, A/B test'i online optimize eder: düşük performanslı varyantlara daha az trafik yönlendirir. Thompson Sampling ve UCB1 en yaygın algoritmalardır.

bandit.py
import numpy as np

class ThompsonSamplingBandit:
    """Beta-Binomial Thompson Sampling."""

    def __init__(self, n_arms: int):
        self.n_arms = n_arms
        self.alpha  = np.ones(n_arms)  # başarılar + 1
        self.beta_  = np.ones(n_arms)  # başarısızlıklar + 1

    def select(self) -> int:
        """Her kol için Beta dağılımından örnek çek."""
        samples = np.random.beta(self.alpha, self.beta_)
        return int(np.argmax(samples))

    def update(self, arm: int, reward: int) -> None:
        """reward: 1 (tıklama) veya 0 (tıklamama)."""
        self.alpha[arm]  += reward
        self.beta_[arm]  += 1 - reward

    def best_arm(self) -> int:
        rates = self.alpha / (self.alpha + self.beta_)
        return int(np.argmax(rates))

# ── Simülasyon ────────────────────────────────────────────────
TRUE_CTR = [0.05, 0.10, 0.07, 0.12]  # gerçek tıklama oranları
bandit = ThompsonSamplingBandit(n_arms=4)

total_reward = 0
for t in range(10_000):
    arm = bandit.select()
    reward = int(np.random.random() < TRUE_CTR[arm])
    bandit.update(arm, reward)
    total_reward += reward

print(f"Best arm: {bandit.best_arm()} (CTR: {TRUE_CTR[bandit.best_arm()]})")
print(f"Toplam tıklama: {total_reward} / 10000")
print(f"Tahmini CTR'ler: {bandit.alpha / (bandit.alpha + bandit.beta_)}")

09 Üretim Pipeline — Feature Store, Embedding Cache, Serving

Düşük gecikme ve yüksek throughput gerektiren öneri sistemi üretim pipeline'ı, feature store, embedding cache ve özel serving katmanı gerektirir.

Feature store, gerçek zamanlı ve batch özelliklerini tutarlı şekilde sunar; Feast, Tecton veya Redis tabanlı custom implementasyonlar yaygındır. Embedding cache, milyonlarca item embedding'ini belleğe alır; sorgu başına yeniden hesaplama yapmak çok yavaştır. Model serving: Triton Inference Server, TorchServe veya özel REST API. Tüm pipeline, eşzamanlı erişim ve güncelleme sırasında tutarlılığı korumalıdır.

production_pipeline.py
import redis, numpy as np, faiss, pickle, time
from typing import List

class RecSysServer:
    """Basit üretim öneri servisi."""

    def __init__(self, index_path: str, redis_url: str):
        # FAISS index yükle
        self.faiss_index = faiss.read_index(index_path)
        self.faiss_index.nprobe = 64
        # Redis bağlantısı (feature store / cache)
        self.redis = redis.from_url(redis_url)

    def get_user_embedding(self, user_id: str) -> np.ndarray:
        """Redis'ten kullanıcı embedding'i çek."""
        key = f"user_emb:{user_id}"
        raw = self.redis.get(key)
        if raw:
            return np.frombuffer(raw, dtype=np.float32).reshape(1, -1)
        # Cache miss: hesapla ve cache'e yaz
        emb = self.compute_user_embedding(user_id)
        self.redis.setex(key, 3600, emb.tobytes())  # 1 saat TTL
        return emb.reshape(1, -1)

    def compute_user_embedding(self, user_id: str) -> np.ndarray:
        # Gerçek implementasyonda model inference
        return np.random.randn(128).astype(np.float32)

    def recommend(self, user_id: str, k: int = 50) -> List[int]:
        t0 = time.perf_counter()
        user_emb = self.get_user_embedding(user_id)
        # Normalize
        user_emb = user_emb / (np.linalg.norm(user_emb) + 1e-8)
        # FAISS retrieval
        scores, item_ids = self.faiss_index.search(user_emb, k)
        dt = (time.perf_counter() - t0) * 1000
        print(f"Öneri süresi: {dt:.2f} ms")
        return item_ids[0].tolist()

server = RecSysServer("item_index.faiss", "redis://localhost:6379")
recs = server.recommend("user_42", k=50)
print(f"Önerilen {len(recs)} item")
ÜRETIM

Öneri sistemi gecikme bütçesi: toplam <100 ms. Dağılım: feature lookup 5-10 ms, retrieval 5-15 ms, ranking 30-50 ms, reranking <5 ms, network overhead <20 ms. Her aşama için SLA belirleyin ve P99 metriklerini izleyin.