---
title: 'Veri Hazırlama ve Öznitelik Mühendisliği'
source: 'https://academia.sh/tr/kurslar/oznitelik-muhendisligi'
language: tr
updated: '2026-08-17T04:42:04+00:00'
license: 'CC BY-SA 4.0'
lessons: 12
outcomes:
    - 'Modelleme için veriyi doğru sırayla hazırlar ve her adımın katkısını ayrılmış kümede ölçer'
    - 'Öznitelik ölçekleme ve kodlama kararlarını model ailesine göre verir ve gerekçesini sayıya bağlar'
    - 'Boru hattı kurarak hazırlama adımlarını sızıntısız uygular ve kat içinde hazırlama disiplinini sayar'
---

# Veri Hazırlama ve Öznitelik Mühendisliği

Model sabit tutulup yalnız öznitelikler değiştirildiğinde her hazırlama kararının ayrılmış kümede ne kattığının ölçülmesi: temizleme kararının iki model ailesinde ters işaret vermesi, ölçeklemenin uzaklık ve eğim temelli yordamlarda katkı üretip ağaçta üretmemesi, kardinalite arttıkça bir-sıcak kodlamanın sütun sayısı ile hedef temelli kodlamanın sızıntısı, eksikliğin kendisinin bilgi taşıması, dengesiz sınıfta doğruluğun yanıltıcı taban olması, aynı aykırı değerin iki modelde zıt etkisi, üretilen özniteliğin katkısı, bir alandan çıkan sayı adedi, seçimin kendisinin ürettiği şişme, bileşen sayısı ile yorumlanabilirlik değişimi, boru hattı olmadan sızan adım sayısı ve kat içinde hazırlamanın verdiği iki sayı.

## Hazırlama Adımları

Var olan sütunlar üzerinde alınan her kararın, model sabitken ayrılmış kümedeki sayıyı ne kadar oynattığının sayılması: temizleme kararının modele kattığı ve iki model ailesinde ters işaret alması, standartlaştırma ile normalleştirmenin hangi yordamda katkı ürettiği ve ölçeğin yalnız eğitimden öğrenilmesi, bir-sıcak, sıralı ve hedef temelli kodlamanın sütun sayısı ile sızıntısı, doldurma, eksiklik göstergesi ve satırı atmanın üç ayrı sayısı, çoğaltma, seyreltme ve sınıf ağırlığının dengesiz kümede verdiği sonuç, aynı aykırı değerin eğim temelli ve ağaç temelli yordamda zıt etkisi.

### Veri Temizleme

Source: https://academia.sh/tr/kurslar/oznitelik-muhendisligi/veri-temizleme

Bir temizleme kararının modele ne kattığının ayrılmış kümede ölçülmesi: ham kayıttaki üç kusur tek tek düzeltilir, model sabit tutulur ve sınama kümesindeki iki sayı yan yana basılır. Birim karışıklığının düzeltilmesi uzaklık temelli yordama on iki abone kazandırırken karar ağacına iki abone kaybettirir; üç kusur birden düzeltildiğinde ağacın sınama doğruluğu 0,7659'dan 0,7778'e, uzaklık temelli yordamınki 0,7540'tan 0,7857'ye çıkar ve taban çizgisi her ikisinde de 0,7579'dur.

Önceki kurs on bir derste ayrımı kurdu: bir modelin gördüğü veride ölçülen sayı ile görmediği
veride ölçülen sayı iki ayrı sayıdır ve her sayının yanına kendi kümesinde hesaplanmış bir taban
çizgisi yazılır. O kursun kapanışı bir eksikle bitti. Modele verilen **öznitelikler** hep hazır
kabul edildi: ortalama tüketim bir sayıydı, bölge beş ikili sütundu, oynaklık hesaplanmıştı.
Hangi sütunun nasıl bir sayıya çevrildiği ve o çevirinin tabanla arasındaki farkı ne kadar
oynattığı hiç sorulmadı.

Bu ders o soruyu sorar ve kursun ölçü birimini kurar. Yöntem her derste aynıdır: **model sabit
tutulur, yalnız öznitelikler değişir.** Tek bir hazırlama kararı açılır ve kapatılır, ayrılmış
kümedeki iki sayı yan yana basılır, aradaki fark **katkı** olarak adlandırılır. Kursun kuralı
buradan çıkar: **bir hazırlama adımı, ayrılmış kümede ölçülen katkısı kadar vardır. Katkısı
ölçülmemiş adım, adım değil alışkanlıktır.**

- **HA1.** Küme M27/K01'den devralınır ve **kurgudur**: bölgesel ölçüm ağından türetilen 1.260
  abone, kaçak ya da arıza şüphesi etiketi, tohum 20260218, bölme 756/252/252.
- **HA2.** Bu kursta **ayrılmış küme sınama kümesidir** ve taban çizgisi orada 0,7579'dur —
  eğitim kümesinin en sık sınıfını her aboneye söylemek. K01'in kuralı gereği her bakış bir seçim
  payı biriktirir; bu payın hesabı kursun sonuna bırakılır.
- **HA3.** K01'in kullandığı tablo **temizlenmiş** tablodur. Bu ders bir adım geriye gider: aynı
  kurgunun ham kayıt hali, temizleme kararları alınmadan önceki biçimi.
- **HA4.** Ham kayıtta üç kusur vardır ve üçü de **kurgudur**. Eski sayaçların bir bölümü tüketimi
  litre cinsinden bildirir; bölge adı on bir ayrı yazımla kaydedilir; okunamayan sayaç ikinci kez
  okunduğu için aynı abone birden çok satırda görünür.
- **HA5.** Kusurlar yalnız **öznitelik sütunlarını** bozar. Etiket denetim sonucudur ve hiçbir
  kusurdan etkilenmez; bu yüzden taban çizgisi 0,7579 sabit kalır.
- **HA6.** Bölme **abone üzerinden** yapılır. Yinelenen satırlar kümeler arasına geçmez, yalnız
  eğitim kümesindeki ağırlığı değiştirir.
- **HA7.** İki model ailesi karşılaştırılır: K01'in derinlik 6 karar ağacı ve en yakın on beş
  komşuya bakan uzaklık temelli bir yordam. İkisi de ders içinde yazılır ve ikisi de birer
  modeldir; kurs boyunca ayarları değişmez.
- **HA8.** Ölçü doğruluktur. Sınama kümesi 252 abonedir, bu yüzden **bir abone 0,0040 eder** ve
  bundan küçük farklar ölçülmemiş sayılır.

Temizleme yordamının kendisi bu derste kurulmuyor. Eksik değer, tip dönüşümü, yinelenen kayıt ve
kategori birleştirme M26'nın Veri Toplama ve Hazırlama kursunda ölçüldü; oradaki ölçü **satır
sayısıydı** — kaç satır düzeldi, kaç satır atıldı. Buradaki soru başkadır: aynı karar **ayrılmış
kümedeki sayıyı** ne kadar oynatıyor ve hangi model ailesi ona duyarlı.

#### Ham Kayıt ve Üç Kusur

Kurulum kümeyi K01'deki tohumla yeniden üretir, abone üzerinden böler ve eğitim ile sınama
kümelerinin ham kayıt halini kurar. Etikete dokunulmaz.

```python
# hazirlama.py — MODELDIR. M27/K01'in kurgu abone tablosu ayni tohumla yeniden
# uretilir; uzerine kaydin HAM halindeki uc kusur konur.
import math

TOHUM, ADAY, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


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


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE = []
for i in range(ADAY):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
OKUMA = {}
for k in ABONE:
    r = uretec(TOHUM + 7000 + k["no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        OKUMA.setdefault(k["no"], []).append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()

VERI = []
for k in ABONE:
    v = OKUMA.get(k["no"])
    if v is None:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    VERI.append({"no": k["no"], "hane": k["hane"], "memnuniyet": k["memnuniyet"],
                 "donem": len(v), "ort_tuketim": round(ort, 2),
                 "oynaklik": round(oyn, 2), "sifir_okuma": int(min(v) == 0.0),
                 "bolge": k["bolge"][0], "supheli": int(z > 1.35)})


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT0, SIN0 = K[:756], K[1008:]
TABAN = sum(x["supheli"] == 0 for x in SIN0) / len(SIN0)

YAZIM = {"kuzey": ["kuzey", "Kuzey", "KUZEY"], "guney": ["guney", "Guney"],
         "dogu": ["dogu", "Dogu"], "bati": ["bati", "BATI"],
         "merkez": ["merkez", "Merkez "]}


def hamla(satirlar, tohum):                 # litre kaydi + yazim tutarsizligi
    r, out = uretec(tohum), []
    for x in satirlar:
        y = dict(x)
        if r() < 0.35:
            y["ort_tuketim"] = round(y["ort_tuketim"] * 1000, 1)
            y["oynaklik"] = round(y["oynaklik"] * 1000, 1)
        v = YAZIM[y["bolge"]]
        y["bolge"] = v[0] if r() < 0.70 else v[int(r() * (len(v) - 1)) + 1]
        out.append(y)
    return out


def yinele(satirlar, tohum):                # okunamayan sayac ikinci kez okunur
    r, out = uretec(tohum), []
    for x in satirlar:
        out.append(x)
        if x["sifir_okuma"] == 1 and r() < 0.85:
            out += [dict(x), dict(x)]
    return out


HAM_EGT = yinele(hamla(EGT0, TOHUM + 61000), TOHUM + 62000)
HAM_SIN = hamla(SIN0, TOHUM + 63000)
print(f"egitim {len(EGT0)} abone, ham kayitta {len(HAM_EGT)} satir; "
      f"sinama {len(SIN0)} abone, {len(HAM_SIN)} satir")
print(f"litre cinsinden kaydedilmis satir: egitim "
      f"{sum(1 for x in HAM_EGT if x['ort_tuketim'] > 1000)}, sinama "
      f"{sum(1 for x in HAM_SIN if x['ort_tuketim'] > 1000)}")
print(f"bes bolge icin {len({x['bolge'] for x in HAM_EGT})} ayri yazim, "
      f"taban cizgisi (en sik sinif) sinamada {TABAN:.4f}")
```

```
egitim 756 abone, ham kayitta 864 satir; sinama 252 abone, 252 satir
litre cinsinden kaydedilmis satir: egitim 295, sinama 86
bes bolge icin 11 ayri yazim, taban cizgisi (en sik sinif) sinamada 0.7579
```

Üç kusurun büyüklüğü ölçülebilir: 756 abone 864 satıra çıkmış, 295 satırda tüketim bin katına
kaymış, beş bölge on bir ayrı yazımla kaydedilmiş. Bunlar M26'da satır hesabıyla anlatılan
kusurların aynısıdır. Yeni olan, her birinin bir **karar** olması ve kararın bedelinin modelde
okunacak olmasıdır.

#### Aynı Karar, İki Model Ailesi

Aşağıdaki kod iki modeli yazar ve üç temizleme kararını tek tek uygular. Karar ağacı her düğümde
bir öznitelik ve bir eşik seçer, kümeyi ikiye ayırır ve yaprakta çoğunluk sınıfını söyler.
Uzaklık temelli yordam bir aboneye en yakın on beş komşuyu bulur ve onların çoğunluğunu söyler;
yakınlık, bütün sütunlardaki farkların karelerinin toplamıdır.

```python
def gini(s):
    p = sum(x["supheli"] for x in s) / len(s) if s else 0.0
    return 2 * p * (1 - p)


def agac(s, derinlik, alan, enaz=2):        # MODELDIR: karar agaci, derinlik 6
    p = sum(x["supheli"] for x in s) / len(s)
    en = None
    if derinlik and len(s) >= 2 * enaz and 0.0 < p < 1.0:
        for a in alan:
            d = sorted({x[a] for x in s})
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < enaz:
                    continue
                k = gini(s) - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(s)
                if en is None or k > en[0]:
                    en = (k, a, v, sol, sag)
    if en is None or en[0] <= 1e-9:
        return {"tahmin": int(p > 0.5)}
    return {"alan": en[1], "esik": en[2], "sol": agac(en[3], derinlik - 1, alan, enaz),
            "sag": agac(en[4], derinlik - 1, alan, enaz)}


def agac_tahmin(d, x):
    while "tahmin" not in d:
        d = d["sol"] if x[d["alan"]] < d["esik"] else d["sag"]
    return d["tahmin"]


def komsu_tahmin(egt, x, alan, k=15):       # MODELDIR: uzaklik temelli yordam
    u = sorted((sum((x[a] - y[a]) ** 2 for a in alan), y["supheli"]) for y in egt)[:k]
    return int(sum(t[1] for t in u) * 2 > k)


def sutunla(egt, sin):                      # bolge yazimlari ikili sutuna acilir
    kat = sorted({x["bolge"] for x in egt})
    alan = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem", "sifir_okuma"]
    ac = (lambda s: [dict(x, **{"b_" + k: int(x["bolge"] == k) for k in kat})
                     for x in s])
    return alan + ["b_" + k for k in kat], ac(egt), ac(sin)


def olc(egt, sin):
    alan, e, s = sutunla(egt, sin)
    m = agac(e, 6, alan)
    return (sum(agac_tahmin(m, x) == x["supheli"] for x in s) / len(s),
            sum(komsu_tahmin(e, x, alan) == x["supheli"] for x in s) / len(s))


def birim(s):                               # litre kaydi metrekupe cevrilir
    return [dict(x, ort_tuketim=round(x["ort_tuketim"] / 1000, 2),
                 oynaklik=round(x["oynaklik"] / 1000, 2))
            if x["ort_tuketim"] > 1000 else dict(x) for x in s]


def yazim(s):
    return [dict(x, bolge=x["bolge"].strip().lower()) for x in s]


def yinelenen(s):                           # ilk okuma tutulur, kopyalar atilir
    g, out = set(), []
    for x in s:
        if x["no"] not in g:
            g.add(x["no"])
            out.append(x)
    return out


KARAR = [("hicbiri (ham kayit)", lambda e, s: (e, s)),
         ("birim duzeltildi", lambda e, s: (birim(e), birim(s))),
         ("yazim birlestirildi", lambda e, s: (yazim(e), yazim(s))),
         ("yinelenen atildi", lambda e, s: (yinelenen(e), s)),
         ("ucu birden", lambda e, s: (yinelenen(yazim(birim(e))), yazim(birim(s))))]

SONUC = {}
print(f"{'uygulanan karar':<22} {'agac':>8} {'uzaklik':>9}")
print(f"{'taban cizgisi':<22} {TABAN:>8.4f} {TABAN:>9.4f}")
for ad, f in KARAR:
    SONUC[ad] = olc(*f(HAM_EGT, HAM_SIN))
    print(f"{ad:<22} {SONUC[ad][0]:>8.4f} {SONUC[ad][1]:>9.4f}")
```

```
uygulanan karar            agac   uzaklik
taban cizgisi            0.7579    0.7579
hicbiri (ham kayit)      0.7659    0.7540
birim duzeltildi         0.7579    0.8016
yazim birlestirildi      0.7738    0.7540
yinelenen atildi         0.7778    0.7659
ucu birden               0.7778    0.7857
```

İkinci satır dersin çıkış noktasıdır. Ham kayıtla eğitilen uzaklık temelli yordam sınama kümesinde
0,7540 okuyor ve bu, taban çizgisinin **altındadır**. Hiçbir özniteliğe bakmayan, hiçbir şey
öğrenmeyen ve her aboneye aynı yanıtı veren bir yordam ondan iyidir. Aynı ham kayıtla eğitilen
ağaç ise tabanı 0,0079 geçiyor. İki model, aynı tablo, iki ayrı işaret.

Üçüncü satır dersin asıl sayısıdır. Birim düzeltmesi uzaklık temelli yordamı 0,7540'tan 0,8016'ya
çıkarıyor; ağacı 0,7659'dan 0,7579'a **indiriyor**. Aynı karar, aynı veri, ters işaret. Sebep
modellerin sütunu okuma biçimidir. Ağaç her sütunda yalnız **sıraya** bakar ve tek bir eşikle
böler; birim karışıklığı sıralamayı bozduğunda ağaç önce bin sınırında bölüp sonra sütunu her
dalda yeniden kullanabilir, ödediği bedel bir düğüm derinliğidir. Uzaklık temelli yordam ise
bütün sütunların farklarını **toplar**; bin kat büyümüş bir sütun toplamı tek başına belirler ve
hane, memnuniyet, dönem, bölge sütunlarının hepsi hesaba katılmaz duruma gelir.

#### Katkı Kaç Abone Eder

Farkları abone sayısına çevirmek, hangi satırın okunmaya değer olduğunu ayırır.

```python
HAMD = SONUC["hicbiri (ham kayit)"]
print(f"{'karar':<22} {'agac katki':>11} {'abone':>6} "
      f"{'uzaklik katki':>14} {'abone':>6}")
for ad, _ in KARAR[1:]:
    ka, ku = SONUC[ad][0] - HAMD[0], SONUC[ad][1] - HAMD[1]
    print(f"{ad:<22} {ka:>+11.4f} {round(ka * 252):>6} "
          f"{ku:>+14.4f} {round(ku * 252):>6}")
print(f"\ntabanin ustundeki fark, ham kayitta: agac {HAMD[0] - TABAN:+.4f}, "
      f"uzaklik {HAMD[1] - TABAN:+.4f}")
u = SONUC["ucu birden"]
print(f"tabanin ustundeki fark, ucu birden: agac {u[0] - TABAN:+.4f}, "
      f"uzaklik {u[1] - TABAN:+.4f}")
```

```
karar                   agac katki  abone  uzaklik katki  abone
birim duzeltildi           -0.0079     -2        +0.0476     12
yazim birlestirildi        +0.0079      2        +0.0000      0
yinelenen atildi           +0.0119      3        +0.0119      3
ucu birden                 +0.0119      3        +0.0317      8

tabanin ustundeki fark, ham kayitta: agac +0.0079, uzaklik -0.0040
tabanin ustundeki fark, ucu birden: agac +0.0198, uzaklik +0.0278
```

Tabloda okunmaya değer tek bir büyük sayı var: birim düzeltmesinin uzaklık temelli yordama
kattığı on iki abone. Geri kalan hücreler iki ya da üç abonedir ve 252 abonelik bir kümede iki
abone, kümenin kendi rastgeleliğinden ayırt edilemez. Ağacın birim düzeltmesinden gördüğü
−0,0079'luk zarar da bu boydadır; işareti eksidir ama okunacak doğru cümle "ağaç için bu adımın
katkısı ölçülmemiştir" olur, "ağaca zarar verir" değil. Bu ayrım kurs boyunca sürer: bir katkının
işareti, büyüklüğü ölçüm çözünürlüğünü aştığında anlam kazanır.

Son iki satır kararların nereden geldiğini gösteriyor. Üç kusur birden düzeltildiğinde ağaç tabanı
0,0198, uzaklık temelli yordam 0,0278 geçiyor. Bu iki fark **öğrenmeden** gelir: kusurlar
etiketten bağımsız üretildi, hiçbiri şüpheli abonelerde daha sık değil, dolayısıyla hiçbir
temizleme kararı modele etiket hakkında saklı bir bilgi taşımıyor. Kursun ilerleyen derslerinde
bunun tersi de ölçülecek — katkısı öğrenmeden değil sızıntıdan gelen adımlar vardır ve ikisi
ancak kaynağı sorulduğunda ayrılır.

Bir doğrulama daha var. Üç kusur birden düzeltildiğinde ağacın sınama doğruluğu 0,7778 çıkıyor ve
bu, M27/K01'in aynı model, aynı bölme ve aynı tohumla okuduğu sayının **birebir aynısıdır**. Bu
tesadüf değil: temizlenmiş tablo, K01'in en baştan kullandığı tablodur. Önceki kurs boyunca
görünmeyen şey, o tablonun kendisinin bir dizi karardan çıkmış olmasıydı.

#### Özet

- Ham kayıtla eğitilen uzaklık temelli yordam sınama kümesinde 0,7540 okuyor ve taban çizgisinin
  0,0040 altında kalıyor; aynı tabloyla eğitilen karar ağacı 0,7659 ile tabanı 0,0079 geçiyor.
- Birim karışıklığının düzeltilmesi uzaklık temelli yordama +0,0476, yani on iki abone katıyor;
  aynı karar karar ağacında −0,0079, yani iki abone eksiye düşürüyor.
- İşaretin ters dönme sebebi modellerin sütunu okuma biçimidir: ağaç her sütunda yalnız sıraya
  bakar ve karışık birimi bir düğüm derinliğiyle telafi eder, uzaklık temelli yordam bütün
  sütunları toplar ve bin kat büyümüş bir sütun toplamı tek başına belirler.
- Yazım birleştirme ve yinelenen kayıt atma her iki modelde de iki ya da üç abone oynatıyor; 252
  abonelik sınama kümesinde bir abone 0,0040 ettiği için bu farklar ölçülmemiş sayılır.
- Üç kusur birden düzeltildiğinde ağaç 0,7778 okuyor; bu sayı M27/K01'in aynı model ve aynı
  bölmeyle okuduğu sayının aynısıdır, çünkü K01 zaten temizlenmiş tabloyu kullanıyordu.

#### Sonraki Adım

Bu derste birim karışıklığının uzaklık temelli yordamı yıktığı görüldü, ama düzeltmeden sonra da
o yordam ağaçtan geride kaldı ve tabanı yalnız 0,0278 geçebildi. Sebep tabloda hâlâ duruyor:
sütunlar aynı birime çevrildi ama **aynı büyüklüğe** çevrilmedi. Ortalama tüketim sıfır ile
kırk üç arasında değişirken hane büyüklüğü bir ile altı, bölge sütunları sıfır ile bir arasında
değişiyor; farkların karelerini toplayan bir yordam için bu, sütunlara sessizce ağırlık vermektir.
Sonraki ders iki ölçekleme kararını — standartlaştırma ve normalleştirme — üç model ailesinde ayrı
ayrı ölçer, ölçeğin **yalnız eğitim kümesinden** öğrenilip ayrılmış kümeye uygulandığı disiplini
kurar ve hangi model ailesinde katkının sıfır çıktığını sayar.

### Öznitelik Ölçekleme

Source: https://academia.sh/tr/kurslar/oznitelik-muhendisligi/oznitelik-olcekleme

Standartlaştırma ve normalleştirme kararlarının üç model ailesinde ayrı ayrı ölçülmesi: standartlaştırma eğim temelli doğrusal modele +0,0437 katarken uzaklık temelli yordamda −0,0159 verir ve karar ağacında katkı tam olarak sıfırdır. Ölçek eğitim kümesinden öğrenilen bir nesnedir; sınama kümesine uygulandığında iki değer [0, 1] aralığının dışına çıkar ve standart puanlar 5,11'e kadar yükselir. On bir sütunun hepsi ölçeklendiğinde uzaklık temelli yordam bir abone daha kaybeder.

Önceki ders ham kayıttaki üç kusuru düzeltti ve uzaklık temelli yordamı 0,7540'tan 0,7857'ye
çıkardı. Bu sayı taban çizgisini yalnız 0,0278 geçiyor — yedi abone. Sebep temizlenmemiş bir şey
değil, hiç sorulmamış bir şey: sütunlar aynı birime çevrildi ama **aynı büyüklüğe** çevrilmedi.
Ortalama tüketim sıfır ile kırk üç arasında değişirken hane büyüklüğü bir ile altı, bölge
sütunları sıfır ile bir arasında değişiyor. Farkların karelerini toplayan bir yordam için bu,
sütunlara sessizce ağırlık vermektir.

Bu ders iki ölçekleme kararını ölçer. **Standartlaştırma** her değerden sütunun ortalamasını
çıkarıp değişkenliğine böler; sonuç standart puandır ve M26'nın Veri Toplama ve Hazırlama
kursunda tanımlanmıştı. **Normalleştirme** her sütunu en küçük değeri sıfır, en büyüğü bir olacak
biçimde aralığa sıkıştırır; M26/K03'ün metin normalleştirmesiyle adı ortaktır ama işi başkadır,
biri yazımı tekleştirir, bu sayıyı aralığa oturtur. İkisi de yordam olarak yeni değil; ölçülen
şey, her birinin **hangi model ailesinde** kaç abone ettiğidir.

- **HA9.** Bu dersin başlangıç tablosu önceki dersin çıktısıdır: üç kusuru düzeltilmiş 1.260
  abonelik kurgu tablo, aynı 756/252/252 bölmesi, sınama kümesinde taban çizgisi 0,7579.
- **HA10.** Üç model ailesi karşılaştırılır ve üçü de ders içinde yazılır: en yakın on beş komşuya
  bakan uzaklık temelli yordam, ağırlıklarını eğim yönünde güncelleyen doğrusal model ve derinlik
  6 karar ağacı. Ayarları kurs boyunca sabittir.
- **HA11.** Eğim temelli modelin çıktısı S biçimli bir işlevle sıfır ile bir arasına sıkıştırılır;
  0,5 eşiği aşan abone şüpheli sayılır. Eşik bir seçimdir ve bu kursta değişmez.
- **HA12.** Ölçekleme yalnız beş sayısal sütuna uygulanır: ortalama tüketim, oynaklık, hane
  büyüklüğü, memnuniyet, dönem sayısı. İkili sütunların ölçeklenmesi dersin sonunda ayrı bir karar
  olarak ölçülür.
- **HA13.** Ölçek **eğitilmiş bir nesnedir** ve iki yüzü vardır: **öğrenme**, ölçek sayılarının
  eğitim kümesinden çıkarılması; **uygulama**, o sayıların bir kümeye uygulanması. Kursun ikinci
  kuralı burada ilk kez yordama dönüşür: **her hazırlama adımı yalnız eğitim kümesinden öğrenilir
  ve ayrılmış kümeye uygulanır.**
- **HA14.** Bütün kümeden öğrenilen ölçek bir sızıntıdır. M27/K01 bunu dört sızıntı türünden biri
  olarak saymıştı; buradaki iş o sayıları tekrarlamak değil, sızmayan biçimi yordam olarak
  kurmaktır.
- **HA15.** Ölçü doğruluktur ve sınama kümesi 252 abonedir; bir abone 0,0040 eder.
- **HA16.** Kayan noktalı karşılaştırma `==` ile yapılmaz. Aralık dışı sayımında `0.0 <= v <= 1.0`
  kullanılır; sınır değerinin kendisi aralık içi sayılır.

#### Üç Model, İki Ölçekleme Kararı

Kurulum tabloyu üretir ve beş sayısal sütunun ham aralıklarını basar.

```python
# olcekleme.py — MODELDIR. M27/K01'in kurgu abone tablosu, onceki derste
# temizlenmis haliyle yeniden uretilir.
import math
import statistics

TOHUM, ADAY, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


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


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE, OKUMA = [], {}
for i in range(ADAY):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
for k in ABONE:
    r = uretec(TOHUM + 7000 + k["no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        OKUMA.setdefault(k["no"], []).append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()

VERI = []
for k in ABONE:
    v = OKUMA.get(k["no"])
    if v is None:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    x = {"hane": k["hane"], "memnuniyet": k["memnuniyet"], "donem": len(v),
         "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35)}
    for b in BOLGE:
        x["b_" + b[0]] = int(k["bolge"][0] == b[0])
    VERI.append(x)


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT, SIN = K[:756], K[1008:]
TABAN = sum(x["supheli"] == 0 for x in SIN) / len(SIN)
SAYISAL = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem"]
ALAN = SAYISAL + ["sifir_okuma", "b_kuzey", "b_guney", "b_dogu", "b_bati", "b_merkez"]
print(f"egitim {len(EGT)}, sinama {len(SIN)}, taban cizgisi {TABAN:.4f}")
print(f"{'sutun':<12} {'en kucuk':>9} {'en buyuk':>9} {'ortalama':>9}")
for a in SAYISAL:
    v = [x[a] for x in EGT]
    print(f"{a:<12} {min(v):>9.2f} {max(v):>9.2f} {statistics.fmean(v):>9.2f}")
```

```
egitim 756, sinama 252, taban cizgisi 0.7579
sutun         en kucuk  en buyuk  ortalama
ort_tuketim       0.00     42.90     19.77
oynaklik          0.00     40.57      9.62
hane              1.00      6.00      3.22
memnuniyet        1.00      5.00      3.37
donem             1.00      3.00      2.53
```

Aralıklar arasındaki oran doksan katı buluyor. Şimdi üç model ve iki ölçekleme kararı yazılır.
Ölçekleme işlevlerinin **öğrenme** ve **uygulama** olarak ikiye ayrılması bir biçim tercihi değil,
kursun kuralının koda geçmiş halidir.

