---
title: 'Öznitelik Depoları'
source: 'https://academia.sh/tr/kurslar/mlops-temelleri/oznitelik-depolari'
course: 'MLOps Temelleri'
language: tr
updated: '2026-08-17T18:08:52+00:00'
license: 'CC BY-SA 4.0'
---

# Öznitelik Depoları

Eğitim ile çıkarım arasında özniteliğin aynı yolla hesaplanmasının ölçülmesi: aynı ağırlıklarla sınama isabeti eğitim yoluyla 0,8067, sunum yoluyla 0,7800 çıkıyor ve model hiç değişmiyor. Aradaki 0,0267 tümüyle özniteliğin hangi geçmişle hesaplandığından geliyor, 600 sınama satırının 22'sinde tahmini çeviriyor, 19'unu bozup üçünü düzeltiyor ve öznitelik penceresi ikiye çıkarıldığında tümüyle kapanıyor. Öznitelik deposu iki hesabı tek hesaba indirip sapmayı sıfırlıyor; karşılığında 6600 değerlik bir saklama, modele bakan kolda dördüncü bir aşama ve öznitelik kodu değiştiğinde saklananın tamamının yeniden yazılması bedelini getiriyor.

Üç derste sürümlenen şey **koşumun kendisi** oldu: hangi parametreyle çalıştığı, hangi ölçütü
ürettiği, hangi veri sürümünü okuduğu kayda geçti. Bu kayıtların hepsi koşuma **dışarıdan** bakar
ve hiçbiri modele giren sayının kendisine, yani **özniteliğin nasıl hesaplandığına** inmedi.

Bu ders o eksiği kapatır. Modeli eğiten kod bir yerde çalışır, tahmin üreten kod başka bir yerde;
iki yerde de aynı adı taşıyan bir öznitelik hesaplanır ve iki hesabın aynı sayıyı vermesi kimsenin
denetlediği bir şey değildir. Ölçülecek olan o farkın isabete yansımasıdır ve dersin taşıdığı sayı
şudur: **model hiç değişmeden isabet düşer.**

- **YU41.** Kaynak, hattın ilk üç aşaması ve eşik tabanlı model **kurgudur**; gerçek bir öğrenme
  kitaplığı, öznitelik deposu ürünü ya da sunum çatısı çağrılmaz. Tohum **20260218**, çıktı
  koşumdan bağımsızdır.
- **YU42.** **Öznitelik (feature)** modele giren, ham satırdan **hesaplanmış** sayıdır. Burada üç
  tane var: abonenin geçmiş tüketim ortalaması, bölgesinin geçmiş tüketim ortalaması ve işyeri
  göstergesi. Öznitelik üretme yordamı Veri Hazırlama ve Öznitelik Mühendisliği kursunda ölçüldü
  ve **tekrarlanmaz**.
- **YU43.** **Eğitim yolu** özniteliği **tam geçmişten** hesaplar; **sunum yolu** yalnız **son
  dönemi** görür. Aynı ağırlıkla iki yolun verdiği isabet arasındaki fark **eğitim–sunum sapmasıdır
  (training–serving skew)**.
- **YU44.** Etiket dengesizdir: `{0: 1752, 1: 448}`, **taban sınıf oranı 0,7964**, sınama kümesinde
  **0,8067**. İsabet tek başına okunduğunda yanıltır ve bu derste iki kez yanıltacaktır.
- **YU45.** Model üç ağırlıklı bir eşiktir; eğitim, **27 adaylık bir ızgarada** aramaktır. Izgara
  araması Denetimli Öğrenme kursunda ölçüldü ve **tekrarlanmaz**.
- **YU46.** **Çıkarım (inference)** eğitilmiş ağırlıklarla tek satıra tahmin üretmektir; bu kursta
  eğitim de çıkarım da **tek makinede** ele alınır.
- **YU47.** Taban çizgisi **tek yollu kurulumdur**: öznitelik iki ayrı yerde ayrı ayrı hesaplanır
  ve ikisinin aynı olduğunu söyleyen hiçbir kayıt yoktur.
