İçeriğe geç

Kitaplar / Yöntem ile Makine Öğrenmesi / Bölüm 3 — Denetimsiz öğrenme

Bölüm 3 — Denetimsiz öğrenme

Denetimsiz öğrenmede doğru cevaplar yoktur; algoritma verinin kendi yapısını bulur. Kümeleme benzer noktaları gruplar, boyut azaltma ise veriyi daha az sayıyla özetler. Bu yöntemler genellikle bir projenin ilk adımıdır: veriyi tanımak, görselleştirmek, aykırıları bulmak.

9. K-ortalamalar

Fikir. k merkez seçeriz. Her nokta en yakın merkeze atanır, sonra her merkez kendi noktalarının ortalamasına taşınır. Bu iki adım, atamalar değişmeyene kadar tekrarlanır.

minμ1…μk∑i∥xi−μc(i)∥2\min_{\mu_1 \ldots \mu_k} \; \sum_i \lVert x_i - \mu_{c(i)} \rVert^2

Yöntem
program KOrtalamalar
{ Etiketsiz noktaları k kümeye ayırır. İki adım sırayla tekrarlanır:
  1) Atama: her nokta en yakın merkeze atanır.   2) Güncelleme: her merkez, kümesinin ortalamasına taşınır.
  Amaç (eylemsizlik): Σ ‖x − merkez‖²  her turda küçülür ya da aynı kalır. }
kullan Matematik

değişken
  P, merkez: ondalık dizisi dizisi
  küme: tamsayı dizisi
  i, c, tur, k: tamsayı
  üyeler: ondalık dizisi dizisi
  bulut: ondalık dizisi

