İçeriğe geç
academia.sh

Ders 04 / 12

Ö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.

İçindekiler

Üç 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.

# 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.

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.

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.

# 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.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat