İçeriğe geç
academia.sh

Ders 07 / 20

Bağımsızlık Varsayımlı Olasılıksal Sınıflandırıcı

Sütunları toplamak yerine sayan bir ailenin ölçülmesi: sekiz sütunun 201.600 bileşimi yerine 80 hücrelik bir sayım tablosu tutulur, yirmi aday denenir ve doğrulama kümesinden seçilen aday sınama kümesinde 0,7817 verir. Sıfır sayım bir sınıfı tümüyle eler; 60 satırlık eğitimde 137 sınama satırı vetolanır ve düzleştirme vetoyu kaldırır ama doğruluğu güvenceye almaz. Aynı bilgiyi taşıyan sütun sekiz kez eklendiğinde uç olasılıklı abone 2'den 169'a, ortalama güven 0,842'den 0,970'e çıkar; sıralamanın üst 61'indeki isabet 29'dan 33'e gider.

İçindekiler

Önceki ders on iki sütunu tek bir uzaklık toplamına indirdi. O toplamda bölge göstergesiyle ortalama tüketim aynı terim listesinde durur ve kararı hangisinin taşıdığı görünmez; kategorik bir alan için “uzaklık” zaten sonradan takılmış bir kavramdır. Bu ders sütunları toplamayan, sayan bir aile alır: her sütunun her düzeyinin her sınıf altında kaç kez göründüğünü sayar.

Sayarak sınıflandırmanın önünde bir engel vardır. Belirli bir sütun bileşiminin kaç kez şüpheli çıktığını doğrudan saymak mümkün değildir, çünkü bileşimlerin neredeyse tamamı eğitim kümesinde hiç görülmemiştir. Aile bu engeli tek bir varsayımla aşar: sınıf verildiğinde sütunların birbirinden bağımsız olduğunu kabul eder ve ortak sayımı tek tek sütun sayımlarının çarpımıyla değiştirir. Varsayım bu kümede doğru değildir; ders yanlışlığın iki ayrı sonucunu ayrı ayrı sayar.

  • DO9. Küme, bölme ve taban çizgisi değişmez: 1.260 abonelik kurgu tablo, tohum 20260218, bölme 756/252/252, sınama kümesinde taban çizgisi 0,7579 ve 61 şüpheli abone.
  • DO10. Öznitelik kümesi sabittir; değişen yalnız gösterimidir. Bölgenin beş ikili sütunu tek bir beş düzeyli sütuna döner, ortalama tüketim M26’dan gelen 10, 25 ve 40 m³ tarife basamaklarına, oynaklık ile kişi başına tüketim eşit sayıda satır taşıyan aralıklara ayrılır. Aralık sınırları yalnız eğitim kümesinden öğrenilir.
  • DO11. İki ayar değişkeni vardır ve ikisi de veriden öğrenilmez: aralık sayısı ve düzleştirme katsayısı. Çarpım logaritma toplamı olarak hesaplanır; sekiz küçük olasılığın doğrudan çarpımı kayan noktada sıfıra iner.
  • DO12. Önsel, bir sınıfın eğitim kümesindeki oranıdır; sonsal, sütunlar görüldükten sonraki olasılığıdır. Karar eşiği 0,5’tir; eşik seçimi kursun değerlendirme konusuna bırakılır.
  • DO13. Metin sınıflandırma kurulmaz ve M27/K06’ya bırakılır; kullanılan bütün alanlar kurgu abone tablosunun kategorik alanlarıdır.
  • DO14. Seçim doğrulama kümesinden okunur. Sıralama ölçümünde en yüksek olasılıklı 61 abone alınır; 61, sınama kümesindeki gerçek şüpheli sayısıdır.

Sayımdan Olasılığa

Kurulum kümeyi üretir, düzey gösterimini kurar ve varsayımın gerekçesini iki sayıyla verir.

# sayan-model.py — MODELDIR. M27/K01 ve K02'nin KURGU abone tablosu ayni tohumla
# uretilir; oznitelik kumesi sabittir, yalniz gosterimi duzeylere cevrilir.
import collections
import math
import statistics

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 = {"hane": k["hane"], "memnuniyet": k["memnuniyet"], "donem": len(v),
         "ort_tuketim": round(ort, 2), "oynaklik": round(oyn, 2),
         "bolge_ad": k["bolge"][0],
         "sifir_okuma": int(min(v) == 0.0), "supheli": int(z > 1.35)}
    x["kisi_basi"] = round(x["ort_tuketim"] / x["hane"], 3)
    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, DOG, SIN = K[:756], K[756:1008], K[1008:]
TABAN_SIN = sum(x["supheli"] == 0 for x in SIN) / len(SIN)


def basamak(v):                             # tarife basamaklari 10 / 25 / 40 m3
    return 0 if v < 10 else (1 if v < 25 else (2 if v < 40 else 3))


def kesimler(egt, a, n):                    # OGRENME: aralik sinirlari egitimden
    v = sorted(x[a] for x in egt)
    return [v[int(i * len(v) / n)] for i in range(1, n)]


def duzey(v, kesim):
    return sum(1 for k in kesim if v >= k)


def kategorik(s, kesim):                    # UYGULAMA
    return [({"tarife": basamak(x["ort_tuketim"]), "bolge": x["bolge_ad"],
              "hane": x["hane"], "memnuniyet": x["memnuniyet"], "donem": x["donem"],
              "sifir_okuma": x["sifir_okuma"],
              "oynaklik": duzey(x["oynaklik"], kesim["oynaklik"]),
              "kisi_basi": duzey(x["kisi_basi"], kesim["kisi_basi"])}, x["supheli"])
            for x in s]


SUTUN = ["tarife", "bolge", "hane", "memnuniyet", "donem", "sifir_okuma",
         "oynaklik", "kisi_basi"]
KESIM = {a: kesimler(EGT, a, 8) for a in ("oynaklik", "kisi_basi")}
KE, KD, KS = kategorik(EGT, KESIM), kategorik(DOG, KESIM), kategorik(SIN, KESIM)
print(f"egitim {len(KE)}, sinama {len(KS)}, sinamada supheli {sum(y for _, y in KS)}, "
      f"taban cizgisi {TABAN_SIN:.4f}")
DUZEY = {a: {x[a] for x, _ in KE} for a in SUTUN}
bilesim, hucre = 1, 0
for a in SUTUN:
    bilesim, hucre = bilesim * len(DUZEY[a]), hucre + 2 * len(DUZEY[a])
print(f"sutun {len(SUTUN)}, bilesim sayisi {bilesim}, egitim satiri {len(KE)}, "
      f"sutunlara ayrilmis sayim tablosunda hucre {hucre}")
egitim 756, sinama 252, sinamada supheli 61, taban cizgisi 0.7579
sutun 8, bilesim sayisi 201600, egitim satiri 756, sutunlara ayrilmis sayim tablosunda hucre 80

İkinci satır varsayımın gerekçesidir. Sekiz sütunun düzeyleri 201.600 ayrı bileşim üretiyor ve elde 756 eğitim satırı var; ortak sayımı doğrudan tutmak bir seçenek değildir. Sütunları ayrı ayrı saymak aynı tabloyu 80 hücreye indirir. Bedeli çarpmanın kendisindedir: bir abonenin şüpheli olma olasılığı, sınıfın önseli ile her sütundaki düzeyinin o sınıf altındaki oranlarının çarpımıdır ve bu çarpım ancak sütunlar sınıf verildiğinde bağımsızsa doğru sayıyı verir.

Modelin Sayısı ve Yirmi Aday

Model iki ayar değişkeniyle yazılır ve bir ızgara taranır. Basılan tablo doğrulama kümesinindir; sınama kümesinin sayıları özet satırlarında verilir.

def ogren(kume, sutun):                     # MODELDIR: sayimlar ve onsel oranlar
    top = collections.Counter(y for _, y in kume)
    say = {(a, c): collections.Counter(d[a] for d, y in kume if y == c)
           for a in sutun for c in (0, 1)}
    return top, say, {a: len({d[a] for d, _ in kume}) for a in sutun}


def sonsal(m, d, sutun, alfa):              # sinif 1 olasiligi; carpim log toplamidir
    top, say, ds = m
    n, lg = sum(top.values()), []
    for c in (0, 1):
        t = math.log(top[c] / n)
        for a in sutun:
            pay = say[(a, c)][d[a]] + alfa
            t = -math.inf if pay <= 0.0 else t + math.log(pay / (top[c] + alfa * ds[a]))
        lg.append(t)
    if lg[0] == -math.inf and lg[1] == -math.inf:
        return None                         # her iki sinif da elendi: model susuyor
    e = [math.exp(t - max(lg)) for t in lg]
    return e[1] / (e[0] + e[1])