```python
def gini(s):
    p = sum(x["supheli"] for x in s) / len(s) if s else 0.0
    return 2 * p * (1 - p)


def agac(s, derinlik, alan, enaz=2):        # MODELDIR: karar agaci, derinlik 6
    p = sum(x["supheli"] for x in s) / len(s)
    en = None
    if derinlik and len(s) >= 2 * enaz and 0.0 < p < 1.0:
        for a in alan:
            d = sorted({x[a] for x in s})
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < enaz:
                    continue
                k = gini(s) - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(s)
                if en is None or k > en[0]:
                    en = (k, a, v, sol, sag)
    if en is None or en[0] <= 1e-9:
        return {"tahmin": int(p > 0.5)}
    return {"alan": en[1], "esik": en[2], "sol": agac(en[3], derinlik - 1, alan, enaz),
            "sag": agac(en[4], derinlik - 1, alan, enaz)}


def agac_tahmin(d, x):
    while "tahmin" not in d:
        d = d["sol"] if x[d["alan"]] < d["esik"] else d["sag"]
    return d["tahmin"]


def komsu_tahmin(egt, x, alan, k=15):       # MODELDIR: uzaklik temelli yordam
    u = sorted((sum((x[a] - y[a]) ** 2 for a in alan), y["supheli"]) for y in egt)[:k]
    return int(sum(t[1] for t in u) * 2 > k)


def sikistir(z):                            # S bicimli isleve tasma korumasi
    if z >= 0:
        return 1.0 / (1.0 + math.exp(-z)) if z < 700 else 1.0
    e = math.exp(z) if z > -700 else 0.0
    return e / (1.0 + e)


def dogrusal(egt, alan, adim=0.5, tur=400):  # MODELDIR: egim temelli dogrusal model
    w, b, n = [0.0] * len(alan), 0.0, len(egt)
    for _ in range(tur):
        gw, gb = [0.0] * len(alan), 0.0
        for x in egt:
            h = sikistir(sum(w[i] * x[a] for i, a in enumerate(alan)) + b) - x["supheli"]
            for i, a in enumerate(alan):
                gw[i] += h * x[a]
            gb += h
        for i in range(len(alan)):
            w[i] -= adim * gw[i] / n
        b -= adim * gb / n
    return w, b


def dogrusal_tahmin(m, x, alan):
    return int(sikistir(sum(m[0][i] * x[a] for i, a in enumerate(alan)) + m[1]) > 0.5)


def uc_model(egt, sin, alan=None):
    alan = alan or ALAN
    m1, m3 = agac(egt, 6, alan), dogrusal(egt, alan)
    return (sum(komsu_tahmin(egt, x, alan) == x["supheli"] for x in sin) / len(sin),
            sum(dogrusal_tahmin(m3, x, alan) == x["supheli"] for x in sin) / len(sin),
            sum(agac_tahmin(m1, x) == x["supheli"] for x in sin) / len(sin))


def standart_ogren(egt, sutun):             # OGRENME: ortalama ve degiskenlik
    return {a: (statistics.fmean(x[a] for x in egt),
                statistics.pstdev([x[a] for x in egt])) for a in sutun}


def standart_uygula(p, s):                  # UYGULAMA
    return [dict(x, **{a: (x[a] - p[a][0]) / p[a][1] for a in p}) for x in s]


def normal_ogren(egt, sutun):               # OGRENME: en kucuk ve en buyuk
    return {a: (min(x[a] for x in egt), max(x[a] for x in egt)) for a in sutun}


def normal_uygula(p, s):                    # UYGULAMA
    return [dict(x, **{a: (x[a] - p[a][0]) / (p[a][1] - p[a][0]) for a in p}) for x in s]


PS, PN = standart_ogren(EGT, SAYISAL), normal_ogren(EGT, SAYISAL)
DURUM = [("olceksiz (ham sutun)", EGT, SIN),
         ("standartlastirma", standart_uygula(PS, EGT), standart_uygula(PS, SIN)),
         ("normallestirme", normal_uygula(PN, EGT), normal_uygula(PN, SIN))]
S = {}
print(f"{'karar':<24} {'uzaklik':>8} {'egim temelli':>13} {'agac':>8}")
print(f"{'taban cizgisi':<24} {TABAN:>8.4f} {TABAN:>13.4f} {TABAN:>8.4f}")
for ad, e, s in DURUM:
    S[ad] = uc_model(e, s)
    print(f"{ad:<24} {S[ad][0]:>8.4f} {S[ad][1]:>13.4f} {S[ad][2]:>8.4f}")
h = S["olceksiz (ham sutun)"]
for ad in ("standartlastirma", "normallestirme"):
    k = [S[ad][i] - h[i] for i in range(3)]
    print(f"{ad + ' katkisi':<24} {k[0]:>+8.4f} {k[1]:>+13.4f} {k[2]:>+8.4f}")
```

```
karar                     uzaklik  egim temelli     agac
taban cizgisi              0.7579        0.7579   0.7579
olceksiz (ham sutun)       0.7857        0.7579   0.7778
standartlastirma           0.7698        0.8016   0.7778
normallestirme             0.7619        0.7817   0.7778
standartlastirma katkisi  -0.0159       +0.0437  +0.0000
normallestirme katkisi    -0.0238       +0.0238  +0.0000
```

Ağaç sütunu üç durumda da aynı sayıyı veriyor ve bu, ölçüm gürültüsünün altında kalmış küçük bir
fark değil, **tam bir sıfırdır**. Sebebi gösterilebilir: ağaç her sütunda yalnız değerlerin
sırasına bakar, adayları sıralı ayrık değerler arasından seçer ve bir eşikle böler.
Standartlaştırma ile normalleştirme artan dönüşümlerdir; sırayı değiştirmezler, dolayısıyla ağacın
seçebileceği bölmeler birebir aynı kalır. Ağaç temelli bir yordamın önüne ölçekleme adımı koymak
sonucu değiştirmez; katkısı sıfırdır ve bu, adımın alışkanlık olduğunun tanımıdır.

Eğim temelli doğrusal model ters uçtadır. Ölçeksiz tabloda 0,7579 okuyor — yani tam olarak taban
çizgisi. Bu sayı iyi bir sayı değil, bir çöküş belirtisidir: model her aboneye aynı yanıtı veriyor.
Ortalama tüketim kırk üçe kadar çıkarken hane büyüklüğü altıyı geçmiyor, bu yüzden aynı adım
büyüklüğü bir sütun için çok büyük, öteki için çok küçük kalıyor ve ağırlıklar dört yüz turda
kullanılabilir bir yere ulaşamıyor. Standartlaştırma bu modele 0,0437, yani on bir abone katıyor.

Uzaklık temelli yordamın satırı ise yaygın bir beklentiyi bozuyor. Standartlaştırma ona
**0,0159 kaybettiriyor**, normalleştirme 0,0238. Ölçekleme uzaklık temelli yordamlara yardım eden
bir adım değildir; sütunlara **eşit ağırlık veren** bir adımdır. Bu tabloda ham ölçek zaten en
geniş aralığı en bilgilendirici iki sütuna, ortalama tüketim ile oynaklığa vermişti. Eşitleme o
ağırlığı alıp hane büyüklüğüne ve dönem sayısına dağıtıyor. Kararın işareti, ölçeğin kendisinin
kazara taşıdığı bilgiye bağlıdır ve bu ancak ölçülerek bilinir.

#### Ölçek Eğitilmiş Bir Nesnedir

Ölçekleme adımının öğrendiği şey birkaç sayıdır ve o sayılar yalnız eğitim kümesinden gelir.

```python
print(f"{'sutun':<12} {'ortalama':>9} {'degiskenlik':>12} "
      f"{'en kucuk':>9} {'en buyuk':>9}")
for a in SAYISAL:
    print(f"{a:<12} {PS[a][0]:>9.3f} {PS[a][1]:>12.3f} "
          f"{PN[a][0]:>9.2f} {PN[a][1]:>9.2f}")
sn = normal_uygula(PN, SIN)
print(f"\nsinamada [0, 1] araligi disina cikan deger "
      f"{sum(1 for x in sn for a in SAYISAL if not 0.0 <= x[a] <= 1.0)}, "
      f"abone {sum(1 for x in sn if any(not 0.0 <= x[a] <= 1.0 for a in SAYISAL))}")
ss = standart_uygula(PS, SIN)
print(f"{'sutun':<12} {'sinamada en dusuk':>18} {'en yuksek':>10} (standart puan)")
for a in SAYISAL:
    v = [x[a] for x in ss]
    print(f"{a:<12} {min(v):>18.2f} {max(v):>10.2f}")
PT = standart_ogren(EGT, ALAN)
t = uc_model(standart_uygula(PT, EGT), standart_uygula(PT, SIN))
print(f"\non bir sutunun hepsi standartlastirilinca: uzaklik {t[0]:.4f}, "
      f"egim temelli {t[1]:.4f}, agac {t[2]:.4f}")
```

```
sutun         ortalama  degiskenlik  en kucuk  en buyuk
ort_tuketim     19.767        6.200      0.00     42.90
oynaklik         9.622        8.120      0.00     40.57
hane             3.224        1.270      1.00      6.00
memnuniyet       3.372        1.147      1.00      5.00
donem            2.532        0.715      1.00      3.00

sinamada [0, 1] araligi disina cikan deger 2, abone 2
sutun         sinamada en dusuk  en yuksek (standart puan)
ort_tuketim               -2.63       3.83
oynaklik                  -1.19       5.11
hane                      -1.75       2.19
memnuniyet                -2.07       1.42
donem                     -2.14       0.66

on bir sutunun hepsi standartlastirilinca: uzaklik 0.7659, egim temelli 0.8016, agac 0.7778
```

Öğrenilen nesne on sayıdır ve hepsi 756 abonelik eğitim kümesinden çıkmıştır. Uygulanan yerde
bunun görünür bir sonucu var: normalleştirme "her değeri sıfır ile bir arasına oturtur" diye
tanımlanır, ama sınama kümesinde iki değer o aralığın dışına çıkıyor. Bu bir hata değildir ve
düzeltilmez. Düzeltmenin tek yolu en küçüğü ve en büyüğü sınama kümesine de bakarak seçmek
olurdu; o da ölçeği ayrılmış kümeden öğrenmek, yani sızdırmak olurdu. M27/K01 ayrımdan önce
ölçeklemeyi dört sızıntı türünden biri olarak saymıştı; buradaki iş o sayıları tekrarlamak değil,
sızmayan biçimi bir nesne olarak kurmaktır. Standart puan tablosu aynı şeyi öteki yönden gösterir:
oynaklık sütununda sınama kümesindeki en yüksek değer 5,11 standart puandır ve eğitim kümesinden
öğrenilmiş ölçek onu kırpmadan geçirir.

Son satır üçüncü bir kararı ölçüyor: **hangi sütunlar ölçeklenir.** Beş sayısal sütun yerine
ikili sütunlar da dahil on bir sütunun hepsi standartlaştırıldığında eğim temelli model 0,8016'da
kalıyor, ağaç 0,7778'de kalıyor, uzaklık temelli yordam ise 0,7698'den 0,7659'a düşüyor. Bir
abonelik bu fark tek başına okunmaz, ama yönü açıklanabilir: bir ikili sütun standartlaştırıldığında
seyrek olan kategori büyük bir standart puana taşınır ve bölge farkı uzaklık toplamında olduğundan
ağır basar. Ölçekleme kararı tek bir düğme değildir; hangi sütuna uygulandığı da karardır ve o da
ayrılmış kümede ölçülür.

#### Özet

- Standartlaştırma eğim temelli doğrusal modele 0,0437, yani on bir abone katıyor; aynı karar
  uzaklık temelli yordamda −0,0159, karar ağacında tam olarak 0,0000 veriyor.
- Ağaçtaki sıfır ölçüm gürültüsü değil kanıtlanabilir bir sonuçtur: standartlaştırma ve
  normalleştirme artan dönüşümlerdir, sütun içindeki sırayı değiştirmezler ve ağaç yalnız sıraya
  bakar.
- Eğim temelli model ölçeksiz tabloda tam olarak taban çizgisini, 0,7579'u okuyor; bu iyi bir sayı
  değil, tek adım büyüklüğünün doksan kat farklı aralıklara yetmediğinin belirtisidir.
- Ölçek eğitim kümesinden öğrenilen on sayıdır; sınama kümesine uygulandığında iki değer [0, 1]
  aralığının dışına çıkar ve bu düzeltilmez, çünkü düzeltmenin tek yolu ölçeği ayrılmış kümeden
  öğrenmektir.
- Ölçeklenecek sütunların seçimi ayrı bir karardır: ikili sütunlar da ölçeklendiğinde uzaklık
  temelli yordam 0,7698'den 0,7659'a iniyor, öteki iki model değişmiyor.

#### Sonraki Adım

Bu derste bölge bilgisi hazır beş ikili sütun olarak kullanıldı ve o beş sütunun nereden geldiği
sorulmadı. Oysa bölge bir metin alanıdır ve sayıya çevrilmesi bir karardır: her kategori için bir
sütun açılabilir, kategorilere sıra numarası verilebilir ya da her kategori kendi eğitim
kümesindeki şüpheli oranıyla değiştirilebilir. Üçünün maliyeti farklıdır; birincisi sütun sayısını
kategori sayısı kadar büyütür, üçüncüsü tek sütunla yetinir ama etiketi kodlamanın içine sokar.
Sonraki ders bu üç kodlamayı iki yüz kırk kodluk bir okuma rotası alanında ölçer, ayrılmış kümede
görülmemiş kategorileri sayar ve hedef temelli kodlamanın eğitim kümesinde ürettiği şişmenin kat
içinde hesaplandığında ne kadar geri çekildiğini basar.

### Kategorik Kodlama

Source: https://academia.sh/tr/kurslar/oznitelik-muhendisligi/kategorik-kodlama

Bir kategorik alanın sayıya çevrilmesinde üç kodlamanın sütun sayısı ve katkı olarak ölçülmesi: bölgenin bir-sıcak kodlanması sınama doğruluğunu 0,7302'den 0,7778'e çıkarır, 240 kodluk okuma rotası alanı bir-sıcak kodlandığında 231 ek sütuna karşılık +0,0119, sıralı kodlandığında tek sütunla aynı +0,0119 verir. Hedef temelli kodlama eğitim doğruluğunu 0,9114'e şişirirken sınamada 0,7421'e düşürür; kat içinde hesaplandığında eğitimdeki şişme 0,0595 geri çekilir ve sınama 0,7540'a döner.

Önceki ders bölge bilgisini hazır beş ikili sütun olarak kullandı ve o sütunların nereden geldiğini
sormadı. Bölge bir metin alanıdır. Modelin girdisi sayıdır. Aradaki çeviri bir karardır ve
kararın en az iki bedeli vardır: kaç sütun ürettiği ve ayrılmış kümedeki sayıyı ne kadar oynattığı.

Bu ders üç kodlamayı aynı tabloda karşılaştırır. **Bir-sıcak kodlama** her kategori için ayrı bir
ikili sütun açar. **Sıralı kodlama** kategorilere birer tamsayı verir ve tek sütunla yetinir.
**Hedef temelli kodlama** her kategoriyi, o kategorinin eğitim kümesindeki şüpheli oranıyla
değiştirir; tek sütun üretir ama etiketi kodlamanın içine sokar. Ölçüm iki kategorik alan üzerinde
yapılır: beş kategorili bölge ve **kardinalitesi** iki yüz kırk olan okuma rotası kodu.

- **HA17.** Okuma rotası kodu ham kaydın bir alanıdır ve **kurgudur**: her abone bir okuma
  rotasına bağlıdır, rotalar bölgeye ve ağın genişleme dalgasına göre açılmıştır. M26 bu alanı
  kullanmamıştı.
- **HA18.** Rota kodu etiketle doğrudan üretilmez. Taşıdığı bilgi bölgeden ve ağa katılma
  dalgasından gelir; ikincisi öznitelik sütunlarında yoktur.
- **HA19.** Ölçüm kümesi, bölme ve taban çizgisi önceki derslerdekiyle aynıdır: 756/252/252 ve
  sınama kümesinde 0,7579.
- **HA20.** Model sabittir: derinlik 6 karar ağacı. Ölçekleme uygulanmaz, çünkü önceki derste
  ağaçtaki katkısının tam olarak sıfır olduğu gösterildi.
- **HA21.** Kodlama **eğitilmiş bir nesnedir**. Öğrenilen şey bir-sıcak kodlamada kategori
  listesi, sıralı kodlamada kategori–tamsayı eşlemesi, hedef temelli kodlamada kategori–oran
  tablosudur. Üçü de yalnız eğitim kümesinden çıkarılır.
- **HA22.** **Görülmemiş kategori**, ayrılmış kümede çıkıp eğitim kümesinde bulunmayan koddur. Her
  kodlama ona bir yanıt vermek zorundadır ve verdiği yanıt kodlamanın parçasıdır.
- **HA23.** Hedef temelli kodlamanın **kat içinde** hesaplanması şudur: eğitim kümesi beş kata
  bölünür ve her satırın kodlama değeri, o satırın bulunmadığı dört kattan hesaplanır. Böylece
  hiçbir satırın kendi etiketi kendi kodlama değerine girmez.
- **HA24.** Çapraz doğrulamanın kendisi bu derste kurulmuyor; kat yalnız kodlamanın hesaplandığı
  bölme olarak kullanılıyor.
- **HA25.** Sınama kümesi 252 abonedir, bir abone 0,0040 eder.

#### Üç Kodlama, İki Sayı

Kurulum tabloyu yeniden üretir ve rota alanını ekler.

```python
# kodlama.py — MODELDIR. Kurgu abone tablosu ayni tohumla uretilir; ham kayittan
# gelen KURGU okuma rotasi kodu kategorik sutun olarak eklenir.
import math

TOHUM, ADAY, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


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


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE, OKUMA = [], {}
for i in range(ADAY):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
for k in ABONE:
    r = uretec(TOHUM + 7000 + k["no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        OKUMA.setdefault(k["no"], []).append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()

VERI, rr = [], uretec(TOHUM + 41000)
for k in ABONE:
    v = OKUMA.get(k["no"])
    if v is None:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    x = {"hane": k["hane"], "memnuniyet": k["memnuniyet"], "donem": len(v),
         "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35),
         "bolge": k["bolge"][0]}
    dalga = min(7, int((k["no"] - 10001) / 1399 * 8))       # aga katilma dalgasi
    x["rota"] = "R%03d" % ([b[0] for b in BOLGE].index(k["bolge"][0]) * 48
                           + dalga * 6 + int(rr() * 6) + 1)
    VERI.append(x)


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT, SIN = K[:756], K[1008:]
TABAN = sum(x["supheli"] == 0 for x in SIN) / len(SIN)
SAY = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem", "sifir_okuma"]
EK = {x["rota"] for x in EGT}
BOY = {}
for x in EGT:
    BOY[x["rota"]] = BOY.get(x["rota"], 0) + 1
print(f"rota kodu: tabloda {len({x['rota'] for x in VERI})}, egitimde {len(EK)}, "
      f"en kalabalik rota {max(BOY.values())} abone, ortalama "
      f"{sum(BOY.values()) / len(BOY):.1f}")
print(f"sinamada gorulmemis rota kodu {len({x['rota'] for x in SIN} - EK)}, "
      f"bu kodlardaki abone {sum(1 for x in SIN if x['rota'] not in EK)}")
print(f"bolge {len({x['bolge'] for x in EGT})} kategori, taban cizgisi {TABAN:.4f}")
```

```
rota kodu: tabloda 240, egitimde 231, en kalabalik rota 9 abone, ortalama 3.3
sinamada gorulmemis rota kodu 6, bu kodlardaki abone 10
bolge 5 kategori, taban cizgisi 0.7579
```

Rota başına ortalama 3,3 abone düşüyor ve en kalabalık rota dokuz abone. Bu sayı dersin ikinci
yarısında belirleyici olacak. Şimdi dört kodlama sırayla uygulanır.

```python
def gini(s):
    p = sum(x["supheli"] for x in s) / len(s) if s else 0.0
    return 2 * p * (1 - p)


def agac(s, derinlik, alan, enaz=2):        # MODELDIR: karar agaci, derinlik 6
    p = sum(x["supheli"] for x in s) / len(s)
    en = None
    if derinlik and len(s) >= 2 * enaz and 0.0 < p < 1.0:
        for a in alan:
            d = sorted({x[a] for x in s})
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < enaz:
                    continue
                k = gini(s) - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(s)
                if en is None or k > en[0]:
                    en = (k, a, v, sol, sag)
    if en is None or en[0] <= 1e-9:
        return {"tahmin": int(p > 0.5)}
    return {"alan": en[1], "esik": en[2], "sol": agac(en[3], derinlik - 1, alan, enaz),
            "sag": agac(en[4], derinlik - 1, alan, enaz)}


def tahmin(d, x):
    while "tahmin" not in d:
        d = d["sol"] if x[d["alan"]] < d["esik"] else d["sag"]
    return d["tahmin"]


def bir_sicak(sutun, egt, sin):             # OGRENME: egitimdeki kategori listesi
    kat = sorted({x[sutun] for x in egt})
    ac = (lambda s: [dict(x, **{sutun + "_" + k: int(x[sutun] == k) for k in kat})
                     for x in s])
    return [sutun + "_" + k for k in kat], ac(egt), ac(sin)


def sirali(sutun, egt, sin, sira=None):     # OGRENME: kategori -> tamsayi eslemesi
    kat = sira or {k: i for i, k in enumerate(sorted({x[sutun] for x in egt}))}
    ac = lambda s: [dict(x, rota_s=kat.get(x[sutun], -1)) for x in s]
    return ["rota_s"], ac(egt), ac(sin)


def oran(satirlar):
    m = {}
    for x in satirlar:
        m.setdefault(x["rota"], []).append(x["supheli"])
    return {k: sum(v) / len(v) for k, v in m.items()}


def hedef(egt, sin):                        # OGRENME: kategori basina supheli orani
    g, t = sum(x["supheli"] for x in egt) / len(egt), oran(egt)
    ac = lambda s: [dict(x, rota_h=t.get(x["rota"], g)) for x in s]
    return ["rota_h"], ac(egt), ac(sin)


def hedef_kat_icinde(egt, sin, kat=5):      # her satir kendi katinin disindan
    g, t = sum(x["supheli"] for x in egt) / len(egt), oran(egt)
    tablo = [oran([y for j, y in enumerate(egt) if j % kat != i]) for i in range(kat)]
    e = [dict(x, rota_h=tablo[i % kat].get(x["rota"], g)) for i, x in enumerate(egt)]
    return ["rota_h"], e, [dict(x, rota_h=t.get(x["rota"], g)) for x in sin]


SONUC = {}


def olc(ad, alan, e, s):
    m = agac(e, 6, alan)
    de = sum(tahmin(m, x) == x["supheli"] for x in e) / len(e)
    ds = sum(tahmin(m, x) == x["supheli"] for x in s) / len(s)
    SONUC[ad] = (len(alan), de, ds)
    print(f"{ad:<28} {len(alan):>6} {de:>8.4f} {ds:>8.4f}")


print(f"{'kodlama':<28} {'sutun':>6} {'egitim':>8} {'sinama':>8}")
olc("kategorik sutun yok", SAY, EGT, SIN)
ab, eb, sb = bir_sicak("bolge", EGT, SIN)
olc("bolge bir-sicak", SAY + ab, eb, sb)
for ad, fn in (("+ rota bir-sicak", lambda: bir_sicak("rota", eb, sb)),
               ("+ rota sirali", lambda: sirali("rota", eb, sb)),
               ("+ rota hedef temelli", lambda: hedef(eb, sb)),
               ("+ rota hedef, kat icinde", lambda: hedef_kat_icinde(eb, sb))):
    a, e, s = fn()
    olc(ad, SAY + ab + a, e, s)
```

```
kodlama                       sutun   egitim   sinama
kategorik sutun yok               6   0.8492   0.7302
bolge bir-sicak                  11   0.8466   0.7778
+ rota bir-sicak                242   0.8452   0.7897
+ rota sirali                    12   0.8651   0.7897
+ rota hedef temelli             12   0.9114   0.7421
+ rota hedef, kat icinde         12   0.8519   0.7540
```

İlk iki satır kodlamanın kendisinin ne ettiğini söylüyor. Kategorik alanlar hiç kullanılmadığında
model 0,7302 okuyor ve taban çizgisinin 0,0277 altında kalıyor. Beş kategorili bölge alanı beş
ikili sütuna açıldığında sayı 0,7778'e çıkıyor: on iki abone. Bu, kursun şimdiye kadarki en büyük
tek katkısıdır ve kararın kendisi görünmezdir, çünkü bölgenin sayıya çevrilmesi genellikle
tartışılmadan yapılır.

#### Aynı Katkı, İki Yüz Otuz Bir Sütun Farkı

Rota alanının üç kodlaması aynı tabloda yan yana durunca ödenen bedel görünür oluyor.

```python
t = SONUC["bolge bir-sicak"]
print(f"{'kodlama':<28} {'ek sutun':>9} {'egitim katki':>13} {'sinama katki':>13}")
for ad in ("+ rota bir-sicak", "+ rota sirali", "+ rota hedef temelli",
           "+ rota hedef, kat icinde"):
    v = SONUC[ad]
    print(f"{ad:<28} {v[0] - t[0]:>9} {v[1] - t[1]:>+13.4f} {v[2] - t[2]:>+13.4f}")

kod = sorted(EK)
kr = uretec(TOHUM + 43000)
kk = list(range(len(kod)))
for i in range(len(kk) - 1, 0, -1):
    j = int(kr() * (i + 1))
    kk[i], kk[j] = kk[j], kk[i]
a, e, s = sirali("rota", eb, sb, {k: kk[i] for i, k in enumerate(kod)})
m = agac(e, 6, SAY + ab + a)
print(f"\nsirali kodlama, kod sirasi karistirilinca: sinama "
      f"{sum(tahmin(m, x) == x['supheli'] for x in s) / len(s):.4f}")

g = sum(x["supheli"] for x in EGT) / len(EGT)
_, _, sh = hedef(eb, sb)
yeni = [x for x in sh if x["rota"] not in EK]
print(f"gorulmemis rotadaki {len(yeni)} abone: bir-sicak kodlamada 231 sutunun hepsi 0, "
      f"sirali kodlamada -1, hedef temelli kodlamada {g:.4f}")
print(f"kat icinde hesaplanan kodlamanin egitimdeki sismesi "
      f"{SONUC['+ rota hedef temelli'][1] - SONUC['+ rota hedef, kat icinde'][1]:+.4f}, "
      f"sinamada geri alinan "
      f"{SONUC['+ rota hedef, kat icinde'][2] - SONUC['+ rota hedef temelli'][2]:+.4f}")
```

```
kodlama                       ek sutun  egitim katki  sinama katki
+ rota bir-sicak                   231       -0.0013       +0.0119
+ rota sirali                        1       +0.0185       +0.0119
+ rota hedef temelli                 1       +0.0648       -0.0357
+ rota hedef, kat icinde             1       +0.0053       -0.0238

sirali kodlama, kod sirasi karistirilinca: sinama 0.7778
gorulmemis rotadaki 10 abone: bir-sicak kodlamada 231 sutunun hepsi 0, sirali kodlamada -1, hedef temelli kodlamada 0.2222
kat icinde hesaplanan kodlamanin egitimdeki sismesi +0.0595, sinamada geri alinan +0.0119
```

İlk iki satır aynı katkıyı iki farklı fiyata veriyor: bir-sıcak kodlama 231 ek sütun karşılığında
0,0119, sıralı kodlama tek sütun karşılığında yine 0,0119. Üç abonelik bu fark tek başına
okunmazdı; okunan şey **fiyat farkıdır.** Bir-sıcak kodlamanın maliyeti kardinaliteyle birlikte
doğrusal büyür ve 756 satırlık bir eğitim kümesinde 231 sütun, her sütuna ortalama üç satır
düşmesi demektir.

Sıralı kodlamanın bu tabloda işe yaramasının bir sebebi var ve karıştırma sınaması onu ortaya
koyuyor. Kod numaraları rastgele verilmemiş; önce bölgeye, sonra ağa katılma dalgasına göre
sıralanmışlar, dolayısıyla "rota numarası küçüktür" demek bir şeyler söylüyor. Eşleme
karıştırıldığında sınama sayısı 0,7778'e, yani rota alanı hiç kullanılmamış gibi bir yere düşüyor.
Sıralı kodlamanın katkısı kategorilerden değil, **kodun kendi sırasından** gelir; sıra uydurma
olduğunda katkı sıfırdır.

Üçüncü satır dersin asıl sayısıdır. Hedef temelli kodlama eğitim kümesinde 0,0648 kazandırıyor ve
sınama kümesinde 0,0357 kaybettiriyor. İki sayının ters işareti tesadüf değil, aynı olayın iki
yüzüdür. Rota başına ortalama 3,3 abone düşüyor; üç abonelik bir rotanın şüpheli oranı, o üç
satırın etiketlerinin ortalamasıdır ve her satırın kendi etiketi o ortalamanın üçte biridir. Ağaç
bu sütunu okuduğunda satırın etiketinin bir kopyasını okumuş olur. Eğitim kümesinde 0,9114 çıkması
öğrenmenin değil, kendini okumanın sonucudur.

Kat içinde hesaplama bunu düzeltir. Her satırın kodlama değeri, o satırın bulunmadığı dört kattan
çıkarıldığında eğitimdeki şişmenin 0,0595'i kayboluyor ve sınama sayısı 0,0119 geri geliyor. Yine
de kalan katkı −0,0238'dir: sızıntı geri çekildikten sonra bile hedef temelli kodlama bu alanda
tabanın altında kalıyor. Ölçülen sonuç şudur — sızıntıyı gidermek adımı iyi yapmaz, yalnız adımın
gerçek katkısını görünür kılar.

Son satır kodlamanın sessiz yüzüdür. Sınama kümesinde on abone, eğitim kümesinde hiç görülmemiş
altı rotaya bağlı. Bir-sıcak kodlama onlara 231 sıfırdan oluşan bir satır veriyor, sıralı kodlama
eğitim aralığının dışında bir −1 veriyor, hedef temelli kodlama eğitim kümesinin genel oranını,
0,2222'yi veriyor. Üçü de bir karardır ve üçü de kodlamanın parçasıdır; hiçbiri kendiliğinden
doğru değildir.

#### Özet

- Kategorik alanlar hiç kullanılmadığında model 0,7302 ile taban çizgisinin altında kalıyor; beş
  kategorili bölge alanı bir-sıcak kodlandığında 0,7778'e çıkıyor, katkı +0,0476.
- Kardinalitesi 240 olan rota alanı bir-sıcak kodlandığında 231 ek sütuna karşılık +0,0119, sıralı
  kodlandığında tek sütuna karşılık yine +0,0119 veriyor; aynı katkının iki farklı fiyatı vardır.
- Sıralı kodlamanın katkısı kodun kendi sırasından gelir: kategori–tamsayı eşlemesi
  karıştırıldığında sınama sayısı 0,7778'e, yani alan hiç kullanılmamış düzeye düşüyor.
- Hedef temelli kodlama eğitimde +0,0648, sınamada −0,0357 veriyor; rota başına 3,3 abone düştüğü
  için her satırın kendi etiketi kendi kodlama değerinin içindedir.
- Kat içinde hesaplama eğitimdeki şişmenin 0,0595'ini siliyor ve sınamada 0,0119 geri getiriyor,
  ama kalan katkı −0,0238'dir; sızıntıyı gidermek adımı iyi yapmaz, gerçek katkısını gösterir.

#### Sonraki Adım

Bu derste memnuniyet sütunu her abone için dolu kabul edildi. Ham kayıtta öyle değil: memnuniyet
bir anket yanıtıdır ve anketi yanıtlamayan abone vardır. Şimdiye kadarki bütün derslerde o boşluk
sessizce eğitim kümesinin ortancasıyla dolduruldu ve doldurmanın kendisi hiç ölçülmedi. Sonraki
ders boşluğu geri koyar ve üç seçeneği yan yana basar: ortanca ile doldurmak, doldurup yanına bir
eksiklik göstergesi eklemek ve eksik satırı eğitim kümesinden atmak. Eksikliğin abonenin kendisi
hakkında bilgi taşıdığı durumda göstergenin katkısı ölçülür ve satır atmanın ayrılmış kümede
niçin uygulanamayacağı sayıyla gösterilir.

### Eksik Değer Stratejileri

Source: https://academia.sh/tr/kurslar/oznitelik-muhendisligi/eksik-deger-stratejileri

Eksik bir sütun için üç stratejinin ayrılmış kümede ayrı ayrı ölçülmesi: ortanca ile doldurma 0,7619, doldurma ile birlikte eksiklik göstergesi eklemek 0,8333, eksik satırı eğitim kümesinden atmak 0,7659 veriyor ve göstergenin katkısı +0,0714, yani on sekiz abonedir. Gösterge, hiç eksik olmayan sütunun verdiği 0,7778 üst sınırını da 0,0555 geçiyor, çünkü eksikliğin kendisi doldurulan değerin taşımadığı bir bilgi taşıyor: anketi yanıtlamayan abonelerde şüpheli oranı 0,5276, yanıtlayanlarda 0,1383.

Önceki üç derste memnuniyet sütunu her abone için dolu kabul edildi. Ham kayıtta öyle değil.
Memnuniyet bir anket yanıtıdır ve anketi yanıtlamayan abone vardır. Boşluğun ne yapılacağı bir
karardır ve şimdiye kadar o karar hiç görünmedi, çünkü sessizce alındı.

Doldurma yordamının kendisi bu derste kurulmuyor. Eksik değerin bulunması, ortanca ile
doldurulması ve satırın atılması M26'nın Veri Toplama ve Hazırlama kursunda ölçüldü; oradaki ölçü
**kaç satırın etkilendiğiydi**. Buradaki soru başkadır: aynı üç seçenek ayrılmış kümedeki sayıyı
ne kadar oynatıyor ve **eksikliğin kendisi** bir öznitelik midir.

- **HA26.** Anketi yanıtlamama **kurgudur** ve etiketten bağımsız değildir: kaçak ya da arıza
  şüphesi taşıyan aboneler anketi daha seyrek yanıtlar. Kurguya bilerek konmuştur ve bu dersin
  ölçtüğü şeydir.
- **HA27.** Eksiklik yalnız memnuniyet sütunundadır. Öteki on sütun ve etiket eksiksizdir.
- **HA28.** Doldurma değeri **eğitilmiş bir nesnedir**: ortanca yalnız eğitim kümesindeki dolu
  satırlardan hesaplanır ve iki kümeye de o değer uygulanır.
- **HA29.** Doldurma istatistiğinin kendisi bir karardır: ortanca seçildi, çünkü memnuniyet
  sıralayıcı bir ölçekte ölçülür ve o ölçekte ortalama meşru bir özet değildir. Seçim değişirse
  dolan değer de değişir.
- **HA30.** **Eksiklik göstergesi**, satırın o sütununun boş olup olmadığını söyleyen bir ikili
  sütundur. Doldurmanın yerine değil, yanına eklenir.
- **HA31.** Ölçüm kümesi, bölme, model ve taban çizgisi önceki derslerle aynıdır: 756/252/252,
  derinlik 6 karar ağacı, sınama kümesinde 0,7579.
- **HA32.** Eksiksiz sütunla ölçülen sayı bir **üst sınır** olarak basılır: eksiklik hiç
  olmasaydı ne okunurdu. Bu satır bir strateji değil, karşılaştırma çizgisidir.
- **HA33.** Sınama kümesi 252 abonedir, bir abone 0,0040 eder.

#### Boşluğun Kendisi Ne Kadar Konuşuyor

Kurulum tabloyu üretir, anket eksikliğini koyar ve iki kümede eksikliğin etiketle ilişkisini basar.

```python
# eksik.py — MODELDIR. Kurgu abone tablosu ayni tohumla uretilir; memnuniyet
# sutunu ham kayittaki gibi KURGU bir anket yaniti olarak eksikli hale getirilir.
import math

TOHUM, ADAY, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


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


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE, OKUMA = [], {}
for i in range(ADAY):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
for k in ABONE:
    r = uretec(TOHUM + 7000 + k["no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        OKUMA.setdefault(k["no"], []).append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()

VERI = []
for k in ABONE:
    v = OKUMA.get(k["no"])
    if v is None:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    x = {"hane": k["hane"], "memnuniyet": k["memnuniyet"], "donem": len(v),
         "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35)}
    for b in BOLGE:
        x["b_" + b[0]] = int(k["bolge"][0] == b[0])
    VERI.append(x)

ar = uretec(TOHUM + 31000)                  # anketi yanitlamama KURGUDUR
for x in VERI:
    x["anket"] = None if ar() < (0.55 if x["supheli"] else 0.15) else x["memnuniyet"]


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT, SIN = K[:756], K[1008:]
TABAN = sum(x["supheli"] == 0 for x in SIN) / len(SIN)
ALAN = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem", "sifir_okuma",
        "b_kuzey", "b_guney", "b_dogu", "b_bati", "b_merkez"]
print(f"{'kume':<8} {'satir':>6} {'eksik':>6} {'eksik orani':>12} "
      f"{'eksiklerde supheli':>19} {'tamlarda':>9}")
for ad, s in (("egitim", EGT), ("sinama", SIN)):
    e = [x for x in s if x["anket"] is None]
    t = [x for x in s if x["anket"] is not None]
    print(f"{ad:<8} {len(s):>6} {len(e):>6} {len(e) / len(s):>12.4f} "
          f"{sum(x['supheli'] for x in e) / len(e):>19.4f} "
          f"{sum(x['supheli'] for x in t) / len(t):>9.4f}")
d = sorted(x["anket"] for x in EGT if x["anket"] is not None)
ORTANCA = d[len(d) // 2]                    # OGRENME: yalniz egitim kumesinden
print(f"egitimden ogrenilen ortanca {ORTANCA}, taban cizgisi {TABAN:.4f}")
```

```
kume      satir  eksik  eksik orani  eksiklerde supheli  tamlarda
egitim      756    163       0.2156              0.5276    0.1383
sinama      252     58       0.2302              0.6207    0.1289
egitimden ogrenilen ortanca 4, taban cizgisi 0.7579
```

Son iki sütun dersin bütün konusudur. Anketi yanıtlamayan abonelerde şüpheli oranı 0,5276,
yanıtlayanlarda 0,1383. Boşluk boş değildir; sütunun **taşıdığı değerden** başka bir şey söyler.
Doldurma kararı bu farkı ya korur ya siler.

Doldurma değerinin kendisi de bir eğitilmiş nesnedir. Eğitim kümesindeki dolu satırların ortancası
4 çıkıyor ve bu tek sayı iki kümeye birden uygulanıyor. Sınama kümesinin kendi ortancasına bakmak
daha "doğru" bir doldurma üretirdi, ama o değeri hesaplamak ayrılmış kümeye bakmak olurdu; ölçekte
kurulan disiplin burada da aynen geçerlidir. Eksiklik oranının iki kümede aynı çıkmaması da
beklenen bir şeydir: eğitimde 0,2156, sınamada 0,2302 ve aradaki fark bölmenin rastgeleliğinden
gelir.

#### Üç Strateji, Üç Sayı

Aşağıdaki kod modeli yazar ve beş stratejiyi bir üst sınır satırıyla birlikte ölçer.

```python
def gini(s):
    p = sum(x["supheli"] for x in s) / len(s) if s else 0.0
    return 2 * p * (1 - p)


def agac(s, derinlik, alan, enaz=2):        # MODELDIR: karar agaci, derinlik 6
    p = sum(x["supheli"] for x in s) / len(s)
    en = None
    if derinlik and len(s) >= 2 * enaz and 0.0 < p < 1.0:
        for a in alan:
            d = sorted({x[a] for x in s})
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < enaz:
                    continue
                k = gini(s) - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(s)
                if en is None or k > en[0]:
                    en = (k, a, v, sol, sag)
    if en is None or en[0] <= 1e-9:
        return {"tahmin": int(p > 0.5)}
    return {"alan": en[1], "esik": en[2], "sol": agac(en[3], derinlik - 1, alan, enaz),
            "sag": agac(en[4], derinlik - 1, alan, enaz)}


def tahmin(d, x):
    while "tahmin" not in d:
        d = d["sol"] if x[d["alan"]] < d["esik"] else d["sag"]
    return d["tahmin"]


def doldur(s, deger):                       # UYGULAMA: bosluga tek bir deger
    return [dict(x, memnuniyet=(deger if x["anket"] is None else x["anket"]))
            for x in s]


def gosterge(s, deger):                     # UYGULAMA: doldurma + eksiklik sutunu
    return [dict(x, memnuniyet=(deger if x["anket"] is None else x["anket"]),
                 anket_eksik=int(x["anket"] is None)) for x in s]


SONUC = {}


def olc(ad, alan, e, s):
    m = agac(e, 6, alan)
    de = sum(tahmin(m, x) == x["supheli"] for x in e) / len(e)
    ds = sum(tahmin(m, x) == x["supheli"] for x in s) / len(s)
    SONUC[ad] = (len(e), len(alan), de, ds)
    print(f"{ad:<32} {len(e):>7} {len(alan):>6} {de:>8.4f} {ds:>8.4f}")


DO, DS = doldur(EGT, ORTANCA), doldur(SIN, ORTANCA)
print(f"{'yordam':<32} {'egitim':>7} {'sutun':>6} {'egitimde':>8} {'sinama':>8}")
olc("sutun tumuyle atildi", [a for a in ALAN if a != "memnuniyet"], EGT, SIN)
olc("ortanca ile dolduruldu", ALAN, DO, DS)
olc("doldurma + eksiklik gostergesi", ALAN + ["anket_eksik"],
    gosterge(EGT, ORTANCA), gosterge(SIN, ORTANCA))
olc("sabit deger -1 ile dolduruldu", ALAN, doldur(EGT, -1), doldur(SIN, -1))
olc("eksik satir egitimden atildi", ALAN,
    [x for x in DO if x["anket"] is not None], DS)
olc("eksiksiz sutun (ust sinir)", ALAN, EGT, SIN)
```

```
yordam                            egitim  sutun egitimde   sinama
sutun tumuyle atildi                 756     10   0.8439   0.7540
ortanca ile dolduruldu               756     11   0.8466   0.7619
doldurma + eksiklik gostergesi       756     12   0.8823   0.8333
sabit deger -1 ile dolduruldu        756     11   0.8823   0.8333
eksik satir egitimden atildi         593     11   0.9191   0.7659
eksiksiz sutun (ust sinir)           756     11   0.8466   0.7778
```

Ortanca ile doldurma 0,7619 veriyor: taban çizgisinin yalnız bir abone üstünde. Sütunu tümüyle
atmak 0,7540 veriyor: tabanın altında. İkisi arasında iki abonelik bir fark var ve bu, ölçüm
çözünürlüğünün altındadır. Yaygın olan iki seçenek de bu tabloda birbirinden ayırt edilemez.

Eğitim sütunu ayrıca okunmalıdır, çünkü stratejileri iki gruba ayırıyor. Doldurma ve sütun atma
eğitimde 0,84 dolayında kalıyor; gösterge ve sabit değer 0,8823'e çıkıyor; satır atma 0,9191'e.
İlk ikisinin eğitimdeki yükselişi ayrılmış kümede de karşılığını buluyor, üçüncüsününki bulmuyor.
Eğitim kümesindeki artışın hangi tür olduğu ancak yanına ayrılmış küme sayısı yazıldığında
anlaşılır; bu, önceki kursun kuralının bu tabloda çalışan halidir.

#### Katkı Boşluktan Geliyor

Farkları doldurma satırına göre okumak, hangi kararın gerçekten bir adım olduğunu ayırır.

```python
t = SONUC["ortanca ile dolduruldu"]
print(f"{'yordam':<32} {'katki':>8} {'abone':>6}")
for ad in ("sutun tumuyle atildi", "doldurma + eksiklik gostergesi",
           "sabit deger -1 ile dolduruldu", "eksik satir egitimden atildi",
           "eksiksiz sutun (ust sinir)"):
    k = SONUC[ad][3] - t[3]
    print(f"{ad:<32} {k:>+8.4f} {round(k * 252):>6}")
ae = [x for x in EGT if x["anket"] is None]
print(f"\negitimden atilan {len(ae)} satirin {sum(x['supheli'] for x in ae)} tanesi "
      f"supheli; egitimde kalan supheli sayisi "
      f"{sum(x['supheli'] for x in EGT) - sum(x['supheli'] for x in ae)} / "
      f"{sum(x['supheli'] for x in EGT)}")
print(f"sinamada anketi eksik {sum(1 for x in SIN if x['anket'] is None)} abone icin "
      f"yine de tahmin uretilir; satir atma ayrilmis kumede uygulanamaz")
```

```
yordam                              katki  abone
sutun tumuyle atildi              -0.0079     -2
doldurma + eksiklik gostergesi    +0.0714     18
sabit deger -1 ile dolduruldu     +0.0714     18
eksik satir egitimden atildi      +0.0040      1
eksiksiz sutun (ust sinir)        +0.0159      4

egitimden atilan 163 satirin 86 tanesi supheli; egitimde kalan supheli sayisi 82 / 168
sinamada anketi eksik 58 abone icin yine de tahmin uretilir; satir atma ayrilmis kumede uygulanamaz
```

Tek satır okunmaya değer: eksiklik göstergesi +0,0714, yani on sekiz abone. Bu, kursun şimdiye
kadarki en büyük katkısıdır ve nereden geldiği açıktır. Doldurma boşluğu kapatırken hangi
satırların boş olduğunu da siler; gösterge o bilgiyi geri koyar. Anketi yanıtlamayan abonelerde
şüpheli oranının 0,5276 olduğu bir tabloda, "yanıtlamamış" olmanın kendisi memnuniyet
puanından daha çok şey söyler.

Bunun kanıtı üst sınır satırındadır. Eksiklik hiç olmasaydı model 0,7778 okurdu; gösterge onu
0,0555 **geçiyor**. Bir öznitelik, eksiksiz halinden daha iyi bir sayı veriyorsa katkı sütunun
değerinden değil, sütunun eksik olma biçiminden geliyordur.

Sabit değerle doldurma aynı sayıyı, 0,8333'ü veriyor. Sebebi ağacın çalışma biçimidir: eksik
satırlara verilen −1, ölçeğin dışında tek bir değer olduğu için ağaç onu tek bir eşikle ayırabilir
ve sütunu örtük bir göstergeye çevirir. Bu eşitlik burada **ağaç için** ölçülmüştür; sütunları
toplayarak uzaklık hesaplayan ya da ağırlıkla çarpan bir yordam için −1 sıradan bir sayıdır ve
sonucu ayrıca ölçülmelidir.

Satır atma satırı iki ayrı sorun taşıyor. Eğitim kümesinde 0,9191 okuyor ama sınamada 0,7659'da
kalıyor; 593 satıra inen bir kümede ağaç daha çok ezberliyor. Asıl sorun ikinci çıktıdadır: atılan
163 satırın 86'sı şüpheli, yani azınlık sınıfının yarısından çoğu atılıyor ve eğitim kümesinde 168
şüpheliden yalnız 82'si kalıyor. Üçüncü ve kesin engel şudur: sınama kümesinde anketi eksik 58
abone var ve onlar için de bir tahmin üretilmesi gerekir. Satır atmak eğitimde bir seçenektir,
ayrılmış kümede seçenek değildir; bu yüzden hiçbir zaman tek başına bir strateji olamaz.

Katkının kaynağı da sorulmalı. Anketi yanıtlamamak tahmin anında bilinen bir olgudur ve bu haliyle
gerçek bir özniteliktir; katkı öğrenmeden gelir. Ama eksiklik denetim **sonucunda** oluşuyorsa —
şüpheli bulunan abone anketi yanıtlamayı bırakıyorsa — aynı sütun geleceği gören bir özniteliğe
dönüşür ve katkı sızıntıdan gelir. İki durum tabloda birbirinin aynısıdır. Ayrım yalnız eksikliğin
**ne zaman** oluştuğu bilinerek yapılır ve bu bilgi veride yoktur.

#### Özet

- Anketi yanıtlamayan abonelerde şüpheli oranı 0,5276, yanıtlayanlarda 0,1383; eksiklik etiketle
  ilişkilidir ve doldurma bu ilişkiyi siler.
- Ortanca ile doldurma 0,7619, sütunu tümüyle atmak 0,7540 veriyor; aradaki iki abonelik fark
  ölçüm çözünürlüğünün altındadır ve iki seçenek ayırt edilemez.
- Doldurmanın yanına eksiklik göstergesi eklemek 0,8333 veriyor, katkı +0,0714, yani on sekiz
  abone; bu sayı eksiklik hiç olmasaydı okunacak 0,7778 üst sınırını da 0,0555 geçiyor.
- Sabit değerle doldurma ağaçta göstergeyle aynı sayıyı veriyor, çünkü ölçeğin dışındaki tek bir
  değer ağaç için örtük bir göstergedir; bu eşitlik başka model aileleri için ölçülmemiştir.
- Eksik satırı eğitimden atmak azınlık sınıfının yarısından çoğunu atıyor (168 şüpheliden 82'si
  kalıyor) ve sınamadaki 58 abone için tahmin gerektiği için ayrılmış kümede hiç uygulanamaz.

#### Sonraki Adım

Bu derste azınlık sınıfının yarısının atılması bir yan etki olarak görüldü, ama sınıfların
büyüklük farkının kendisi hiç ölçülmedi. Eğitim kümesinde 756 abonenin 168'i şüpheli, sınama
kümesinde 252 abonenin 61'i. Bu oranda doğruluk yanıltıcı bir ölçüdür: hiç kimseyi şüpheli
işaretlemeyen taban çizgisi 0,7579 okur ve yakaladığı şüpheli sayısı sıfırdır. Sonraki ders bu
iki sayıyı yan yana basar ve dengesizliğe verilen üç yanıtı — azınlığı çoğaltma, çoğunluğu
seyreltme ve sınıf ağırlığı — hem doğrulukta hem yakalanan şüpheli sayısında ölçer. Ölçüt
kuramı, kesinlik ve duyarlılık tanımları bu kursun konusu değildir ve Denetimli Öğrenme kursuna
bırakılır.

### Dengesiz Sınıflar

Source: https://academia.sh/tr/kurslar/oznitelik-muhendisligi/dengesiz-siniflar

Sınıf dengesizliğine verilen üç yanıtın iki ölçüyle birden okunması: taban çizgisi doğrulukta 0,7579 verirken yakaladığı şüpheli sayısı sıfırdır, ham eğitim kümesiyle model 0,7778 doğruluk ve 61 şüpheliden 21'ini yakalar. Azınlığı iki kat çoğaltmak doğruluğu 0,7619'a indirip yakalananı 33'e çıkarır, çoğunluğu seyreltmek 0,7143 ve 36 verir, sınıf ağırlığı 3,5 ise 0,6349 ve 39 verir. Azınlığı iki kat çoğaltmak ile sınıf ağırlığı 2,0 sınama kümesindeki 252 abonenin hepsinde aynı tahmini üretir.

Önceki ders eksik satırların atılmasının azınlık sınıfının yarısından çoğunu götürdüğünü yan
etki olarak kaydetti. Sınıfların büyüklük farkının kendisi ise hiç ölçülmedi. Eğitim kümesinde 756
abonenin 168'i şüpheli, sınama kümesinde 252 abonenin 61'i. Bu tabloda **doğruluk** yanıltıcı bir
ölçüdür ve yanıltıcılığın büyüklüğü sayılabilir: hiç kimseyi şüpheli işaretlemeyen taban çizgisi
0,7579 okur ve yakaladığı şüpheli sayısı sıfırdır.

Bu ders dengesizliğe verilen üç yanıtı ölçer: azınlığı **çoğaltma**, çoğunluğu **seyreltme** ve
**sınıf ağırlığı** verme. Her yanıt iki sayıyla birden okunur; ölçüt kuramı bu kursun konusu
değildir. Doğru işaretlenen azınlık payına **kesinlik (precision)**, yakalanan azınlık payına
**duyarlılık (recall)** denir; ikisinin tanımı, birbirine karşı okunması ve eşik seçimi Denetimli
Öğrenme kursuna bırakılır. Burada yalnız ham sayılar basılır.

- **HA34.** Küme, bölme, öznitelikler ve model önceki derslerdekiyle aynıdır: 756/252/252,
  derinlik 6 karar ağacı, on bir öznitelik.
- **HA35.** İki ölçü basılır: **doğruluk** ve **yakalanan şüpheli sayısı** — sınama kümesindeki 61
  şüpheliden kaçının şüpheli işaretlendiği. Yanına iki ham sayı daha yazılır: kaçırılan şüpheli ve
  şüpheli işaretlenip şüpheli olmayan abone.
- **HA36.** Taban çizgisi değişmez: eğitim kümesinin en sık sınıfını her aboneye söylemek. Bu
  yordam doğrulukta 0,7579, yakalananda 0 verir.
- **HA37.** Çoğaltma azınlık satırlarını olduğu gibi yeniden yazar; yeni satır **üretilmez**.
- **HA38.** Seyreltme çoğunluk satırlarını rastgele azaltır ve eğitim kümesini küçültür; atılan
  satırlar geri gelmez.
- **HA39.** Sınıf ağırlığı, ağacın bölme ölçütünü ağırlıklı hesaplatır: her satırın katkısı
  kendi ağırlığıyla çarpılır. Model kodu bu derste ağırlık kabul edecek biçimde yazılır.
- **HA40.** Üç yanıt da **yalnız eğitim kümesine** uygulanır. Sınama kümesi hiçbir durumda
  çoğaltılmaz, seyreltilmez ya da ağırlıklandırılmaz; ölçü gerçek dağılım üzerinde okunur.

#### İki Sayı, Bir Taban

Kurulum kümeyi üretir ve taban çizgisini iki ölçüde birden basar.

```python
# dengesiz.py — MODELDIR. Kurgu abone tablosu ayni tohumla yeniden uretilir.
import math

TOHUM, ADAY, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


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


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE, OKUMA = [], {}
for i in range(ADAY):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
for k in ABONE:
    r = uretec(TOHUM + 7000 + k["no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        OKUMA.setdefault(k["no"], []).append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()

VERI = []
for k in ABONE:
    v = OKUMA.get(k["no"])
    if v is None:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    x = {"hane": k["hane"], "memnuniyet": k["memnuniyet"], "donem": len(v),
         "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35)}
    for b in BOLGE:
        x["b_" + b[0]] = int(k["bolge"][0] == b[0])
    VERI.append(x)


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT, SIN = K[:756], K[1008:]
ALAN = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem", "sifir_okuma",
        "b_kuzey", "b_guney", "b_dogu", "b_bati", "b_merkez"]
NE, NS = sum(x["supheli"] for x in EGT), sum(x["supheli"] for x in SIN)
print(f"egitim {len(EGT)} abone, supheli {NE} ({NE / len(EGT):.4f}); "
      f"sinama {len(SIN)} abone, supheli {NS} ({NS / len(SIN):.4f})")
print(f"cogunluk / azinlik orani egitimde {(len(EGT) - NE) / NE:.2f}")
print(f"taban cizgisi (en sik sinif): dogruluk "
      f"{sum(x['supheli'] == 0 for x in SIN) / len(SIN):.4f}, "
      f"yakalanan supheli 0 / {NS}, supheli isaretlenen 0")
```

```
egitim 756 abone, supheli 168 (0.2222); sinama 252 abone, supheli 61 (0.2421)
cogunluk / azinlik orani egitimde 3.50
taban cizgisi (en sik sinif): dogruluk 0.7579, yakalanan supheli 0 / 61, supheli isaretlenen 0
```

Son satır kursun taban çizgisini ikinci bir ölçüde gösteriyor ve tablo değişiyor. Doğrulukta
0,7579 iyi görünen bir sayıdır; yakalanan şüpheli sayısında sıfır, kullanılamaz bir sonuçtur. Aynı
yordam, aynı küme, iki ölçü, iki ayrı yargı. Dengesiz bir kümede ölçünün seçilmemiş olması, taban
çizgisinin seçilmemiş olmasıyla aynı şeydir.

#### Üç Yanıt

Aşağıdaki kod ağacı ağırlık kabul edecek biçimde yazar ve üç yanıtı sırayla ölçer.

```python
def gini(s):
    n = sum(x.get("w", 1.0) for x in s)
    if n <= 0:
        return 0.0
    p = sum(x.get("w", 1.0) * x["supheli"] for x in s) / n
    return 2 * p * (1 - p)


def agac(s, derinlik, alan, enaz=2):        # MODELDIR: agirlikli karar agaci
    n = sum(x.get("w", 1.0) for x in s)
    p = sum(x.get("w", 1.0) * x["supheli"] for x in s) / n
    en = None
    if derinlik and len(s) >= 2 * enaz and 0.0 < p < 1.0:
        for a in alan:
            d = sorted({x[a] for x in s})
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < enaz:
                    continue
                k = gini(s) - (sum(x.get("w", 1.0) for x in sol) * gini(sol)
                               + sum(x.get("w", 1.0) for x in sag) * gini(sag)) / n
                if en is None or k > en[0]:
                    en = (k, a, v, sol, sag)
    if en is None or en[0] <= 1e-9:
        return {"tahmin": int(p > 0.5)}
    return {"alan": en[1], "esik": en[2], "sol": agac(en[3], derinlik - 1, alan, enaz),
            "sag": agac(en[4], derinlik - 1, alan, enaz)}


def tahmin(d, x):
    while "tahmin" not in d:
        d = d["sol"] if x[d["alan"]] < d["esik"] else d["sag"]
    return d["tahmin"]


def cogalt(egt, kat):                       # azinlik satiri kat kez yazilir
    out = []
    for x in egt:
        out += [x] * (kat if x["supheli"] else 1)
    return out


def seyrelt(egt, tohum):                    # cogunluk azinlik boyuna indirilir
    az = [x for x in egt if x["supheli"]]
    cok = karistir([x for x in egt if not x["supheli"]], tohum)[:len(az)]
    return karistir(az + cok, tohum + 1)


def agirlikla(egt, w):
    return [dict(x, w=(w if x["supheli"] else 1.0)) for x in egt]


SONUC = {}


def olc(ad, egt):
    m = agac(egt, 6, ALAN)
    t = [tahmin(m, x) for x in SIN]
    d = sum(t[i] == SIN[i]["supheli"] for i in range(len(SIN))) / len(SIN)
    y = sum(1 for i, x in enumerate(SIN) if t[i] == 1 and x["supheli"] == 1)
    SONUC[ad] = (len(egt), d, y, sum(t))
    print(f"{ad:<30} {len(egt):>7} {d:>9.4f} {y:>10} {sum(t):>10}")


print(f"{'yordam':<30} {'egitim':>7} {'dogruluk':>9} {'yakalanan':>10} "
      f"{'isaretli':>10}")
print(f"{'taban (en sik sinif)':<30} {'-':>7} "
      f"{sum(x['supheli'] == 0 for x in SIN) / len(SIN):>9.4f} {0:>10} {0:>10}")
olc("ham egitim kumesi", EGT)
olc("cogaltma, azinlik iki kat", cogalt(EGT, 2))
olc("cogaltma, azinlik uc kat", cogalt(EGT, 3))
olc("seyreltme, cogunluk azinliga", seyrelt(EGT, TOHUM + 22000))
olc("sinif agirligi 2.0", agirlikla(EGT, 2.0))
olc("sinif agirligi 3.5", agirlikla(EGT, 3.5))
```

