İçeriğe geç
academia.sh

Ders 08 / 12

Makine Öğrenmesi için Sürekli Tümleştirme

Koşum ile yayın arasına konan kod, veri ve model kapılarının hangi bozulmayı durdurduğunun sayılması: altı bozulma sınıfından dördünü veri kapısı, üçünü model kapısı durduruyor, kod kapısı hiçbirini durdurmuyor ama öznitelik kodu değişimini üretim verisine hiç bakmadan birinci adımda yakalıyor; birim değişimi yalnız model kapısında, eğitim isabetini 0,7931'den 0,5919'a düşürerek duruyor; geç gelen satır hiçbir kapıda durmuyor çünkü eğitim satırındaki sapması tam 0,0500 ve hoşgörü katı karşılaştırmayla 0,0500; veri kapısının durdurduğu dört koşumun ikisi başvuruyla birebir aynı modeli verecekti ve sekiz koşumun sekizinde sınama isabeti 0,8067 ile taban sınıf oranının üstüne çıkamıyor.

İçindekiler

Bir önceki dersin hattı koştuğu her seferde bir model üretiyordu ve üretilen modelin yayına çıkıp çıkmayacağı hiç sorulmadı. Geç gelen satırların eksikliğiyle 1520 satır üzerinde eğitilen model de, geri doldurulmuş 1600 satırla eğitilen model de aynı 0,8067’yi rapor ediyordu; ikisi de aynı kolaylıkla yayına gidebilirdi.

Bu ders koşum ile yayın arasına kapılar koyar. Bir kapı, bir koşumu yayına çıkmadan durduran denetimdir. Üçü vardır ve üçü ayrı şeye bakar: kod kapısı yazılan koda, veri kapısı hattın girdisine, model kapısı eğitilen modelin sayılarına. Sorunun kendisi tek cümledir: Veri Mühendisliğine Giriş kursunda tanımlanan altı bozulma sınıfı bu üç kapıya nasıl dağılır ve hiçbirinin durdurmadığı sınıf hangisidir.

  • ED11. Kaynak, hat, model ve kapılar kurgudur; gerçek bir öğrenme kitaplığı ya da sürekli tümleştirme ürünü çağrılmaz. Tohum 20260218.
  • ED12. Sürekli tümleştirmenin kendi yordamı, tetikleyiciler, aşama sıralaması ve dağıtım biçimleri DevOps ve Platform Mühendisliği müfredatındaki Sürekli Tümleştirme ve Teslimat kursunda, hat orkestrasyonu ise Veri Hatları ve Orkestrasyon kursunda ölçüldü. Tekrarlanmaz; bu ders yalnız veri ve model kapılarını ekler.
  • ED13. Altı bozulma sınıfının hiçbiri saldırı değildir; hepsi üretici tarafındaki kazalardır ve her biri tek bir dönemi (2026-07) bozar.
  • ED14. Kapılar sırayla koşar: kod 1, veri 2, model 3. Bir olayı ilk durduran kapının numarası, bozulmanın hattı ne kadar kirlettiğini de söyler.
  • ED15. Kod kapısı üretim verisini hiç görmez; sabit, üç satırlık bir girdi üzerinde koşar.
  • ED16. Model kapısının karşılaştırdığı başvuru, sağlam kaynakla koşulan modeldir; hoşgörüler eğitim satırında 0,0500, eğitim isabetinde 0,0100.
  • ED17. Etiket dağılımı dengesizdir; taban sınıf oranı bu derste de yazılır.
  • ED18. Taban çizgisi kapısız kurulumdur: hiçbir kapı yok, her koşum yayına çıkar.
  • ED19. Çözünürlük: sınama kümesi 600 satır, isabetin adımı 0,0017; kaynak 2400 satır, satır oranının adımı 0,0004.
  • ED20. Süre ham milisaniye olarak yazılmaz; ölçü koşum, satır ve adımdır.

Üç Kapı

İlk blok kaynağı, altı bozulma sınıfını, hattın ilk üç aşamasını ve modeli kurar. Hiçbiri bu derse özgü değildir.