def degerlendir(egt, hedef, sutun, alfa):
    m, p, susan = ogren(egt, sutun), [], 0
    for d, y in hedef:
        o = sonsal(m, d, sutun, alfa)
        if o is None:
            susan, o = susan + 1, 0.0
        p.append((o, y))
    return sum((o > 0.5) == y for o, y in p) / len(p), p, susan


IZGARA, ALFALAR = {}, (0.0, 0.25, 1.0, 4.0)
for n in (3, 4, 6, 8, 12):
    KS_N = {a: kesimler(EGT, a, n) for a in ("oynaklik", "kisi_basi")}
    E2, D2, S2 = kategorik(EGT, KS_N), kategorik(DOG, KS_N), kategorik(SIN, KS_N)
    for alfa in ALFALAR:
        IZGARA[(n, alfa)] = (degerlendir(E2, D2, SUTUN, alfa)[0],
                             degerlendir(E2, S2, SUTUN, alfa)[0])
print(f"{'aralik':>6}" + "".join(f"{a:>9}" for a in ALFALAR) + "   (dogrulama)")
for n in (3, 4, 6, 8, 12):
    print(f"{n:>6}" + "".join(f"{IZGARA[(n, a)][0]:>9.4f}" for a in ALFALAR))
sd = max(IZGARA, key=lambda a: (IZGARA[a][0], -a[0], -a[1]))
ss = max(IZGARA, key=lambda a: (IZGARA[a][1], -a[0], -a[1]))
print(f"\ndenenen aday {len(IZGARA)}")
for ad, a in (("dogrulamadan", sd), ("sinamadan", ss)):
    print(f"{ad + ' secilen:':<22}aralik {a[0]}, alfa {a[1]} -> dogrulama "
          f"{IZGARA[a][0]:.4f}, sinama {IZGARA[a][1]:.4f}")
print(f"ailenin sayisi {IZGARA[sd][1]:.4f}, taban cizgisinin "
      f"{IZGARA[sd][1] - TABAN_SIN:+.4f} ustunde; iki secim kumesi "
      f"{IZGARA[ss][1] - IZGARA[sd][1]:+.4f} ayriliyor")
aralik      0.0     0.25      1.0      4.0   (dogrulama)
     3   0.7540   0.7540   0.7540   0.7579
     4   0.7540   0.7540   0.7540   0.7579
     6   0.7738   0.7738   0.7659   0.7698
     8   0.7778   0.7778   0.7857   0.7857
    12   0.7579   0.7579   0.7619   0.7698

denenen aday 20
dogrulamadan secilen: aralik 8, alfa 1.0 -> dogrulama 0.7857, sinama 0.7817
sinamadan secilen:    aralik 4, alfa 4.0 -> dogrulama 0.7579, sinama 0.7857
ailenin sayisi 0.7817, taban cizgisinin +0.0238 ustunde; iki secim kumesi +0.0040 ayriliyor

Ailenin sayısı 0,7817’dir, taban çizgisini 0,0238 — altı abone — geçer ve 20 aday denenerek bulunmuştur. Önceki dersin örnek tabanlı yordamı aynı öznitelik kümesiyle 0,7937 okumuştu. Olasılık üreten ve açık bir varsayıma dayanan bu aile, uzaklık toplayan yordamın gerisinde kaldı; fark üç abone. Kursun ikinci iddiasının ilk örneği budur ve saklanmaz.

İki seçim kümesi ayrı adaylar veriyor. Doğrulamadan sekiz aralık ile 1,0 çıkıyor; sınamadan bakılsaydı dört aralık ile 4,0 seçilir ve bildirilen sayı 0,7857 olurdu. O adayın doğrulama sayısı 0,7579’dur, yani yirmi adayın en kötüleri arasındadır.

Sıfır Sayım ve Düzleştirme

Seksen hücrenin en seyreği tarife sütununun en üst basamağı ile şüpheli sınıfının kesişimidir ve sayımı sıfırdır: kırk metreküpü aşan tüketimi olan bir abone eğitimde şüpheli olarak hiç görülmemiş. Çarpımda bir sıfır, öteki yedi sütunun söylediği her şeyi siler. Düzleştirme her sayıma küçük bir sabit ekler; gerekçesi bir başarım ayarı değil bu yapısal kusurdur.

