3 puan yazan GN⁺ 2023-08-27 | 1 yorum | WhatsApp'ta paylaş
  • Sinir ağı eğitiminin temelindeki otomatik türevleme, skalarlar için bir Tensor sınıfı doğrudan uygulanarak; değer hesaplama ile türev hesaplamanın aynı hesaplama grafiği üzerinde nasıl ilerlediğini gösteriyor
  • Normal Python değişkenlerinde z = x + y ifadesinden sonra yalnızca sonuç değeri kalır ve ilişki kaybolur; bu yüzden Tensorun değerle birlikte işlem geçmişini de saklaması gerekir
  • Children(a, b, op) ve forward() özyinelemeli çağrılarıyla bir ikili ağaç hesaplama grafiği oluşturuluyor; toplama ve çarpmanın yeniden tanımlanması sayesinde değerler sonradan verilse bile ifade yeniden hesaplanabiliyor
  • grad(deriv_to), kendisine göre türevi 1, başka bir skalara göre türevi 0 alıyor ve temel işlemlerin türev kurallarını özyinelemeli biçimde uygulayarak yeni bir hesaplama grafiği oluşturuyor
  • Uygulama yalnızca skalarları işliyor ve yavaş olabilir; bu nedenle dizi işlemleri, 0 çarpımı dallarını budama, sabit düğüm işleme ve tekrar eden hesaplamaları azaltan önbellek gelecekte iyileştirilecek alanlar olarak öne çıkıyor

Normal Python değişkenlerinde ilişki kaybolur

  • x = 3, y = 5, z = x + y gibi bir hesaplama yapıldığında z içinde yalnızca sonuç değeri 8 kalır
  • Daha sonra x ya da y değerleri değişse bile znin hangi değişkenlerden üretildiği izlenemez
  • Değişkenler arasındaki ilişki korunmadığı için, belirli bir değişkene göre türevi otomatik hesaplamak zordur

Tensor ile işlem geçmişini korumak

  • Yeni Tensor tipi değeri (value) saklar ve operatörleri yeniden tanımlayarak Tensorlar arasında hesaplama yapıldığında yeni bir Tensor döndürür
  • İlk uygulama yalnızca __add__ metodunu yeniden tanımlar; böylece Tensor(3) + Tensor(5) ifadesi T:8 üretebilir
  • Ancak bu aşamada znin x + y sonucunda oluştuğuna dair işlem geçmişi henüz korunmaz

Hesaplama grafiği ve forward()

  • İşlem geçmişini korumak için Children = namedtuple('Children', ['a', 'b', 'op']) tanıtılır
    • a: sol giriş tensörü
    • b: sağ giriş tensörü
    • op: np.add, np.multiply gibi gerçek işlem
  • Her Tensor, sayısal değerin yanı sıra children da taşıyabilir ve bunun üzerinden ikili ağaç biçiminde bir hesaplama grafiği kurulur
  • forward(), alt düğümleri özyinelemeli olarak ziyaret ederek gerçek değeri hesaplar
    • x = Tensor(3), y = Tensor(5) iken z1 = x + y, z2 = z1 * y sonucu T:40 olur
    • Grafik önce x = Tensor(None), y = Tensor(None) ile kurulup daha sonra x.value = 3, y.value = 5 atanarak z2.forward() çağrıldığında da T:40 hesaplanır

Otomatik türevlemeyi hesaplama grafiği olarak kurmak

  • Otomatik türevleme, Tensorun desteklediği her temel işleme bir türev kuralı eklenerek uygulanır
  • grad(self, deriv_to), hesaplama grafiğini özyinelemeli olarak dolaşır ve karmaşık bir fonksiyonu basit fonksiyonların birleşimine ayırır
  • Temel kurallar şunlardır
    • Bir tensörün kendisine göre türevi Tensor(1)
    • Çocuğu olmayan bir skalerin başka bir tensöre göre türevi Tensor(0)
    • Toplama: (a + b)' = a' + b'
    • Çarpma: (ab)' = a'b + ab'
  • z2 = (x + y) * y ifadesi yye göre türevlendiğinde sonuç g, basit bir değer değil kısmi türevi gösteren yeni bir hesaplama grafiği olur
    • Denklem olarak g = ∂z2/∂y = x + 2*y
    • x = 3, y = 5 iken gnin değeri 13 olur

Çıkarma, bölme ve üstel fonksiyona kadar genişletme

  • Daha karmaşık ifadeleri ele almak için Tensora çıkarma, bölme, üstel fonksiyon ve negatif alma işlemleri eklenir
  • grad() içine her işleme karşılık gelen türev kuralları konur
    • Çıkarma: (a - b)' = a' - b'
    • Bölme: (a/b)' = (a'b - ab') / b²
    • Üstel fonksiyon: exp(a)' = a' * exp(a)
  • forward() da yalnızca tek terim gerektiren işlemleri ele alacak şekilde değiştirilir
    • Örnek: exp(a) için ikinci terim b gerekmez
    • -x, 0 - x biçiminde işlenir

Örnek ifade ve Sympy ile doğrulama

  • Aşağıdaki ifade Tensor ile yazılır ve x, y için kısmi türevleri hesaplanır
z = (12 - (x * e^y)) / (45 + x * y * e^-x)
  • Kodda şu şekilde ifade edilir
x = Tensor(3)
y = Tensor(5)
z = (Tensor(12) - (x * y.exp())) / (Tensor(45) + x * y * (-x).exp())
  • Hesaplanan kısmi türev değerleri şöyledir
    • z.grad(x)T:-3.34729777301069
    • z.grad(y)T:-9.70176956641438
  • Aynı ifadenin Sympy içindeki diff() ve evalf() ile hesaplanan sonucu da aynıdır
    • xs = 3, ys = 5 iken xe göre türev değeri -3.34729777301069
    • yye göre türev değeri -9.70176956641438

Basit uygulamanın kısıtları ve optimizasyon noktaları

  • Bu uygulama, en basit otomatik türevleme sistemlerinden birine yakın ve aynı zamanda oldukça yavaş olabilir
  • Mevcut sınıf yalnızca skalarları işler
    • Daha kullanışlı bir kütüphane olması için rastgele boyutlu dizi işlemlerinin eklenmesi gerekir
  • Hesaplama grafiğine bakıldığında çeşitli optimizasyonlar mümkündür
    • Çarpma düğümünde çocuklardan biri 0 ise daha derine inmeye gerek kalmaz
    • Bir düğüm ve çocukları, türevi alınan x tensörüne bağlı değilse o düğüm sabit kabul edilip dolaşım durdurulabilir
    • Aynı işlemler tekrarlandığında önbellek kullanılarak aynı hesaplamanın birden fazla kez yapılması önlenebilir

1 yorum

 
GN⁺ 2023-08-27
Hacker News yorumları
  • Bu tür küçük ve zarif kod demolarını seviyorum. Çünkü kavramı ellerinizi kirleterek anlamanızı sağlıyorlar.
    Sasha Rush’ın GPU bulmacaları ve tensör bulmacaları da benzer örnekler.
    https://github.com/srush/GPU-Puzzles
    https://github.com/srush/Tensor-Puzzles

  • Sadece bununla otomatik türevlemeyi tamamen anladığınıza inanıyorsanız kendinizi kandırıyorsunuz.
    Grafik bir ağaç olduğunda, bu yazıdaki gibi her şey çok basittir. Ama grafik daha genel bir yönlü çevrimsiz grafikse; örneğin x = 5; y = 2x; z = xy ise, uygulama hâlâ çok basit olsa da neden doğru olduğunu anlamak kolay değildir. Bunun “sadece normal zincir kuralı” olduğunu düşünüyorsanız yine kendinizi kandırıyorsunuz.
    İlk açıklamalardan birini Paul Werbos yaptı; gereken kurala sıralı türevler için zincir kuralı adını verdi ve bunu normal zincir kuralından tümevarımla kanıtladı. Yine de normal zincir kuralından hemen apaçık çıkmaz. Aksini düşünen biri varsa, yanıldığımı kanıtlamasını isterim; bu beni çok mutlu eder.

    • O hâlde daha fazla nereden okumalı? autograd, PyTorch, mxnet gibi framework’leri yapan kişiler bunu bir yerlerde ayrıntılı öğrenmiş olmalı; kaynağını merak ediyorum. Bildiğim kadarıyla mxnet akademiden, muhtemelen CMU’dan çıktı.
    • Açıkçası bu tür tartışmalarda insanların ne istediğini pek anlamıyorum; belki de ima edilen soyutlama olan sıralı türevler ideal olmadığı içindir.
      Hesaplama grafiğinin, yani yönlü çevrimsiz grafiğin kenarları boyunca normal zincir kuralını uygularsanız her adımda doğru değeri elde edersiniz. Gereken ek kural, “bir değişken hesaplamada birden fazla kez kullanılırsa, yani aynı düğümden birden çok kenar çıkıyorsa veya ters yönde birden çok kenar giriyorsa, ayrı ayrı hesaplanan gradyanlar toplanmalıdır” gibi bir şeydir; bunu da oldukça temel ve sezgisel buluyorum.
      Örneğin f(x, y) içinde hem x hem de y yerine z koyarsanız d/dz f(z, z) = f_x(z, z) + f_y(z, z) olur; alt indisler kısmi türevleri ifade eder. Bana göre bu yaklaşım, ikisini karıştırıp “zincir kuralının ötesinde” bir şeymiş gibi göstermekten matematiksel olarak daha basit; ayrıca gerçek uygulamaya, özellikle de en aşina olduğum PyTorch’un yaptığı işe daha yakın görünüyor.
    • Zincir kuralı kısmi türevler için tanımlandığından, teknik olarak hâlâ sadece zincir kuralı denebilir.
  • Otomatik türevleme sihir gibi geliyor.
    Birçok bilgisayar bilimci buna kapılıp tekniği daha geniş bir perspektiften tanıtan yazılar yazdı. Benim yazım da onlardan biri; ayrıca operatör overloading olmadan karmaşık sayılar kullanan “fakir işi” bir varyant da içeriyor.
    https://pizzaseminar.speicherleck.de/automatic-differentiati...

    • 1994-1995’te makine öğrenmesi yaparken otomatik türevlemeyi bilmiyordum; amaç fonksiyonunu oluşturan profesör de analitik türevleri kendisi çıkarıyordu. Bunu ancak birkaç yıl önce öğrendim; 90’ların sonunda Mathematica’yı yeterince öğrenip analitik türevleri kendim ürettiğim zamanı düşününce şaşırtıcıydı.
    • Bu, 2003’te J. Martins, P. Sturdza ve J. Alonso’nun karmaşık adım türev yaklaşımı çalışmasına kadar gidiyor gibi görünüyor. Makale okunmaya değer.
      [0]: https://doi.org/10.1145/838250.838251
    • Gerçekten sihir gibi geliyor. Benzer şekilde yazılmış bir geri yayılım başlangıç kaynağı varsa bilmek isterim.
  • Benim yaptığım 26 satırlık Python otomatik türevleme uygulaması var: https://gist.github.com/sradc/d9d66e3898ffe3a02e0b6b266629b0...

    • Kısa olması güzel, ama sanırım beynim makul miktarda boşluk olduğunda çok daha iyi çalışıyor. Bu başka tarzlar üzerinde de biraz pratik yapmam gerekecek.
  • Bilgi tabanlı mühendislik sistemlerinde kullanılan tekniğe çok benziyor; orada buna bağımlılık izleme deniyor. Düğüm ya da tensör önbellekleme ile birlikte kullanıldığında hesaplama miktarını azaltabilir; özellikle büyük parametrik 3D modeller için yararlıdır.
    Bir değeri alırken ikili/bağımlılık ağacını özyinelemeli olarak çağırıp hangi değişkenlerin değiştiğini kontrol eder ve yalnızca gerekenleri yeniden hesaplar. __set__, __get__ metodlarına sahip özel Python nesneleri ve özellikler kullanırsanız bunu nesne yönelimli modelin yerleşik bir özelliği gibi yapabilirsiniz.
    x = Tensor(3)
    y = Tensor(5)
    z = x + y
    print(x, y) # 3, 5
    print(z) # 8
    x.value = 4 # değer ayarlanırken hiçbir şey yeniden hesaplanmaz
    print(z) # 9, çünkü değer alındığı anda değişen bağımlılık yeniden hesaplanır

  • Andrej Karpathy’nin bir autograd motoru oluşturduğu ilginç bir videosu var ve oldukça ufuk açıcı.
    https://youtu.be/VMj-3S1tku0?si=wuKhELwOwoYbzpt7
    Depo:
    https://github.com/karpathy/micrograd

  • Bildiğim otomatik türev alma varyasyonu bir işlem grafiği oluşturmaz. Bunun yerine ilgili değeri anında hesaplar

    • Muhtemelen ileri mod otomatik türev almayı düşünüyorsunuz. Fonksiyonun çıktı boyutu görece büyük olduğunda daha yararlıdır; çıktı boyutu görece küçük olduğunda daha yararlı olan geri mod otomatik türev almadan farklıdır.
      İkisi de çalışır, ancak duruma göre biri daha verimlidir. “Sinir ağı eğitimi” gibi durumlarda, çok sayıda hedef için tek bir kayıp çıktısını optimize etmek sık görüldüğünden genelde geri mod kullanılır
  • Otomatik türev almaya keşke sadece sayısal zincir kuralı dense ya da en azından öyle açıklansa. Kelimenin tam anlamıyla bundan ibaret; belirli işlemlerde Jacobian matrisini açıkça hesaplamamak için eklenmiş birkaç numara var sadece, bu yüzden çok daha anlaşılır olur

    • Burada anlatılan ve geri yayılım uygulamalarında en sık kullanılan “autodiff” ters mod otomatik türev almadır; ama ileri mod da vardır ve iki uç arasında stratejiler de bulunur. Sonuçta hepsi zincir kuralına dayanır, fakat algoritma düzeyinde hangi yöntemin seçileceği hiç de önemsiz değildir.
      Gerçekten de bir hesaplama grafiği üzerinden gradyanları yaymak için zincir kuralını kullanmanız istendiğinde, çoğu kişi sezgisel olarak varsayılan şekilde ileri modu düşünür gibi geliyor. Ben de öyle yapardım.
      https://en.wikipedia.org/wiki/Automatic_differentiation#Beyo...
      Bu açıdan bakınca, zincir kuralının sağladığı ifadeleri dolaşıp gradyanları biriktirmenin belirli bir yöntemini ifade eden bir terim olarak kullanmak yararlı görünüyor
    • Teknik olarak yanlış. Sayısal zincir kuralı sonlu farklar yöntemini kullanır ve hesaplama ilerledikçe hata birikir.
      “Diğer yöntemlerle karşılaştırma” bölümüne bakabilirsiniz: https://en.m.wikipedia.org/wiki/Automatic_differentiation
      Yakındaki yorumlarda denildiği gibi, asıl nokta uygulamanın gerçekten önemli ve öğrenmeye değer olması. Otomatik türev almayı zincir kuralını uygulama yöntemlerinin bir bütünü olarak adlandırmak sorun değil, ama buna “sadece” sayısal zincir kuralı demek yanlış
    • Daha doğru olabilir, ama daha anlaşılır demezdim
  • Otomatik türev alma, düzgün fonksiyonlar kategorisinde Jacobian matrisi ve toplam türevin Kartezyen lensinden ibaret; bunda sorun ne? https://www.youtube.com/watch?v=ne99laPUxN4

  • Sınıf adının neden Tensor olduğunu merak ediyorum. Bir ifadeyi ya da onun türevini tensör olarak düşünmenin bir yolu var mı? Yoksa skalerler de tensör olduğu ve bu yapı başka tensör türlerini de destekleyecek şekilde genişletilebildiği için mi?

    • Yanılıyor olabilirim ama matematiksel olarak 2 boyutlu nesnelere matris, 3 boyut ve üzerindeki nesnelere tensör denir diye düşünüyorum.
      Açıklanan otomatik türev alma algoritması keyfi yüksek boyutlu nesneler için çalıştığından, bu nesnelere tensör demek mantıklı görünüyor