fonksiyon U2(a, b: ondalık dizisi): ondalık
başla
  sonuç := (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2
bitir

başla
  Tohum(5)
  P := []
  { üç bulut: (0,0), (5,1), (2,5) çevresinde 15'er nokta }
  [[0, 0], [5, 1], [2, 5]] içindeki her bulut için
    1'den 15'e kadar her i için P.Ekle([bulut[0] + Normal(0, 0.8), bulut[1] + Normal(0, 0.8)])
  k := 3
  merkez := [P[0], P[1], P[2]]       { kötü başlangıç: hepsi aynı buluttan }
  küme := Eşle(P, fonksiyon(p: ondalık dizisi) => 0)
  1'den 6'ya kadar her tur için
  başla
    0'dan Uzunluk(P) - 1'e kadar her i için
      küme[i] := Sırala([0, 1, 2], fonksiyon(j: tamsayı) => U2(P[i], merkez[j]))[0]
    0'dan k - 1'e kadar her c için
    başla
      üyeler := []
      0'dan Uzunluk(P) - 1'e kadar her i için eğer küme[i] = c ise üyeler.Ekle(P[i])
      merkez[c] := [Toplam(Eşle(üyeler, fonksiyon(p: ondalık dizisi) => p[0])) / Uzunluk(üyeler),
                    Toplam(Eşle(üyeler, fonksiyon(p: ondalık dizisi) => p[1])) / Uzunluk(üyeler)]
    bitir
    yaz(Biçimle('Tur {}: eylemsizlik {:7.2}  merkezler ', tur,
      Toplam(Eşle(Aralık(Uzunluk(P)), fonksiyon(j: tamsayı) => U2(P[j], merkez[küme[j]])))), 0)
    yaz(Eşle(merkez, fonksiyon(m: ondalık dizisi) => [Yuvarla(m[0], 1), Yuvarla(m[1], 1)]), 1)
  bitir
bitir.

Programın çıktısı şöyledir:

Çıktı
Tur 1: eylemsizlik  324.04  merkezler [[1.2, 3.1], [4.2, 1.0], [-0.1, -1.8]]
Tur 2: eylemsizlik   83.05  merkezler [[2.1, 5.0], [5.0, 1.2], [0.2, -0.4]]
Tur 3: eylemsizlik   70.41  merkezler [[2.3, 5.2], [5.0, 1.2], [0.1, -0.3]]
Tur 4: eylemsizlik   70.41  merkezler [[2.3, 5.2], [5.0, 1.2], [0.1, -0.3]]
Tur 5: eylemsizlik   70.41  merkezler [[2.3, 5.2], [5.0, 1.2], [0.1, -0.3]]
Tur 6: eylemsizlik   70.41  merkezler [[2.3, 5.2], [5.0, 1.2], [0.1, -0.3]]

Ne öğrendik. Üç merkez de aynı buluttan başladığı halde algoritma 3 turda gerçek merkezleri (0,0), (5,1), (2,5) yaklaşık olarak buldu. k-ortalamalar k’yi sizin seçmenizi ister ve yuvarlak, benzer büyüklükte kümeler varsayar; halka gibi biçimlerde başarısız olur (bkz. DBSCAN). Hazır sürümü MakineOgrenmesi birimindeki KOrtalamalar fonksiyonudur.

Alıştırma. k := 2 ve k := 4 ile çalıştırınız (merkez listesini de buna göre ayarlayınız). Eylemsizlik k ile nasıl değişiyor? “Dirsek yöntemi” bu eğriye bakarak k’yi seçer.

10. Hiyerarşik kümeleme

Fikir. Her nokta kendi kümesiyle başlar; her adımda birbirine en yakın iki küme birleşir. Birleşmelerin sırası ve yüksekliği bir ağaç (dendrogram) oluşturur. Ağacı hangi yükseklikten keserseniz o kadar küme elde edersiniz; k’yi önceden seçmek gerekmez.

d(A,B)=1|A||B|∑a∈A∑b∈B∥a−b∥d(A, B) = \frac{1}{|A|\,|B|} \sum_{a \in A} \sum_{b \in B} \lVert a - b \rVert

Yöntem
program HiyerarşikKümeleme
{ Birleştirici (aşağıdan yukarı) kümeleme: her nokta kendi kümesiyle başlar; her adımda en yakın
  iki küme birleşir. Birleşme sırası bir ağaç (dendrogram) oluşturur; ağacı istediğiniz yükseklikte
  keserek istediğiniz sayıda küme elde edersiniz. Kümeler arası uzaklık: ortalama bağlantı. }
kullan Matematik

değişken
  P: ondalık dizisi dizisi
  adlar: metin dizisi
  kümeler: tamsayı dizisi dizisi
  a, b, i, j, enA, enB: tamsayı
  d, enD: ondalık

fonksiyon Uzaklık(A, B: tamsayı dizisi): ondalık
değişken x, y: tamsayı
başla
  sonuç := 0
  A içindeki her x için
    B içindeki her y için
      sonuç := sonuç + Kök((P[x][0] - P[y][0]) ** 2 + (P[x][1] - P[y][1]) ** 2)
  sonuç := sonuç / (Uzunluk(A) * Uzunluk(B))
bitir

fonksiyon Ad(K: tamsayı dizisi): metin
başla
  sonuç := '{' + Birleştir(Eşle(K, fonksiyon(x: tamsayı) => adlar[x]), ' ') + '}'
bitir

başla
  { Altı şehrin kabaca konumu (yüz km cinsinden, doğu ve kuzey) }
  adlar := ['İstanbul', 'Bursa', 'İzmir', 'Ankara', 'Konya', 'Kayseri']
  P := [[2.9, 4.1], [2.9, 3.4], [0.9, 2.6], [5.9, 3.1], [5.3, 1.6], [7.7, 2.3]]
  kümeler := Eşle(Aralık(Uzunluk(P)), fonksiyon(x: tamsayı) => [x])
  i := 1
  Uzunluk(kümeler) > 1 iken
  başla
    enD := 1e9
    0'dan Uzunluk(kümeler) - 2'ye kadar her a için
      a + 1'den Uzunluk(kümeler) - 1'e kadar her b için
      başla
        d := Uzaklık(kümeler[a], kümeler[b])
        eğer d < enD ise başla enD := d; enA := a; enB := b bitir
      bitir
    yaz(Biçimle('Adım {}: {:5.2} yükseklikte  {} + {}', i, enD, Ad(kümeler[enA]), Ad(kümeler[enB])), 1)
    kümeler[enA] := kümeler[enA] + kümeler[enB]
    kümeler.Sil(enB)
    eğer Uzunluk(kümeler) = 3 ise yaz('        → 3 kümede kesilirse: ' + Birleştir(Eşle(kümeler, @Ad), ', '), 1)
    i := i + 1
  bitir
bitir.

Programın çıktısı şöyledir:

Çıktı
Adım 1:  0.70 yükseklikte  {İstanbul} + {Bursa}
Adım 2:  1.62 yükseklikte  {Ankara} + {Konya}
Adım 3:  2.23 yükseklikte  {Ankara Konya} + {Kayseri}
        → 3 kümede kesilirse: {İstanbul Bursa}, {İzmir}, {Ankara Konya Kayseri}
Adım 4:  2.33 yükseklikte  {İstanbul Bursa} + {İzmir}
Adım 5:  4.34 yükseklikte  {İstanbul Bursa İzmir} + {Ankara Konya Kayseri}

Ne öğrendik. En son birleşme (4,34) öncekilerden çok daha yüksektedir: veri doğal olarak iki gruba (batı ve iç Anadolu) ayrılır. Birleşme yüksekliklerindeki büyük sıçrama, kaç küme seçileceğine dair iyi bir ipucudur. Yöntem tüm çiftlerin uzaklığını hesapladığı için binlerce noktada yavaşlar.

Alıştırma. Uzaklık fonksiyonunu ortalama yerine en kısa uzaklığı (tek bağlantı) döndürecek şekilde değiştiriniz. Birleşme sırası değişiyor mu?

11. DBSCAN

Fikir. Kümeleri yoğunluktan tanımlarız. ε yarıçapında en az minNokta komşusu olan nokta bir “çekirdek”tir; birbirine yakın çekirdekler zincir gibi aynı kümeye bağlanır. Hiçbir çekirdeğe ulaşamayan noktalar gürültü olarak işaretlenir. Küme sayısı önceden verilmez ve kümeler her biçimde olabilir.

Nε(p)={q:∥p−q∥≤ε},p çekirdektir⇔|Nε(p)|≥minNoktaN_\varepsilon(p) = \{\, q : \lVert p - q \rVert \le \varepsilon \,\}, \qquad p \text{ çekirdektir} \iff |N_\varepsilon(p)| \ge \text{minNokta}

Yöntem
program DBSCAN
{ Yoğunluğa dayalı kümeleme. ε yarıçaplı komşuluğunda en az minNokta nokta bulunan noktalar "çekirdek"tir.
  Birbirine ε'dan yakın çekirdekler aynı kümeye girer; hiçbir çekirdeğe ulaşamayan noktalar gürültüdür.
  Küme sayısını önceden vermek gerekmez ve kümeler her biçimde olabilir (burada bir halka). }
kullan Matematik

değişken
  P: ondalık dizisi dizisi
  etiket: tamsayı dizisi       { −2: ziyaret edilmedi, −1: gürültü, 0, 1, …: küme }
  i, j, c, kume: tamsayı
  açı, eps: ondalık
  kuyruk, kom: tamsayı dizisi
  minNokta: tamsayı

fonksiyon Komşular(i: tamsayı): tamsayı dizisi
başla
  sonuç := Süz(Aralık(Uzunluk(P)), fonksiyon(j: tamsayı) =>
    Kök((P[i][0] - P[j][0]) ** 2 + (P[i][1] - P[j][1]) ** 2) <= eps)
bitir

başla
  Tohum(2)
  P := []
  0'dan 39'a kadar her i için       { yarıçapı 4 olan halka }
  başla
    açı := i * 2 * Matematik.pi / 40
    P.Ekle([4 * Cos(açı) + Normal(0, 0.2), 4 * Sin(açı) + Normal(0, 0.2)])
  bitir
  1'den 15'e kadar her i için P.Ekle([Normal(0, 0.4), Normal(0, 0.4)])   { ortada yoğun bir bulut }
  P := P + [[7, 7], [-6, 5], [6, -6]]                                     { üç aykırı nokta }

  eps := 1.5; minNokta := 3
  etiket := Eşle(P, fonksiyon(p: ondalık dizisi) => -2)
  kume := 0
  0'dan Uzunluk(P) - 1'e kadar her i için
  başla
    eğer etiket[i] <> -2 ise devam
    kom := Komşular(i)
    eğer Uzunluk(kom) < minNokta ise başla etiket[i] := -1; devam bitir
    { yeni küme: çekirdekten başlayıp ulaşılabilen herkesi genişleyerek ekle }
    etiket[i] := kume
    kuyruk := kom
    Uzunluk(kuyruk) > 0 iken
    başla
      j := kuyruk.Çıkar(0)
      eğer etiket[j] = -1 ise etiket[j] := kume      { gürültü sanılan bir sınır noktası }
      eğer etiket[j] <> -2 ise devam
      etiket[j] := kume
      kom := Komşular(j)
      eğer Uzunluk(kom) >= minNokta ise kuyruk := kuyruk + kom
    bitir
    kume := kume + 1
  bitir

  yaz('Bulunan küme sayısı: ' + kume, 1)
  0'dan kume - 1'e kadar her c için
    yaz(Biçimle('  Küme {}: {} nokta', c, Uzunluk(Süz(etiket, fonksiyon(e: tamsayı) => e = c))), 1)
  yaz('  Gürültü: ' + Eşle(Süz(Aralık(Uzunluk(P)), fonksiyon(j: tamsayı) => etiket[j] = -1), fonksiyon(j: tamsayı) => P[j]), 1)
bitir.

Programın çıktısı şöyledir:

Çıktı
Bulunan küme sayısı: 2
  Küme 0: 40 nokta
  Küme 1: 15 nokta
  Gürültü: [[7, 7], [-6, 5], [6, -6]]

Ne öğrendik. DBSCAN halkayı (40 nokta) ve ortadaki bulutu (15 nokta) iki ayrı küme olarak buldu, üç aykırı noktayı da gürültü diye ayırdı. k-ortalamalar aynı veride halkayı dilimlere bölerdi. Bunun bedeli, ε seçimine duyarlılıktır. ε = 1,2 ile halka, noktalar arasındaki küçük boşluklardan üç parçaya kopar.

Alıştırma. eps := 1.2 ve eps := 5 deneyiniz. İkincisinde ne oluyor, neden?

12. Temel bileşenler analizi (PCA)

Fikir. Veri bulutunun en çok yayıldığı yönü buluruz; bu yön birinci temel bileşendir. Ona dik ve en çok yayılan ikinci yön ikinci bileşendir; böyle devam eder. Matematikte bu yönler kovaryans matrisinin özvektörleridir. Az sayıda bileşeni tutarak veriyi bilgi kaybetmeden küçültebiliriz.

C=1n−1X⊤X,Cv=λvC = \frac{1}{n-1} X^\top X, \qquad C v = \lambda v

Yöntem
program TemelBileşenler
{ Temel Bileşenler Analizi (PCA): veriyi en çok yayıldığı yönlere döndürür.
  1) Veriyi ortala.  2) Kovaryans matrisini kur: C = XᵀX / (n − 1).
  3) C'nin özvektörleri yönleri, özdeğerleri o yöndeki varyansı verir. En büyükleri tutup boyut azaltılır.
  Özvektörü "kuvvet yinelemesi"yle buluyoruz: v ← C·v / ‖C·v‖ işlemi en büyük özvektöre yakınsar. }