- **YU48.** Çözünürlük: sınama kümesi **600 satır**, isabetin en küçük adımı **1/600 = 0,0017**.
  Bu dersin sapması **0,0267**, yani adımın **on altı katı** ve ölçülmüştür.

## Modele Giren Sayının İki Hesabı

Bir öznitelik ham satırın kendisi değildir; ham satırdan bir **kuralla** üretilir. "Abonenin geçmiş
tüketim ortalaması" böyle bir kuraldır ve tek cümleyle yazıldığında eksiksiz görünür. Eksik olan,
cümlenin sessizce varsaydığı şeydir: **geçmiş nereden başlıyor.**

Eğitimde yanıt kendiliğinden gelir; on iki dönemin hepsi masadadır ve ortalama tam geçmişten
alınır. Sunumda durum başkadır: tahmin dönem gelir gelmez üretilir, elde çoğu zaman **yalnız o
dönem** vardır ve aynı adı taşıyan öznitelik son dönemin değeriyle hesaplanır. İki kural da doğru
yazılmıştır, ikisi de aynı adı taşır, ve ikisi aynı satır için farklı sayı üretir.

Aşağıdaki blok kaynağı, hattın modele bakan ilk üç aşamasını, etiketi ve kurgu modeli kurar.
`oznitelikler` işlevinin `pencere` değiştirgesi iki yolu ayırır: `None` tam geçmiş, `1` yalnız son
dönem demektir. Öznitelikler ağırlıktan bağımsızdır ve **bir kez** hesaplanır.