# KURGUDUR. Kaynak, hat, model ve kapilar ders icinde tanimlanir; gercek bir
# ogrenme kitapligi ya da surekli tumlestirme urunu 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


# ---- Alti bozulma sinifi. Hicbiri saldiri degil , hepsi uretici tarafi kazasi.
BOZULMA = ["gec_gelen", "yinelenen", "sema_degisti", "birim_degisti", "eksik_bolum", "kayan_tip"]


def boz(kayit, sinif, donem="2026-07"):
    """Tek bir donemi bozar , kalan on bir donem saglam kalir."""
    yeni = []
    for k in kayit:
        if k["donem"] != donem:
            yeni.append(k)
        elif sinif == "gec_gelen":
            if k["abone"] > "A040":                  # 40 abone bu donemde hic gelmedi
                yeni.append(k)
        elif sinif == "yinelenen":
            yeni.append(k)
            if k["abone"] <= "A060":                 # 60 kayit iki kez geldi
                yeni.append(dict(k))
        elif sinif == "sema_degisti":
            y = dict(k)
            y["endeks_degeri"] = y.pop("endeks")     # alan adi degisti
            yeni.append(y)
        elif sinif == "birim_degisti":
            yeni.append(dict(k, endeks=k["endeks"] * 1000, birim="m3"))   # etiket degismedi
        elif sinif == "eksik_bolum":
            if k["bolge"] != "kuzey":                # bir bolgenin bolumu yazilmadi
                yeni.append(k)
        elif sinif == "kayan_tip":
            yeni.append(dict(k, endeks=str(k["endeks"])))                 # sayi metne dondu
    return yeni


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):
    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, SINAMA, ESIK = DONEM[1:9], DONEM[9:], 40


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


def oznitelikler(kayitlar, pencere=None, basamak=4):
    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

    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"]


def egit(kayitlar, oz):
    kume = [k for k in kayitlar if k["donem"] in EGITIM]
    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))
        if d / len(kume) > en_iyi:
            en_iyi, en_iyi_w = d / len(kume), w
    return {"agirlik": en_iyi_w, "egitim_isabeti": round(en_iyi, 4),
            "egitim_satiri": len(kume)}


def sina(kayitlar, model, oz):
    kume = [k for k in kayitlar if k["donem"] in SINAMA]
    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), round(sum(1 for k in kume if etiket(k) == 0) / len(kume), 4)


def kosum(kayit, pencere=None):
    v, _ = a1_ayikla(kayit)
    v, _ = a2_tekille(v)
    v, _ = a3_tuketim(v)
    oz = oznitelikler(v, pencere)
    m = egit(v, oz)
    isabet, taban = sina(v, m, oz)
    return m, isabet, taban, len(v)

İkinci blok üç kapıyı tanımlar ve sekiz olayı sırayla kapılardan geçirir. Yedi olay veri tarafındadır: sağlam koşum ve altı bozulma sınıfı. Sekizincisi kod tarafındadır: öznitelik penceresinin tam geçmiş yerine tek döneme indirilmesi.

# ---- KAPI 1: kod. Sabit mini girdi , uretim verisini hic gormez.
MINI = [{"abone": "A001", "donem": d, "bolge": "dogu", "tip": "isyeri", "m3": v}
        for d, v in (("2026-02", 30), ("2026-03", 50), ("2026-04", 10))]
BEKLENEN = ({"abone_ort": 40.0, "bolge_ort": 40.0, "isyeri": 1}, [0, 1, 0])


def kod_kapisi(pencere=None):
    oz = oznitelikler(MINI, pencere)
    return (oz[("A001", "2026-04")], [etiket(k) for k in MINI]) != BEKLENEN