```
yordam                          egitim  dogruluk  yakalanan   isaretli
taban (en sik sinif)                 -    0.7579          0          0
ham egitim kumesi                  756    0.7778         21         37
cogaltma, azinlik iki kat          924    0.7619         33         65
cogaltma, azinlik uc kat          1092    0.7341         34         74
seyreltme, cogunluk azinliga       336    0.7143         36         83
sinif agirligi 2.0                 756    0.7619         33         65
sinif agirligi 3.5                 756    0.6349         39        109
```

İki sütun ters yönlerde ilerliyor. Doğruluk 0,7778'den 0,6349'a inerken yakalanan şüpheli sayısı
21'den 39'a çıkıyor. Ham eğitim kümesiyle model 61 şüpheliden 40'ını kaçırıyor; en sert
ağırlıkla 22'sini kaçırıyor ama şüpheli işaretlediği abone sayısı 37'den 109'a çıkıyor.

İşaretli sütunu tek başına da bir şey söylüyor. Sınama kümesinde 61 şüpheli var, ham eğitim
kümesiyle eğitilen model ise yalnız 37 abone işaretliyor. Model azınlığı **eksik** işaretliyor ve
bu bir ayar hatası değil, bölme ölçütünün doğrudan sonucudur: ölçüt yaprakta çoğunluğu söylemeyi
ödüllendirir ve azınlık oranı yüzde ellinin altında kaldığı sürece bir yaprağın azınlık demesi
ancak çok saf bir bölge bulunduğunda kârlıdır. Dengesizliğe verilen üç yanıt da tam olarak bu
hesabı bozar: kimi satırı ikinci kez yazarak, kimi karşı sınıfı azaltarak, kimi ağırlığı doğrudan
değiştirerek yaprakların azınlık deme eşiğini aşağı çeker. M26'nın İstatistiksel Analiz ve Keşifsel
Veri Analizi kursu dengesiz kümelerde oran okumanın tuzaklarını ölçmüştü; buradaki fark oranın
değil **kararın** değişiyor olmasıdır.

#### Katkı Hangi Sayıda Ölçülür

Aynı kararlar ham eğitim kümesine göre okunduğunda tablonun anlamı netleşiyor.

```python
h = SONUC["ham egitim kumesi"]
print(f"{'yordam':<30} {'dogruluk katki':>15} {'yakalanan +':>12} "
      f"{'kacirilan':>10} {'yanlis isaret':>14}")
for ad in ("cogaltma, azinlik iki kat", "cogaltma, azinlik uc kat",
           "seyreltme, cogunluk azinliga", "sinif agirligi 2.0",
           "sinif agirligi 3.5"):
    v = SONUC[ad]
    print(f"{ad:<30} {v[1] - h[1]:>+15.4f} {v[2] - h[2]:>+12} "
          f"{NS - v[2]:>10} {v[3] - v[2]:>14}")
a = agac(cogalt(EGT, 2), 6, ALAN)
b = agac(agirlikla(EGT, 2.0), 6, ALAN)
print(f"\ncogaltma iki kat ile sinif agirligi 2.0 ayni tahmini veren abone "
      f"{sum(tahmin(a, x) == tahmin(b, x) for x in SIN)} / {len(SIN)}")
```

```
yordam                          dogruluk katki  yakalanan +  kacirilan  yanlis isaret
cogaltma, azinlik iki kat              -0.0159          +12         28             32
cogaltma, azinlik uc kat               -0.0437          +13         27             40
seyreltme, cogunluk azinliga           -0.0635          +15         25             47
sinif agirligi 2.0                     -0.0159          +12         28             32
sinif agirligi 3.5                     -0.1429          +18         22             70

cogaltma iki kat ile sinif agirligi 2.0 ayni tahmini veren abone 252 / 252
```

Doğruluk sütununda beş satırın beşi de eksidir. Kursun kuralı harfi harfine uygulanırsa bu beş
adımın da katkısı yoktur ve hiçbiri alınmamalıdır. Ama yakalanan sütunu on iki ile on sekiz abone
arasında artıyor ve o abonelerin her biri, gözden kaçmış bir kaçak ya da arıza şüphesidir. Çelişki
yordamlarda değil, ölçünün seçiminde. Doğruluk seçildiği sürece dengesizliğe verilen her yanıtın
katkısı eksidir; ölçü değiştiğinde işaret de değişir. Hangi sayının önemli olduğu veriden
okunmaz, işten okunur — bir denetim ekibinin kaç yanlış adrese gidebileceği bir kaynak sorusudur.

Seyreltme satırı ayrıca bir kararlılık sorusu açıyor. Aynı seyreltme başka bir tohumla
yapılsaydı hangi 168 çoğunluk satırının kalacağı değişirdi ve model onunla birlikte değişirdi;
çoğaltma ve ağırlık ise hiçbir rastgele seçim içermez, aynı kümede her koşumda aynı ağacı üretir.
Bir hazırlama adımının katkısı kadar, katkısının **koşumdan koşuma ne kadar oynadığı** da
sorulmalıdır.

Son iki satırın maliyet yüzü açık. Sınıf ağırlığı 3,5 on sekiz şüpheliyi daha yakalarken yanlış
işaretlenen abone sayısını 16'dan 70'e çıkarıyor. Seyreltme ise başka bir bedel ödüyor: eğitim
kümesi 756 satırdan 336'ya iniyor ve atılan 420 çoğunluk satırındaki bilgi geri gelmiyor. Küçük
kümelerde bu bedel doğrudan modelin kararlılığına yansır.

Son çıktı iki yordamın aynı şey olduğunu gösteriyor. Azınlığı iki kat çoğaltmak ile azınlığa 2,0
sınıf ağırlığı vermek sınama kümesindeki 252 abonenin hepsinde aynı tahmini üretiyor. Sebep
ağacın bölme ölçütündedir: ölçüt satırların ağırlıklı toplamıyla hesaplanır ve bir satırı iki kez
yazmak, o satırın ağırlığını iki yapmakla aynı toplamı verir. İkisinin ayrıldığı tek yer satır
sayısına bakan durdurma koşuludur — çoğaltmada küme 924 satıra çıkar, ağırlıkta 756'da kalır — ve
bu tabloda o koşul hiçbir düğümde bağlamamıştır. Bellek ve süre açısından ağırlık ucuzdur;
çoğaltma aynı sonucu daha büyük bir kümeyle üretir.

#### Özet

- Taban çizgisi dengesiz kümede iki ayrı sayı verir: doğrulukta 0,7579, yakalanan şüpheli
  sayısında 61 üzerinden sıfır; doğruluk tek başına yanıltıcı bir tabandır.
- Ham eğitim kümesiyle model 0,7778 doğruluk verir ve 61 şüpheliden 21'ini yakalar, 40'ını
  kaçırır.
- Çoğaltma, seyreltme ve sınıf ağırlığının hepsi doğrulukta eksi katkı verir (−0,0159 ile −0,1429
  arası) ve yakalanan şüpheli sayısında on iki ile on sekiz abone arası artı verir; işaret ölçüyle
  birlikte döner.
- Seyreltme eğitim kümesini 756 satırdan 336'ya indirir ve atılan 420 çoğunluk satırındaki bilgi
  geri gelmez; çoğaltma ve ağırlık hiçbir satır atmaz.
- Azınlığı iki kat çoğaltmak ile sınıf ağırlığı 2,0 vermek sınama kümesindeki 252 abonenin
  hepsinde aynı tahmini üretir, çünkü ağacın bölme ölçütü ağırlıklı toplamla hesaplanır.

#### Sonraki Adım

Bu derste şüpheli abonelerin sayısı azlığı yüzünden sorun oldu; sonraki derste birkaç abonenin
**değeri** yüzünden sorun olacak. Ortalama tüketim sütununda eğitim kümesinin çeyrekler açıklığından
hesaplanan çitin dışında kalan aboneler var ve bunların bir bölümü gerçek uçlar, bir bölümü takılı
kalmış sayaçların ürettiği kayıt hataları. İkisi tabloda birbirinin aynısı görünür. Sonraki ders
tek bir kırpma kararını iki model ailesinde ölçer, aynı kararın eğim temelli yordamda artı, ağaç
temelli yordamda eksi katkı verdiğini gösterir ve hiçbir satır silmeden aykırı değerin
yönetilebileceğini sayıyla ortaya koyar.

### Aykırı Değer Yönetimi

Source: https://academia.sh/tr/kurslar/oznitelik-muhendisligi/aykiri-deger-yonetimi

Tek bir kırpma kararının iki model ailesinde ters işaret verdiğinin ölçülmesi: eğitim kümesinden öğrenilen çitle kırpma, eğim temelli doğrusal modele gerçek uçlarda +0,0040, takılı sayaç kaydı da varken +0,0317 katıyor; aynı karar karar ağacında −0,0278 ve −0,0357 veriyor. Çit dışındaki 34 abonenin şüpheli oranı 0,6765 olduğu için kırpma gerçek bilgiyi de siliyor ve hiçbir satır silinmeden 41 değer kırpılıyor.

Önceki ders şüpheli abonelerin **sayıca** azlığını ölçtü. Bu ders birkaç abonenin **değerce**
uzaklığını ölçer. Ortalama tüketim ve oynaklık sütunlarında, eğitim kümesinin çeyrekler
açıklığından hesaplanan çitin dışında kalan aboneler var. Bunların bir bölümü gerçek uçtur —
kalabalık bir haneden ya da yüksek tarifeli bir bağlantıdan gelir. Bir bölümü ise takılı kalmış
sayaçların ürettiği kayıt hatasıdır. Tabloda ikisi birbirinin aynısı görünür: büyük bir sayı.

Çeyrekler açıklığı, çit ve uç değerin bulunması M26'nın Veri Toplama ve Hazırlama kursunda
kuruldu; oradaki ölçü kaç satırın çit dışında kaldığıydı. Buradaki soru başkadır: **kırpma
kararı** ayrılmış kümedeki sayıyı hangi modelde ne kadar oynatıyor.

- **HA41.** Takılı sayaç arızası **kurgudur**: küçük bir abone kümesinin ortalama tüketimi otuz
  katına çıkmış olarak kaydedilir. Etiketten bağımsız seçilir.
- **HA42.** Bu arıza `01`'deki üç kusurdan farklıdır ve onların düzeltmeleriyle yakalanamaz:
  değer doğru birimdedir, doğru yazılmıştır ve tek satırdadır. Yalnız yanlıştır.
- **HA43.** **Çit eğitilmiş bir nesnedir**: çeyrekler yalnız eğitim kümesinden hesaplanır, çit
  çeyrekler açıklığının 1,5 katıyla kurulur ve iki kümeye de aynı çit uygulanır.
- **HA44.** Kırpma çit dışındaki değeri çite çeker. **Hiçbir satır silinmez**; sınama kümesindeki
  her abone için tahmin üretilir.
- **HA45.** İki model ailesi karşılaştırılır: eğim temelli doğrusal model ve derinlik 6 karar
  ağacı. Doğrusal model önceki derste kurulan disipline göre standartlaştırılmış sütunlarla
  eğitilir; ağaçta ölçeklemenin katkısı sıfır olduğu için ham sütunlarla eğitilir.

#### Uçun İki Kaynağı

Kurulum tabloyu üretir, arızayı koyar ve çiti eğitim kümesinden öğrenir.

```python
# aykiri.py — MODELDIR. Kurgu abone tablosu ayni tohumla uretilir; kucuk bir abone
# kumesinin tuketimi KURGU bir takili sayac arizasiyla otuz kat yuksek kaydedilir.
import math
import statistics

TOHUM, ADAY, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


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


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE, OKUMA = [], {}
for i in range(ADAY):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
for k in ABONE:
    r = uretec(TOHUM + 7000 + k["no"])
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        OKUMA.setdefault(k["no"], []).append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()

VERI = []
for k in ABONE:
    v = OKUMA.get(k["no"])
    if v is None:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    x = {"hane": k["hane"], "memnuniyet": k["memnuniyet"], "donem": len(v),
         "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35)}
    for b in BOLGE:
        x["b_" + b[0]] = int(k["bolge"][0] == b[0])
    VERI.append(x)

sr = uretec(TOHUM + 81000)                  # takili sayac arizasi KURGUDUR
for x in VERI:
    x["ariza"] = round(x["ort_tuketim"] * 30, 2) if sr() < 0.010 else x["ort_tuketim"]


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT, SIN = K[:756], K[1008:]
TABAN = sum(x["supheli"] == 0 for x in SIN) / len(SIN)
SAY = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem"]
ALAN = SAY + ["sifir_okuma", "b_kuzey", "b_guney", "b_dogu", "b_bati", "b_merkez"]
UC = ["ort_tuketim", "oynaklik"]


def ceyrek(v, q):
    s = sorted(v)
    i = q * (len(s) - 1)
    a = int(i)
    return s[a] if a + 1 >= len(s) else s[a] + (i - a) * (s[a + 1] - s[a])


def cit_ogren(egt, sutun, k=1.5):           # OGRENME: ceyrekler acikligi ve cit
    d = {}
    for a in sutun:
        q1, q3 = ceyrek([x[a] for x in egt], 0.25), ceyrek([x[a] for x in egt], 0.75)
        d[a] = (q1 - k * (q3 - q1), q3 + k * (q3 - q1))
    return d


def kirp(c, s):                             # UYGULAMA: hicbir satir silinmez
    return [dict(x, **{a: min(max(x[a], c[a][0]), c[a][1]) for a in c}) for x in s]


ar = [x for x in EGT if x["ariza"] != x["ort_tuketim"]]
print(f"takili sayac: egitimde {len(ar)} abone, sinamada "
      f"{sum(1 for x in SIN if x['ariza'] != x['ort_tuketim'])}; "
      f"bunlarin {sum(x['supheli'] for x in ar)} tanesi supheli")
c = cit_ogren(EGT, UC)
print(f"{'sutun':<12} {'ust cit':>8} {'en buyuk':>9} {'cit disi egitim':>16} "
      f"{'sinama':>7}")
for a in UC:
    print(f"{a:<12} {c[a][1]:>8.2f} {max(x[a] for x in EGT):>9.2f} "
          f"{sum(1 for x in EGT if x[a] > c[a][1]):>16} "
          f"{sum(1 for x in SIN if x[a] > c[a][1]):>7}")
u = [x for x in EGT if any(x[a] > c[a][1] for a in UC)]
print(f"cit disindaki {len(u)} abonenin supheli orani "
      f"{sum(x['supheli'] for x in u) / len(u):.4f}, taban cizgisi {TABAN:.4f}")
```

```
takili sayac: egitimde 9 abone, sinamada 1; bunlarin 2 tanesi supheli
sutun         ust cit  en buyuk  cit disi egitim  sinama
ort_tuketim     35.80     42.90               11       4
oynaklik        29.41     40.57               23       7
cit disindaki 34 abonenin supheli orani 0.6765, taban cizgisi 0.7579
```

Son satır kırpma kararının bedelini önceden söylüyor. Çit dışındaki 34 abonenin şüpheli oranı
0,6765; eğitim kümesinin genelinde bu oran 0,2222. Uçtaki abonelerin üçte ikisi şüpheli, yani uç
değerler gürültü değil, kümenin en bilgilendirici bölgesi.

#### Aynı Kırpma, İki Model

```python
def gini(s):
    p = sum(x["supheli"] for x in s) / len(s) if s else 0.0
    return 2 * p * (1 - p)


def agac(s, derinlik, alan, enaz=2):        # MODELDIR: karar agaci, derinlik 6
    p = sum(x["supheli"] for x in s) / len(s)
    en = None
    if derinlik and len(s) >= 2 * enaz and 0.0 < p < 1.0:
        for a in alan:
            d = sorted({x[a] for x in s})
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < enaz:
                    continue
                k = gini(s) - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(s)
                if en is None or k > en[0]:
                    en = (k, a, v, sol, sag)
    if en is None or en[0] <= 1e-9:
        return {"tahmin": int(p > 0.5)}
    return {"alan": en[1], "esik": en[2], "sol": agac(en[3], derinlik - 1, alan, enaz),
            "sag": agac(en[4], derinlik - 1, alan, enaz)}


def agac_tahmin(d, x):
    while "tahmin" not in d:
        d = d["sol"] if x[d["alan"]] < d["esik"] else d["sag"]
    return d["tahmin"]


def sikistir(z):
    if z >= 0:
        return 1.0 / (1.0 + math.exp(-z)) if z < 700 else 1.0
    e = math.exp(z) if z > -700 else 0.0
    return e / (1.0 + e)


def dogrusal(egt, alan, adim=0.5, tur=400):  # MODELDIR: egim temelli dogrusal model
    w, b, n = [0.0] * len(alan), 0.0, len(egt)
    for _ in range(tur):
        gw, gb = [0.0] * len(alan), 0.0
        for x in egt:
            h = sikistir(sum(w[i] * x[a] for i, a in enumerate(alan)) + b) - x["supheli"]
            for i, a in enumerate(alan):
                gw[i] += h * x[a]
            gb += h
        for i in range(len(alan)):
            w[i] -= adim * gw[i] / n
        b -= adim * gb / n
    return w, b


def standartla(egt, kumeler):               # OGRENME + UYGULAMA (onceki ders)
    p = {a: (statistics.fmean(x[a] for x in egt),
             statistics.pstdev([x[a] for x in egt])) for a in SAY}
    return [[dict(x, **{a: (x[a] - p[a][0]) / p[a][1] for a in SAY}) for x in k]
            for k in kumeler]


def kur(kaynak, kirpma):
    e = [dict(x, ort_tuketim=x[kaynak]) for x in EGT]
    s = [dict(x, ort_tuketim=x[kaynak]) for x in SIN]
    if kirpma:
        c = cit_ogren(e, UC)
        e, s = kirp(c, e), kirp(c, s)
    return e, s


SONUC = {}
print(f"{'durum':<38} {'egim temelli':>13} {'agac':>8}")
print(f"{'taban cizgisi':<38} {TABAN:>13.4f} {TABAN:>8.4f}")
for ad, kaynak, kirpma in (("yalniz gercek uc, kirpma yok", "ort_tuketim", False),
                           ("yalniz gercek uc, cit ile kirpildi", "ort_tuketim", True),
                           ("takili sayac da var, kirpma yok", "ariza", False),
                           ("takili sayac da var, cit ile kirpildi", "ariza", True)):
    e, s = kur(kaynak, kirpma)
    es, ss = standartla(e, [e, s])
    m = dogrusal(es, ALAN)
    d = sum(int(sikistir(sum(m[0][i] * x[a] for i, a in enumerate(ALAN)) + m[1]) > 0.5)
            == x["supheli"] for x in ss) / len(ss)
    t = agac(e, 6, ALAN)
    g = sum(agac_tahmin(t, x) == x["supheli"] for x in s) / len(s)
    SONUC[ad] = (d, g)
    print(f"{ad:<38} {d:>13.4f} {g:>8.4f}")
```

```
durum                                   egim temelli     agac
taban cizgisi                                 0.7579   0.7579
yalniz gercek uc, kirpma yok                  0.8016   0.7778
yalniz gercek uc, cit ile kirpildi            0.8056   0.7500
takili sayac da var, kirpma yok               0.7698   0.7857
takili sayac da var, cit ile kirpildi         0.8016   0.7500
```

Ağaç sütunu dört durumda üç ayrı sayı veriyor ve ilginç olan, kırpılmış iki satırın aynı sayıyı
okumasıdır. Kırpma her iki tabloyu da aynı yere getiriyor: çit üstündeki bütün değerler tek bir
noktaya toplandığında arızalı kayıtla gerçek uç birbirinden ayırt edilemez duruma geliyor.

#### Katkı Kaç Abone Eder

```python
print(f"{'kirpma karari':<34} {'egim temelli':>13} {'abone':>6} "
      f"{'agac':>8} {'abone':>6}")
for ad, a, b in (("yalniz gercek uc", "yalniz gercek uc, kirpma yok",
                  "yalniz gercek uc, cit ile kirpildi"),
                 ("takili sayac da var", "takili sayac da var, kirpma yok",
                  "takili sayac da var, cit ile kirpildi")):
    kd, kg = SONUC[b][0] - SONUC[a][0], SONUC[b][1] - SONUC[a][1]
    print(f"{ad:<34} {kd:>+13.4f} {round(kd * 252):>6} "
          f"{kg:>+8.4f} {round(kg * 252):>6}")
e, s = kur("ariza", False)
c = cit_ogren(e, UC)
print(f"\narizali tabloda ogrenilen ust cit: ort_tuketim {c['ort_tuketim'][1]:.2f}, "
      f"oynaklik {c['oynaklik'][1]:.2f}")
print(f"kirpilan deger: egitim {sum(1 for x in e for a in UC if x[a] > c[a][1])}, "
      f"sinama {sum(1 for x in s for a in UC if x[a] > c[a][1])}; "
      f"silinen satir 0")
print(f"arizanin egim temelli modele maliyeti "
      f"{SONUC['takili sayac da var, kirpma yok'][0] - SONUC['yalniz gercek uc, kirpma yok'][0]:+.4f}, "
      f"agaca {SONUC['takili sayac da var, kirpma yok'][1] - SONUC['yalniz gercek uc, kirpma yok'][1]:+.4f}")
print(f"alti derste kullanilan oznitelik sayisi {len(ALAN)}, uretilen yeni buyukluk 0")
```

```
kirpma karari                       egim temelli  abone     agac  abone
yalniz gercek uc                         +0.0040      1  -0.0278     -7
takili sayac da var                      +0.0317      8  -0.0357     -9

arizali tabloda ogrenilen ust cit: ort_tuketim 36.20, oynaklik 29.41
kirpilan deger: egitim 41, sinama 12; silinen satir 0
arizanin egim temelli modele maliyeti -0.0317, agaca +0.0079
alti derste kullanilan oznitelik sayisi 11, uretilen yeni buyukluk 0
```

İki satırda da işaretler ters. Kırpma eğim temelli modelde artı, ağaçta eksi katkı veriyor ve
takılı sayaç bulunduğunda iki yön de okunacak büyüklüğe çıkıyor: sekiz abone ve dokuz abone.
Mekanizma iki modelin uç değeri işleme biçiminde. Eğim temelli model her turda bütün satırların
hatasını toplayıp ağırlıkları o toplama göre kaydırır; ortalamadan otuz kat uzak bir satır o
toplamı tek başına belirler ve on bir ağırlığın hepsini birden çeker. Ağaç ise uç satırı tek bir
bölmeyle kenara ayırabilir ve kalan kümeye dokunmaz — ama kırpma o bölmeyi ortadan kaldırır, çünkü
çit üstündeki sıralama silinir ve orada kalan 34 abonenin 0,6765'lik şüpheli oranı bir eşiğe
dönüştürülemez duruma gelir.

Arıza satırı bunu ikinci yönden doğruluyor. Dokuz satırlık bir kayıt hatası eğim temelli modele
0,0317 kaybettiriyor, ağaca ise 0,0079 kazandırıyor — yani ağaç için ölçülmemiş bir fark. Kırpma
uygulandığında eğim temelli model tam olarak 0,8016'ya, arıza hiç olmasaydı okuyacağı sayıya
dönüyor. Aynı adım aynı veride bir modelin zararını tamamen geri alırken ötekini tabanın 0,0079
üstüne kadar indiriyor.

Kırpmanın yordamsal yüzü de kayda değer. Çit yalnız eğitim kümesinden öğrenildi, arızalı tabloda
üst çit 36,20'ye kaydı, 41 değer kırpıldı ve **silinen satır sıfır**. Kaynağı bilinmeyen bir uç
için tek bir doğru yanıt yoktur; ölçülebilen tek şey, o yanıtın hangi modelde kaç abone ettiğidir.

#### Özet

- Çit dışındaki 34 abonenin şüpheli oranı 0,6765, kümenin genelinde 0,2222; uç değerler bu tabloda
  gürültü değil, en bilgilendirici bölgedir.
- Aynı kırpma kararı eğim temelli doğrusal modelde artı, karar ağacında eksi katkı veriyor: gerçek
  uçlarda +0,0040 ve −0,0278, takılı sayaç kaydı da varken +0,0317 ve −0,0357.
- Eğim temelli model bütün satırların hatasını toplayarak ağırlık günceller ve ortalamadan otuz kat
  uzak bir satır o toplamı tek başına belirler; ağaç uç satırı tek bir bölmeyle ayırabilirken
  kırpma o bölmeyi ortadan kaldırır.
- Dokuz satırlık kayıt hatası eğim temelli modele 0,0317 kaybettiriyor, ağaçta 0,0079'luk
  ölçülmemiş bir fark bırakıyor; kırpma eğim temelli modeli tam olarak arızasız haldeki 0,8016'ya
  geri getiriyor.
- Çit yalnız eğitim kümesinden öğrenildi, 41 değer kırpıldı ve hiçbir satır silinmedi; sınama
  kümesindeki her abone için tahmin üretildi.

#### Sonraki Adım

Son çıktının son satırı altı dersin ortak sınırını söylüyor: kullanılan öznitelik sayısı on bir ve
üretilen yeni büyüklük sıfır. Altı derste yapılan her şey **var olan bir alanın okunma biçimini**
değiştirmekti — birimi düzeltmek, ölçeği eğitimden öğrenmek, kategoriyi sayıya çevirmek, boşluğun
kendisini bir sütuna yazmak, satırların ağırlığını değiştirmek, ucu çite çekmek. Hiçbir derste iki
sütunun oranı, farkı ya da çarpımı alınmadı; bir tarihten dönemsel gösterge, bir metinden uzunluk
çıkarılmadı. Ve ters yönde de bir şey yapılmadı: hiçbir sütun "katkısı ölçülmedi" gerekçesiyle
tablodan çıkarılmadı, on bir öznitelik altı ders boyunca olduğu gibi kaldı. Sonraki konu bu iki işi
üstlenir. Önce alan bilgisinden yeni sütunlar türetilir ve her birinin katkısı tek tek ölçülür —
katkısı sıfır çıkanlar da yazılır. Sonra ters soru sorulur: hangi sütunlar tablodan çıkarılmalı, üç
seçim yöntemi aynı kümede kaç öznitelik seçiyor ve **seçimin kendisi** ayrılmış kümedeki sayıyı ne
kadar şişiriyor.

## Öznitelik Üretimi ve Seçimi

Yeni sütun üretmenin, sütun atmanın ve hazırlamayı bir nesneye bağlamanın ayrılmış kümede ne kattığının sayılması: oran, fark, etkileşim ve eşik göstergesinin tek tek katkısı ile katkısı sıfır çıkanlar, bir tarihten ve kısa metinden çıkan sayı adedi, süzgeç, sarmalayıcı ve gömülü yöntemlerin seçtiği öznitelik sayısı ve seçimin bütün kümeden yapılmasının ürettiği şişme, bileşen sayısı ile ayrılmış küme sayısı arasındaki ilişki ve yorumlanabilirlikten kaybedilen, boru hattı olmadan bütün kümeden öğrenilen adım sayısı, kat dışında ve kat içinde hazırlamanın verdiği iki çapraz doğrulama sayısı.

### Öznitelik Üretme

Source: https://academia.sh/tr/kurslar/oznitelik-muhendisligi/oznitelik-uretme

Var olan sütunlardan yeni sütun çıkarmanın ayrılmış kümede ne kattığının tek tek sayılması: oran özniteliği 0,7778 olan sınama sayısını 0,7897'ye taşırken fark ve eğitimden öğrenilmiş gösterge 0,0040 katıyor, elle konan 25 m³ eşiği 0,0159 ve iki sütunun çarpımı 0,0238 kaybettiriyor, 40 m³ eşiği ise tam sıfır katkı üretiyor. Altı sütun birikimli olarak eklendiğinde eğitim sayısı 0,8466'dan 0,8677'ye çıkarken ayrılmış küme sayısı 0,7897'de tepe yapıp 0,7698'e düşüyor; tek tek pozitif katkı veren üç sütun birlikte eklendiğinde toplam katkı 0,0000 oluyor.