```python
# KURGUDUR. Olcum aginin sayac okumalari, hattin ilk uc asamasi ve esik tabanli
# model ders icinde tanimlanir; gercek bir ogrenme kitapligi, oznitelik deposu
# urunu ya da sunum catisi cagrilmaz.
TOHUM, M32 = 20260218, 0xFFFFFFFF


def uretec(t):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def sonraki():
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return sonraki


BOLGE = ["dogu", "bati", "kuzey", "guney", "merkez"]
TIP = ["mesken", "isyeri"]
DONEM = [f"2026-{a:02d}" for a in range(1, 13)]
ABONE = {}
for i in range(200):
    r = uretec(TOHUM + 37 * i)
    ABONE[f"A{i + 1:03d}"] = {"bolge": BOLGE[int(r() * 5)], "tip": TIP[int(r() * 2)],
                              "baslangic": 10000 + int(r() * 60000)}


def kaynak_uret():
    kayit = []
    for i, (ab, a) in enumerate(ABONE.items()):
        e = a["baslangic"]
        for d in DONEM:
            r = uretec(TOHUM + 101 * i + 7 * DONEM.index(d))
            e += 4 + int(r() * 46)
            kayit.append({"abone": ab, "donem": d, "bolge": a["bolge"], "tip": a["tip"],
                          "endeks": e, "birim": "m3"})
    return kayit


def a1_ayikla(kayit):
    ZOR = ("abone", "donem", "bolge", "endeks", "birim")
    tut = [k for k in kayit if all(a in k for a in ZOR)]
    return tut, len(kayit) - len(tut)


def a2_tekille(kayit):
    gor, tut = set(), []
    for k in kayit:
        ad = (k["abone"], k["donem"])
        if ad in gor:
            continue
        gor.add(ad)
        tut.append(k)
    return tut, len(kayit) - len(tut)


def a3_tuketim(kayit):
    """Donem farkindan tuketim. Onceki donem yoksa satir hesaplanamaz."""
    tablo = {(k["abone"], k["donem"]): k for k in kayit}
    cikti, dusen = [], 0
    for k in kayit:
        i = DONEM.index(k["donem"])
        if i == 0:
            continue
        onceki = tablo.get((k["abone"], DONEM[i - 1]))
        if onceki is None:
            dusen += 1
            continue
        try:
            f = int(k["endeks"]) - int(onceki["endeks"])
        except (TypeError, ValueError):
            dusen += 1
            continue
        cikti.append(dict(k, m3=f))
    return cikti, dusen


EGITIM = DONEM[1:9]        # 2026-02 .. 2026-09
SINAMA = DONEM[9:]         # 2026-10 .. 2026-12
ESIK = 40                  # bu m3 uzerindeki tuketim "yuksek" sayilir


def satirlar():
    v, _ = a1_ayikla(kaynak_uret())
    v, _ = a2_tekille(v)
    v, _ = a3_tuketim(v)
    return v


def etiket(k):
    return 1 if k["m3"] > ESIK else 0


def oznitelikler(kayitlar, pencere=None, basamak=4):
    """pencere=None -> tam gecmis (egitim yolu); pencere=1 -> son donem (sunum yolu)."""
    sirali = sorted(kayitlar, key=lambda k: DONEM.index(k["donem"]))
    ab, bo = {}, {}
    for k in sirali:
        ab.setdefault(k["abone"], []).append((DONEM.index(k["donem"]), k["m3"]))
        bo.setdefault(k["bolge"], []).append((DONEM.index(k["donem"]), k["m3"]))

    def ortalama(dizi, i):
        secili = [v for d, v in dizi if (d < i and (pencere is None or d >= i - pencere))]
        return round(sum(secili) / len(secili), basamak) if secili else 0.0

    oz = {}
    for k in kayitlar:
        i = DONEM.index(k["donem"])
        oz[(k["abone"], k["donem"])] = {
            "abone_ort": ortalama(ab[k["abone"]], i),
            "bolge_ort": ortalama(bo[k["bolge"]], i),
            "isyeri": 1 if k["tip"] == "isyeri" else 0}
    return oz


IZGARA = [(a, b, c) for a in (0.6, 0.8, 1.0) for b in (0.0, 0.2, 0.4) for c in (0, 4, 8)]


def puan(o, w):
    return w[0] * o["abone_ort"] + w[1] * o["bolge_ort"] + w[2] * o["isyeri"]


def egit(kayitlar, oz, izgara=None, esik=ESIK, donemler=None):
    """Egitim = agirlik uclusunu izgarada aramak. Oznitelik disaridan gelir."""
    izgara = IZGARA if izgara is None else izgara
    donemler = EGITIM if donemler is None else donemler
    kume = [k for k in kayitlar if k["donem"] in donemler]
    en_iyi, en_iyi_w = -1.0, None
    for w in izgara:
        d = sum(1 for k in kume
                if (1 if puan(oz[(k["abone"], k["donem"])], w) > esik else 0) == etiket(k))
        o = d / len(kume)
        if o > en_iyi:
            en_iyi, en_iyi_w = o, w
    return {"agirlik": en_iyi_w, "egitim_isabeti": round(en_iyi, 4),
            "aday": len(izgara), "egitim_satiri": len(kume)}


def sina(kayitlar, model, oz, esik=ESIK, donemler=None):
    donemler = SINAMA if donemler is None else donemler
    kume = [k for k in kayitlar if k["donem"] in donemler]
    d = sum(1 for k in kume
            if (1 if puan(oz[(k["abone"], k["donem"])], model["agirlik"]) > esik else 0)
            == etiket(k))
    return round(d / len(kume), 4)
```

## Aynı Ağırlık, İki İsabet

İkinci blok modeli **bir kez** eğitir ve çıkan ağırlık üçlüsünü iki yolda sınar: aynı üç sayı, aynı
eşik, aynı sınama dönemleri. Değişen tek şey, özniteliğin hangi geçmişle hesaplandığıdır.