def veto_hesabi(egt, hedef, sutun, tum_duzey):
    gorulen = {(a, c): {d[a] for d, y in egt if y == c} for a in sutun for c in (0, 1)}
    bos = sum(len(tum_duzey[a] - gorulen[(a, c)]) for a in sutun for c in (0, 1))
    veto = yanlis = 0
    for d, y in hedef:
        elenen = [c for c in (0, 1) if any(d[a] not in gorulen[(a, c)] for a in sutun)]
        if elenen:
            veto, yanlis = veto + 1, yanlis + (y in elenen)
    return bos, veto, yanlis


print(f"{'egitim':>6} {'bos hucre':>10} {'vetolanan':>10} {'yanlis veto':>12} "
      f"{'alfa=0':>8} {'alfa=1':>8}")
for m in (60, 120, 250, 500, 756):
    b, v, y = veto_hesabi(KE[:m], KS, SUTUN, DUZEY)
    print(f"{m:>6} {b:>10} {v:>10} {y:>12} "
          f"{degerlendir(KE[:m], KS, SUTUN, 0.0)[0]:>8.4f} "
          f"{degerlendir(KE[:m], KS, SUTUN, 1.0)[0]:>8.4f}")
egitim  bos hucre  vetolanan  yanlis veto   alfa=0   alfa=1
    60          8        137           24   0.7341   0.7302
   120          4         56            9   0.7460   0.7579
   250          2          1            1   0.7381   0.7540
   500          1          1            1   0.7659   0.7698
   756          1          1            1   0.7778   0.7817

Altmış satırlık eğitimde seksen hücrenin sekizi boştur ve bu sekiz boşluk 252 sınama satırının 137’sini vetoluyor; 24’ünde vetolanan sınıf abonenin gerçek sınıfıdır, yani karar sekiz sütunun kanıtına değil tek bir eksik sayıma dayanıyor. Eğitim 756 satıra çıktığında boş hücre bire, veto tek satıra iner; düzleştirme katsayısı sıfırdan büyük olduğu anda veto tümüyle kalkar.

Son iki sütun düzeltmenin sınırını gösteriyor. Altmış satırlık eğitimde düzleştirme sayıyı 0,7341’den 0,7302’ye düşürüyor; 120 satırda 0,7460’tan 0,7579’a çıkarıyor. Ödediği şey doğruluk değil, modelin susmaması.

Varsayım Bozulduğunda

Koşullu bağımsızlık ölçülebilir bir iddiadır: iki sütunun sınıf içindeki ortak sayımı tek tek oranlarının çarpımından ne kadar sapıyor. Sapma sıfırsa varsayım o çift için doğrudur.

def bagimlilik(kume, a, b):     # sinif verildiginde ortak sayimin carpimdan sapmasi
    t = 0.0
    for c in (0, 1):
        alt = [d for d, y in kume if y == c]
        n = len(alt)
        pa = collections.Counter(d[a] for d in alt)
        pb = collections.Counter(d[b] for d in alt)
        pab = collections.Counter((d[a], d[b]) for d in alt)
        t += n / len(kume) * sum(abs(pab[(u, v)] / n - pa[u] * pb[v] / n / n)
                                 for u in pa for v in pb) / 2
    return t


for a, b in (("hane", "kisi_basi"), ("donem", "oynaklik"),
             ("donem", "sifir_okuma")):
    print(f"{a:<8} {b:<12} sapma {bagimlilik(KE, a, b):.3f}")


def kopyali(kume, k):           # ayni bilgiyi tasiyan sutunun tabloya yeniden girmesi
    return [(dict(d, **{f"tarife_{i}": d["tarife"] for i in range(k)}), y)
            for d, y in kume]


POZ = sum(y for _, y in KS)
print(f"\n{'kopya':>5} {'sutun':>6} {'dogruluk':>9} {'uc olasilik':>12} "
      f"{'ort guven':>10} {'ust-' + str(POZ) + ' isabet':>14}")