Önceki altı ders var olan sütunlar üzerinde çalıştı: eksik değer dolduruldu, ölçek yalnız eğitim
kümesinden öğrenildi, kategoriler kodlandı, dengesizlik ele alındı, aykırı değerler modele göre
değerlendirildi. Hepsinde sütun kümesi aynı kaldı. Hiç **yeni sütun** üretilmedi ve hiçbir sütun
atılmadı; model her derste aynı on bir sayıya baktı.

Bu ders sütun sayısını ilk kez artırır. Sorusu şudur: var olan sütunlardan aritmetikle çıkarılan
yeni bir sayı, modele **bilgi mi** ekler yoksa yalnız **arama alanı mı** genişletir. Kursun kuralı
burada da geçerlidir — bir hazırlama adımı ayrılmış kümede ölçülen katkısı kadar vardır — ve bu
derste altı öznitelik tek tek üretilip tek tek ölçülür. Katkısı sıfır ve eksi çıkanlar da tabloya
yazılır.

- **OS1.** Küme M27/K01'in **kurgu** abone tablosudur: aynı tohum (20260218), aynı bölme
  (756 eğitim, 252 doğrulama, 252 sınama), aynı etiket. Ayrılmış küme sınama kümesidir.
- **OS2.** Model kurs boyunca **sabit** tutulur: derinlik 6 karar ağacı. Değişen tek şey sütun
  kümesidir. Taban çizgisi en sık sınıfı söylemektir ve sınama kümesinde 0,7579'dur.
- **OS3.** Karar öncesi çizgi, hazırlama adımlarının bıraktığı on bir sütunun çizgisidir:
  eğitimde 0,8466, sınamada 0,7778.
- **OS4.** Ham okumalar hâlâ elde tutulur. `ort_tuketim` ve `oynaklik` bu okumaların bir özetidir
  ve o özetleme kendisi bir öznitelik üretme kararıydı; ders bu kararı geri açar.
- **OS5.** Satır düzeyinde hesaplanan öznitelikler (oran, fark, çarpım, sabit eşik) kümeden hiçbir
  şey öğrenmez ve bölmeden bağımsızdır.
- **OS6.** Kümeden bir sayı öğrenen öznitelik — bölge ortancasının üstünde olmak — o sayıyı
  **yalnız eğitim kümesinden** okur ve üç kümeye de aynı sayıyla uygulanır.
- **OS7.** Katkı, aynı modelin ayrılmış kümedeki sayısının karar öncesi çizgiden farkıdır. 252
  satırlık kümede bir satır 0,0040'a karşılık gelir.

#### Aynı Sütunlardan Altı Yeni Sayı

Kurulum M27/K01'inkiyle aynıdır ve aynı tohumla aynı kümeyi üretir. Bu kez ham okuma listesi de
saklanır, çünkü üretilecek özniteliklerden biri ona dayanır.

```python
# uretme.py — MODELDIR. M27/K01'in KURGU abone tablosu ayni tohumla yeniden uretilir;
# model derinlik 6 karar agacidir ve kurs boyunca sabit kalir.
import math

TOHUM, HAM, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


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


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE, VERI = [], []
for i in range(HAM):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
for k in ABONE:
    r, v = uretec(TOHUM + 7000 + k["no"]), []
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        v.append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()
    if not v:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    x = {"no": k["no"], "hane": k["hane"], "memnuniyet": k["memnuniyet"],
         "donem": len(v), "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35),
         "okuma": [round(q, 2) for q in v], "bolge_ad": k["bolge"][0]}
    for b in BOLGE:
        x["b_" + b[0]] = int(k["bolge"][0] == b[0])
    VERI.append(x)

TEMEL = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem", "sifir_okuma",
         "b_kuzey", "b_guney", "b_dogu", "b_bati", "b_merkez"]


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT, DOG, SIN = K[:756], K[756:1008], K[1008:]


def gini(s):
    p = sum(x["supheli"] for x in s) / len(s) if s else 0.0
    return 2 * p * (1 - p)


def agac(s, derinlik, alanlar, enaz=2):
    p, en = sum(x["supheli"] for x in s) / len(s), None
    if derinlik and len(s) >= 2 * enaz and 0.0 < p < 1.0:
        for a in alanlar:
            d = sorted({x[a] for x in s})
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < enaz:
                    continue
                k = gini(s) - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(s)
                if en is None or k > en[0]:
                    en = (k, a, v, sol, sag)
    if en is None or en[0] <= 1e-9:
        return {"tahmin": int(p > 0.5)}
    return {"alan": en[1], "esik": en[2],
            "sol": agac(en[3], derinlik - 1, alanlar, enaz),
            "sag": agac(en[4], derinlik - 1, alanlar, enaz)}


def tahmin(d, x):
    while "tahmin" not in d:
        d = d["sol"] if x[d["alan"]] < d["esik"] else d["sag"]
    return d["tahmin"]


def dogruluk(d, s):
    return sum(tahmin(d, x) == x["supheli"] for x in s) / len(s)


def taban(egt, s):
    c = int(sum(x["supheli"] for x in egt) * 2 > len(egt))
    return sum(x["supheli"] == c for x in s) / len(s)


def olc(alanlar, derinlik=6):
    m = agac(EGT, derinlik, alanlar)
    return dogruluk(m, EGT), dogruluk(m, SIN)
```

Üretilecek altı öznitelik beş türü temsil eder. **Oran**, iki sütunun bölümüdür: kişi başına
düşen tüketim, hanenin büyüklüğünü tüketimden ayırır. **Fark**, ham okumaların sonuncusu ile
ilki arasındaki değişimdir; `oynaklik` yalnız açıklığı taşır, yönü taşımaz. **Etkileşim
özniteliği** (interaction feature) iki sütunun çarpımıdır. **Eşik göstergesi** alan bilgisinden
gelir: tarife basamakları 25 ve 40 m³'te durur. Sonuncusu kümeden bir sayı **öğrenir** —
abonenin kendi bölgesindeki ortancanın üstünde olup olmadığı — ve o ortanca yalnız eğitim
kümesinden okunur.

```python
for x in VERI:
    o = x["okuma"]
    x["kisi_basi"] = round(x["ort_tuketim"] / x["hane"], 3)
    x["son_ilk_fark"] = round(o[-1] - o[0], 2)
    x["hane_memnuniyet"] = x["hane"] * x["memnuniyet"]
    x["esik_25"] = int(x["ort_tuketim"] > 25)
    x["esik_40"] = int(x["ort_tuketim"] > 40)

ORTANCA = {}
for b in BOLGE:
    v = sorted(x["ort_tuketim"] for x in EGT if x["bolge_ad"] == b[0])
    ORTANCA[b[0]] = v[len(v) // 2]
for x in VERI:
    x["bolge_ustu"] = int(x["ort_tuketim"] > ORTANCA[x["bolge_ad"]])

ONCE = olc(TEMEL)
YENI = ["kisi_basi", "son_ilk_fark", "bolge_ustu", "esik_25", "hane_memnuniyet",
        "esik_40"]
print(f"{'oznitelik':<18} {'tur':<12} {'egitimde':>9} {'ayrilmis':>9} {'katki':>8}")
print(f"{'-- yok --':<18} {'11 sutun':<12} {ONCE[0]:>9.4f} {ONCE[1]:>9.4f}")
for a, t in zip(YENI, ["oran", "fark", "ogrenilmis", "esik", "etkilesim", "esik"]):
    e, s = olc(TEMEL + [a])
    print(f"{a:<18} {t:<12} {e:>9.4f} {s:>9.4f} {s - ONCE[1]:>+8.4f}")
print(f"\ntaban cizgisi {taban(EGT, SIN):.4f}; 25 m3 ustu {sum(x['esik_25'] for x in VERI)} "
      f"satir, 40 m3 ustu {sum(x['esik_40'] for x in VERI)} satir")
```

```
oznitelik          tur           egitimde  ayrilmis    katki
-- yok --          11 sutun        0.8466    0.7778
kisi_basi          oran            0.8519    0.7897  +0.0119
son_ilk_fark       fark            0.8571    0.7817  +0.0040
bolge_ustu         ogrenilmis      0.8492    0.7817  +0.0040
esik_25            esik            0.8479    0.7619  -0.0159
hane_memnuniyet    etkilesim       0.8492    0.7540  -0.0238
esik_40            esik            0.8466    0.7778  +0.0000

taban cizgisi 0.7579; 25 m3 ustu 243 satir, 40 m3 ustu 2 satir
```

Katkı sütununda üç işaret birden var. En büyük kazanç oran özniteliğinden geliyor: 0,0119, yani
252 satırlık kümede üç abone. Bu, aritmetik olarak yeni bilgi taşımayan bir sütundur —
`ort_tuketim` ve `hane` zaten kümededir — ama ağaç bölmelerini **tek sütun üzerinde** yapar ve
iki sütunun bölümünü bir bölmeyle kuramaz. Oranın kattığı şey bilgi değil, o bilginin
**bölünebilir biçimidir**.

Fark özniteliği ile eğitimden öğrenilmiş gösterge birer satır kazandırıyor: 0,0040. Bu, tek bir
abonenin doğru sınıflanmasıdır ve rastlantıdan ayırt edilemeyecek kadar küçüktür. Böyle bir sayı
"katkı vardır" diye raporlanamaz; en fazla "ölçüldü ve sıfırdan ayrılamadı" diye raporlanır.

Bu iki sütun arasında bir tür farkı var ve kursun ikinci kuralı tam oradan geçiyor. `son_ilk_fark`
her satırda kendi içinde hesaplanır; kümedeki başka hiçbir satırı görmez, bölmeden önce de sonra
da aynı sayıyı verir. `bolge_ustu` ise bir sayıya — bölge ortancasına — dayanır ve o sayı veriden
**okunur**. Kodda bu ayrım tek satırda duruyor: ortanca sözlüğü `EGT` üzerinde kuruluyor, `VERI`
üzerinde değil. Kaynak `VERI` yapılsaydı sütunun değerleri hiç değişmezdi, sınama kümesindeki
252 satır kendi eşiklerinin belirlenmesine katkıda bulunmuş olurdu ve ölçülen katkının bir payı
öğrenmeden değil sızıntıdan gelirdi. Üretilen öznitelik de **öğrenilmiş bir nesnedir** ve
öğrendiği yeri yazmak, kattığı sayıyı yazmak kadar zorunludur. Bu ayrım beşinci derste boru
hattının kurucu ilkesi olacak.

#### Sıfırın İki Ayrı Nedeni

Tablonun en öğretici satırı 40 m³ eşiğidir: katkı **tam sıfır**, eğitim sayısı bile kılpayı
değişmemiş. Nedeni son satırda görünüyor — kümede 40 m³'ün üstünde yalnız 2 satır var. Sabite
yakın bir sütun hiçbir bölmede en az iki satırlık iki yan üretemez, dolayısıyla ağaç onu hiç
seçmez. Sütun eklenmiştir, ölçülmüştür ve modelin gördüğü ağaç birebir aynıdır.

25 m³ eşiği farklı bir sıfır türüdür ve aslında sıfır bile değil, eksidir: 0,0159 kayıp, dört
abone. Bu sütun 243 satırı ayırıyor, yani sabit değil. Ama taşıdığı bilgi `ort_tuketim`'in içinde
zaten var ve ağaç kendi eşiğini veriden **arayarak** buluyor. Elle konan eşik yeni bir şey
söylemiyor, buna karşılık her düğümde bir bölme adayı olarak yarışıyor; eğitim kümesinde biraz
daha iyi görünen bir bölme seçtirip ayrılmış kümede daha kötü bir ağaç üretiyor.

En büyük kayıp etkileşim özniteliğinden: 0,0238, altı abone. `hane` ile `memnuniyet`in çarpımı
iki sütunu tek sayıya sıkıştırıyor ve etiketle ilişkisi bu iki sütunun kendisinden zayıf. Ağaç
çarpımı seçtiğinde ayrımı daha kaba yapıyor. Alan bilgisinden gelmeyen, "olur da tutar" diye
üretilen etkileşimlerin tipik davranışı budur.

Üç ayrı sıfır-ya-altı nedeni birbirinden ayrılmalıdır: sütun **hiç bölünemiyor** (40 m³),
bilgisi **zaten var** (25 m³), ya da bilgiyi **bozarak** taşıyor (çarpım). Üçü de "katkısı yok"
diye özetlenirse tabloda kaybolan şey, hangi düzeltmenin işe yarayacağıdır.

#### Katkılar Toplanmaz

Tek tek ölçülen katkılar üç pozitif değer verdi: 0,0119, 0,0040, 0,0040. Toplamları 0,0199 eder.
Sütunlar birikimli olarak eklendiğinde ne olduğuna bakmak gerekir.

```python
birikim = list(TEMEL)
print(f"{'eklenen':<18} {'sutun':>6} {'egitimde':>9} {'ayrilmis':>9} {'katki':>8}")
for a in YENI:
    birikim.append(a)
    e, s = olc(birikim)
    print(f"{a:<18} {len(birikim):>6} {e:>9.4f} {s:>9.4f} {s - ONCE[1]:>+8.4f}")
UC = ["kisi_basi", "son_ilk_fark", "bolge_ustu"]
e, s = olc(TEMEL + UC[:2])
print(f"\nyalniz iki katkili sutun {e:.4f} {s:.4f} {s - ONCE[1]:+.4f}")
e, s = olc(TEMEL + UC)
print(f"yalniz uc katkili sutun  {e:.4f} {s:.4f} {s - ONCE[1]:+.4f}")
```

```
eklenen             sutun  egitimde  ayrilmis    katki
kisi_basi              12    0.8519    0.7897  +0.0119
son_ilk_fark           13    0.8598    0.7897  +0.0119
bolge_ustu             14    0.8611    0.7778  +0.0000
esik_25                15    0.8664    0.7698  -0.0079
hane_memnuniyet        16    0.8677    0.7698  -0.0079
esik_40                17    0.8677    0.7698  -0.0079

yalniz iki katkili sutun 0.8598 0.7897 +0.0119
yalniz uc katkili sutun  0.8611 0.7778 +0.0000
```

İki sütun birbirinden bağımsız olsaydı katkıları toplanırdı. Toplanmıyor. Tek tek 0,0119 ve 0,0040
katan iki sütun birlikte 0,0119 katıyor; üçüncüsü eklendiğinde toplam katkı **0,0000**'a iniyor.
Tek tek pozitif ölçülen üç öznitelik, birlikte karar öncesi çizgiye geri dönüyor.

Nedeni ağacın açgözlü olmasıdır: her düğümde o an en iyi görünen bölmeyi seçer ve seçim
geri alınmaz. Yeni bir sütun, kök düğümde daha iyi görünen bir bölme sunup ağacın geri kalanını
tümüyle başka bir yola sokabilir. Katkı, sütunun kendi bilgisi değil, **sütunun modelin arama
yoluna yaptığı etkidir** ve bu etki toplanabilir bir büyüklük değildir.

İki sütun ayrı ayrı okunmalıdır. Eğitim sayısı boyunca tek yönde artıyor: 0,8466'dan 0,8677'ye,
altı sütunun her biri onu biraz daha yukarı çekiyor. Ayrılmış küme sayısı ise on iki sütunda tepe
yapıp düşüyor ve on yedi sütunda karar öncesi çizginin altında kalıyor. M27/K01'in ikinci dersi
bu ayrışmayı kapasiteyi artırarak üretmişti; burada model sabit, artan tek şey **sütun sayısı**.
Öznitelik eklemek de kapasite artırmaktır.

Kursun kuralı bu tabloda somutlaşıyor. Altı sütun üretildi, altısı da makul bir gerekçeyle
üretildi ve ayrılmış kümede ölçüldüğünde geriye tek bir tanesi kaldı. Ölçülmeseydi altısı da
kümede kalırdı ve model on yedi sütunla 0,7698'de, karar öncesi çizginin 0,0080 altında
çalışırdı — üstelik eğitim sayısı 0,8677'ye çıktığı için tablo daha iyi görünürdü.

#### Özet

- Oran özniteliği ayrılmış küme sayısını 0,7778'den 0,7897'ye taşıyor (0,0119, üç abone); fark
  özniteliği ve eğitimden öğrenilmiş bölge göstergesi 0,0040 katıyor, yani birer abone.
- Katkısı sıfır ve eksi çıkanlar da yazılır: 40 m³ eşiği tam 0,0000, 25 m³ eşiği 0,0159 kayıp,
  iki sütunun çarpımı 0,0238 kayıp.
- Sıfırın nedeni ayrıştırılır: sütun sabite yakın olduğu için bölünemiyor, bilgisi başka sütunda
  zaten var, ya da bilgiyi bozarak taşıyor.
- Katkılar toplanmaz: tek tek 0,0119, 0,0040 ve 0,0040 katan üç sütun birlikte 0,0000 katıyor,
  çünkü katkı sütunun bilgisi değil modelin arama yoluna etkisidir.
- Sütun sayısı 11'den 17'ye çıkarken eğitim sayısı 0,8466'dan 0,8677'ye tek yönde artıyor,
  ayrılmış küme sayısı 0,7897'de tepe yapıp 0,7698'e düşüyor; öznitelik eklemek de kapasite
  artırmaktır.

#### Sonraki Adım

Bu dersin altı özniteliği sayısal sütunlardan aritmetikle çıkarıldı. Abone tablosunda ise
sayısal olmayan iki alan daha var: sözleşmenin başladığı tarih ve saha ekibinin bıraktığı kısa
not. İkisi de olduğu gibi bir modele verilemez; ikisinden de birden çok sayı çıkarılabilir. Bir
tarihten kaç sayı çıkar, kısa bir metinden kaç sayı çıkar ve bu sayıların **toplam** katkısı ne
olur — sonraki ders bunu ölçer.

### Zaman ve Metin Öznitelikleri

Source: https://academia.sh/tr/kurslar/oznitelik-muhendisligi/zaman-ve-metin-oznitelikleri

Bir tarihten ve bir kısa metinden beşer sayı çıkarıp toplam katkılarının ayrılmış kümede sayılması: karar öncesi çizgi 0,7897 iken tarihten çıkan beş sayı 0,0198 kaybettiriyor, nottan çıkan beş sayı 0,0317 katıyor, on sayı birlikte 0,0198 katıyor. Katkının tamamı tek bir göstergeden geliyor; mühür izi göstergesi tek başına 0,0357 katarken metin uzunluğu 0,0357 ve sözcük sayısı 0,0516 kaybettiriyor. Bir alandan kaç sayı çıktığı katkıyı belirlemiyor, alanın etiketle ilişkisi belirliyor.

Önceki ders altı öznitelik üretti, altısını da tek tek ölçtü ve geriye tek bir sütun kaldı: kişi
başına tüketim, karar öncesi çizgiyi 0,7778'den 0,7897'ye taşıyan oran özniteliği. Öteki beşi
kümeden çıkarıldı. Karar öncesi çizgi bu dersten itibaren **on iki sütunun** çizgisidir.

Abone tablosunda modele verilmemiş iki alan daha var ve ikisi de sayı değil: sözleşmenin
başladığı **tarih** ve saha ekibinin okuma sırasında bıraktığı **kısa not**. Bir karar ağacı
bunların hiçbirini olduğu gibi bölemez; tarih bir sıralı değerdir ama sıralaması yılın
dönemselliğini taşımaz, metin ise hiç sıralı değildir. İkisinden de birden çok sayı çıkarılabilir
ve bu dersin ölçüsü tam olarak budur: **bir alandan kaç sayı çıkıyor ve o sayıların toplam
katkısı ne.**

- **OS8.** Karar öncesi çizgi on iki sütundur: M27/K01'in on bir sütunu ve `kisi_basi`. Model
  değişmez: derinlik 6 karar ağacı. Taban çizgisi 0,7579'dur.
- **OS9.** Sözleşme tarihi ve saha notu **kurgudur**, aynı tohumdan (20260218) abone numarasına
  bağlı olarak üretilir ve tabloya bu derste eklenir.
- **OS10.** Saha notu **okuma döneminde** yazılır; şüphe etiketi dönem kapandıktan sonra
  kesinleşir. Notun içeriği tahmin anında eldedir.
- **OS11.** Mühür izi kaydı şüpheli abonelerde 0,42, ötekilerde 0,10 olasılıkla nota girer;
  erişim kaydı dönem sayısı üçten azsa 0,30, değilse 0,08 olasılıkla girer.
- **OS12.** Ek not 0,25 olasılıkla girer ve üç eş olasılıklı konudan biridir; araç kaydı böylece
  yaklaşık 0,08 olasılıkla nota düşer ve etiketten **bağımsızdır**; tuzak göstergedir.
- **OS13.** Metin işlemleri M26/K04'ün veri çerçeveleri kursunda kuruldu; burada dizgi yordamı
  tekrarlanmaz, yalnız metinden çıkan sayı ölçülür. Sözcük torbası ve gömme uzayı bu kursun
  konusu değildir.
- **OS14.** Katkı, on iki sütunun ayrılmış kümedeki sayısından farktır; bir satır 0,0040'tır.

#### Bir Tarihten Beş, Bir Nottan Beş Sayı

Kurulum önceki derslerinkiyle aynı kümeyi üretir; tek fark, kalıcı olduğu ölçülmüş `kisi_basi`
sütununun baştan eklenmiş olmasıdır.

```python
# ORTAK — MODELDIR. M27/K01'in KURGU abone tablosu ayni tohumla yeniden uretilir;
# model derinlik 6 karar agacidir. Onceki dersten kalan tek yeni sutun kisi_basi.
import math

TOHUM, HAM, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


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


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE, VERI = [], []
for i in range(HAM):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
for k in ABONE:
    r, v = uretec(TOHUM + 7000 + k["no"]), []
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        v.append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()
    if not v:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    x = {"no": k["no"], "hane": k["hane"], "memnuniyet": k["memnuniyet"],
         "donem": len(v), "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35),
         "bolge_ad": k["bolge"][0]}
    for b in BOLGE:
        x["b_" + b[0]] = int(k["bolge"][0] == b[0])
    VERI.append(x)

TEMEL = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem", "sifir_okuma",
         "b_kuzey", "b_guney", "b_dogu", "b_bati", "b_merkez"]
TABAN_ALAN = TEMEL + ["kisi_basi"]
for x in VERI:
    x["kisi_basi"] = round(x["ort_tuketim"] / x["hane"], 3)


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT, DOG, SIN = K[:756], K[756:1008], K[1008:]


def gini(s):
    p = sum(x["supheli"] for x in s) / len(s) if s else 0.0
    return 2 * p * (1 - p)


def agac(s, derinlik, alanlar, enaz=2):
    p, en = sum(x["supheli"] for x in s) / len(s), None
    if derinlik and len(s) >= 2 * enaz and 0.0 < p < 1.0:
        for a in alanlar:
            d = sorted({x[a] for x in s})
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < enaz:
                    continue
                k = gini(s) - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(s)
                if en is None or k > en[0]:
                    en = (k, a, v, sol, sag)
    if en is None or en[0] <= 1e-9:
        return {"tahmin": int(p > 0.5)}
    return {"alan": en[1], "esik": en[2],
            "sol": agac(en[3], derinlik - 1, alanlar, enaz),
            "sag": agac(en[4], derinlik - 1, alanlar, enaz)}


def tahmin(d, x):
    while "tahmin" not in d:
        d = d["sol"] if x[d["alan"]] < d["esik"] else d["sag"]
    return d["tahmin"]


def dogruluk(d, s):
    return sum(tahmin(d, x) == x["supheli"] for x in s) / len(s)


def taban(egt, s):
    c = int(sum(x["supheli"] for x in egt) * 2 > len(egt))
    return sum(x["supheli"] == c for x in s) / len(s)


def olc(alanlar, derinlik=6):
    m = agac(EGT, derinlik, alanlar)
    return dogruluk(m, EGT), dogruluk(m, SIN)
```

Tarihten çıkarılan beş sayı iki farklı soruya karşılık gelir. Ay, çeyrek, haftanın günü ve yılın
kaçıncı günü **dönemsel** göstergelerdir: her biri kendi içinde dönen bir sayıdır ve amaçları
mevsimlik bir düzeni yakalamaktır. Ay ile çeyrek aynı takvim tarihinde her yıl aynı değeri verir;
haftanın günü yıldan yıla kayar, yılın kaçıncı günü ise artık yılda bir gün oynar. Sözleşme
yaşı ise **doğrusal** bir sayıdır ve her gün büyür.
Nottan çıkarılan beş sayı da iki gruba ayrılır: iki tanesi metnin **biçimini** ölçer (karakter
sayısı, sözcük sayısı), üç tanesi belirli bir **anahtar sözcüğün** varlığını gösterir.

```python
import datetime

BAS = datetime.date(2024, 1, 1)
KOK = ["sayac erisimi saglandi", "okuma normal", "abone evde degildi",
       "kapak kirli", "olcum tekrarlandi", "vana kontrol edildi"]
EK = ["arac park halinde", "kapi zili calismiyor", "bahce kapisi acik"]

for x in VERI:
    r = uretec(TOHUM + 61000 + x["no"])
    x["sozlesme_gun"] = int(r() * 1460)
    g = BAS + datetime.timedelta(days=x["sozlesme_gun"])
    x["ay"], x["ceyrek"] = g.month, (g.month - 1) // 3 + 1
    x["hafta_gunu"], x["yil_ici_gun"] = g.weekday(), g.timetuple().tm_yday
    x["sozlesme_yasi"] = 1460 - x["sozlesme_gun"]
    p = [KOK[int(r() * len(KOK))]]
    if r() < (0.42 if x["supheli"] else 0.10):
        p.append("muhur izi var")
    if r() < (0.30 if x["donem"] < 3 else 0.08):
        p.append("erisim saglanamadi")
    if r() < 0.25:
        p.append(EK[int(r() * len(EK))])
    n = ", ".join(p)
    x["saha_notu"], x["not_uzunluk"], x["not_sozcuk"] = n, len(n), len(n.split())
    x["a_muhur"] = int("muhur" in n)
    x["a_erisim"] = int("erisim saglanamadi" in n)
    x["a_arac"] = int("arac" in n)

ZAMAN = ["ay", "ceyrek", "hafta_gunu", "yil_ici_gun", "sozlesme_yasi"]
METIN = ["not_uzunluk", "not_sozcuk", "a_muhur", "a_erisim", "a_arac"]
for i in (4, 9, 10):
    print(f"not ornegi: {VERI[i]['saha_notu']}")
print(f"muhur izi kayitli {sum(x['a_muhur'] for x in VERI)} satir; suphelilerin "
      f"{sum(x['a_muhur'] for x in VERI if x['supheli']) / sum(x['supheli'] for x in VERI):.3f}"
      f"'unde, otekilerin "
      f"{sum(x['a_muhur'] for x in VERI if not x['supheli']) / sum(1 - x['supheli'] for x in VERI):.3f}"
      f"'unde\n")
ONCE = olc(TABAN_ALAN)
print(f"{'blok':<16} {'sayi':>4} {'egitimde':>9} {'ayrilmis':>9} {'katki':>8}")
print(f"{'-- yok --':<16} {0:>4} {ONCE[0]:>9.4f} {ONCE[1]:>9.4f}")
for ad, al in (("tarihten", ZAMAN), ("metinden", METIN), ("ikisi", ZAMAN + METIN)):
    e, s = olc(TABAN_ALAN + al)
    print(f"{ad:<16} {len(al):>4} {e:>9.4f} {s:>9.4f} {s - ONCE[1]:>+8.4f}")
```

```
not ornegi: olcum tekrarlandi
not ornegi: abone evde degildi, erisim saglanamadi, arac park halinde
not ornegi: kapak kirli, muhur izi var
muhur izi kayitli 219 satir; suphelilerin 0.453'unde, otekilerin 0.096'unde

blok             sayi  egitimde  ayrilmis    katki
-- yok --           0    0.8519    0.7897
tarihten            5    0.8611    0.7698  -0.0198
metinden            5    0.8942    0.8214  +0.0317
ikisi              10    0.8955    0.8095  +0.0198
```

Üç sayı yan yana durunca alan sayısıyla katkı arasında bağ olmadığı görünüyor. İki alandan beşer
sayı çıktı; biri 0,0317 kazandırdı, öteki 0,0198 kaybettirdi. On sayı birlikte verildiğinde toplam
katkı 0,0198'e iniyor — yani tarih alanı, metin alanının kazandırdığı sekiz abonenin üçünü geri
alıyor. Eğitim sayısı ise her üç kurulumda da yükseliyor: 0,8519'dan 0,8955'e. Sütun sayısı
arttıkça eğitim sayısının artması ölçüm değil, tanımın gereğidir.

Tarih bloğunun eksi çıkması şaşırtıcı değildir, çünkü bu kurguda sözleşme tarihi ile şüphe
arasında hiçbir bağ kurulmamıştır. Kaybın nedeni bağın yokluğu değil, **beş sütunun bölme adayı
olarak yarışması**: yılın kaçıncı günü sütununun 366 ayrı değeri vardır ve bu kadar çok eşik
adayı arasından eğitim kümesinde iyi görünen bir tanesini bulmak kolaydır. Çok değerli bir gürültü
sütunu, az değerli bir gürültü sütunundan daha tehlikelidir.