```python
import collections

S = satirlar()
SIN = [k for k in S if k["donem"] in SINAMA]
ANAHTAR = [(k["abone"], k["donem"]) for k in SIN]
GERCEK = {(k["abone"], k["donem"]): etiket(k) for k in SIN}
OZ_EGITIM = oznitelikler(S)                    # tam gecmis  -> egitim yolu
OZ_SUNUM = oznitelikler(S, pencere=1)          # son donem   -> sunum yolu
MODEL = egit(S, OZ_EGITIM)

dag = collections.Counter(etiket(k) for k in S)
tab = collections.Counter(GERCEK.values())
print("satir:", len(S), " egitim:", MODEL["egitim_satiri"], " sinama:", len(SIN))
print("etiket dagilimi:", dict(sorted(dag.items())),
      " taban sinif orani:", round(dag[0] / len(S), 4),
      " sinamada:", round(tab[0] / len(SIN), 4))
print("model agirligi:", MODEL["agirlik"], " aday:", MODEL["aday"],
      " egitim isabeti:", MODEL["egitim_isabeti"])


def tahminler(oz):
    return {a: (1 if puan(oz[a], MODEL["agirlik"]) > ESIK else 0) for a in ANAHTAR}


TE, TS = tahminler(OZ_EGITIM), tahminler(OZ_SUNUM)
print(f"\n{'yol':<8}{'oznitelik farkli':>18}{'yuksek dedi':>13}{'dogru pozitif':>15}{'isabet':>9}")
for ad, oz, t in (("egitim", OZ_EGITIM, TE), ("sunum", OZ_SUNUM, TS)):
    print(f"{ad:<8}{sum(1 for a in ANAHTAR if oz[a] != OZ_EGITIM[a]):>18}"
          f"{sum(t.values()):>13}{sum(1 for a in ANAHTAR if t[a] == 1 == GERCEK[a]):>15}"
          f"{sina(S, MODEL, oz):>9.4f}")
bozulan = sum(1 for a in ANAHTAR if TE[a] == GERCEK[a] != TS[a])
duzelen = sum(1 for a in ANAHTAR if TS[a] == GERCEK[a] != TE[a])
print("degisen tahmin:", bozulan + duzelen, " bozulan:", bozulan, " duzelen:", duzelen,
      " net:", bozulan - duzelen)
print("sapma:", round(sina(S, MODEL, OZ_EGITIM) - sina(S, MODEL, OZ_SUNUM), 4),
      "= net / 600 ; isabet adimi 1/600 =", round(1 / len(SIN), 4))
```

```
satir: 2200  egitim: 1600  sinama: 600
etiket dagilimi: {0: 1752, 1: 448}  taban sinif orani: 0.7964  sinamada: 0.8067
model agirligi: (0.6, 0.4, 0)  aday: 27  egitim isabeti: 0.7931

yol       oznitelik farkli  yuksek dedi  dogru pozitif   isabet
egitim                   0            0              0   0.8067
sunum                  600           22              3   0.7800
degisen tahmin: 22  bozulan: 19  duzelen: 3  net: 16
sapma: 0.0267 = net / 600 ; isabet adimi 1/600 = 0.0017
```

Ağırlık üçlüsü `(0.6, 0.4, 0)` iki satırda da aynıdır. İsabet **0,8067'den 0,7800'e** düşüyor ve
düşüşün tamamı öznitelik yolundan geliyor; modelde değişen tek bir sayı yoktur. Ayrıştırma farkın
nasıl oluştuğunu gösterir: **600 satırın 600'ünde öznitelik farklı**, ama tahmin yalnız
**22'sinde** çevriliyor, çünkü fark ancak eşiğin öbür yanına taşıyabildiğinde tahmini değiştirir.
Çevrilen 22 tahminin **19'u bozuluyor**, **üçü düzeliyor**, net **16 satır** kaybediliyor ve
16/600 tam olarak **0,0267** eder — çözünürlüğün on altı katı, yani ölçülmüş bir büyüklük.

İsabetin ikinci kez yanılttığı yer de burasıdır. Sınama kümesinin taban sınıf oranı **0,8067**;
`yuksek dedi` sütunu eğitim yolunda **sıfırdır**. Yani 0,8067 puanlayan model hiçbir satıra
"yüksek" demiyor ve tam olarak **çoğunluk sınıfını söyleyen** bir modelin sayısını üretiyor. İki
sayı da bir modelin işe yaradığını göstermiyor; ölçülen şey **iki yolun ne kadar saptığıdır**.

## Sapma Nereden Geliyor

Fark bir hata değildir; hesabın gördüğü geçmiş kısaldıkça ortalama tek bir dönemin oynaklığına
yaklaşır. Üçüncü blok pencereyi 1'den tam geçmişe süpürür ve aynı ağırlıklarla her adımda sınar.
Yanına, sunum yolunda **eğitilmiş** bir model konur.