kullan YZ, Matematik

değişken
  X, C, v, Cv, v2: tensör
  boy, kilo: ondalık dizisi
  i: tamsayı
  l1, l2: ondalık

başla
  { 8 kişinin boyu (cm) ve kilosu (kg): iki öznitelik güçlü biçimde ilişkili }
  boy := [160, 165, 170, 172, 175, 180, 185, 190]
  kilo := [55, 60, 63, 70, 72, 78, 85, 90]
  X := Tensör([boy, kilo]).T                                   { 8 × 2 matris }
  X := X - X.Ortalama(0)                                        { her sütunu ortala }
  C := X.T @ X / (8 - 1)
  yaz('Kovaryans matrisi:', 1); yaz(C, 1)

  v := Tensör([1.0, 0.0])
  1'den 50'ye kadar her i için
  başla
    Cv := C @ v
    v := Cv / Kök((Cv * Cv).Toplam.Değer)
  bitir
  l1 := (v @ C @ v).Değer
  v2 := Tensör([-v[1], v[0]])            { 2 boyutta ikinci bileşen birinciye diktir }
  l2 := (v2 @ C @ v2).Değer
  yaz(Biçimle('1. bileşen: ({:.3}, {:.3})  varyans {:.1}  → açıklanan oran %{:.1}', v[0], v[1], l1, 100 * l1 / (l1 + l2)), 1)
  yaz(Biçimle('2. bileşen: ({:.3}, {:.3})  varyans {:.1}  → açıklanan oran %{:.1}', v2[0], v2[1], l2, 100 * l2 / (l1 + l2)), 1)
  yaz('Tek boyuta indirgenmiş veri (1. bileşen üzerindeki izdüşüm):', 1)
  yaz(X @ v, 1)