#### Katkı Nereden Geliyor

Bloklar tek tek açıldığında toplam sayıların hangi sütundan geldiği görünür.

```python
print(f"{'tek oznitelik':<16} {'kaynak':<8} {'egitimde':>9} {'ayrilmis':>9} {'katki':>8}")
for a in ZAMAN + METIN:
    e, s = olc(TABAN_ALAN + [a])
    print(f"{a:<16} {'tarih' if a in ZAMAN else 'metin':<8} {e:>9.4f} {s:>9.4f} "
          f"{s - ONCE[1]:>+8.4f}")
for ad, al in (("yalniz uc anahtar", ["a_muhur", "a_erisim", "a_arac"]),
               ("yalniz iki sayac", ["not_uzunluk", "not_sozcuk"]),
               ("yalniz muhur", ["a_muhur"])):
    e, s = olc(TABAN_ALAN + al)
    print(f"\n{ad:<18} {e:.4f} {s:.4f} {s - ONCE[1]:+.4f}", end="")
print()
```

```
tek oznitelik    kaynak    egitimde  ayrilmis    katki
ay               tarih       0.8519    0.7897  +0.0000
ceyrek           tarih       0.8519    0.7897  +0.0000
hafta_gunu       tarih       0.8532    0.7778  -0.0119
yil_ici_gun      tarih       0.8519    0.7897  +0.0000
sozlesme_yasi    tarih       0.8585    0.7817  -0.0079
not_uzunluk      metin       0.8823    0.7540  -0.0357
not_sozcuk       metin       0.8783    0.7381  -0.0516
a_muhur          metin       0.8876    0.8254  +0.0357
a_erisim         metin       0.8519    0.7857  -0.0040
a_arac           metin       0.8519    0.7897  +0.0000

yalniz uc anahtar  0.8876 0.8254 +0.0357
yalniz iki sayac   0.8942 0.7381 -0.0516
yalniz muhur       0.8876 0.8254 +0.0357
```

On özniteliğin dokuzu sıfır ya da eksi katkı veriyor. Kazancın tamamı `a_muhur` sütunundan
geliyor: tek başına 0,0357, yani dokuz abone. Üç anahtar sözcük göstergesi birlikte de 0,0357
veriyor ve yalnız mühür göstergesi de 0,0357 veriyor — öteki iki anahtar hiçbir şey eklemiyor.
Sayının kaynağı belli: mühür izi şüphelilerin 0,453'ünde, ötekilerin 0,096'sında kayıtlı, yani
bu gösterge etiketle ilişkilidir. Araç kaydı ise etiketten bağımsız üretildi ve katkısı tam sıfır
çıktı; tuzak gösterge tuzak olarak davrandı.

En sert satırlar metnin **biçim** ölçüleridir. Karakter sayısı 0,0357, sözcük sayısı 0,0516
kaybettiriyor; ikisi birlikte 0,0516 kaybettiriyor ve eğitim sayısını 0,8942'ye çıkarıyor. Bu iki
sütun aslında anahtar sözcük göstergelerinin bulanık bir kopyasıdır: not uzarsa içinde ek bir kayıt
var demektir, ama hangi kayıt olduğu belli değildir. Model uzunluğu kullanarak eğitim kümesinde
mühür kaydını dolaylı yoldan tahmin ediyor ve ayrılmış kümede bu dolaylı yol tutmuyor. Bir metinden
sayı çıkarmanın kolay yolu ile işe yarayan yolu aynı yol değildir.

Bir alandan çıkan sayı adedi, o alanın katkısını ölçmez. On sayı çıkarıldı, katkının tamamı
bir tanesinden geldi, öteki dokuz sayı toplamda 0,0159 götürdü. Alan başına sütun sayısını
artırmak modele daha çok bilgi vermez, daha çok **arama alanı** verir.

#### Notun Zamanı

Mühür göstergesi ile M27/K01'in sızıntı dersindeki denetim sütunu istatistik olarak aynı biçime
sahiptir: ikisi de şüpheli abonelerde daha sık görünen bir gösterge sütunudur. Aralarındaki tek
fark **zamandır**. Denetim ataması etiket kesinleştikten sonra yazılıyordu ve o yüzden sızıntıydı;
saha notu okuma döneminde, etiket kesinleşmeden yazılır ve tahminin verileceği anda kayıttadır.

Bu ayrım tabloya bakarak yapılamaz. Şişme ile katkı ayrılmış kümede aynı işareti taşır ve aynı
sütunda görünür. Ayrımı yalnız kaynak sistemin **yazma sırası** verir: bu değer hangi olaydan
sonra kaydediliyor. Sorunun kendisi M27/K01'de kuruldu; bu ders onu bir öznitelik üretme kararının
parçası hâline getiriyor. Metinden çıkarılan her gösterge için soru yeniden sorulur, çünkü aynı
metin alanı hem dönem içinde hem dönem sonrasında yazılan cümleleri taşıyabilir.

Sözcük torbası ve gömme uzayı gibi metni bütünüyle sayıya çeviren yöntemler bu kursun konusu
değildir; onlar M27/K06'da ele alınır. Burada ölçülen şey daha dar ve daha yaygın bir karardır:
elde bir serbest metin alanı varken ondan **birkaç gösterge** çıkarmak. Ölçüm bu kararın da
katkısız olabileceğini gösteriyor.

#### Özet

- Bir tarihten beş, bir kısa nottan beş öznitelik çıkarıldı; tarih bloğu 0,0198 kaybettirdi, metin
  bloğu 0,0317 katkı verdi, on sayı birlikte 0,0198 verdi.
- Katkının tamamı tek bir göstergeden geliyor: mühür izi tek başına 0,0357 katıyor, üç anahtar
  gösterge birlikte de aynı sayıyı veriyor, etiketten bağımsız üretilen araç göstergesi tam sıfır.
- Metnin biçim ölçüleri en çok kaybettiren sütunlar: karakter sayısı 0,0357, sözcük sayısı 0,0516;
  ikisi de anahtar göstergenin bulanık kopyasıdır ve eğitim sayısını 0,8942'ye çıkarır.
- Çok değerli bir gürültü sütunu az değerli olandan daha tehlikelidir; yılın kaçıncı günü sütunu
  366 eşik adayı sunar ve eğitim kümesinde iyi görünen bir tanesi kolay bulunur.
- Mühür göstergesi ile M27/K01'in denetim sütunu aynı biçimdedir; ayrımı yalnız kaydın zamanı
  verir ve bu soru her metin özniteliği için yeniden sorulur.

#### Sonraki Adım

İki derste sütun sayısı on birden yirmi ikiye çıktı ve her seferinde ölçüm aynı şeyi söyledi:
sütunların çoğu ya hiçbir şey katmıyor ya da kaybettiriyor. Buraya kadar sütunlar **tek tek elle**
elendi. Elemenin kendisi bir yordama bağlanabilir — sütunlar etiketle ilişkilerine göre süzülebilir,
model tekrar tekrar eğitilerek denenebilir ya da modelin kendi öznitelik önemi okunabilir. Üç
yöntem aynı kümede kaç sütun seçer, seçim **bütün kümeden** yapıldığında ayrılmış küme sayısı ne
kadar şişer ve **yalnız eğitimden** yapıldığında ne olur — sonraki ders bunu ölçer.

### Öznitelik Seçimi

Source: https://academia.sh/tr/kurslar/oznitelik-muhendisligi/oznitelik-secimi

Süzgeç, sarmalayıcı ve gömülü yöntemlerin aynı 25 aday sütundan kaç öznitelik seçtiğinin ve seçimin kendisinin ürettiği şişmenin sayılması: seçimsiz 25 sütun sınamada 0,8135 verirken süzgeç 10 sütunla 0,8056, sarmalayıcı 4 sütunla 0,7579, gömülü yöntem 14 sütunla 0,8254 veriyor. Sarmalayıcı puanını okuduğu doğrulama kümesinde 0,8413 raporlarken sınamada 0,7579'a düşüyor; şişme 0,0834. Bin iki yüz gürültü sütunundan seçim bütün kümeden yapıldığında ayrılmış küme sayısı 0,0198, 0,0040 ve 0,0714 şişiyor, üç ölçümde de aynı işaretle.

İki derste sütun sayısı on birden yirmi ikiye çıktı ve eleme her seferinde elle yapıldı: bir
sütun eklendi, ayrılmış kümedeki sayı okundu, katkısı yoksa çıkarıldı. Bu ders elenenleri de
havuza geri alır; yordam yirmi beş adayda işler, iki yüz adayda işlemez. Eleme kararının kendisi bir yordama bağlanabilir.

Üç aile vardır. **Süzgeç yöntemi** (filter) her sütunu tek başına etiketle karşılaştırır ve
modelden bağımsız bir puana göre eler. **Sarmalayıcı yöntem** (wrapper) modeli tekrar tekrar
eğitir ve sütun kümesini bir başarım sayısına göre büyütür. **Gömülü yöntem** (embedded) modelin
öznitelik önemini okur. Bu ders üçünü aynı havuzda çalıştırır ve ikinci bir soru sorar: seçim
yordamının kendisi, ayrılmış kümedeki sayıyı ne kadar şişirir.

- **OS15.** Aday havuzu 25 sütundur: M27/K01'in on bir sütunu ve önceki iki dersin ürettiği on
  dört sütun. Karar öncesi çizgi **seçimsiz** kurulumdur, yani yirmi beş sütunun hepsi.
- **OS16.** Model değişmez: derinlik 6 karar ağacı. Taban çizgisi 0,7579'dur.
- **OS17.** Süzgeç puanı, sütunun tek bir bölmede sağladığı en büyük saflık kazancıdır ve eşik
  0,004'tür; her iki yan en az 20 satır içermelidir. Eşik **önceden** seçilir.
- **OS18.** Sarmalayıcı ileri seçim yapar: her turda en çok kazandıran sütunu ekler, kazanç
  kalmayınca durur, en çok sekiz tur çalışır. Puanını **doğrulama kümesinden** okur.
- **OS19.** Gömülü yöntem derinlik 8 bir ağaç kurar, her bölmenin kazancını bölünen satır sayısıyla
  ağırlıklandırıp sütuna yazar ve toplam önemin yüzde birinden fazlasını taşıyan sütunları seçer.
- **OS20.** Üç yöntemin üçü de kararını **yalnız eğitim ve doğrulama kümelerinden** verir; sınama
  kümesi hiçbir seçime katılmaz. M26/K05'in çoklu karşılaştırma hesabı tekrarlanmaz.
- **OS21.** Şişme ölçümünde 1.200 sütunluk bir gürültü havuzu kullanılır. Sütunlar aynı tohumdan
  abone numarasına bağlı üretilir ve etiketle hiçbir ilişkileri yoktur; katkıları bilinen sıfırdır.

#### Üç Yöntem, Üç Öznitelik Kümesi

```python
# ORTAK — MODELDIR. M27/K01'in KURGU abone tablosu ayni tohumla yeniden uretilir;
# model derinlik 6 karar agacidir. Onceki dersten kalan tek yeni sutun kisi_basi.
import math

TOHUM, HAM, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


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


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE, VERI = [], []
for i in range(HAM):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
for k in ABONE:
    r, v = uretec(TOHUM + 7000 + k["no"]), []
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        v.append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()
    if not v:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    x = {"no": k["no"], "hane": k["hane"], "memnuniyet": k["memnuniyet"],
         "donem": len(v), "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35),
         "bolge_ad": k["bolge"][0]}
    for b in BOLGE:
        x["b_" + b[0]] = int(k["bolge"][0] == b[0])
    VERI.append(x)

TEMEL = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem", "sifir_okuma",
         "b_kuzey", "b_guney", "b_dogu", "b_bati", "b_merkez"]
TABAN_ALAN = TEMEL + ["kisi_basi"]
for x in VERI:
    x["kisi_basi"] = round(x["ort_tuketim"] / x["hane"], 3)


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT, DOG, SIN = K[:756], K[756:1008], K[1008:]


def gini(s):
    p = sum(x["supheli"] for x in s) / len(s) if s else 0.0
    return 2 * p * (1 - p)


def agac(s, derinlik, alanlar, enaz=2):
    p, en = sum(x["supheli"] for x in s) / len(s), None
    if derinlik and len(s) >= 2 * enaz and 0.0 < p < 1.0:
        for a in alanlar:
            d = sorted({x[a] for x in s})
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < enaz:
                    continue
                k = gini(s) - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(s)
                if en is None or k > en[0]:
                    en = (k, a, v, sol, sag)
    if en is None or en[0] <= 1e-9:
        return {"tahmin": int(p > 0.5)}
    return {"alan": en[1], "esik": en[2],
            "sol": agac(en[3], derinlik - 1, alanlar, enaz),
            "sag": agac(en[4], derinlik - 1, alanlar, enaz)}


def tahmin(d, x):
    while "tahmin" not in d:
        d = d["sol"] if x[d["alan"]] < d["esik"] else d["sag"]
    return d["tahmin"]


def dogruluk(d, s):
    return sum(tahmin(d, x) == x["supheli"] for x in s) / len(s)


def taban(egt, s):
    c = int(sum(x["supheli"] for x in egt) * 2 > len(egt))
    return sum(x["supheli"] == c for x in s) / len(s)


def olc(alanlar, derinlik=6):
    m = agac(EGT, derinlik, alanlar)
    return dogruluk(m, EGT), dogruluk(m, SIN)
```

Aday havuzu önceki iki dersin ürettiği on dört sütunu içerir — katkısı ölçülüp eksi çıkanlar da dahil,
çünkü seçim yordamının işi tam olarak onları elemektir.

```python
import datetime

BAS = datetime.date(2024, 1, 1)
KOK = ["sayac erisimi saglandi", "okuma normal", "abone evde degildi",
       "kapak kirli", "olcum tekrarlandi", "vana kontrol edildi"]
EK = ["arac park halinde", "kapi zili calismiyor", "bahce kapisi acik"]
ORTANCA = {}
for b in BOLGE:
    v = sorted(x["ort_tuketim"] for x in EGT if x["bolge_ad"] == b[0])
    ORTANCA[b[0]] = v[len(v) // 2]
for x in VERI:
    x["hane_memnuniyet"] = x["hane"] * x["memnuniyet"]
    x["esik_25"] = int(x["ort_tuketim"] > 25)
    x["bolge_ustu"] = int(x["ort_tuketim"] > ORTANCA[x["bolge_ad"]])
    r = uretec(TOHUM + 61000 + x["no"])
    g = BAS + datetime.timedelta(days=int(r() * 1460))
    x["ay"], x["ceyrek"] = g.month, (g.month - 1) // 3 + 1
    x["hafta_gunu"], x["yil_ici_gun"] = g.weekday(), g.timetuple().tm_yday
    x["sozlesme_yasi"] = 1460 - (g - BAS).days
    p = [KOK[int(r() * len(KOK))]]
    if r() < (0.42 if x["supheli"] else 0.10):
        p.append("muhur izi var")
    if r() < (0.30 if x["donem"] < 3 else 0.08):
        p.append("erisim saglanamadi")
    if r() < 0.25:
        p.append(EK[int(r() * len(EK))])
    n = ", ".join(p)
    x["not_uzunluk"], x["not_sozcuk"] = len(n), len(n.split())
    x["a_muhur"] = int("muhur" in n)
    x["a_erisim"] = int("erisim saglanamadi" in n)
    x["a_arac"] = int("arac" in n)

URETILEN = ["kisi_basi", "bolge_ustu", "esik_25", "hane_memnuniyet", "ay", "ceyrek",
            "hafta_gunu", "yil_ici_gun", "sozlesme_yasi", "not_uzunluk", "not_sozcuk",
            "a_muhur", "a_erisim", "a_arac"]
ADAY = TEMEL + URETILEN
ELLE = TABAN_ALAN + ["a_muhur"]


def puan(kaynak, a):
    d, g0, en = sorted({x[a] for x in kaynak}), gini(kaynak), 0.0
    for v in (d[1:] if len(d) < 10 else [d[int(i * len(d) / 10)] for i in range(1, 10)]):
        sol = [x for x in kaynak if x[a] < v]
        sag = [x for x in kaynak if x[a] >= v]
        if min(len(sol), len(sag)) >= 20:
            en = max(en, g0 - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(kaynak))
    return en


def suzgec(havuz, kaynak, esik=0.004):
    return [a for a in havuz if puan(kaynak, a) >= esik]


def sarmalayici(havuz, egt, olcum, tur=8):
    secili, en_iyi = [], 0.0
    for _ in range(tur):
        aday = max((dogruluk(agac(egt, 6, secili + [a]), olcum), a)
                   for a in havuz if a not in secili)
        if aday[0] <= en_iyi:
            break
        en_iyi = aday[0]
        secili.append(aday[1])
    return secili, en_iyi


def gomulu(havuz, egt, derinlik=8, pay=0.01):
    onem = dict.fromkeys(havuz, 0.0)

    def gez(s, d):
        p = sum(x["supheli"] for x in s) / len(s)
        if not d or len(s) < 4 or not 0.0 < p < 1.0:
            return
        en = None
        for a in havuz:
            dd = sorted({x[a] for x in s})
            for v in (dd[1:] if len(dd) < 10 else
                      [dd[int(i * len(dd) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < 2:
                    continue
                k = gini(s) - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(s)
                if en is None or k > en[0]:
                    en = (k, a, sol, sag)
        if en is None or en[0] <= 1e-9:
            return
        onem[en[1]] += en[0] * len(s)
        gez(en[2], d - 1)
        gez(en[3], d - 1)
    gez(egt, derinlik)
    t = sum(onem.values())
    return [a for a in havuz if onem[a] / t >= pay], onem, t


S1 = suzgec(ADAY, EGT)
S2, RAPOR = sarmalayici(ADAY, EGT, DOG)
S3, ONEM, TOP = gomulu(ADAY, EGT)
ONCE = olc(ADAY)
print(f"{'yontem':<14} {'sutun':>5} {'egitimde':>9} {'ayrilmis':>9} {'katki':>8}")
print(f"{'secim yok':<14} {len(ADAY):>5} {ONCE[0]:>9.4f} {ONCE[1]:>9.4f}")
for ad, s in (("suzgec", S1), ("sarmalayici", S2), ("gomulu", S3), ("elle olculmus", ELLE)):
    e, sn = olc(s)
    print(f"{ad:<14} {len(s):>5} {e:>9.4f} {sn:>9.4f} {sn - ONCE[1]:>+8.4f}")
print(f"\nsarmalayicinin dogrulama kumesinde raporladigi sayi {RAPOR:.4f}, "
      f"sinamada {olc(S2)[1]:.4f}")
print("gomulu onem sirasi:", ", ".join(
    f"{a} {ONEM[a] / TOP:.3f}" for _, a in sorted((-ONEM[a], a) for a in ADAY)[:5]))
```

```
yontem         sutun  egitimde  ayrilmis    katki
secim yok         25    0.8968    0.8135
suzgec            10    0.8823    0.8056  -0.0079
sarmalayici        4    0.8241    0.7579  -0.0556
gomulu            14    0.8995    0.8254  +0.0119
elle olculmus     13    0.8876    0.8254  +0.0119

sarmalayicinin dogrulama kumesinde raporladigi sayi 0.8413, sinamada 0.7579
gomulu onem sirasi: oynaklik 0.216, a_muhur 0.204, ort_tuketim 0.145, sozlesme_yasi 0.076, not_uzunluk 0.056
```

Üç yöntem üç ayrı büyüklükte küme seçiyor: 10, 4 ve 14 sütun. Katkıları da üç ayrı işaret
taşıyor. Gömülü yöntem 0,0119 katıyor ve on dört sütunla 0,8254'e ulaşıyor — tam olarak önceki iki
dersin elle ölçerek bulduğu on üç sütunluk kümenin sayısı. Sütun kümeleri birebir aynı değil ama
ayrılmış kümedeki sayı aynı.

Süzgeç yönteminin on sütunu 0,0079 kaybettiriyor. Nedeni yöntemin tanımındadır: her sütuna
**tek başına** bakar, bu yüzden ancak birlikte bilgi taşıyan sütunları göremez ve birbirinin
kopyası olan sütunları birlikte tutar. Seçtiği on sütunda hem `not_uzunluk` hem `not_sozcuk` var;
ikisi de aynı bilginin bulanık kopyasıdır. Süzgeç ucuzdur — yirmi beş sütun için yirmi beş puan
hesaplanır — ve ucuzluğunun bedeli budur.

#### Seçimin Kendi Sayısı

Sarmalayıcı satırı iki ayrı sayı taşıyor ve kursun bu dersteki asıl konusu o ikisinin arasındadır.
Yöntem dört sütun seçti ve durdu; seçimi yaparken her adımda **doğrulama kümesindeki** doğruluğa
baktı ve son adımda 0,8413 gördü. Aynı dört sütunla kurulan model sınama kümesinde 0,7579 veriyor,
yani taban çizgisinin tam üstünde duruyor. Fark 0,0834'tür.

Bu fark bir hata değil, yordamın tanımının sonucudur. İleri seçim ilk turda yirmi beş, ikinci
turda yirmi dört model kurar; kırk dokuz modelin doğrulama kümesindeki en yükseğini almak, o
kümedeki rastlantısal dalgalanmanın en yükseğini almaktır. Doğrulama kümesi seçime katıldığı anda
ayrılmış olmaktan çıkar ve üzerinde okunan sayı artık ölçüm değil, seçimin kendi hedefidir.

Sonuç, bu kursun ikinci kuralının seçim için okunmuş hâlidir: **seçilen öznitelik kümesi de
eğitilmiş bir nesnedir.** Ölçek gibi, kategori eşlemesi gibi, o da veriden öğrenilir ve hangi
veriden öğrenildiği yazılmak zorundadır.

Gömülü yöntemin önem sırası son satırda duruyor: en önemli üç sütun oynaklık, mühür göstergesi ve
ortalama tüketim, toplam önemin 0,565'ini taşıyor. Dördüncü sıradaki sözleşme yaşı ise etiketle
hiç ilişkisi olmayan bir sütundur ve yine de önemin 0,076'sını almıştır. Öznitelik önemi bir
sıralamadır, kanıt değildir.

#### Seçim Bütün Kümeden Yapılırsa

Seçimin şişirdiğini temiz ölçmek için katkısı **bilinen sıfır** olan bir havuz gerekir. Aşağıdaki
kod 1.200 gürültü sütunu üretiyor; hiçbirinin etiketle ilişkisi yok. Süzgeç bu havuzdan en çok
ayıran birkaç sütunu seçiyor, bir kez yalnız eğitim kümesine bakarak, bir kez bütün kümeye bakarak.

```python
GURULTU = ["g" + str(j) for j in range(1200)]
for x in VERI:
    r = uretec(TOHUM + 41000 + x["no"])
    for a in GURULTU:
        x[a] = round(r(), 4)


def ayirma(kaynak, a):
    v1 = [x[a] for x in kaynak if x["supheli"]]
    v0 = [x[a] for x in kaynak if not x["supheli"]]
    return abs(sum(v1) / len(v1) - sum(v0) / len(v0))


def en_iyi_k(kaynak, k):
    return [a for _, a in sorted((-ayirma(kaynak, a), a) for a in GURULTU)[:k]]


print(f"{'secilen':>8} {'kaynak':<12} {'egitimde':>9} {'ayrilmis':>9} {'sisme':>8}")
for k in (2, 4, 8):
    e1, s1 = olc(en_iyi_k(EGT, k))
    e2, s2 = olc(en_iyi_k(K, k))
    print(f"{k:>8} {'egitim':<12} {e1:>9.4f} {s1:>9.4f}")
    print(f"{k:>8} {'butun kume':<12} {e2:>9.4f} {s2:>9.4f} {s2 - s1:>+8.4f}")
print(f"\ntaban cizgisi {taban(EGT, SIN):.4f}; havuzda {len(GURULTU)} gurultu sutunu var")
```

```
 secilen kaynak        egitimde  ayrilmis    sisme
       2 egitim          0.8320    0.7262
       2 butun kume      0.8069    0.7460  +0.0198
       4 egitim          0.8307    0.7421
       4 butun kume      0.8241    0.7460  +0.0040
       8 egitim          0.8704    0.6667
       8 butun kume      0.8479    0.7381  +0.0714

taban cizgisi 0.7579; havuzda 1200 gurultu sutunu var
```

Şişme sütununun üç değeri de aynı işaretli: +0,0198, +0,0040, +0,0714. M27/K01'in sızıntı dersinde
dört sızıntı türünün şişmesi de aynı işaretliydi. Buradaki kanal da aynı: seçim bütün kümeye
baktığında sınama kümesinin 252 satırının etiketleri hangi sütunların seçileceğine katkıda bulunur,
seçilen sütunlar o satırlarda daha uyumlu çıkar ve ölçüm yükselir. Fark sütun sayısıyla büyüyor;
sekiz sütunda 0,0714, yani on sekiz abone.

Bir ayrıntı bu tabloyu tehlikeli kılıyor: şişmiş sayıların hiçbiri **taban çizgisini geçmiyor**.
Gürültüden model çıkmıyor, sızıntı yalnız sayıyı yukarı çekiyor. Gerçek bir kümede aynı kanal,
tabanı zaten geçen bir modelin sayısına eklenir ve tabloda sıradan bir iyileşme gibi görünür.
Seçimin yalnız eğitim kümesinden yapılması, sayının küçük çıkması pahasına doğru çıkmasını
sağlayan tek düzenlemedir.

#### Özet

- Aynı 25 aday sütunda süzgeç 10, sarmalayıcı 4, gömülü yöntem 14 sütun seçti; seçimsiz kurulum
  0,8135 verirken sırasıyla 0,8056, 0,7579 ve 0,8254 verdiler.
- Gömülü yöntem 0,0119 katkıyla iki dersin elle ölçerek bulduğu sayıya, 0,8254'e, tek yordamla
  ulaştı; süzgeç birbirinin kopyası olan iki sütunu birlikte tuttuğu için 0,0079 kaybettirdi.
- Sarmalayıcı puanını okuduğu doğrulama kümesinde 0,8413 raporladı, sınamada 0,7579 verdi; şişme
  0,0834'tür ve nedeni kırk dokuz model arasından o kümedeki en yükseği seçmiş olmasıdır.
- Seçilen öznitelik kümesi eğitilmiş bir nesnedir; hangi kümeden öğrenildiği yazılmadıkça
  üzerinde okunan sayı ölçüm değildir.
- Katkısı bilinen sıfır olan 1.200 gürültü sütununda seçim bütün kümeden yapıldığında şişme
  +0,0198, +0,0040 ve +0,0714 çıktı; üçü de aynı işaretli ve hiçbiri taban çizgisini geçmiyor.

#### Sonraki Adım

Seçim, sütunları teker teker tutup atıyor ve tuttuklarının adı okunabiliyor. Sütun sayısını
azaltmanın başka bir yolu daha var — sütunları atmak yerine birkaç yeni **bileşene** dönüştürmek. Bu yolda her yeni sütun eski sütunların bir
karışımıdır ve karışımın adı yoktur. Kaç bileşende ham özniteliklerin ayrılmış küme sayısına
yetişiliyor ve öznitelik önemi listesi okunabilirliğini yitirdiğinde ne kaybediliyor — sonraki
ders bunu ölçer.

### Boyut Azaltmanın Öznitelik Rolü

Source: https://academia.sh/tr/kurslar/oznitelik-muhendisligi/boyut-azaltmanin-oznitelik-rolu

On üç ham özniteliğin yerine bileşen temelli bir temsil konduğunda ayrılmış küme sayısının ve yorumlanabilirliğin ne olduğunun ölçülmesi: ham sütunlar sınamada 0,8254 verirken iki bileşen 0,8175, üç bileşen 0,8056, beş bileşen 0,7778, sekiz bileşen 0,7460 veriyor ve hiçbir bileşen sayısında ham temsile yetişilemiyor. En büyük bileşen payını taşıyan bileşen tek başına 0,7381 ile tabanın altında kalırken payı 0,093 olan beşinci bileşen 0,7659 veriyor. Ham temsilde bölmelerin 0,35'i ortalama tüketimden okunurken bileşen temsilinde aynı pay adı olmayan bir karışıma düşüyor.

Önceki ders sütun sayısını azaltmanın bir yolunu ölçtü: sütunları teker teker tutmak ya da atmak.
Gömülü yöntem yirmi beş adaydan on dördünü tuttu ve ayrılmış kümede 0,8254 verdi; iki dersin elle
ölçerek kurduğu on üç sütunluk küme de aynı sayıyı verdi. Tutulan sütunların hepsinin bir adı var
ve ne anlama geldikleri biliniyor.