```python
TAM = sina(S, MODEL, OZ_EGITIM)
print(f"{'pencere':<9}{'oznitelik farkli':>18}{'isabet':>9}{'sapma':>9}")
for p in (1, 2, 3, 6, None):
    ozp = oznitelikler(S, pencere=p)
    print(f"{('tam' if p is None else p):<9}"
          f"{sum(1 for a in ANAHTAR if ozp[a] != OZ_EGITIM[a]):>18}"
          f"{sina(S, MODEL, ozp):>9.4f}{TAM - sina(S, MODEL, ozp):>9.4f}")

SUNUM_MODELI = egit(S, OZ_SUNUM)
print("\nsunum yolunda egitilen model:", SUNUM_MODELI["agirlik"],
      " egitim:", SUNUM_MODELI["egitim_isabeti"],
      " sunum yoluyla sinama:", f"{sina(S, SUNUM_MODELI, OZ_SUNUM):.4f}")
```

```
pencere    oznitelik farkli   isabet    sapma
1                       600   0.7800   0.0267
2                       600   0.8067   0.0000
3                       600   0.8067   0.0000
6                       600   0.8067   0.0000
tam                       0   0.8067   0.0000

sunum yolunda egitilen model: (0.6, 0.0, 0)  egitim: 0.7925  sunum yoluyla sinama: 0.8067
```

Sapma **pencere 2'de tümüyle kapanıyor** ve daha geniş pencerelerde açılmıyor. Bu, sorunun
"geçmişin kısalığı" değil **sıfıra yakınlığı** olduğunu söyler. İkinci sütun bunu keskinleştirir —
pencere 2, 3 ve 6'da öznitelik **600 satırın 600'ünde hâlâ farklı**, buna karşın isabet birebir
aynı. Sapmanın büyüklüğü öznitelik değerlerinin farkıyla değil, o farkın **kaç satırı eşiğin öbür
yanına taşıdığıyla** ölçülür.

Son satır ikinci bir onarım gösterir. Model kısa pencereli sunum yolunun kendisiyle eğitildiğinde
farklı bir ağırlık buluyor `(0.6, 0.0, 0)` ve aynı kısa yolla sınandığında yine **0,8067**
veriyor. Tutarsızlık kalktığı anda sayı geri geliyor; hangi yolun seçildiği değil, **eğitimle
sunumun aynı yolu kullanması** belirleyicidir.

## Öznitelik Deposu ve Bedeli

**Öznitelik deposu (feature store)** bu tutarsızlığı bir kural yerine bir **yapıyla** kapatır:
öznitelik bir kez hesaplanır, `(abone, dönem)` anahtarıyla yazılır, hem eğitim hem çıkarım aynı
anahtardan **okur**. İki hesabın uyuştuğunu kimse denetlemek zorunda değildir, çünkü ortada iki
hesap yoktur. Dördüncü blok kapanan sapmanın yanına deponun kendi kalemlerini yazar.

```python
# Oznitelik deposu: deger BIR KEZ hesaplanir , (abone , donem) anahtariyla yazilir ,
# hem egitim hem sunum AYNI anahtardan okur.
DEPO = oznitelikler(S)
ALAN = ("abone_ort", "bolge_ort", "isyeri")


def depodan(anahtar):
    return DEPO[anahtar]


son = SINAMA[-1]
print("depo anahtari:", len(DEPO), " alan:", len(ALAN), " saklanan deger:", len(DEPO) * len(ALAN))
print("sunumun bir donemde okudugu:", sum(1 for a in DEPO if a[1] == son), "anahtar ,",
      sum(1 for a in DEPO if a[1] == son) * len(ALAN), "deger")
print("model kolunun asamasi:", 3, "->", 4, " oznitelik kuralinin yasadigi yer:", 2, "->", 1)
print("oznitelik kodu degistiginde yeniden yazilan deger:", len(DEPO) * len(ALAN))

for ad, oz in (("iki yol , iki hesap", OZ_SUNUM), ("tek depo , tek hesap", DEPO)):
    print(f"{ad:<22} sunum isabeti: {sina(S, MODEL, oz):.4f}"
          f"  sapma: {TAM - sina(S, MODEL, oz):.4f}")
print("depodan okunan deger egitimdekiyle ayni mi:",
      all(depodan(a) == OZ_EGITIM[a] for a in ANAHTAR))
```