bitir.

Programın çıktısı şöyledir:

Çıktı
Kovaryans matrisi:
tensör([[101.125, 122.125],
        [122.125, 149.4107]])
1. bileşen: (0.635, 0.773)  varyans 249.8  → açıklanan oran %99.7
2. bileşen: (-0.773, 0.635)  varyans 0.8  → açıklanan oran %0.3
Tek boyuta indirgenmiş veri (1. bileşen üzerindeki izdüşüm):
tensör([-22.1297, -15.0923, -9.6002, -2.922, 0.5278, 8.3379, 16.9206, 23.958])

Ne öğrendik. Boy ve kilo neredeyse aynı bilgiyi taşıyor: birinci bileşen varyansın %99,7’sini açıklıyor. Bu bileşen kabaca “iri yapılılık” anlamına gelir; iki sayı yerine tek sayıyla hemen hemen hiç bilgi kaybetmeden çalışabiliriz. Birimleri farklı öznitelikleri (örneğin cm ve TL) PCA’dan önce standartlaştırmak gerekir; yoksa büyük sayılı öznitelik baskın çıkar.

Alıştırma. Üçüncü bir öznitelik olarak ayakkabı numarasını ekleyiniz (3 × 3 kovaryans). Kuvvet yinelemesiyle en büyük bileşeni bulunuz.

