Ders 01 / 13
Veri Kayması
Üretimdeki girdi dağılımının değişmesinin ölçülmesi ve izlenebilir bir modelin üç koşulunun kurulması: karar eşiği etiket eşiğinden ayrı bir ayar olarak arandığında, eğitim ölçütü isabetten dengeli isabete çevrildiğinde ve etiket özniteliklerin gördüğü bir kurala bağlandığında duyarlılık 0,0000'dan 1,0000'a çıkıyor, çünkü aynı 135 adaylık arama eski etiketle dengeli isabette 0,5203'te yani yazı tura düzeyinde kalıyor. Düzeltilmiş kurulumda girdi dönem dönem kayıyor, girdi özeti 25,9220'den 27,0472'ye çıkıyor, tahmin payı 0,4550'den 0,5450'ye gidiyor ve 0,05 bandında üç dönem uyarı ateşliyor, ama isabet 0,9700'den 0,9800'e çıkarak hiç düşmüyor; üç ateşlemenin üçü de yanlış alarm ve kaymanın gerçekten götürdüğü, kaymasız koşumla karşılaştırıldığında 2026-12'de 0,0100, yani iki çözünürlük adımı.
İçindekiler
Otuz dokuz derste modelin üretime çıkmasına kadar olan yol ölçüldü: hattın taşıdığı satır sayıldı, satırın nerede duracağı seçildi, iş bölüştürüldü, koşum sabitlendi, öznitelik tek yerden okundu ve model paketlenip sunuldu. Bu yolun sonundaki sayı çıkış anına aittir ve o an geçtikten sonra hiçbir şey anlatmaz.
Çıktıktan sonrası hiç sorulmadı. Dünya modelin altından değişir: girdi dağılımı kayar, girdi ile etiket arasındaki kural değişir, gerçek etiket haftalar sonra gelir ve bir gün model emekliye ayrılır. Bu kurs o zamanı ölçer, ama ilk işi bir düzeltmedir. İzlenemeyen model izlenemez: MLOps Temelleri kursunun bıraktığı model üretimdeki hiçbir bozulmayı gösteremez ve bunun birbirinden bağımsız üç nedeni vardır.
- MI1. Kaynak, hattın modele bakan ilk üç aşaması, eşik tabanlı model ve kayma senaryosu kurgudur; gerçek bir öğrenme kitaplığı, izleme ürünü ya da bulut servisi çağrılmaz. Tohum 20260218.
- MI2. Kayma (drift) üretimdeki verinin eğitimdekinden uzaklaşmasıdır: veri kayması (data drift) girdi dağılımının, kavram kayması (concept drift) girdi ile etiket arasındaki kuralın değişmesidir. Ölçüm yordamı Model Değerlendirme, Yorumlanabilirlik ve Etik kursunda kuruldu ve tekrarlanmaz; bu kurs yalnız üretimdeki ömrü ekler.
- MI3. İzleme dönemleri 2026-07…2026-12, dönem başına 200 satır. Eğitim penceresi 2026-02…2026-09; ilk üç izleme dönemi onunla örtüşür, kaymanın ölçüldüğü üç dönem dışındadır.
- MI4. Bu kursun etiketi
abone_ort + 6 x isyeri > 30kuralıdır ve bu kural KAVRAM’dır. Veri kayması onu değiştirmez. İzleme dönemlerinde taban sınıf oranı 0,5342; küme dengelidir ve isabet burada anlamlı okunabilir. - MI5. Karar eşiği (decision threshold) puanı tahmine çeviren ayardır ve etiket eşiğinden ayrıdır. Dengeli isabet (balanced accuracy), duyarlılık (recall) ile özgüllüğün (specificity) ortalamasıdır.
- MI6. Taban çizgisi izlemesiz kurulumdur: gösterge yok, uyarı yok; bozulma görülmez. Uyarı eşiği (alert threshold) iki yönlü bir bant olarak yazılır.
- MI7. Çözünürlük: dönem isabetinin adımı 1/200 = 0,0050; bu derste isabetin toplam oynaması 0,0200, yani dört adım.
İzlenemeyen Model İzlenemez
İlk blok kaynağı, hattın modele bakan üç aşamasını, öznitelikleri ve kurgu modeli kurar. Üç
düzeltme kodun içinde adlarıyla işaretlidir: karar eşiği ayrı bir ayardır ve KARAR_IZGARA
içinde ağırlıklarla birlikte aranır, eğitim ölçütü isabet değil dengeli isabettir, etiket
özniteliklerin gördüğü bir kurala bağlanmıştır.
# KURGUDUR. Olcum aginin sayac okumalari, hattin modele bakan ilk uc asamasi, # esik tabanli model ve kayma senaryosu ders icinde tanimlanir; gercek bir # ogrenme kitapligi, izleme urunu ya da bulut servisi 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 cikti.append(dict(k, m3=int(k["endeks"]) - int(onceki["endeks"]))) return cikti, dusen def satirlar(): v, _ = a1_ayikla(kaynak_uret()) v, _ = a2_tekille(v) v, _ = a3_tuketim(v) return v def oznitelikler(kayitlar, basamak=4): """Oznitelik agirliktan bagimsizdir; tam gecmisten BIR KEZ hesaplanir.""" 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] return round(sum(secili) / len(secili), basamak) if secili else 0.0 return {(k["abone"], k["donem"]): { "abone_ort": ortalama(ab[k["abone"]], DONEM.index(k["donem"])), "bolge_ort": ortalama(bo[k["bolge"]], DONEM.index(k["donem"])), "isyeri": 1 if k["tip"] == "isyeri" else 0} for k in kayitlar} 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"] # ---- DUZELTME 3: etiket, ozniteliklerin GORDUGU bir kurala baglanir. Kural KAVRAM'dir. KURAL_ESIK = 30 KURAL_ISYERI = 6 def etiket(oz_k, esik=KURAL_ESIK, isyeri_kat=KURAL_ISYERI): return 1 if oz_k["abone_ort"] + isyeri_kat * oz_k["isyeri"] > esik else 0 IZLEME = DONEM[6:] # 2026-07 .. 2026-12 : uretimde izlenen donemler # ---- DUZELTME 1: karar esigi ETIKET esiginden AYRI bir ayardir ve egitimde aranir. KARAR_IZGARA = (18, 22, 26, 30, 34) def tahmin(oz_k, w, karar): return 1 if puan(oz_k, w) > karar else 0 # ---- DUZELTME 2: egitim olcutu ISABET degil DENGELI ISABETTIR. # kural=None -> bu kursun etiketi; disaridan kural gecmek eski kurulumu olcmeye yarar. def dengeli_isabet(kume, oz, w, karar, kural=None): c = {(0, 0): 0, (0, 1): 0, (1, 0): 0, (1, 1): 0} for k in kume: o_k = oz[(k["abone"], k["donem"])] c[(tahmin(o_k, w, karar), kural(k, o_k) if kural else etiket(o_k))] += 1 du = c[(1, 1)] / max(c[(1, 1)] + c[(0, 1)], 1) # duyarlilik oz_ = c[(0, 0)] / max(c[(0, 0)] + c[(1, 0)], 1) # ozgulluk return {"isabet": (c[(1, 1)] + c[(0, 0)]) / len(kume), "duyarlilik": du, "ozgulluk": oz_, "dengeli": (du + oz_) / 2, "agirlik": w, "karar": karar} def egit(kayitlar, oz, donemler=None, kural=None): """Egitim = agirlik ucLUsu ILE karar esigini birlikte aramak: 27 x 5 = 135 aday.""" kume = [k for k in kayitlar if k["donem"] in (donemler or DONEM[1:9])] return max((dengeli_isabet(kume, oz, w, karar, kural) for w in IZGARA for karar in KARAR_IZGARA), key=lambda o: o["dengeli"]) S = satirlar() OZ = oznitelikler(S) IZL = [k for k in S if k["donem"] in IZLEME] dag = {0: 0, 1: 0} for k in IZL: dag[etiket(OZ[(k["abone"], k["donem"])])] += 1 print("satir:", len(S), " izleme donemi:", len(IZLEME), " izleme satiri:", len(IZL), " donem basina:", len(IZL) // len(IZLEME)) print("izleme etiket dagilimi:", dag, " taban sinif orani:", round(dag[0] / len(IZL), 4))
satir: 2200 izleme donemi: 6 izleme satiri: 1200 donem basina: 200
izleme etiket dagilimi: {0: 641, 1: 559} taban sinif orani: 0.5342
Taban sınıf oranı 0,5342‘dir; MLOps Temelleri kursunda 0,7964’tü ve isabet tek başına okunduğunda yanıltıyordu. İkinci blok düzeltmelerin ne kazandırdığını sayar: üstteki tablo iki kurulumu aynı sınama dönemlerinde karşılaştırır, alttaki aynı aramayı iki farklı etiket kuralıyla koşturur.
SIN = [k for k in S if k["donem"] in DONEM[9:]] MODEL = egit(S, OZ) def eski_kural(k, o_k): return 1 if k["m3"] > 40 else 0 BASLIK = (f"{'kurulum':<36}{'agirlik / karar':>22}{'isabet':>9}" f"{'duyarlilik':>12}{'ozgulluk':>10}{'dengeli':>9}") def yaz(ad, o): print(f"{ad:<36}{str(o['agirlik']) + ' / ' + str(o['karar']):>22}{o['isabet']:>9.4f}" f"{o['duyarlilik']:>12.4f}{o['ozgulluk']:>10.4f}{o['dengeli']:>9.4f}") print("sinama donemlerinde (2026-10 .. 2026-12):\n" + BASLIK) yaz("eski : olcut isabet", dengeli_isabet(SIN, OZ, (0.6, 0.4, 0), 40, eski_kural)) yaz("K06 : olcut dengeli isabet", dengeli_isabet(SIN, OZ, MODEL["agirlik"], MODEL["karar"])) print("\nayni 135 adaylik aramanin en iyisi , egitim donemlerinde:\n" + BASLIK) yaz("etiket m3 > 40", egit(S, OZ, kural=eski_kural)) yaz("etiket abone_ort + 6 x isyeri > 30", MODEL)
sinama donemlerinde (2026-10 .. 2026-12): kurulum agirlik / karar isabet duyarlilik ozgulluk dengeli eski : olcut isabet (0.6, 0.4, 0) / 40 0.8067 0.0000 1.0000 0.5000 K06 : olcut dengeli isabet (0.6, 0.0, 4) / 18 0.9833 1.0000 0.9670 0.9835 ayni 135 adaylik aramanin en iyisi , egitim donemlerinde: kurulum agirlik / karar isabet duyarlilik ozgulluk dengeli etiket m3 > 40 (0.6, 0.2, 4) / 22 0.5075 0.5422 0.4984 0.5203 etiket abone_ort + 6 x isyeri > 30 (0.6, 0.0, 4) / 18 0.9838 1.0000 0.9728 0.9864
Üstteki 0,8067 hatalı değildir; ölçtüğü şey başkadır. Karar eşiği etiket eşiğiyle aynı tutulup ölçüt isabet olduğunda arama, dengesiz kümede hiçbir satıra yüksek demeyen modelde durur: duyarlılık 0,0000, dengeli isabet 0,5000, yani yazı tura düzeyi. Böyle bir model üretimde bozulsa da göstergesi kıpırdamaz, çünkü çıktısı zaten sabittir. Düzeltilmiş kurulumda duyarlılık 1,0000, özgüllük 0,9670, dengeli isabet 0,9835.
Alttaki tablo üçüncü düzeltmenin neden gerektiğini söyler. İlk iki düzeltme yapılsa bile eski etiket kuralıyla 135 adayın en iyisi 0,5203’te kalıyor. Neden yapısaldır: o kural satırın kendi dönemindeki tüketimine bakar, öznitelikler ise yalnız geçmiş dönemleri görür. İlişki olmadığı için öğrenilecek bir şey de yoktur. Etiket özniteliklerin gördüğü bir kurala bağlandığında aynı arama 0,9864 verir.
Girdi Kayarken Ne Oluyor
Veri kaymasında girdi dağılımı değişir, kural aynı kalır. Kurgu senaryo şudur: 2026-09’dan
sonra işyeri abonelerinin tüketimi her dönem 0,06 kat büyüyor. Üçüncü blok kaymayı uygular ve üç
göstergeyi dönem dönem okur: girdi özeti (özniteliğin ortalaması), tahmin payı (modelin
yüksek dediği satırların oranı) ve isabet. Son sütun aynı modelin kaymasız veride verdiği
isabettir, yani karşı olgusal koşum.
# ---- KURGU veri kaymasi: 2026-09'dan sonra isyeri tuketimi her donem 0,06 buyuyor. # KURAL AYNI KALIR; degisen yalniz girdi dagilimidir. def kaydir(kayitlar, baslangic="2026-09", egim=0.06): b = DONEM.index(baslangic) return [dict(k, m3=round(k["m3"] * (1.0 + egim * (DONEM.index(k["donem"]) - b + 1)), 4)) if DONEM.index(k["donem"]) >= b and k["tip"] == "isyeri" else dict(k) for k in kayitlar] def girdi_ozeti(kayitlar, donem, oz): d = [oz[(k["abone"], k["donem"])]["abone_ort"] for k in kayitlar if k["donem"] == donem] return round(sum(d) / len(d), 4) def tahmin_payi(kayitlar, model, oz, donem): d = [tahmin(oz[(k["abone"], k["donem"])], model["agirlik"], model["karar"]) for k in kayitlar if k["donem"] == donem] return round(sum(d) / len(d), 4) def isabet(kayitlar, model, oz, donem, etiketler=None): """etiketler=None -> kural degismemis demektir.""" kume = [k for k in kayitlar if k["donem"] == donem] d = 0 for k in kume: o_k = oz[(k["abone"], k["donem"])] g = etiketler[(k["abone"], k["donem"])] if etiketler else etiket(o_k) d += tahmin(o_k, model["agirlik"], model["karar"]) == g return round(d / len(kume), 4) KAYAN = kaydir(S) OZK = oznitelikler(KAYAN) OLCU = {"girdi ozeti": [(d, girdi_ozeti(KAYAN, d, OZK)) for d in IZLEME], "tahmin payi": [(d, tahmin_payi(KAYAN, MODEL, OZK, d)) for d in IZLEME], "isabet": [(d, isabet(KAYAN, MODEL, OZK, d)) for d in IZLEME]} print(f"{'donem':<10}{'girdi ozeti':>13}{'tahmin payi':>13}{'isabet':>9}{'kaymasiz isabet':>17}") for i, d in enumerate(IZLEME): print(f"{d:<10}{OLCU['girdi ozeti'][i][1]:>13.4f}{OLCU['tahmin payi'][i][1]:>13.4f}" f"{OLCU['isabet'][i][1]:>9.4f}{isabet(S, MODEL, OZ, d):>17.4f}")
donem girdi ozeti tahmin payi isabet kaymasiz isabet 2026-07 25.9220 0.4550 0.9700 0.9700 2026-08 26.0517 0.4700 0.9700 0.9700 2026-09 25.9800 0.4550 0.9900 0.9900 2026-10 26.4685 0.5150 0.9750 0.9750 2026-11 26.6053 0.5200 0.9800 0.9850 2026-12 27.0472 0.5450 0.9800 0.9900
Girdi gerçekten kayıyor: girdi özeti 25,9220’den 27,0472’ye, tahmin payı 0,4550’den
0,5450’ye çıkıyor, yani yüksek diyen çıkarımın oranı yaklaşık yüzde on altı büyüyor. Buna karşılık
isabet 0,9700’den 0,9800’e gidiyor, düşmüyor. Kaymanın gerçekten götürdüğü karşı olgusal
sütunla ölçülür: 2026-12’de kaymasız koşum 0,9900, kaymalı koşum 0,9800 verir; fark 0,0100,
iki çözünürlük adımı. Kural değişmediği sürece doğru bir model kaymış girdide de doğru kalır, çünkü
etiket de tahmin de aynı kaymış özniteliklerden hesaplanır.
Ateşleyen Uyarı Bir Kanıt Değildir
Dördüncü blok göstergelere uyarı bandı takar. Bant tabandan sapmanın oranıdır; taban 2026-08,
kaymanın başladığı dönemden önceki son dönem. Aynı üç bant üç göstergeye de aynı biçimde uygulanır.
# ---- Uyari: bir olcu tabandan `pay` kadar saparsa ates eder. Bant iki yonludur. def uyari(dizi, taban, pay): return [d for d, v in dizi if abs(v - taban) / max(abs(taban), 1e-9) > pay] TABAN_DONEM = IZLEME[1] # 2026-08 : kaymanin basladigi donemden onceki son donem ISABET = dict(OLCU["isabet"]) print(f"{'olcu':<14}{'taban':>10}{'en buyuk sapma':>16}{'0,05':>7}{'0,10':>7}" f"{'0,20':>7}{'ilk atesleyen':>16}") for ad, dizi in OLCU.items(): taban = dict(dizi)[TABAN_DONEM] ates = [uyari(dizi, taban, p) for p in (0.05, 0.10, 0.20)] sapma = max(abs(v - taban) / taban for _, v in dizi) print(f"{ad:<14}{taban:>10.4f}{sapma:>16.4f}{len(ates[0]):>7}{len(ates[1]):>7}" f"{len(ates[2]):>7}{(ates[0][0] if ates[0] else '-'):>16}") ATES = uyari(OLCU["tahmin payi"], dict(OLCU["tahmin payi"])[TABAN_DONEM], 0.05) GERCEK = [d for d in ATES if ISABET[d] < ISABET[TABAN_DONEM]] print("\n0,05 bandinda atesleyen donem:", ATES) print("bunlarin kacinda isabet tabanin altina indi:", len(GERCEK), " yanlis alarm:", len(ATES) - len(GERCEK)) print("isabetin toplam oynamasi:", f"{max(ISABET.values()) - min(ISABET.values()):.4f}", " donem isabetinin adimi:", f"{1 / 200:.4f}", " kaymanin 2026-12'de goturdugu:", f"{isabet(S, MODEL, OZ, IZLEME[-1]) - ISABET[IZLEME[-1]]:.4f}")
olcu taban en buyuk sapma 0,05 0,10 0,20 ilk atesleyen girdi ozeti 26.0517 0.0382 0 0 0 - tahmin payi 0.4700 0.1596 3 2 0 2026-10 isabet 0.9700 0.0206 0 0 0 - 0,05 bandinda atesleyen donem: ['2026-10', '2026-11', '2026-12'] bunlarin kacinda isabet tabanin altina indi: 0 yanlis alarm: 3 isabetin toplam oynamasi: 0.0200 donem isabetinin adimi: 0.0050 kaymanin 2026-12'de goturdugu: 0.0100
Aynı kayma, aynı bant, üç ayrı yanıt. Girdi özetinin en büyük sapması 0,0382’dir ve hiçbir bantta ateşlemez, çünkü bir ortalamadır ve ortalama tek tek satırlardaki değişimi söndürür. Tahmin payının sapması 0,1596’dır; 0,05 bandında üç dönem, 0,10 bandında iki dönem ateşler, çünkü karar eşiğinin çevresindeki satırlara duyarlıdır. İsabetin sapması 0,0206’dır, ateşlemez ve üstelik yukarı yöndedir.
Kursun kuralı buradan okunur. 2026-10’daki uyarı yanlış çalışmamıştır: girdi gerçekten kaymıştır.
Ama söylediği tek şey girdinin kaydığıdır; bozulduğunu söylemez. Ateşleyen üç dönemin
hiçbirinde isabet tabanın altına inmemiştir, üçü de yanlış alarm (false positive) sayılır. Bir
göstergenin ateşlemesi bozulmanın kanıtı değildir; ateşlemesi ile bozulması ayrı ayrı
gösterilmeyen gösterge ölçülmemiş sayılır. İzlemesiz taban çizgisi burada tuhaf bir konuma
düşer: gösterge takmayan kurulum hiç yanılmamıştır, çünkü yanılacak bir uyarısı yoktur.
Özet
- İzlenebilir bir model üç ayara bağlıdır: karar eşiği etiket eşiğinden ayrı bir ayardır ve eğitimde aranır, eğitim ölçütü dengeli isabettir, etiket özniteliklerin gördüğü bir kurala bağlanır.
- Eski kurulumda duyarlılık 0,0000, dengeli isabet 0,5000; düzeltilmişte duyarlılık 1,0000, özgüllük 0,9670, dengeli isabet 0,9835 ve taban sınıf oranı 0,5342.
- İlk iki düzeltme tek başına yetmez: eski etiket kuralıyla 135 adayın en iyisi 0,5203’te, yazı tura düzeyinde kalır, çünkü o kural özniteliklerin görmediği bir sayıya bakar.
- Veri kaymasında girdi özeti 25,9220’den 27,0472’ye, tahmin payı 0,4550’den 0,5450’ye
çıkar, ama isabet 0,9700’den 0,9800’e gider; kaymanın götürdüğü
2026-12’de 0,0100, iki çözünürlük adımı. - Tahmin payı 0,05 bandında üç dönem ateşler ve üçünde de isabet tabanın altına inmemiştir: ateşleyen uyarı bir sorun kanıtı değildir.
Sonraki Adım
Bu derste kayan şey girdiydi ve kural yerinde durduğu için model doğru kalabildi; ateşleyen gösterge de tam olarak bunu gördü. Peki değişen girdi değil de kuralın kendisi olsaydı, yani aynı öznitelikler artık başka bir etiket üretmeye başlasaydı, aynı üç gösterge ne derdi. Girdi özeti kıpırdamaz, çünkü girdi kıpırdamamıştır; tahmin payı da kıpırdamaz, çünkü model aynı girdilere aynı yanıtı vermeyi sürdürür. Sonraki ders bu durumu ölçer ve tam tersi bir sonuç bulur: göstergeler susarken isabet 0,9700’den 0,8150’ye düşer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.