# ---- KAPI 2: veri. Hattin girdisi uzerinde bes denetim.
def veri_kapisi(kayit, beklenen=2400, pay=0.05):
    ALAN = ("abone", "donem", "bolge", "endeks", "birim")
    ad = [(k["abone"], k["donem"]) for k in kayit]
    bulgu = []
    if any(any(a not in k for a in ALAN) for k in kayit):
        bulgu.append("sema")
    if any(not isinstance(k.get("endeks"), int) for k in kayit):
        bulgu.append("tip")
    if len(ad) != len(set(ad)):
        bulgu.append("tekillik")
    if abs(len(kayit) - beklenen) / beklenen > pay:
        bulgu.append("satir_sayisi")
    if set((k["bolge"], k["donem"]) for k in kayit) != set((b, d) for b in BOLGE for d in DONEM):
        bulgu.append("bolum_donem")
    return bulgu


# ---- KAPI 3: model. Egitilen model yayindaki basvuruyla karsilastirilir.
def model_kapisi(m, isabet, taban, basvuru, pay=0.05, dusus=0.0100):
    bulgu = []
    if abs(m["egitim_satiri"] - basvuru["egitim_satiri"]) / basvuru["egitim_satiri"] > pay:
        bulgu.append("egitim_satiri")
    if basvuru["egitim_isabeti"] - m["egitim_isabeti"] > dusus:
        bulgu.append("egitim_dususu")
    if m["agirlik"] != basvuru["agirlik"]:
        bulgu.append("agirlik_kaymasi")
    return bulgu, isabet <= taban


KAYNAK = kaynak_uret()
BASVURU = kosum(KAYNAK)[0]
OLAY = [("saglam", KAYNAK, None)] + [(s, boz(KAYNAK, s), None) for s in BOZULMA] \
    + [("oznitelik_kodu", KAYNAK, 1)]
SONUC = {}
print(f"{'olay':<15}{'kod (1)':>9}{'veri (2)':>26}{'model (3)':>18}{'ilk kapi':>10}")
for ad, kay, pen in OLAY:
    kk = kod_kapisi(pen)
    vk = veri_kapisi(kay)
    m, isabet, taban, satir = kosum(kay, pen)
    mk, taban_alti = model_kapisi(m, isabet, taban, BASVURU)
    ilk = 1 if kk else (2 if vk else (3 if mk else 0))
    SONUC[ad] = (m, isabet, taban, satir, kk, vk, mk, taban_alti, ilk)
    print(f"{ad:<15}{('durdurdu' if kk else '-'):>9}{(','.join(vk) or '-'):>26}"
          f"{(','.join(mk) or '-'):>18}{(ilk or '-'):>10}")
print("hicbir kapinin durdurmadigi olay:", [a for a in SONUC if SONUC[a][8] == 0])
print("sinama isabeti taban sinif oranini gecemeyen kosum:",
      sum(1 for a in SONUC if SONUC[a][7]), "/", len(OLAY))
olay             kod (1)                  veri (2)         model (3)  ilk kapi
saglam                 -                         -                 -         -
gec_gelen              -                         -                 -         -
yinelenen              -                  tekillik                 -         2
sema_degisti           -                  sema,tip     egitim_satiri         2
birim_degisti          -                         -     egitim_dususu         3
eksik_bolum            -               bolum_donem     egitim_satiri         2
kayan_tip              -                       tip                 -         2
oznitelik_kodu  durdurdu                         -   agirlik_kaymasi         1
hicbir kapinin durdurmadigi olay: ['saglam', 'gec_gelen']
sinama isabeti taban sinif oranini gecemeyen kosum: 8 / 8

Hangi Kapı Hangi Bozulmayı Durduruyor

Dağılım eşit değildir. Veri kapısı altı sınıftan dördünü durdurur: yinelenen kaydı tekillik denetimiyle, alan adı değişimini şema ve tür denetimiyle, eksik bölümü dönem duyarlı bütünlük denetimiyle, metne dönen sayıyı tür denetimiyle. Model kapısı üçünü durdurur ve bunlardan birim değişimi yalnız orada durur: hattın girdisi kusursuz görünürken eğitim isabeti 0,7931’den 0,5919’a iner ve 0,2012‘lik düşüş, 0,0100’lük hoşgörünün yirmi katıdır.

Kod kapısı altı sınıftan hiçbirini durdurmaz ve bu bir kusur değildir. Kod kapısı üretim verisini görmez; gördüğü tek şey üç satırlık sabit bir girdidir. Buna karşılık öznitelik penceresinin tek döneme indirilmesini birinci adımda, kaynağa hiç dokunulmadan yakalar. Aynı değişikliği model kapısı da yakalar, ama üçüncü adımda: seçilen ağırlık (0.6, 0.4, 0) yerine (0.6, 0.0, 0) çıktığı için ağırlık kayması ateşler. İki kapı da doğrudur; aralarındaki fark, birinin bozulmayı hat hiç koşmadan, öbürünün 2200 satır işlendikten sonra durdurmasıdır.

Son satır dersin en rahatsız edici sayısıdır. Sekiz koşumun sekizinde sınama isabeti taban sınıf oranını geçemiyor. Etiket dağılımı dengesizdir, taban sınıf oranı 0,7964, sınama kümesinde 0,8067; model tam olarak o sayıyı üretir. Sınama isabetini yayın ölçütü yapan bir model kapısı bu düzenekte hiçbir modeli geçirmez. Bu bir yanlış alarm değildir: modelin taban sınıf oranının üstüne hiç çıkmadığının, isabet tek başına okunduğunda görünmeyen kanıtıdır.

Hiçbir Kapının Durdurmadığı Sınıf

Tablo iki olayı durdurulmamış bırakıyor. Biri sağlam koşumdur ve durmaması beklenir. Öbürü geç gelen satırdır ve bu bir boşluktur. Üçüncü blok boşluğun nereden geldiğini ölçer.

print(f"{'olay':<15}{'kaynak':>8}{'hat':>7}{'egitim':>8}{'egt.isabet':>12}{'sinama':>8}")
for ad, kay, pen in OLAY:
    m, isabet, taban, satir, *_ = SONUC[ad]
    print(f"{ad:<15}{len(kay):>8}{satir:>7}{m['egitim_satiri']:>8}"
          f"{m['egitim_isabeti']:>12.4f}{isabet:>8.4f}")
print()
G, GI, GT, *_ = SONUC["gec_gelen"]
print(f"gec_gelen egitim satiri sapmasi: {abs(G['egitim_satiri'] - 1600) / 1600:.4f}"
      f"  kapi hosgorusu: 0.0500  karsilastirma: kati")
print("hosgoru 0.0400 olsaydi gec_gelen:", model_kapisi(G, GI, GT, BASVURU, pay=0.04)[0],
      " ayni hosgoruyle saglam kosum:",
      model_kapisi(SONUC["saglam"][0], SONUC["saglam"][1], SONUC["saglam"][2],
                   BASVURU, pay=0.04)[0] or "durmadi")
print("veri kapisinin durdurdugu ama basvuruyla birebir ayni modeli veren kosum:",
      [a for a in SONUC if a != "saglam" and SONUC[a][5] and SONUC[a][0] == BASVURU])
olay             kaynak    hat  egitim  egt.isabet  sinama
saglam             2400   2200    1600      0.7931  0.8067
gec_gelen          2360   2120    1520      0.7914  0.8067
yinelenen          2460   2200    1600      0.7931  0.8067
sema_degisti       2400   1800    1200      0.7892  0.8067
birim_degisti      2400   2200    1600      0.5919  0.8067
eksik_bolum        2354   2108    1508      0.7924  0.8067
kayan_tip          2400   2200    1600      0.7931  0.8067
oznitelik_kodu     2400   2200    1600      0.7925  0.8067

gec_gelen egitim satiri sapmasi: 0.0500  kapi hosgorusu: 0.0500  karsilastirma: kati
hosgoru 0.0400 olsaydi gec_gelen: ['egitim_satiri']  ayni hosgoruyle saglam kosum: durmadi
veri kapisinin durdurdugu ama basvuruyla birebir ayni modeli veren kosum: ['yinelenen', 'kayan_tip']

Geç gelen satır iki kapıdan da kıl payı geçiyor. Veri kapısında kaynak 2400 yerine 2360’tır; sapma 0,0167, hoşgörü 0,0500. Model kapısında eğitim satırı 1600 yerine 1520’dir; sapma tam 0,0500, hoşgörü de 0,0500. Kapıyı geçiren şey bir ölçüm değil, bir karşılaştırma işaretidir: katı eşitsizlik kullanıldığı için sınıra oturan sapma içeride sayılır. Hoşgörü 0,0400’e indirildiğinde geç gelen satır duruyor ve sağlam koşum durmuyor.

Bu, eşik tabanlı her kapının taşıdığı kırılganlıktır ve sayısı yazılmadan görülmez. Ölçülen yanlış alarmın sıfır çıkması ise kapının değil bu kümenin bir özelliğidir: kurgu kaynakta her dönem tam 200 satır taşır, yani satır sayısındaki doğal oynama sıfırdır. Doğal oynaması olan bir kaynakta aynı sıkılaştırma sağlam koşumları da durdururdu.

Kapıların bedeli son satırdadır. Veri kapısı dört koşumu durduruyor, ama bunların ikisi başvuruyla birebir aynı modeli üretecekti: yinelenen kayıt tekilleştirme aşamasında, metne dönen sayı tür dönüşümünde yutuluyor ve ikisinde de ağırlık, eğitim satırı ve eğitim isabeti değişmiyor. Kapısız kurulumda sekiz koşumun sekizi yayına çıkar ve altısı bozuktur; kapılı kurulumda altı koşum durur, ikisi geçer ve geçenlerden biri hâlâ bozuktur. Durdurulan koşumların üçte biri ise hiçbir şey kazandırmadan durdurulmuştur.

Kursun kuralı kapıya da uygulanır: yeniden üretilemeyen sayı bir ölçüm değil bir anıdır. Bir kapının yeşil olması da bir ölçüm değildir; geç gelen satır üç kapıdan da yeşil geçerek 1520 satırla eğitilmiş modeli 1600 satırlık modelin yerine yayına taşır.

Özet

  • Kapılar sırayla koşar: kod 1, veri 2, model 3. Bir olayı ilk durduran kapının numarası, bozulmanın hattı ne kadar kirlettiğini de söyler.
  • Altı bozulma sınıfından dördünü veri kapısı, üçünü model kapısı durdurur; birim değişimi yalnız model kapısında, eğitim isabetini 0,7931’den 0,5919’a düşürerek durur.
  • Kod kapısı altı sınıftan hiçbirini durdurmaz, çünkü üretim verisini görmez; buna karşılık öznitelik kodu değişimini birinci adımda, hat hiç koşmadan yakalar.
  • Geç gelen satırı hiçbir kapı durdurmaz: kaynak sapması 0,0167, eğitim satırı sapması tam 0,0500 ve hoşgörü katı karşılaştırmayla 0,0500. Hoşgörü 0,0400 olsaydı durur ve sağlam koşum durmazdı.
  • Etiket dağılımı dengesizdir, taban sınıf oranı 0,7964; sekiz koşumun sekizinde sınama isabeti 0,8067 ile taban sınıf oranının üstüne çıkamaz.
  • Veri kapısının durdurduğu dört koşumun ikisi başvuruyla birebir aynı modeli verecekti; kapı eklemenin bedeli, kazandırmadığı yerde de durdurmasıdır.

Sonraki Adım

Kapılardan geçen bir koşum artık bir model üretmiş sayılır, ama üretilen şeyin ne olduğu hâlâ yazılmadı. Buraya kadar model üç ağırlıktan ve bir eşikten ibaret bir sözlüktü; onu çağıran taraf alanların adını, türünü ve sırasını tahmin ederek kullanıyordu. Sonraki ders modeli paketler: ağırlığın yanına hangi alanların hangi türle beklendiğini ve karşılığında hangi alanın döneceğini yazan bir arayüz sözleşmesi koyar. Ölçü, sözleşme yazılmadığında kırılan çağırandır: alan adı ya da tür değiştiğinde kaç çağrı düşüyor, kaçı hiç düşmeden yanlış cevap veriyor ve sözleşmeyle bunların kaçı yayın öncesi yakalanıyor.

İ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