13. Gauss karışım modelleri (GMM)

Fikir. Verinin birkaç çan eğrisinin (normal dağılımın) üst üste gelmesinden oluştuğunu varsayarız. EM algoritması iki adımı tekrarlar: E adımında her noktanın hangi eğriden gelmiş olabileceği olasılıkla tahmin edilir; M adımında eğrilerin ortalaması, genişliği ve ağırlığı bu olasılıklarla güncellenir. k-ortalamaların “yumuşak” sürümüdür.

p(x)=∑kπk𝒩(x∣μk,σk2)p(x) = \sum_k \pi_k \, \mathcal{N}(x \mid \mu_k, \sigma_k^2)

Yöntem
program GaussKarışım
{ Veriyi iki Gauss (normal) dağılımın karışımı olarak modeller ve parametreleri EM algoritmasıyla bulur.
  E adımı: her nokta için "hangi dağılımdan gelmiş olabilir?" olasılıkları (sorumluluklar) hesaplanır.
  M adımı: her dağılımın ortalaması, sapması ve ağırlığı bu olasılıklarla yeniden hesaplanır.
  k-ortalamalardan farkı: atamalar kesin değil, olasılıklıdır. }
kullan Matematik

değişken
  X, r: ondalık dizisi               { r[i]: i. noktanın 1. dağılıma ait olma olasılığı }
  m1, m2, s1, s2, w1, p1, p2, n1, logOlabilirlik: ondalık
  i, tur: tamsayı

fonksiyon Normal1(x, m, s: ondalık): ondalık
başla
  sonuç := Üstel(-0.5 * ((x - m) / s) ** 2) / (s * Kök(2 * Matematik.pi))
bitir