Sütun sayısını azaltmanın ikinci yolu sütunları atmak yerine **karıştırmaktır**. On üç sütun,
her biri on üçünün ağırlıklı toplamı olan birkaç yeni sütuna dönüştürülebilir; bunlara **bileşen**
denir ve bileşenler kümedeki değişkenliği en çok taşıyan yönlerden başlayarak sıralanır. M26/K05'in
keşifsel analiz kursu bileşen payını orada ölçmüştü ve kuram burada tekrarlanmaz. Bu dersin
sorduğu tek soru şudur: bileşenler bir **öznitelik** olarak ne yapıyor — kaç bileşende ham
özniteliklerin ayrılmış küme sayısına yetişiliyor ve karşılığında ne kaybediliyor.

- **OS22.** Ham temsil on üç sütundur: M27/K01'in on bir sütunu, `kisi_basi` ve `a_muhur`.
  Ayrılmış kümedeki sayısı 0,8254'tür ve bu dersin karar öncesi çizgisidir.
- **OS23.** Ölçek ve bileşen yönleri **yalnız eğitim kümesinden** öğrenilir, üç kümeye de aynı
  sayılarla uygulanır. Bütün kümeden öğrenilseydi ölçüm şişerdi.
- **OS24.** Bileşenler ders içinde yazılır: standartlaştırılmış eğitim sütunlarının eşdeğişim
  dizeyi kurulur, en büyük yönü yineleme ile bulunur, bulunan yön dizeyden çıkarılır ve işlem
  on üç kez yinelenir. Kitaplık çağrılmaz.
- **OS25.** Yineleme sayısı her bileşen için 200'dür; bu sayı sonuca bakılarak seçilmemiştir.
  Kalan büyüklüğü $10^{-12}$'nin altına düşerse yineleme erken durur.
- **OS26.** Bileşen payı, bileşenin taşıdığı değişkenliğin toplam değişkenliğe oranıdır.
- **OS27.** Bileşenler **etiketi görmez**. Sıralamaları değişkenliğe göredir, ayrım gücüne göre
  değildir.
- **OS28.** Model değişmez: derinlik 6 karar ağacı. Taban çizgisi 0,7579'dur.
- **OS29.** Yorumlanabilirlik ölçüsü, ağacın bölmelerinin hangi sütundan okunduğudur; her bölme
  böldüğü satır sayısıyla ağırlıklandırılır.

#### On Üç Sütun, On Üç Bileşen

```python
# ORTAK — MODELDIR. M27/K01'in KURGU abone tablosu ayni tohumla yeniden uretilir;
# model derinlik 6 karar agacidir. Onceki dersten kalan tek yeni sutun kisi_basi.
import math

TOHUM, HAM, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


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


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE, VERI = [], []
for i in range(HAM):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
for k in ABONE:
    r, v = uretec(TOHUM + 7000 + k["no"]), []
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        v.append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()
    if not v:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    x = {"no": k["no"], "hane": k["hane"], "memnuniyet": k["memnuniyet"],
         "donem": len(v), "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35),
         "bolge_ad": k["bolge"][0]}
    for b in BOLGE:
        x["b_" + b[0]] = int(k["bolge"][0] == b[0])
    VERI.append(x)

TEMEL = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem", "sifir_okuma",
         "b_kuzey", "b_guney", "b_dogu", "b_bati", "b_merkez"]
TABAN_ALAN = TEMEL + ["kisi_basi"]
for x in VERI:
    x["kisi_basi"] = round(x["ort_tuketim"] / x["hane"], 3)


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT, DOG, SIN = K[:756], K[756:1008], K[1008:]


def gini(s):
    p = sum(x["supheli"] for x in s) / len(s) if s else 0.0
    return 2 * p * (1 - p)


def agac(s, derinlik, alanlar, enaz=2):
    p, en = sum(x["supheli"] for x in s) / len(s), None
    if derinlik and len(s) >= 2 * enaz and 0.0 < p < 1.0:
        for a in alanlar:
            d = sorted({x[a] for x in s})
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < enaz:
                    continue
                k = gini(s) - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(s)
                if en is None or k > en[0]:
                    en = (k, a, v, sol, sag)
    if en is None or en[0] <= 1e-9:
        return {"tahmin": int(p > 0.5)}
    return {"alan": en[1], "esik": en[2],
            "sol": agac(en[3], derinlik - 1, alanlar, enaz),
            "sag": agac(en[4], derinlik - 1, alanlar, enaz)}


def tahmin(d, x):
    while "tahmin" not in d:
        d = d["sol"] if x[d["alan"]] < d["esik"] else d["sag"]
    return d["tahmin"]


def dogruluk(d, s):
    return sum(tahmin(d, x) == x["supheli"] for x in s) / len(s)


def taban(egt, s):
    c = int(sum(x["supheli"] for x in egt) * 2 > len(egt))
    return sum(x["supheli"] == c for x in s) / len(s)


def olc(alanlar, derinlik=6):
    m = agac(EGT, derinlik, alanlar)
    return dogruluk(m, EGT), dogruluk(m, SIN)
```

Bileşenleri bulmak için üç adım gerekir: sütunları ortak bir ölçeğe getirmek, eşdeğişim dizeyini
kurmak ve en büyük değişkenliği taşıyan yönü bulup dizeyden çıkarmak. Üçüncü adım on üç kez
yinelenince on üç bileşen çıkar. Ölçek ve yönlerin hepsi yalnız eğitim kümesinden okunur.

```python
for x in VERI:
    r = uretec(TOHUM + 61000 + x["no"])
    r(), r()
    x["a_muhur"] = int(r() < (0.42 if x["supheli"] else 0.10))
HAM_ALAN = TABAN_ALAN + ["a_muhur"]

ORT = {a: sum(x[a] for x in EGT) / len(EGT) for a in HAM_ALAN}
SAC = {a: max(1e-9, (sum((x[a] - ORT[a]) ** 2 for x in EGT) / len(EGT)) ** 0.5)
       for a in HAM_ALAN}
for x in VERI:
    x["_z"] = [(x[a] - ORT[a]) / SAC[a] for a in HAM_ALAN]

n = len(HAM_ALAN)
C = [[sum(x["_z"][i] * x["_z"][j] for x in EGT) / len(EGT) for j in range(n)]
     for i in range(n)]


def carp(m, v):
    return [sum(m[i][j] * v[j] for j in range(len(v))) for i in range(len(m))]


BILESEN, PAY = [], []
A = [satir[:] for satir in C]
for _ in range(n):
    v = [1.0 / (i + 1) for i in range(n)]
    for _ in range(200):
        w = carp(A, v)
        u = sum(q * q for q in w) ** 0.5
        if u < 1e-12:
            break
        v = [q / u for q in w]
    lam = sum(v[i] * carp(A, v)[i] for i in range(n))
    BILESEN.append(v)
    PAY.append(max(0.0, lam))
    A = [[A[i][j] - lam * v[i] * v[j] for j in range(n)] for i in range(n)]

TOP = sum(PAY)
for x in VERI:
    for i, v in enumerate(BILESEN):
        x["c" + str(i)] = round(sum(x["_z"][j] * v[j] for j in range(n)), 4)

HAM = olc(HAM_ALAN)
print(f"{'temsil':<22} {'sutun':>5} {'pay':>7} {'egitimde':>9} {'ayrilmis':>9} {'fark':>8}")
print(f"{'ham oznitelikler':<22} {n:>5} {'':>7} {HAM[0]:>9.4f} {HAM[1]:>9.4f}")
for k in (1, 2, 3, 5, 8, 11, 13):
    e, s = olc(["c" + str(i) for i in range(k)])
    print(f"{'bilesen ' + str(k):<22} {k:>5} {sum(PAY[:k]) / TOP:>7.3f} {e:>9.4f} "
          f"{s:>9.4f} {s - HAM[1]:>+8.4f}")
print(f"\ntaban cizgisi {taban(EGT, SIN):.4f}")
```

```
temsil                 sutun     pay  egitimde  ayrilmis     fark
ham oznitelikler          13            0.8876    0.8254
bilesen 1                  1   0.177    0.8003    0.7381  -0.0873
bilesen 2                  2   0.323    0.8651    0.8175  -0.0079
bilesen 3                  3   0.432    0.8677    0.8056  -0.0198
bilesen 5                  5   0.630    0.9074    0.7778  -0.0476
bilesen 8                  8   0.877    0.9074    0.7460  -0.0794
bilesen 11                11   0.990    0.8995    0.7817  -0.0437
bilesen 13                13   1.000    0.9048    0.7897  -0.0357

taban cizgisi 0.7579
```

Sorunun ilk yarısının yanıtı tablodadır: **hiçbir bileşen sayısında ham özniteliklere
yetişilemiyor.** En yakın satır iki bileşendir; 0,8175 ile ham temsilin 0,0079 altında, yani iki
abone. On üç sütun iki sütuna indirilmiş ve iki abone kaybedilmiştir. Bu, bir sıkıştırma kararı
olarak savunulabilir bir dengedir.

Tablonun asıl bulgusu bundan sonra geliyor. Bileşen sayısı arttıkça sayı **iyileşmiyor**: üç
bileşende 0,8056, beşte 0,7778, sekizde 0,7460. Bileşen payı ise düzenli olarak büyüyor —
0,323'ten 0,877'ye. İki sütun aynı yöne gitmiyor. On üç bileşenin hepsi kullanıldığında bilgi
kaybı yoktur, temsil ham temsilin döndürülmüş hâlidir ve yine de sayı 0,7897'de kalıyor; fark
0,0357. Karar ağacı bölmelerini eksenlere dik yapar ve döndürülmüş bir eksende aynı ayrımı kurmak
için daha çok bölme gerekir. Bileşen temsili modelden bağımsız değildir.

#### Pay Sıralaması Ayrım Sıralaması Değildir

Bileşenler etiketi hiç görmez; sıralamaları yalnız hangi yönde daha çok değişkenlik olduğuna
bakar. Etiketin o yönlerde durup durmadığı ayrı bir sorudur ve ölçülmesi gerekir.

```python
def kullanim(d, s):
    say = {}

    def gez(d, s):
        if "tahmin" in d:
            return
        say[d["alan"]] = say.get(d["alan"], 0) + len(s)
        gez(d["sol"], [x for x in s if x[d["alan"]] < d["esik"]])
        gez(d["sag"], [x for x in s if x[d["alan"]] >= d["esik"]])
    gez(d, s)
    t = sum(say.values())
    return ", ".join(f"{a} {say[a] / t:.2f}"
                     for _, a in sorted((-say[a], a) for a in say)[:4])


print("ham temsilde en cok bolunen:", kullanim(agac(EGT, 6, HAM_ALAN), EGT))
print("bilesen temsilinde        :",
      kullanim(agac(EGT, 6, ["c" + str(i) for i in range(13)]), EGT))
for i in (0, 1):
    y = sorted(((-abs(BILESEN[i][j]), HAM_ALAN[j], BILESEN[i][j]) for j in range(n)))
    print(f"c{i} yukleri (pay {PAY[i] / TOP:.3f}): " +
          ", ".join(f"{a} {w:+.2f}" for _, a, w in y[:4]) +
          f", ... kalan {n - 4} sutunun toplam agirligi "
          f"{sum(abs(w) for _, _, w in y[4:]):.2f}")
print()
print(f"{'tek bilesen':<14} {'pay':>7} {'egitimde':>9} {'ayrilmis':>9}")
for i in range(5):
    e, s = olc(["c" + str(i)])
    print(f"{'c' + str(i):<14} {PAY[i] / TOP:>7.3f} {e:>9.4f} {s:>9.4f}")
```

```
ham temsilde en cok bolunen: ort_tuketim 0.35, oynaklik 0.19, a_muhur 0.17, kisi_basi 0.14
bilesen temsilinde        : c0 0.29, c1 0.25, c8 0.12, c2 0.11
c0 yukleri (pay 0.177): kisi_basi +0.54, ort_tuketim +0.53, b_bati -0.34, b_dogu +0.30, ... kalan 9 sutunun toplam agirligi 1.16
c1 yukleri (pay 0.146): oynaklik +0.62, sifir_okuma +0.51, donem +0.42, a_muhur +0.29, ... kalan 9 sutunun toplam agirligi 0.86

tek bilesen        pay  egitimde  ayrilmis
c0               0.177    0.8003    0.7381
c1               0.146    0.8267    0.7421
c2               0.109    0.8188    0.7579
c3               0.105    0.7937    0.7579
c4               0.093    0.8148    0.7659
```

Son tablo pay sıralamasının ayrım sıralaması olmadığını sayıyla gösteriyor. Payı en büyük bileşen
`c0`, tek başına 0,7381 veriyor — taban çizgisinin 0,0198 **altında**. Payı yarısı kadar olan
`c4` ise 0,7659 veriyor ve tabanın üstünde kalıyor. "En çok değişkenliği taşıyan yön" ile "en çok
ayıran yön" iki ayrı şeydir ve bileşenler yalnız birincisine göre sıralandığı için ilk k bileşeni
almak ayrım açısından keyfi bir kesmedir.

İki bileşenin birlikte 0,8175 vermesi de buradan anlaşılıyor: `c0` tüketim büyüklüğünü,
`c1` ise oynaklık ile sıfır okumayı taşıyor. Etiketin iki ayrı nedeni iki ayrı bileşene düşmüş
durumda ve model ikisini birleştirebiliyor.

#### Yorumlanabilirlikten Kaybedilen

Kaybın ikinci yarısı sayıda değil, adlarda. Ham temsilde bölmelerin 0,35'i `ort_tuketim`'den,
0,19'u `oynaklik`'tan, 0,17'si mühür göstergesinden okunuyor. Bu üç satır bir saha ekibine
verilebilir: yüksek tüketim, oynak okuma ve mühür kaydı. Bileşen temsilinde aynı liste `c0` 0,29,
`c1` 0,25, `c8` 0,12 diye çıkıyor ve hiçbir şey söylemiyor.

Bileşenin ne olduğu ancak yükleri okunarak anlaşılır ve yükler tek bir sütuna işaret etmez. `c0`
kişi başı tüketim ile ortalama tüketimi ağırlıklı olarak taşıyor, ama geri kalan dokuz sütunun
toplam ağırlığı 1,16 — yani bileşenin yarıdan fazlası, dört büyük yükün dışındaki sütunlardan
geliyor. "Bu abone niçin şüpheli işaretlendi" sorusunun yanıtı ham temsilde bir sütun adı ve bir
eşiktir; bileşen temsilinde on üç sayının ağırlıklı toplamıdır.

Karar, iki kaybın karşılaştırılmasıdır. İki bileşene inmek 0,0079 sayı ve on bir sütunluk
saklama kazandırır; karşılığında modelin verdiği her kararın gerekçesini bir tabloya yazma
olanağı kaybedilir. Kaçak şüphesi gibi bir alanda ikinci kayıp genellikle daha pahalıdır, çünkü
bir aboneye yapılan işlemin gerekçesi sorulabilir. Bileşen temsilinin öznitelik rolü bu yüzden
dar bir roldür: sütun sayısı gerçekten yönetilemeyecek kadar büyükse ya da sütunlar birbirinin
kopyasıysa değerlidir, tek tek ölçülmüş on üç sütun varken değildir.

#### Özet

- Ham on üç sütun ayrılmış kümede 0,8254 veriyor; hiçbir bileşen sayısı buna yetişemiyor, en yakın
  satır iki bileşenle 0,8175 (fark 0,0079, iki abone).
- Bileşen payı büyürken ayrılmış küme sayısı büyümüyor: üç bileşende 0,8056, beşte 0,7778, sekizde
  0,7460; on üç bileşende bilgi kaybı yokken sayı yine 0,7897'de kalıyor.
- Payı en büyük bileşen tek başına 0,7381 ile tabanın altında kalırken payı 0,093 olan beşinci
  bileşen 0,7659 veriyor; pay sıralaması ayrım sıralaması değildir.
- Yorumlanabilirlik ölçülebilir bir kayıptır: ham temsilde bölmelerin 0,35'i ortalama tüketimden
  okunuyor, bileşen temsilinde aynı pay adı olmayan bir karışıma düşüyor.
- Ölçek ve bileşen yönleri yalnız eğitim kümesinden öğrenilir; bütün kümeden öğrenilseydi ölçüm
  şişerdi.

#### Sonraki Adım

Dört derste hazırlama adımlarının sayısı arttı ve her birinde aynı cümle tekrarlandı: bu adım
yalnız eğitim kümesinden öğrenilir. Ölçek ortalaması, bölge ortancası, seçilen sütun kümesi,
bileşen yönleri — dördü de eğitilmiş nesnelerdir ve dördü de elle, ayrı ayrı, doğru sırayla
uygulanmak zorundadır. Bu zincir elle uygulandığında kaç adımın yanlış kümeden öğrenildiğini
saymak gerekir. Sonraki ders hazırlama ile modeli tek bir nesnede birleştirir ve boru hattı
olmadan kaç adımın sızdığını ölçer.

### Boru Hatları

Source: https://academia.sh/tr/kurslar/oznitelik-muhendisligi/boru-hatlari

Hazırlama zincirinin bir nesneye bağlanması ve boru hattı olmadan kaç adımın sızdığının sayılması: dört adımlı zincir elle uygulandığında dördü de bütün kümeden öğrenilir ve sınama sayısı 0,7817'den 0,8294'e çıkar, şişme 0,0476. Doldurma ve ölçekleme sızdığında sayı hiç değişmez, hedef temelli kodlama sızdığında 0,0397, seçim de sızdığında 0,0476 şişer. Boru hattı 131 sayı öğrenir ve bu sayıların hepsi eğitim kümesinden gelir; ölçekleme adımının ağaç modelindeki katkısı tam sıfır, hedef temelli kodlama adımının katkısı ise eksi 0,0317'dir.

Dört derste bir cümle tekrarlandı: bu adım yalnız eğitim kümesinden öğrenilir. Bölge ortancası,
ölçek ortalaması, seçilen sütun kümesi, bileşen yönleri — dördü de veriden okunmuş sayılardır ve
dördünün de doğru kümeden okunması gerekir. Cümle tekrarlandıkça bir başka şey görünür oldu: bu
adımların hepsi aynı biçime sahiptir. Her birinin bir **öğrenme** yüzü vardır — veriden bir sayı
ya da bir eşleme çıkarır — ve bir **uygulama** yüzü — o sayıyı verilen satırlara işler.

Bu biçim bir nesne tanımıdır. Adımlar tek tek elle çağrıldığında sıra karışabilir, bir adım yanlış
kümeden öğrenebilir, sınama zamanı bir adım unutulabilir. Adımları tek bir nesnede toplayan yapıya
**boru hattı** (pipeline) denir: boru hattının da bir öğrenme yüzü vardır ve yalnız eğitim
kümesini görür, bir uygulama yüzü vardır ve her kümeye aynı öğrenilmiş sayıları uygular. Bu ders
boru hattını yazar ve tek bir soru sorar: aynı zincir elle uygulandığında kaç adım sızar, sayı ne
kadar şişer.

- **OS30.** Zincir dört adımdır: eksik değer doldurma, ölçekleme, hedef temelli kodlama, öznitelik
  seçimi. Beşinci sırada model vardır ve değişmez: derinlik 6 karar ağacı.
- **OS31.** Eksik memnuniyet değerleri ve `tarife_kodu` sütunu **kurgudur**, aynı tohumdan üretilir.
  Tarife kodu bölge ve hane büyüklüğüne bağlıdır, yani etiketle gerçek bir ilişki taşır.
- **OS32.** Hedef temelli kodlama, tarife kodunun her düzeyi için şüpheli oranını öğrenir.
  **Görülmemiş kategori** genel orana düşer.
- **OS33.** Sızıntı ölçümünde ilk `k` adım bütün kümeden öğrenir, kalanlar eğitim kümesinden;
  model her durumda yalnız eğitim kümesinden eğitilir.
- **OS34.** Şişme, boru hattının sınama kümesindeki sayısına göre farktır. Taban çizgisi 0,7579.
- **OS35.** M27/K01'in sızıntı dersi dört sızıntı türünü tek tek saymıştı; buradaki kanal onun
  birinci türüdür — ayrımdan önce hazırlama — ve şişme sayıları tekrarlanmaz.

#### Adımın İki Yüzü

```python
# ORTAK — MODELDIR. M27/K01'in KURGU abone tablosu ayni tohumla yeniden uretilir;
# model derinlik 6 karar agacidir. Onceki dersten kalan tek yeni sutun kisi_basi.
import math

TOHUM, HAM, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


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


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE, VERI = [], []
for i in range(HAM):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
for k in ABONE:
    r, v = uretec(TOHUM + 7000 + k["no"]), []
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        v.append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()
    if not v:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    x = {"no": k["no"], "hane": k["hane"], "memnuniyet": k["memnuniyet"],
         "donem": len(v), "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35),
         "bolge_ad": k["bolge"][0]}
    for b in BOLGE:
        x["b_" + b[0]] = int(k["bolge"][0] == b[0])
    VERI.append(x)

TEMEL = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem", "sifir_okuma",
         "b_kuzey", "b_guney", "b_dogu", "b_bati", "b_merkez"]
TABAN_ALAN = TEMEL + ["kisi_basi"]
for x in VERI:
    x["kisi_basi"] = round(x["ort_tuketim"] / x["hane"], 3)


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT, DOG, SIN = K[:756], K[756:1008], K[1008:]


def gini(s):
    p = sum(x["supheli"] for x in s) / len(s) if s else 0.0
    return 2 * p * (1 - p)


def agac(s, derinlik, alanlar, enaz=2):
    p, en = sum(x["supheli"] for x in s) / len(s), None
    if derinlik and len(s) >= 2 * enaz and 0.0 < p < 1.0:
        for a in alanlar:
            d = sorted({x[a] for x in s})
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < enaz:
                    continue
                k = gini(s) - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(s)
                if en is None or k > en[0]:
                    en = (k, a, v, sol, sag)
    if en is None or en[0] <= 1e-9:
        return {"tahmin": int(p > 0.5)}
    return {"alan": en[1], "esik": en[2],
            "sol": agac(en[3], derinlik - 1, alanlar, enaz),
            "sag": agac(en[4], derinlik - 1, alanlar, enaz)}


def tahmin(d, x):
    while "tahmin" not in d:
        d = d["sol"] if x[d["alan"]] < d["esik"] else d["sag"]
    return d["tahmin"]


def dogruluk(d, s):
    return sum(tahmin(d, x) == x["supheli"] for x in s) / len(s)


def taban(egt, s):
    c = int(sum(x["supheli"] for x in egt) * 2 > len(egt))
    return sum(x["supheli"] == c for x in s) / len(s)
```

Dört adımın dördü de aynı iki yöntemi taşır. `ogren` yalnız kendisine verilen satırlara bakar ve
öğrendiğini `nesne` alanına yazar; `uygula` o nesneyi verilen satırlara işler ve hiçbir şey
öğrenmez. Boru hattı bu ikiliği zincirin tamamına taşır.

```python
for x in VERI:
    r = uretec(TOHUM + 61000 + x["no"])
    r(), r()
    x["a_muhur"] = int(r() < (0.42 if x["supheli"] else 0.10))
    r = uretec(TOHUM + 21000 + x["no"])
    x["tarife_kodu"] = (24 * [b[0] for b in BOLGE].index(x["bolge_ad"])
                        + 4 * (x["hane"] - 1) + int(r() * 4))
    x["memnuniyet"] = None if r() < 0.08 else x["memnuniyet"]
HAVUZ = TABAN_ALAN + ["a_muhur", "tarife_hedef"]


class Doldurma:
    ad = "doldurma"

    def ogren(self, s):
        v = sorted(x["memnuniyet"] for x in s if x["memnuniyet"] is not None)
        self.nesne = {"ortanca": v[len(v) // 2]}

    def uygula(self, s):
        for x in s:
            if x["memnuniyet"] is None:
                x["memnuniyet"] = self.nesne["ortanca"]


class Olcekleme:
    ad = "olcekleme"

    def ogren(self, s):
        self.nesne = {}
        for a in ("ort_tuketim", "oynaklik", "kisi_basi"):
            o = sum(x[a] for x in s) / len(s)
            self.nesne[a] = (o, max(1e-9, (sum((x[a] - o) ** 2 for x in s)
                                           / len(s)) ** 0.5))

    def uygula(self, s):
        for x in s:
            for a, (o, d) in self.nesne.items():
                x[a] = round((x[a] - o) / d, 4)


class HedefKodlama:
    ad = "hedef kodlama"

    def ogren(self, s):
        g, self.genel = {}, sum(x["supheli"] for x in s) / len(s)
        for x in s:
            g.setdefault(x["tarife_kodu"], []).append(x["supheli"])
        self.nesne = {k: sum(v) / len(v) for k, v in g.items()}

    def uygula(self, s):
        for x in s:
            x["tarife_hedef"] = round(self.nesne.get(x["tarife_kodu"], self.genel), 4)


class Secim:
    ad = "secim"

    def __init__(self, havuz):
        self.havuz = havuz

    def ogren(self, s):
        g0, self.nesne = gini(s), []
        for a in self.havuz:
            d, en = sorted({x[a] for x in s}), 0.0
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) >= 20:
                    en = max(en, g0 - (len(sol) * gini(sol)
                                       + len(sag) * gini(sag)) / len(s))
            if en >= 0.004:
                self.nesne.append(a)

    def uygula(self, s):
        pass


class BoruHatti:
    def __init__(self, adimlar, derinlik=6):
        self.adimlar, self.derinlik = adimlar, derinlik

    def ogren(self, egt, sizinti_kaynagi=None, sizan=0):
        egt = [dict(x) for x in egt]
        kirli = [dict(x) for x in (sizinti_kaynagi or [])]
        for i, a in enumerate(self.adimlar):
            if i < sizan:
                a.ogren(kirli)
                a.uygula(kirli)
            else:
                a.ogren(egt)
            a.uygula(egt)
        self.model = agac(egt, self.derinlik, self.adimlar[-1].nesne)
        self.egitimde = dogruluk(self.model, egt)
        return self

    def olc(self, s):
        s = [dict(x) for x in s]
        for a in self.adimlar:
            a.uygula(s)
        return dogruluk(self.model, s)


def zincir(havuz=None):
    return [Doldurma(), Olcekleme(), HedefKodlama(), Secim(havuz or HAVUZ)]


BH = BoruHatti(zincir()).ogren(EGT)
print(f"{'kurulum':<26} {'sizan':>5} {'sutun':>5} {'egitimde':>9} {'ayrilmis':>9} "
      f"{'sisme':>8}")
print(f"{'boru hatti':<26} {0:>5} {len(BH.adimlar[-1].nesne):>5} {BH.egitimde:>9.4f} "
      f"{BH.olc(SIN):>9.4f}")
for k, ad in ((1, "elle: doldurma"), (2, "elle: + olcekleme"),
              (3, "elle: + hedef kodlama"), (4, "elle: + secim")):
    b = BoruHatti(zincir()).ogren(EGT, K, k)
    print(f"{ad:<26} {k:>5} {len(b.adimlar[-1].nesne):>5} {b.egitimde:>9.4f} "
          f"{b.olc(SIN):>9.4f} {b.olc(SIN) - BH.olc(SIN):>+8.4f}")
print(f"\ntaban cizgisi {taban(EGT, SIN):.4f}; tarife kodu "
      f"{len({x['tarife_kodu'] for x in VERI})} duzey, eksik memnuniyet "
      f"{sum(1 for x in VERI if x['memnuniyet'] is None)} satir")
```

```
kurulum                    sizan sutun  egitimde  ayrilmis    sisme
boru hatti                     0     8    0.8955    0.7817
elle: doldurma                 1     8    0.8955    0.7817  +0.0000
elle: + olcekleme              2     8    0.8955    0.7817  +0.0000
elle: + hedef kodlama          3     8    0.9021    0.8214  +0.0397
elle: + secim                  4     9    0.9008    0.8294  +0.0476

taban cizgisi 0.7579; tarife kodu 119 duzey, eksik memnuniyet 103 satir
```

Zincir elle uygulandığında **dört adımın dördü de** bütün kümeden öğrenilebilir, çünkü dördü de
bir `ogren` çağrısıdır ve o çağrıya hangi listenin verildiği kodun başka bir yerinde yazılıdır.
Boru hattında bu karar tek bir yerde durur: `ogren` yöntemi eğitim kümesini alır ve zincirin
tamamı o kümeden öğrenir. Adım eklemek sızıntı riskini artırmaz.

Şişme sütunu adım adım okunmalıdır. İlk iki satır **sıfır**: doldurma değeri bütün kümeden
alınınca sayı değişmiyor, ölçek bütün kümeden alınınca da değişmiyor. Bu, o iki adımın sızmadığı
anlamına gelmez — sızmıştır, ölçülen sayı yalnız bu kümede ve bu modelde değişmemiştir. Karar
ağacı bölmelerini sıralamaya göre yapar ve standartlaştırma sıralamayı değiştirmez; ortanca ile
doldurma ise 103 satırı etkiler ve iki ortanca birbirine yakındır. Sızıntının görünmemesi
yokluğunun kanıtı değildir.