```
depo anahtari: 2200  alan: 3  saklanan deger: 6600
sunumun bir donemde okudugu: 200 anahtar , 600 deger
model kolunun asamasi: 3 -> 4  oznitelik kuralinin yasadigi yer: 2 -> 1
oznitelik kodu degistiginde yeniden yazilan deger: 6600
iki yol , iki hesap    sunum isabeti: 0.7800  sapma: 0.0267
tek depo , tek hesap   sunum isabeti: 0.8067  sapma: 0.0000
depodan okunan deger egitimdekiyle ayni mi: True
```

Kazanç tek satırdır: sapma **0,0267'den 0,0000'a** iner ve depodan okunan her değer eğitimdekiyle
birebir aynıdır. Bedel dört kalemdir. Hattın modele bakan kolu **üç aşamadan dörde** çıkar, çünkü
öznitelik yazma ayrı bir adımdır. **6600 değer** saklanır, oysa bir dönemlik çıkarım bunların
yalnız **600'ünü** okur; kalanı eğitimin geçmişi görebilmesi içindir. Öznitelik kuralı değiştiğinde
**6600 değerin tamamı** yeniden yazılmalıdır, yoksa depo eski kuralla yeni kuralı yan yana taşır.
Dördüncüsü sayılamaz ama gerçektir: sunum artık deponun yazılmış olmasına bağlıdır.

Bu takas kursun kuralının karşılığıdır. Depo yokken 0,8067 gerçekten üretilmiş bir sayıydı, ama
**hangi yolla** üretildiği yazılı olmadığı için sunumda geri gelmedi. Yeniden üretilemeyen bir
sayı bir ölçüm değil, bir anıdır.

## Özet

- Öznitelik ham satır değil, ham satırdan bir kuralla üretilen sayıdır; kural "geçmiş nereden
  başlıyor" sorusunu sessizce yanıtlar ve eğitimle sunum ona farklı yanıt verebilir.
- Aynı ağırlık üçlüsüyle isabet eğitim yoluyla **0,8067**, sunum yoluyla **0,7800**; model hiç
  değişmemiştir ve **0,0267**'lik eğitim–sunum sapmasının tamamı öznitelik yolundan gelir.
- Sapma 600 satırın 600'ünde özniteliği değiştirse de tahmini yalnız **22** satırda çeviriyor:
  19 bozulma, üç düzelme, net 16 satır, yani 16/600.
- Etiket dengesizdir (`{0: 1752, 1: 448}`, taban oranı 0,7964, sınamada 0,8067) ve 0,8067 puanlayan
  model hiçbir satıra "yüksek" demiyor; isabet tek başına bunu gizler.
- Sapma pencere 2'de tümüyle kapanıyor; öznitelik deposu aynı sonucu tek hesapla verir, bedeli
  6600 saklanan değer, dörde çıkan aşama ve kural değişince tamamının yeniden yazılmasıdır.

## Sonraki Adım

Öznitelik deposu bir kalemi sabitledi: özniteliğin **hangi kodla** hesaplandığı artık tek yerde
duruyor. Ama sabitlenen yalnızca oydu. Aynı iş yeniden koşturulduğunda okunan veri kesitinin,
sınama bölünmesinin, aranan ızgaranın ve sayıların kaç basamağa yuvarlandığının değişmediğini
söyleyen hiçbir kayıt yok, ve bunlardan herhangi birinin oynaması sonucu değiştirebilir. Sonraki
ders bu beş kalemi tek tek sayar: kaçının sabitlenmesinin üç koşumu **birebir** aynı yaptığını ve
katkısı ölçülemeyen bir sabitlemenin ne tür bir güvence verdiğini ölçer.