başla
  Tohum(4)
  X := []
  1'den 60'a kadar her i için X.Ekle(Normal(165, 6))     { 1. grup }
  1'den 40'a kadar her i için X.Ekle(Normal(180, 5))     { 2. grup }
  m1 := 160; m2 := 190; s1 := 10; s2 := 10; w1 := 0.5    { kaba bir başlangıç tahmini }
  r := Eşle(X, fonksiyon(x: ondalık) => 0.0)
  1'den 30'a kadar her tur için
  başla
    { E adımı }
    logOlabilirlik := 0
    0'dan Uzunluk(X) - 1'e kadar her i için
    başla
      p1 := w1 * Normal1(X[i], m1, s1)
      p2 := (1 - w1) * Normal1(X[i], m2, s2)
      r[i] := p1 / (p1 + p2)
      logOlabilirlik := logOlabilirlik + Ln(p1 + p2)
    bitir
    { M adımı }
    n1 := Toplam(r)
    m1 := 0; m2 := 0
    0'dan Uzunluk(X) - 1'e kadar her i için başla m1 := m1 + r[i] * X[i]; m2 := m2 + (1 - r[i]) * X[i] bitir
    m1 := m1 / n1; m2 := m2 / (Uzunluk(X) - n1)
    s1 := 0; s2 := 0
    0'dan Uzunluk(X) - 1'e kadar her i için
    başla
      s1 := s1 + r[i] * (X[i] - m1) ** 2
      s2 := s2 + (1 - r[i]) * (X[i] - m2) ** 2
    bitir
    s1 := Kök(s1 / n1); s2 := Kök(s2 / (Uzunluk(X) - n1))
    w1 := n1 / Uzunluk(X)
    eğer tur <= 3 veya tur mod 10 = 0 ise
      yaz(Biçimle('Tur {:2}: log-olabilirlik {:8.2} | 1: ort {:6.1} sapma {:4.1} ağırlık {:.2} | 2: ort {:6.1} sapma {:4.1}',
        tur, logOlabilirlik, m1, s1, w1, m2, s2), 1)
  bitir
  yaz(Biçimle('172 cm boyundaki biri 1. gruptan olasılıkla {:.2}', w1 * Normal1(172, m1, s1) /
    (w1 * Normal1(172, m1, s1) + (1 - w1) * Normal1(172, m2, s2))), 1)
bitir.

Programın çıktısı şöyledir:

Çıktı
Tur  1: log-olabilirlik  -408.17 | 1: ort  165.9 sapma  6.1 ağırlık 0.64 | 2: ort  180.1 sapma  5.9
Tur  2: log-olabilirlik  -355.73 | 1: ort  165.4 sapma  5.6 ağırlık 0.63 | 2: ort  180.6 sapma  5.3
Tur  3: log-olabilirlik  -353.54 | 1: ort  165.2 sapma  5.2 ağırlık 0.63 | 2: ort  180.9 sapma  4.8
Tur 10: log-olabilirlik  -352.10 | 1: ort  164.9 sapma  4.9 ağırlık 0.62 | 2: ort  181.0 sapma  4.5
Tur 20: log-olabilirlik  -352.10 | 1: ort  164.8 sapma  4.8 ağırlık 0.61 | 2: ort  180.9 sapma  4.6
Tur 30: log-olabilirlik  -352.09 | 1: ort  164.8 sapma  4.8 ağırlık 0.61 | 2: ort  180.8 sapma  4.6
172 cm boyundaki biri 1. gruptan olasılıkla 0.75

Ne öğrendik. Veri gerçekte ortalaması 165 ve 180 olan 60 + 40 kişiden üretildi; EM, etiketleri hiç görmeden ortalamaları 164,8 ve 180,8, ağırlığı 0,61 (gerçek 0,60) buldu. Log-olabilirlik her turda artar; artış durunca algoritma yakınsamıştır. GMM’nin k-ortalamalara göre kazancı son satırda görülür: 172 cm’lik biri için kesin bir etiket değil, %75’lik bir olasılık verir.

Alıştırma. Başlangıç tahminini m1 := 170; m2 := 171 yapınız. Algoritma yine aynı çözüme ulaşıyor mu, kaç turda?