Üçüncü satır hedef temelli kodlamadır ve sayı 0,0397 şişiyor: on abone. Neden açık — bu adım
etiketi doğrudan kullanır. Tarife kodunun 119 düzeyi vardır ve her düzeye ortalama on satır düşer;
kodlama bütün kümeden öğrenildiğinde bir sınama satırının kendi etiketi, o satırın göreceği
sayının onda birini oluşturur. Dördüncü satırda seçim de bütün kümeden yapılıyor, seçilen sütun
sayısı sekizden dokuza çıkıyor ve toplam şişme 0,0476'ya, on iki aboneye ulaşıyor.

Kursun ikinci kuralı bu tabloda tamamlanıyor: **her hazırlama adımı yalnız eğitim kümesinden
öğrenilir ve ayrılmış kümeye uygulanır.** Boru hattı bu kuralı bir yordama çevirir; kural yazılı
bir uyarı olmaktan çıkıp nesnenin arayüzü olur.

#### Boru Hattının Ölçtüğü

Boru hattının ikinci yararı sızıntıyı önlemek değil, adımları **ölçülebilir** kılmaktır. Zincirden
bir adım çıkarılıp aynı ölçüm tekrarlandığında o adımın dürüst katkısı okunur.

```python
DAR = TABAN_ALAN + ["a_muhur"]
print("boru hattinin ogrendigi nesneler: " + ", ".join(
    f"{a.ad} {len(a.nesne)}" for a in BH.adimlar))
gor = set(BH.adimlar[2].nesne)
print(f"tarife kodunun {len({x['tarife_kodu'] for x in VERI})} duzeyinden {len(gor)} "
      f"tanesi egitimde goruldu; sinamada gorulmemis kategoriye dusen "
      f"{sum(x['tarife_kodu'] not in gor for x in SIN)} satir\n")
print(f"{'zincir':<22} {'adim':>4} {'sutun':>5} {'egitimde':>9} {'ayrilmis':>9} "
      f"{'katki':>8}")
print(f"{'dort adim':<22} {4:>4} {len(BH.adimlar[-1].nesne):>5} {BH.egitimde:>9.4f} "
      f"{BH.olc(SIN):>9.4f}")
for ad, al in (("olcekleme cikarildi", [Doldurma(), HedefKodlama(), Secim(HAVUZ)]),
               ("hedef kodlama cikti", [Doldurma(), Olcekleme(), Secim(DAR)]),
               ("yalniz doldurma+secim", [Doldurma(), Secim(DAR)])):
    b = BoruHatti(al).ogren(EGT)
    print(f"{ad:<22} {len(al):>4} {len(b.adimlar[-1].nesne):>5} {b.egitimde:>9.4f} "
          f"{b.olc(SIN):>9.4f} {b.olc(SIN) - BH.olc(SIN):>+8.4f}")
```

```
boru hattinin ogrendigi nesneler: doldurma 1, olcekleme 3, hedef kodlama 116, secim 8
tarife kodunun 119 duzeyinden 116 tanesi egitimde goruldu; sinamada gorulmemis kategoriye dusen 1 satir

zincir                 adim sutun  egitimde  ayrilmis    katki
dort adim                 4     8    0.8955    0.7817
olcekleme cikarildi       3     8    0.8955    0.7817  +0.0000
hedef kodlama cikti       3     7    0.8730    0.8135  +0.0317
yalniz doldurma+secim     2     7    0.8730    0.8135  +0.0317
```

Boru hattı toplam 131 sayı öğreniyor: bir ortanca, üç sütun için ikişer sayı, 116 tarife düzeyi
için birer oran ve sekiz sütunluk bir liste. Bu 131 sayının hepsi eğitim kümesinin 756 satırından
gelir ve hiçbiri sınama kümesine bakmaz. Sızıntı tartışması bu sayının ne kadarının hangi kümeden
geldiği tartışmasıdır.

Tarife kodunun 119 düzeyinin 116'sı eğitim kümesinde görülmüş; kalan üç düzeyden biri sınama
kümesinde bir satırla karşımıza çıkıyor ve **görülmemiş kategori** olarak genel orana düşüyor.
Elle yazılmış bir hazırlama zincirinde bu satır tipik olarak bir hataya ya da sessiz bir boş
değere dönüşür; boru hattında adımın uygulama yüzü kararı bir kez verir ve her kümede aynı kararı
verir.

Alt tablo iki adımın dürüst katkısını okuyor. Ölçekleme çıkarıldığında sayı **hiç** değişmiyor:
katkı tam sıfır, çünkü karar ağacı sıralamaya bakar ve doğrusal bir ölçek dönüşümü sıralamayı
değiştirmez. Adım yanlış yapıldığında da bir şey değiştirmediği için sessizce yanlış kalabilir.
Hedef temelli kodlama çıkarıldığında ise sayı 0,7817'den 0,8135'e **yükseliyor**: adımın dürüst
katkısı eksi 0,0317'dir. Yüksek kardinaliteli bir sütunu hedefle kodlamak, doğru kümeden
öğrenilse bile bu kümede zarar veriyor; bütün kümeden öğrenildiğinde ise 0,0397 kazandırıyor
görünüyordu. Aynı adım, sızdırıldığında en kârlı, dürüst uygulandığında en zararlı adımdır.

Bu dersin karar öncesi ve karar sonrası sayıları bu yüzden ters yönde okunur. Elle uygulanan
zincir 0,8294, boru hattı 0,7817 veriyor; katkı eksi 0,0476. Eksi işaret burada kaybı değil,
**kaybedilen sayının öğrenilmiş olmadığını** gösterir. Boru hattının kattığı şey sayı değil,
sayının doğruluğudur.

#### Özet

- Hazırlama adımı iki yüzlü bir nesnedir: `ogren` yalnız eğitim kümesini görür ve öğrendiğini
  saklar, `uygula` hiçbir şey öğrenmeden o sayıları her kümeye işler.
- Zincir elle uygulandığında dört adımın dördü de bütün kümeden öğrenilebilir; boru hattında karar
  tek bir yerde durur ve adım eklemek sızıntı riskini artırmaz.
- Şişme adım adım 0,0000, 0,0000, 0,0397 ve 0,0476 çıkıyor; ilk iki adımın sızması sayıyı
  değiştirmiyor ve sızıntının görünmemesi yokluğunun kanıtı değildir.
- Boru hattı 131 sayı öğreniyor ve hepsi eğitim kümesinin 756 satırından geliyor; tarife kodunun
  119 düzeyinden 116'sı görülmüş, sınamada bir satır görülmemiş kategoriye düşüyor.
- Aynı adım sızdırıldığında 0,0397 kazandırıyor, dürüst uygulandığında 0,0317 kaybettiriyor;
  boru hattının katkısı eksi 0,0476'dır ve bu eksi, kaybedilen sayının öğrenilmiş olmadığını
  gösterir.

#### Sonraki Adım

Boru hattı zincirin tamamını tek bir kümeden öğrenmeye bağladı ve ölçüm tek bir bölmeden okundu:
756 satır eğitim, 252 satır sınama. O 252 satır bir kez kullanıldı ve verdiği sayı tek bir sayıdır;
başka bir bölmede başka bir sayı çıkardı. Ölçümü birden çok bölme üzerinden almanın adı çapraz
doğrulamadır ve hazırlama zinciri buraya girince yeni bir soru doğar: zincir kat dışında bir kez
mi öğrenilir, yoksa her katın içinde yeniden mi. İki yordam iki ayrı sayı verir ve sonraki ders
hangisinin ayrılmış kümedeki gerçek sayıya yakın çıktığını ölçer.

### Çapraz Doğrulama ile Uyum

Source: https://academia.sh/tr/kurslar/oznitelik-muhendisligi/capraz-dogrulama-ile-uyum

Hazırlama zincirinin kat dışında ve kat içinde öğrenilmesinin verdiği iki çapraz doğrulama sayısının ayrılmış kümedeki gerçek sayıyla karşılaştırılması: sınama kümesindeki gerçek sayı 0,7817 iken kat içinde hazırlama 0,7966, kat dışında hazırlama 0,8105 veriyor ve kat içinde hazırlamanın sapması 0,0149, kat dışındakinin 0,0288'dir. Zincirin ikinci adımı, hedef temelli kodlama, kat dışına çıktığı anda sayı 0,7966'dan 0,8115'e sıçrıyor. Tek bölmenin verdiği 0,8254 dört katın en yükseğine eşittir ve gerçek sayıdan 0,0437 uzaktır. Kurs kapanışında on iki dersin karar öncesi ve karar sonrası sayıları tek tabloda toplanır.

Önceki ders hazırlama zincirini bir nesneye bağladı ve zincirin tamamının yalnız eğitim kümesinden
öğrenildiğini güvence altına aldı. Ölçüm yine tek bir bölmeden okundu: 756 satır eğitim, 252 satır
sınama. O 252 satır bir kez kullanıldı ve tek bir sayı verdi. Başka bir bölme başka bir sayı
verirdi ve hangisinin doğru olduğunu söylemenin yolu yoktu.

**Çapraz doğrulama** (cross-validation) bu soruna yanıt verir: elde kalan veri **kat**lara
bölünür, her kat sırayla ölçüm kümesi olur, kalanlar eğitim kümesi olur ve sonunda birkaç sayı
ile bunların saçılması elde edilir. Hazırlama zinciri işin içine girince yeni bir soru doğar ve
kursun ikinci kuralı burada son biçimini alır: zincir kat dışında bir kez mi öğrenilir, yoksa
her katın içinde yeniden mi. İki yordam iki ayrı sayı verir; bu ders hangisinin **ayrılmış
kümedeki gerçek sayıya** yakın çıktığını ölçer.

- **OS36.** Çapraz doğrulama havuzu K01'in eğitim ve doğrulama kümeleridir: 1.008 satır. Sınama
  kümesinin 252 satırı hiçbir kata, hiçbir hazırlama adımına ve hiçbir seçime girmez.
- **OS37.** Kat sayısı **dörttür**; her katta 252 satır ölçüm kümesi olur, yani sınama kümesiyle
  aynı büyüklükte. Kat sayısı sonuca bakılarak seçilmemiştir.
- **OS38.** Zincir önceki dersin zinciridir, ölçekleme adımı çıkarılmıştır: doldurma, hedef
  temelli kodlama, öznitelik seçimi. Ölçeklemenin karar ağacındaki katkısı orada tam sıfır ölçüldü.
- **OS39.** Kat dışında öğrenilen adım sayısı 0'dan 3'e kadar değiştirilir. Sıfır, zincirin
  tamamının kat içinde öğrenilmesidir.
- **OS40.** Gerçek sayı, zincirin 1.008 satırdan öğrenildiği ve sınama kümesinde ölçüldüğü
  sayıdır: bu kursun tek bir kez okunan sayısıdır.
- **OS41.** Saçılma, dört kat sayısının kendi ortalamasından ortalama uzaklığıdır ve M26'nın
  değişkenlik ölçüsüdür.
- **OS42.** Ayar bütçesi hesabı bu dersin konusu değildir ve M27/K03'e bırakılır; burada hiçbir
  model ayarı seçilmez, model kurs boyunca sabit kalan derinlik 6 karar ağacıdır.

#### Kat Dışında ve Kat İçinde

```python
# ORTAK — MODELDIR. M27/K01'in KURGU abone tablosu ayni tohumla yeniden uretilir;
# model derinlik 6 karar agacidir. Onceki dersten kalan tek yeni sutun kisi_basi.
import math

TOHUM, HAM, M32 = 20260218, 1400, 0xFFFFFFFF
BOLGE = [("kuzey", 0.28, 21, 0.00), ("guney", 0.22, 17, -0.10),
         ("dogu", 0.18, 26, 0.30), ("bati", 0.14, 14, -0.05),
         ("merkez", 0.18, 23, 0.05)]


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


def ayrik(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


ABONE, VERI = [], []
for i in range(HAM):
    r = uretec(TOHUM + i)
    b = BOLGE[ayrik(r(), [x[1] for x in BOLGE])]
    hane = 1 + ayrik(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    memnun = 1 + ayrik(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    if r() >= 0.046:
        ABONE.append({"no": 10001 + i, "bolge": b, "hane": hane, "memnuniyet": memnun})
for k in ABONE:
    r, v = uretec(TOHUM + 7000 + k["no"]), []
    for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])):
        v.append(0.0 if r() < 0.038 else math.floor(
            k["bolge"][2] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100)
        r()
    if not v:
        continue
    ort, oyn, r = sum(v) / len(v), max(v) - min(v), uretec(TOHUM + 51000 + k["no"])
    z = (0.052 * (ort - 20) + 0.85 * (min(v) == 0.0) + 0.026 * oyn
         + 0.24 * (k["hane"] >= 5) - 0.20 * (k["memnuniyet"] >= 4) + k["bolge"][3]
         + 0.9 * (k["no"] - 10001) / 1399 + (r() + r() + r() - 1.5) * 1.30)
    x = {"no": k["no"], "hane": k["hane"], "memnuniyet": k["memnuniyet"],
         "donem": len(v), "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35),
         "bolge_ad": k["bolge"][0]}
    for b in BOLGE:
        x["b_" + b[0]] = int(k["bolge"][0] == b[0])
    VERI.append(x)

TEMEL = ["ort_tuketim", "oynaklik", "hane", "memnuniyet", "donem", "sifir_okuma",
         "b_kuzey", "b_guney", "b_dogu", "b_bati", "b_merkez"]
TABAN_ALAN = TEMEL + ["kisi_basi"]
for x in VERI:
    x["kisi_basi"] = round(x["ort_tuketim"] / x["hane"], 3)


def karistir(veri, tohum):
    r, s = uretec(tohum), list(range(len(veri)))
    for i in range(len(s) - 1, 0, -1):
        j = int(r() * (i + 1))
        s[i], s[j] = s[j], s[i]
    return [veri[i] for i in s]


K = karistir(VERI, TOHUM + 90000)
EGT, DOG, SIN = K[:756], K[756:1008], K[1008:]


def gini(s):
    p = sum(x["supheli"] for x in s) / len(s) if s else 0.0
    return 2 * p * (1 - p)


def agac(s, derinlik, alanlar, enaz=2):
    p, en = sum(x["supheli"] for x in s) / len(s), None
    if derinlik and len(s) >= 2 * enaz and 0.0 < p < 1.0:
        for a in alanlar:
            d = sorted({x[a] for x in s})
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) < enaz:
                    continue
                k = gini(s) - (len(sol) * gini(sol) + len(sag) * gini(sag)) / len(s)
                if en is None or k > en[0]:
                    en = (k, a, v, sol, sag)
    if en is None or en[0] <= 1e-9:
        return {"tahmin": int(p > 0.5)}
    return {"alan": en[1], "esik": en[2],
            "sol": agac(en[3], derinlik - 1, alanlar, enaz),
            "sag": agac(en[4], derinlik - 1, alanlar, enaz)}


def tahmin(d, x):
    while "tahmin" not in d:
        d = d["sol"] if x[d["alan"]] < d["esik"] else d["sag"]
    return d["tahmin"]


def dogruluk(d, s):
    return sum(tahmin(d, x) == x["supheli"] for x in s) / len(s)


def taban(egt, s):
    c = int(sum(x["supheli"] for x in egt) * 2 > len(egt))
    return sum(x["supheli"] == c for x in s) / len(s)
```

Boru hattı önceki dersin nesnesidir. `ogren` yöntemi bu kez fazladan iki değer alır: kat dışındaki
kaynak ve o kaynaktan öğrenecek adım sayısı. Sıfır verildiğinde zincirin tamamı kendisine verilen
kat içi kümeden öğrenir.

```python
for x in VERI:
    r = uretec(TOHUM + 61000 + x["no"])
    r(), r()
    x["a_muhur"] = int(r() < (0.42 if x["supheli"] else 0.10))
    r = uretec(TOHUM + 21000 + x["no"])
    x["tarife_kodu"] = (24 * [b[0] for b in BOLGE].index(x["bolge_ad"])
                        + 4 * (x["hane"] - 1) + int(r() * 4))
    x["memnuniyet"] = None if r() < 0.08 else x["memnuniyet"]
HAVUZ = TABAN_ALAN + ["a_muhur", "tarife_hedef"]


class Doldurma:
    ad = "doldurma"

    def ogren(self, s):
        v = sorted(x["memnuniyet"] for x in s if x["memnuniyet"] is not None)
        self.nesne = {"ortanca": v[len(v) // 2]}

    def uygula(self, s):
        for x in s:
            if x["memnuniyet"] is None:
                x["memnuniyet"] = self.nesne["ortanca"]


class HedefKodlama:
    ad = "hedef kodlama"

    def ogren(self, s):
        g, self.genel = {}, sum(x["supheli"] for x in s) / len(s)
        for x in s:
            g.setdefault(x["tarife_kodu"], []).append(x["supheli"])
        self.nesne = {k: sum(v) / len(v) for k, v in g.items()}

    def uygula(self, s):
        for x in s:
            x["tarife_hedef"] = round(self.nesne.get(x["tarife_kodu"], self.genel), 4)


class Secim:
    ad = "secim"

    def __init__(self, havuz):
        self.havuz = havuz

    def ogren(self, s):
        g0, self.nesne = gini(s), []
        for a in self.havuz:
            d, en = sorted({x[a] for x in s}), 0.0
            for v in (d[1:] if len(d) < 10 else
                      [d[int(i * len(d) / 10)] for i in range(1, 10)]):
                sol = [x for x in s if x[a] < v]
                sag = [x for x in s if x[a] >= v]
                if min(len(sol), len(sag)) >= 20:
                    en = max(en, g0 - (len(sol) * gini(sol)
                                       + len(sag) * gini(sag)) / len(s))
            if en >= 0.004:
                self.nesne.append(a)

    def uygula(self, s):
        pass


class BoruHatti:
    def __init__(self, adimlar, derinlik=6):
        self.adimlar, self.derinlik = adimlar, derinlik

    def ogren(self, egt, dis_kaynak=None, dis_sayisi=0):
        egt = [dict(x) for x in egt]
        dis = [dict(x) for x in (dis_kaynak or [])]
        for i, a in enumerate(self.adimlar):
            if i < dis_sayisi:
                a.ogren(dis)
                a.uygula(dis)
            else:
                a.ogren(egt)
            a.uygula(egt)
        self.model = agac(egt, self.derinlik, self.adimlar[-1].nesne)
        self.egitimde = dogruluk(self.model, egt)
        return self

    def olc(self, s):
        s = [dict(x) for x in s]
        for a in self.adimlar:
            a.uygula(s)
        return dogruluk(self.model, s)


def zincir(havuz=None):
    return [Doldurma(), HedefKodlama(), Secim(havuz or HAVUZ)]


KALAN, KAT = K[:1008], 4


def kat_bol(veri, i, k=KAT):
    n = len(veri) // k
    return ([x for j, x in enumerate(veri) if not i * n <= j < (i + 1) * n],
            veri[i * n:(i + 1) * n])


def capraz(dis_sayisi):
    v = []
    for i in range(KAT):
        ic, disari = kat_bol(KALAN, i)
        v.append(BoruHatti(zincir()).ogren(ic, KALAN, dis_sayisi).olc(disari))
    return v


def ortalama(v):
    return sum(v) / len(v)


def sacilma(v):
    o = ortalama(v)
    return (sum((q - o) ** 2 for q in v) / len(v)) ** 0.5


G = BoruHatti(zincir()).ogren(KALAN).olc(SIN)
TEK = BoruHatti(zincir()).ogren(K[:756]).olc(K[756:1008])
print(f"{'kat disinda':<12} " + " ".join(f"{'kat ' + str(i + 1):>7}" for i in range(KAT))
      + f" {'ortalama':>9} {'sacilma':>8} {'fark':>8}")
for d in range(4):
    v = capraz(d)
    print(f"{str(d) + ' adim':<12} " + " ".join(f"{q:>7.4f}" for q in v)
          + f" {ortalama(v):>9.4f} {sacilma(v):>8.4f} {ortalama(v) - G:>+8.4f}")
print(f"\ntek bolme 756/252 {TEK:.4f}, fark {TEK - G:+.4f}")
print(f"sinama kumesindeki gercek sayi {G:.4f}, taban cizgisi {taban(KALAN, SIN):.4f}")
```

```
kat disinda    kat 1   kat 2   kat 3   kat 4  ortalama  sacilma     fark
0 adim        0.7976  0.7738  0.7897  0.8254    0.7966   0.0187  +0.0149
1 adim        0.7976  0.7738  0.7897  0.8254    0.7966   0.0187  +0.0149
2 adim        0.8016  0.8135  0.8333  0.7976    0.8115   0.0139  +0.0298
3 adim        0.8016  0.8056  0.8452  0.7897    0.8105   0.0209  +0.0288

tek bolme 756/252 0.8254, fark +0.0437
sinama kumesindeki gercek sayi 0.7817, taban cizgisi 0.7579
```

Son sütun dersin yanıtıdır. Zincirin tamamı kat içinde öğrenildiğinde çapraz doğrulama 0,7966
veriyor ve gerçek sayının 0,0149 üstünde kalıyor. Zincirin tamamı kat dışında öğrenildiğinde
0,8105 veriyor ve sapma 0,0288'e, yani neredeyse iki katına çıkıyor. **Kat içinde hazırlama,
gerçek sayıya
yakın olan yordamdır.**

Sıçramanın nereden geldiği ara satırlarda görünüyor. Doldurma kat dışına çıktığında hiçbir şey
değişmiyor; hedef temelli kodlama kat dışına çıktığı anda ortalama 0,7966'dan 0,8115'e atlıyor.
Seçim buna ölçüm çözünürlüğünün altında bir düşüş ekliyor. Kat dışında hazırlamanın maliyetinin
tamamı tek bir adımdan geliyor ve o adım etiketi kullanan adımdır.

Bu, önceki dersteki tabloyla aynı yapıdadır ve aynı nedenle oluşur. Kat dışında öğrenilen hedef
temelli kodlama, her katın ölçüm satırlarının etiketlerini o satırların göreceği sayıya karıştırır.
Kat sayısı arttıkça ölçüm satırlarının payı düşer ve şişme küçülür; azaldıkça büyür. Bu yüzden
**kat dışında hazırlanmış bir çapraz doğrulama sayısı kendi kat sayısından bağımsız okunamaz.**

Sapmanın işareti de dikkat ister. Kat içinde hazırlama gerçek sayıyı 0,0149 aşıyor, yani hâlâ
iyimser; nedeni sızıntı değil, zincirin her katta bir kez daha veriye bakmasıdır. Çapraz doğrulama
gerçek sayıyı ölçmez, gerçek sayının etrafındaki bir aralığı ölçer.

#### Tek Bölmenin Tek Sayısı

Tek bölme 0,8254 veriyor ve gerçek sayıdan 0,0437 uzakta. Bu sayı, kat içinde hazırlanmış dört kat
sayısının **en yükseğine** eşittir. Tek bölme yanlış bir hesap yapmadı; yalnız bu aralığın bir
ucuna denk geldi ve tek bir sayı olduğu için hangi uca denk geldiğini söyleyemedi.

Dört katın saçılması 0,0187'dir. Bir bölmeden okunan sayı bu büyüklükte bir dalgalanma taşır ve
0,0119'luk bir katkıyı bu dalgalanmadan ayırmak tek bölmeyle olanaksızdır. Kursun ilk üç dersinde
ölçülen 0,0119'luk katkılar bu yüzden "ölçüldü ve sıfırdan ayrılamadı" diye yazıldı.

Çapraz doğrulamanın kattığı şey daha büyük bir sayı değil, sayının yanında bir **aralıktır**. Ve
o aralık ancak hazırlama zinciri kat içinde öğrenildiğinde gerçek aralıktır.

#### Özet

- Sınama kümesindeki gerçek sayı 0,7817; kat içinde hazırlama 0,7966 (sapma 0,0149), kat dışında
  hazırlama 0,8105 (sapma 0,0288) veriyor.
- Sapmanın tamamı tek bir adımdan geliyor: doldurma kat dışına çıktığında sayı değişmiyor, hedef
  temelli kodlama kat dışına çıktığında ortalama 0,7966'dan 0,8115'e sıçrıyor.
- Kat dışında hazırlanmış bir çapraz doğrulama sayısı kat sayısından bağımsız okunamaz, çünkü
  şişmenin büyüklüğü ölçüm satırlarının paya oranına bağlıdır.
- Tek bölmenin verdiği 0,8254, dört katın en yükseğine eşittir ve gerçek sayıdan 0,0437 uzaktır;
  tek sayı hangi uca denk geldiğini söyleyemez.
- Katların saçılması 0,0187'dir; bu büyüklükteki bir dalgalanma varken 0,0119'luk bir katkı tek
  bölmeyle sıfırdan ayrılamaz.

#### Kurs Kapanışı

On iki dersin hesabı tek tabloda duruyor ve her sayı kendi dersinden okundu. Karar öncesi ve karar
sonrası sütunları hep **ayrılmış kümede** ölçüldü; taban çizgisi bütün derslerde 0,7579'dur.

| Ders | Karar öncesi | Karar sonrası | Katkı |
|---|---|---|---|
| Hazırlama 01 veri temizleme | 0,7659 ham kayıt | 0,7778 üç düzeltme | +0,0119 |
| Hazırlama 02 öznitelik ölçekleme | 0,7579 ölçeksiz | 0,8016 standart puan | +0,0437 |
| Hazırlama 03 kategorik kodlama | 0,7302 alan kullanılmadı | 0,7778 bir-sıcak kodlama | +0,0476 |
| Hazırlama 04 eksik değer | 0,7619 ortanca ile doldurma | 0,8333 eksiklik göstergesi | +0,0714 |
| Hazırlama 05 dengesiz sınıflar | 0,7778 ham küme | 0,7619 iki kat çoğaltma | −0,0159 |
| Hazırlama 06 aykırı değer | 0,7698 kırpma yok | 0,8016 çit ile kırpıldı | +0,0317 |
| Seçim 01 öznitelik üretme | 0,7778 on bir sütun | 0,7897 oran özniteliği | +0,0119 |
| Seçim 02 zaman ve metin | 0,7897 on iki sütun | 0,8214 nottan beş sayı | +0,0317 |
| Seçim 03 öznitelik seçimi | 0,8135 seçim yok | 0,8254 gömülü yöntem | +0,0119 |
| Seçim 04 boyut azaltma | 0,8254 ham on üç sütun | 0,8175 iki bileşen | −0,0079 |
| Seçim 05 boru hatları | 0,8294 elle zincir | 0,7817 boru hattı | −0,0476 |
| Seçim 06 çapraz doğrulama | 0,8105 kat dışında | 0,7966 kat içinde | −0,0139 |

İkinci ve altıncı hazırlama dersinin satırları eğim temelli doğrusal modelden, kalan on satır
kursun sabit karar ağacından okunur; beşinci hazırlama dersinin satırı doğruluk sütunudur ve aynı
karar yakalanan şüpheli sayısını 21'den 33'e çıkarır.

Kursun birinci kuralı tablonun kendisidir: **bir hazırlama adımı, ayrılmış kümede ölçülen katkısı
kadar vardır.** Katkı sütununda **dört** satır sıfır ya da eksidir. Bu dördü ölçülmeseydi kümede
kalırdı ve dördü de makul bir gerekçeyle savunulurdu.

Kursun ikinci kuralı son iki satırda okunur: **her hazırlama adımı yalnız eğitim kümesinden
öğrenilir ve ayrılmış kümeye uygulanır.** O iki satırdaki eksi işaretler kayıp değil, geri
alınmış şişmedir; boru hattı 0,0476, kat içinde hazırlama 0,0139 sayı düşürdü ve düşen sayının
hiçbiri öğrenmeden gelmiyordu. Ölçülen katkının işaretine bakmadan önce nereden geldiğine bakmak
gerekir.

Geriye kursun başında açılan bir hesap kalıyor. Birinci ders, sınama kümesine her bakışın bir
**seçim payı** biriktirdiğini yazmış ve payın hesabını kursun sonuna bırakmıştı. Tablonun kendisi
o hesabı veriyor: on iki satırın her biri iki sayı taşıyor, yani sınama kümesine **en az 24 kez**
bakıldı ve derslerin içindeki süpürmelerle bu sayı çok daha yüksek. Tablodaki hiçbir sayı bu
yüzden bir genelleme kestirimi değildir; hepsi bir karşılaştırmadır. Gerçek sayı ancak bu kurs
boyunca hiç dokunulmamış bir kümede okunur ve o küme artık yoktur.

On iki derste öznitelikler kuruldu ve her adımın katkısı ayrılmış kümede ölçüldü. Ama bütün bu
ölçümler boyunca **model** hep aynı iki üç aile arasından seçildi: derinlik 6 bir karar ağacı,
yanında bir eğim temelli doğrusal model ve bir uzaklık temelli yordam. Ailenin kendisinin ne
kattığı hiç sorulmadı; derinliğin niçin altı olduğu, komşu sayısının niçin on beş olduğu ve bu
sayıların hangi kümeden okunması gerektiği hiç ölçülmedi. Sonraki kurs, Denetimli Öğrenme, bu
soruları ele alır.