for k in (0, 1, 2, 4, 8):
    sut = SUTUN + [f"tarife_{i}" for i in range(k)]
    d, p, _ = degerlendir(kopyali(KE, k), kopyali(KS, k), sut, 1.0)
    ust = sorted(p, key=lambda t: -t[0])[:POZ]
    print(f"{k:>5} {len(sut):>6} {d:>9.4f} "
          f"{sum(1 for o, _ in p if o > 0.99 or o < 0.01):>12} "
          f"{statistics.fmean(max(o, 1 - o) for o, _ in p):>10.3f} "
          f"{sum(y for _, y in ust):>14}")
hane     kisi_basi    sapma 0.407
donem    oynaklik     sapma 0.235
donem    sifir_okuma  sapma 0.011

kopya  sutun  dogruluk  uc olasilik  ort guven  ust-61 isabet
    0      8    0.7817            2      0.842             29
    1      9    0.7778            5      0.862             32
    2     10    0.7778           17      0.889             33
    4     12    0.7817           72      0.932             32
    8     16    0.7857          169      0.970             33

Varsayım bu tabloda zaten bozuktur. Hane büyüklüğü ile kişi başına tüketimin sapması 0,407’dir ve bu tanımın kendisidir: kişi başına tüketim, ortalama tüketimin hane büyüklüğüne bölünmüş halidir. Dönem sayısıyla oynaklık 0,235 sapıyor, çünkü tek dönemi olan abonenin oynaklığı sıfırdır. En bağımsız çift 0,011 ile dönem sayısı ve sıfır okumadır.

Alt tablo iki sonucu ayırır. Olasılık çıktısı uçlara gidiyor: uç olasılıklı abone sayısı 2’den 169’a, ortalama güven 0,842’den 0,970’e çıkıyor; aynı kanıt sekiz kez çarpıldığında model bağımsız sekiz kanıt görmüş gibi davranır. Sıralama ise ayakta kalıyor: üst 61 abonedeki gerçek şüpheli sayısı 29’dan 33’e çıkıyor, doğruluk 0,7817’den 0,7857’ye gidiyor. Kopyalanan sütun bütün abonelerin skorunu aynı yönde ittiği için sıralamayı bozmuyor; bozduğu şey sayının büyüklüğüdür. Bu ailenin çıktısı bir sıralama skoru olarak kullanılabilir, bir güven ölçüsü olarak kullanılamaz.

Özet

  • Sekiz sütunun 201.600 bileşimine karşılık 756 eğitim satırı vardır; koşullu bağımsızlık varsayımı sayım tablosunu 80 hücreye indirir ve ailenin tek gerekçesi budur.
  • Ailenin sayısı 0,7817’dir, taban çizgisini 0,0238 geçer ve 20 aday denenerek bulunmuştur; önceki dersin örnek tabanlı yordamı aynı öznitelik kümesiyle 0,7937 okumuştu.
  • Sınamadan seçim yapılsaydı başka bir aday seçilir ve bildirilen sayı 0,7857 olurdu.
  • Sıfır sayım bir sınıfı tümüyle eler: 60 satırlık eğitimde 137 sınama satırı vetolanıyor, 24’ünde vetolanan sınıf doğru sınıftır. Düzleştirme vetoyu kaldırır ama doğruluğu güvenceye almaz; 60 satırda sayı 0,7341’den 0,7302’ye düşüyor.
  • Varsayım bozulduğunda iki sonuç ayrışır: uç olasılıklı abone 2’den 169’a çıkarken üst 61’deki isabet 29’dan 33’e gidiyor.

Sonraki Adım

Bu ailenin kararı bütün eğitim satırlarının sayımına dayanıyor: sınırdan uzakta duran abone ile tam sınırın üstünde duran abone aynı hücreyi bir birim büyütüyor. Önceki dersin komşu yordamında da 31 komşunun her biri eşit oy veriyordu. Sonraki ders bunun tersini yapan bir aile alır. Marj temelli sınıflandırıcı yalnız sınıra yakın örneklerle ilgilenir; sınırı iki sınıf arasındaki boşluğun ortasına oturtur ve kararı taşıyan örnekleri destek vektörü olarak açıkça sayar. Ders marj genişliğini ayarlayan katsayıyı süpürür, her genişlikte kaç örneğin kararı taşıdığını basar ve eğri sınırı sütun açmadan üreten çekirdek dönüşümünü açıkça hesaplanmış polinom öznitelikleriyle ölçer.

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

Notlarım

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

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat