Evrimsel Algoritmalar
Ne zamandır, evrimsel algoritmalar ve genetik algoritmalarına göz atmak istiyordum.. Bugün biraz fırsat bulup, evrimsel algoritmalara göz attım.
Evrimsel algoritma, bana biraz breadth-first search algoritmalarını andırdı. Genel gidişat şu şekilde:
- Rastgele bireylerden ilk popülasyonu oluştur
- Her bireyin, aranan sonuca benzerliğini test et
- En iyi bireylerden, mutasyon ve eşleşme ile yeni bireyler oluştur.
- En iyi bireylerden yeni popülasyon oluştur.
- Yeterince iyi bireyler elde edene kadar tekrarla
Bu tür algoritmaları genelde arama uzayının çok büyük olduğu durumlarda kullanıyorlar. Daha önce gördüğüm algoritmalara nazaran, doğru sonuca bir hayli hızlı yaklaşıyor, ancak, bazı sıkıntılar da yaşadım, bunlardan birazdan bahsedeceğim.
Problem
Küçükken, diyanet takvimlerinin arkasında, çözmekten zevk aldığım bir bulmaca vardı. 5x5'lik bir alanı, 1'den 25'e kadar sayılarla öyle bir şekilde doldurmalısın ki, bütün satır ve sütun toplamları aynı olsun.
Problem, NP-hard gibi görünüyor. Evrimsel algoritmam için iyi bir aday olabilir diye düşündüm. İlk iş, bir sınıf oluşturdum.
Burada sadece daha sonra kullanacağım değişkenleri başlattım. Pek değişik birşey yok. Evrimsel algoritmayı kullanmak için, önce rastgele bir popülasyon oluşturmamız gerekiyor.
Bu fonksiyon, 1'den n karaye kadar olan sayıları rastgele dizerek, verilen büyüklükte bir popülasyon oluşturuyor. Daha sonra yapmamız
gereken şey, bireylerin aradığımız sonuca ne kadar yakın olduğunu bulmak. Bunun için, cost function (maliyet fonksiyonu) denen fonksiyonlar
kullanılıyor. Ben her bir satır ve sütun için, hata terimini (olmasını istediğimiz toplam - satır veya sütun toplamı)**2
olarak tanımladım.
Her bireyin maliyeti, hata terimlerinin toplamının karekökü olacak.
Asıl maliyet, errorterms
sözlüğünün içindeki 'SSR' anahtarında. Her satır ve sütunun için ayrı ayrı hata terimlerini gerek duyabilirim diye
döndürüyorum. Artık, yeni bir nesil oluşturabilmek için gerekli fonksiyonlar tanımlandı. Evrimsel algoritmanın en merkezi noktası burası:
Ben burada bir sonraki nesili elde etmek için, farklı mutasyonlar kullandım. Eşleşme yapmayı da denemiştim, ama güzel sonuç vermedi. Burada, eski nesilden en uygun çeyreğini, olduğu gibi yeni nesile kopyaladım. Ayrıca, bunların her biri için, rastgele iki kare seçip bunları yerlerini değiştirip, bu yeni hallerini yeni nesile ekledim. Bu rastgele iki kare değiştirme işlemini, her birey için 2 şer kez yaptım. Daha sonra, yine bu en uygun 1/4 lük bireyler için, her birinden bir satır veya bir sütun seçip, bunu sağa veya aşağı doğru, bir adım kaydırdım. Son olarak, en uygun bireye, ekstra mutasyon uyguladım. Bu nesilden nesile geçişi nasıl kodladığınız, algoritmanızın sonuç üretebilme gücünü bir hayli değiştiriyor.
Son olarak, ihtiyacım olan şey, ana döngüyü kurmak.
Evet, ana döngüde ekstra birşey yok. Belli şartlar yerine getirilinceye kadar, popülasyonu yeniliyor.
Çalışma Süresi
Bu algoritmayı, 3x3, 4x4, 5x5 ve 6x6 lık oyunlar için yüzer defa çalıştırdım. Sonuca ulaşmak için gereken nesil sayıları şöyle oldu.
3x3lük oyunda, çoğu zaman 10 nesilden daha kısa bir zamanda sonuç bulunuyor. 4x4'de bu rakam 80'lere, 5x5'de 300'lere ulaşıyor. 6x6'da ise, sonucu bulmak için, 100'den fazla nesil gerekebiliyor.
Bu biraz benim acemeliğime de gelmiş olabilir ancak, algoritmayı pek scaleable bulmadım açıkçası. Ancak, algoritmanın şöyle güzel bir yanı var ki, doğru sonuca çok hızlı yaklaşıyor. Örneğin, 5x5lik bir oyunda, her nesilin sonuca ne kadar yaklaştığını gösteren grafiğe bir bakalım.
Yukardaki grafikte, 79. nesilde gelinen durum, doğru sonuca bir değişim uzak kalınan durum. Yani bir sonraki nesil, doğru
iki kareyi kendi arasında değiştirdiğinde, doğru sonuca ulaşılmış olacak. Ancak bu değişimin gerçekleşme ihtimali çok düşük.
Örneğin, 5x5lik oyunda, yapılabilecek 25*24 = 600
farklı ikili değişim var. Yani, doğru değişimi yapma ihtimali, 1/600. Ancak,
her nesilde tek bir değişim değil, bir sürü değişim yapılıyor tabi ki. Ancak, yine de doğru değişimi yapma ihtimali çok düşük. Bu da
yukarıdaki grafikte, neden doğru sonuca 154. nesilde erişildiğini açıklıyor bence.
Bu algoritma doğru sonuca hızlı yaklaştığı için, belli bir yakınlığa erişinceye kadar bunu, o noktadan sonra, daha kontrollü bir seçimler yapan bir algoritmayı kullanmak, sonucu bir hayli hızlandırır diye düşünüyorum, ancak henüz bunu denemedim. Belki müsait bir zamanda onu da denerim.
Evrimsel algoritma kodları her zamanki gibi, gist üzerinde erişilebilir durumda.