Evrimsel oyun teorisi ve seçilim

Martin Nowak evolutionary dynamics kitabında üremeyi şöyle basitçe modeller:

\(\dot{x} = \frac{dx}{dt} = r\cdot x \)

Burada \(x \) değişkeni türün nüfusunu belirtiyor. Dolayısıyla \(\dot{x} \) terimi de türün nüfusunun zamana göre değişimini gösteriyor, yani zaman geçtikçe nüfusunun ne kadar arttığını ya da azaldığını ifade ediyor. Bu artış ya da azalış da o anki nüfusun sabit bir \(r \) sayısıyla çarpımına eşit olduğu bir modelle gösterilsin diyor. Bu arada doğum ve ölüm oranlarının çoğalma katsayısında olduğunu da unutmamak lazım. Yani aradaki fark çoğalmayı veriyor ve bu fark aslında nüfusun azalması da demek olabilir.

Bu şekildeki bir modelde nüfus artışı tabii ki üssel bir davranış gösterecektir.

\(r = 1.1 \) değeri için yukarıdaki grafiği elde ettim. Toplamda yirmi adım bile gidilmeden nüfus bir milyonu geçti. Böyle bir ortamda bütün türler aynı hızda çoğalırdı ama. Nowak hemen sonra gelen seçilim kısmının başında “seçilim değişik türlerin bireyli değişik oranlarda çoğalırsa meydana gelir” diyor. Eğer her tür aynı oranda çoğalsaydı nüfuslarını birbirlerinden ayıran tek fark başlangıçtaki nüfusları olacaktı. Diğer bir deyişle nüfusların başlangıçtaki oranları nesiller geçtikçe hiç değişmeyecekti. Bu durumda gerçekten de bir seçilimden bahsetmek zor olabilir.

İkinci cümlede ise seçilim olması için en az iki tür olmalıdır diyor. Bu da seçimden bahsedilebilmesi için anlaşılır bir varsayım hatta tanım bile olabilir.

O zaman iki türden oluşan modeli şöyle veriyor:

\(\dot{x} = a\cdot {x}\)

\(\dot{y} = b\cdot {y}\)

Burada \(a > b \) ise birinci tür ikinci türden çok daha hızlı üreyecektir ve zaman geçtikçe birinci türün nüfusunun ikinci türün nüfusuna oranı sürekli artacaktır. \(b > a \) olduğu durumda da tersi senaryo gözlenecektir.

Tabii ki her türün sınırsız büyüdüğü modeller pek gerçekçi değil. Ekosistemimizin maksimum birey sayısına sahip olduğunu varsayalım ve modelimizi buna uygun hale getirelim. Bu modelde toplam bir sayıdan bahsetmek yerine nüfus oranları kullanılacak. Yine aynı değişkenleri kullanıyoruz ama artık \(x \) değişkeni birinci türün nüfusunu değil de birinci türün nüfusunun toplam birey sayısına oranını veriyor. Bu oranlar haliyle minimum 0 ve maksimum 1 değerine sahip olabilirler.

Kitapta bunun için şu model veriliyor:

\(\dot{x} = a\cdot {(x-\phi)}\)

\(\dot{y} = b\cdot {(y- \phi)}\)

Buradaki \(\phi \) teriminin görevi türlerin nüfuslarının oranlarının toplamını 1 değerinde sabit tutmak. Bunu sağlamak için de

\(\phi = a\cdot {x} + b\cdot{y} \)

eşitliğinin sağlanması gerekiyor. Yani her adımda \(\phi \) değeri o adımdaki nüfusların oranına göre tekrar hesaplanıyor.

Aşağıdaki python programıyla bu modeli denedim.

import numpy as np
import matplotlib.pyplot as plt


a = 1.1
b = 1.3
number_of_iterations = 100

def constrainedA(x, phi) :
    return x*(a - phi)

def constrainedB(y, phi) :
    return y*(b - phi)

def phi(x, y):
    return a*x + b*y;
x = 0.5
y = 0.5

x_population = np.array([[0, x]])
y_population = np.array([[0, y]])
total = np.array([[0, x+y]])

for i in range(0, number_of_iterations):
    phi_value = phi(x, y)
    delta_x = constrainedA(x, phi_value)
    delta_y = constrainedB(y, phi_value)
    x += delta_x
    y += delta_y
    x_population = np.append(x_population, np.array([[i, x]]), axis = 0)
    y_population = np.append(y_population, np.array([[i, y]]), axis = 0)
    total = np.append(total, np.array([[i, x+y]]), axis = 0)

plt.plot(x_population[:, 0], x_population[:, 1])
plt.plot(y_population[:, 0], y_population[:, 1])
plt.plot(total[:, 0], total[:, 1])
plt.xlabel("zaman")
plt.ylabel("nüfus")
#plt.plot(y_population[:, 0], y_population[:, 1])
plt.show()

Sonuç olarak da şu grafiği elde ettim:

Bu programda adım sayısını 20’den 100’e yükselttim. Bu sayede türlerin birinin diğerini ekosistemden nasıl sildiğini görebiliyoruz. Kırmızı çizgiyle gösterilen türün çoğalma katsayısı 1.3, mavi çizgiyle gösterilen türünkü ise 1.1 idi. Yeşi çizgi de nüfus oranlarının toplamını gösteriyor ve sürekli 1 değerine sahip.

Martin Nowak bu modeli daha iyinin hayatta kalmasına (Survival of the fitter) örnek olarak veriyor. Gerçekten de bu modelde daha iyi çoğalma yeteneğine kalan tür hayatta kalıyor ve diğeri yok oluyor. Sonra aynı modeli ikiden fazla tür için kurup bu sefer de en iyinin hayatta kalması (Survival of the fittest) fikrini gösteriyor. Bu modelde iki genellemeye gidiyor:

\(\phi = \sum_{i=1}^{n} x_{i} \cdot {f_{i}} \)

\(\dot{x_{i}} = x_{i} \cdot (f_{i} – \phi) \)

Bu modelde \(x_{i}\) i numaralı türün nüfus oranını \(f_{i}\) de aynı türün çoğalma fonksiyonunu (fitness) gösteriyor. Modelin gerisi aynı şekilde çalıştırılıyor.

Bu noktadan sonra Nowak iki yeni durum için modelimiz nasıl olmalı diye bir soru soruyor:

  1. Ekosistemdeki ilk mevcut tür daha sonradan gelen tür ne kadar iyi olursa olsun mücadeleyi kazansın (Survival of the first)
  2. Ekosistemdeki her tür hayatta kalsın (Survival of all)

Bu durumda Nowak doğrusal fitness fonksiyonu şartından vazgeçmemiz gerekir diyor ve şu modeli sunuyor:

\(\dot{x} = a\cdot {x^c}-\phi \cdot {x}\)

\(\dot{y} = b\cdot {y^c}- \phi \cdot {y}\)

Bu modelde nüfus oranlarının toplamını sabitlemek için şu eşitliğe ihtiyacımız var:

\(\phi = a\cdot{x^c} + b\cdot {y^c} \)

Modeldeki \(c \) sabitinin birden büyük seçersek 1. olasılığı modellemiş oluyoruz. 1. senaryonun aklıma takılan kısmı bunu nasıl programlayacağımdı. Bu senaryoda söylenen şey şu: Mesela bütün toplam nüfus sadece \(x \) türünden oluşuyorsa sisteme ekleyeceğimiz bir \(y \) türü bireyi çoğalma potansiyeli diğer türden yüksek olsa bile yok olmaya mahkumdur. Yani programda ilk anda \(x = 1 \) ile başlamam gerekecek. Ardından sisteme minimum bir \(y \) değeri eklemem lazım. Bunun yerine başlangıçta minimum \(y \) ile başlamayı seçtim.

import numpy as np
import matplotlib.pyplot as plt


a = 1.1
b = 2.0
c = 1.3
number_of_iterations = 20

def constrainedA(x, phi) :
    return a*pow(x, c) - x*phi

def constrainedB(y, phi) :
    return b*pow(y, c) - y*phi

def phi(x, y):
    return a*pow(x, c) + b*pow(y, c);
x = 0.999
y = 0.001

x_population = np.array([[0, x]])
y_population = np.array([[0, y]])

for i in range(0, number_of_iterations):
    phi_value = phi(x, y)
    delta_x = constrainedA(x, phi_value)
    delta_y = constrainedB(y, phi_value)
    x += delta_x
    y += delta_y
    x_population = np.append(x_population, np.array([[i, x]]), axis = 0)
    y_population = np.append(y_population, np.array([[i, y]]), axis = 0)

plt.plot(x_population[:, 0], x_population[:, 1])
plt.plot(y_population[:, 0], y_population[:, 1])
plt.show()

Bu sefer programda toplam nüfus oranlarını göstermek istemedim, çünkü o zaman birinci türün çok çabuk 1 değerine ulaşması net gözükmüyordu.

Grafikte çok net görülmüyor ama daha ilk adımda birinci tür bütün popülasyonu ele geçirdi.

Bu senaryo tabii ki her başlangıç değeri için bu sonucu vermiyor. Örneğin aynı \(c \) sabiti için \(x \) türünü nüfusun yüzde sekseni olacak şekilde başlatınca bu sonuç çıkıyor.

Görüldüğü gibi daha iyi fitness fonksiyonuna sahip olan \(y \) yok olmadığı gibi bütün ekosistemi de ele geçirdi.

Eğer \(c < 1 \) olacak şekilde bir seçim yaparsak iki tür de yok olmadan beraber yaşama şansını yakalayabiliyor.

Bunun denemesini de aşağıdaki programla yaptım.

import numpy as np
import matplotlib.pyplot as plt


a = 1.2
b = 0.8
c = 0.5
number_of_iterations = 20

def constrainedA(x, phi) :
    return a*pow(x, c) - x*phi

def constrainedB(y, phi) :
    return b*pow(y, c) - y*phi

def phi(x, y):
    return a*pow(x, c) + b*pow(y, c);
x = 0.5
y = 0.5

x_population = np.array([[0, x]])
y_population = np.array([[0, y]])

for i in range(0, number_of_iterations):
    phi_value = phi(x, y)
    delta_x = constrainedA(x, phi_value)
    delta_y = constrainedB(y, phi_value)
    x += delta_x
    y += delta_y
    x_population = np.append(x_population, np.array([[i, x]]), axis = 0)
    y_population = np.append(y_population, np.array([[i, y]]), axis = 0)

plt.plot(x_population[:, 0], x_population[:, 1], label='x')
plt.plot(y_population[:, 0], y_population[:, 1], label='y')
plt.legend()
plt.show()

Grafikte de görüldüğü gibi iki tür de yok olmadı.

Heralde bu özellikleri sağlayan daha başka modeller de vardır ama konuya bu şekilde girilmesi hoşuma gitti. Bir modelin olası sonuçlarından çok, istenen sonucu açıklayabilecek bir model sunma tekniği ilgimi çekti. Çalışmalarıma biraz da bu kitaptan devam edeyim, belki şansım bu sefer döner.

Network karşılıklılığı (5)

Bu tür oyunlarda işbirlikçiler duruma göre ihanetçilerle beraber yaşayabiliyor ve hatta bazı durumlarda daha üstün bile olabiliyorlar. Anladığım kadarıyla burada Oyuncular bir çizgenin (graph) köşeleri olacak şekilde tanımlanıyor. Bu durumda her köşe ya işbirlikçi ya da ihanetçi stratejilerine sahip oluyor. Sonra bir \(k \) tamsayısı ile her köşenin kaç kenara sahip olduğu tanımlanıyor. Yani her oyuncu bu kadar komşuya sahip oluyor. Her oyuncu sadece kendi komşularıyla oynuyor ve standard kazanç matrisine göre kazançlar hesaplanıyor. Bir oyuncu birden fazla komşuyla oynadığında kazançları da oyunlardaki kazançların toplamı oluyor.

Bu modelin evrimleşmesi de şu şekilde gerçekleşiyor. Herhangi bir adımda seçilen rastgele bir oyuncu ölüyor. Sonra bu ölen oyuncunun köşesini kapmak için o köşenin komşu oyuncularından birisi kazançlarına göre seçiliyor.

Her komşu iki oyuncu birbiriyle şu matrise göre oyun oynuyor:

\(M = \begin{bmatrix} R&&S \\T&&P \end{bmatrix} \)

Yukarıdaki kurallarla birleştirince matris şu biçime dönüşüyor:

\(M = \begin{bmatrix} R&&S + H\\T – H&&P \end{bmatrix} \)

\(H = \frac{(k+1)(R – P) – T + S}{(k+1)(k – 2)} \)

Aşağıdaki python programıyla bu oyunun simülasyonunu yapmaya çalıştım:

import numpy as np
import matplotlib.pyplot as plt

strategies = np.array([[1, 0], [0, 1]])

k = 3 #number of edges

R = 3
S = 5
T = 5
P = 1

H = ((k + 1) * (R - P) - T + S)/((k + 1)*(k - 2))
payoff = np.array([[R, S + H], [T - H, P]])

number_of_iterations = 100

increment = 0.01
steady_states = np.array([[0, 0]])


for s1 in np.arange(0, 1.0, increment):
    s2 = 1 - s1
    species = np.array([s1, s2])

    for i in range(0, number_of_iterations):

        difference = (payoff.dot(species) - species.dot(payoff).dot(species))*species
        species = species + difference
        species = np.clip(species, 0, 1)

        
    steady_states = np.append(steady_states, np.array([[s1, species[0]]]), axis = 0)

plt.plot(steady_states[:, 0], steady_states[:, 1])
plt.ylabel("işbirlikçi türün sondaki oranı")
plt.xlabel("işbirlikçi türün başlangıçtaki oranı")
plt.show()

Bu simülasyonun sonucunda da aşağıdaki grafiği elde ettim.

k = 3 için elde ettiğim sonuç

Bu kazanç matrisi ve model için işbirlikçilik daha iyi bir stratejiymiş.

Grup seçilimi (4)

Bu seçilim türünde oyunların sadece kişiler arasında değil, gruplar arasında da oynandığı varsayılır. Anladığım kadarıyla geçtiğimiz altmış yıl içinde bu yönde de bir sürü model üzerine çalışılmış. Makalede izlenen yöntem şöyle. Bütün nüfus \(m \) gruba bölünüyor. Her grupta en fazla \(n \) birey olabiliyor. Aynı gruptaki bireyler birbirleriyle tutsak ikilemi şeklinde karşılaşıyorlar. Her adımda bütün nüfustan rastgele bir birey üremek için seçiliyor. Oluşan yeni birey aynı gruba ekleniyor. Eğer bu grup maksimum büyüklüğe ulaşırsa belli bir olasılıkla bu grup iki gruba ayrılıyor ve toplam nüfus sınırsızca büyümesin diye rastgele seçilen bir grup yok ediliyor. Geri kalan olasılıkla ise grup ikiye bölünmüyor, bunun yerine aynı gruptan rastgele seçilen bir birey ölüyor.

Bu modelde yukarıda da anlattığım gibi oyunlar sadece grup içlerinde oynanıyor. Gruplar arası karşılaşmalar yok ama grupların bölünme dinamikleri her gruptaki bireylerin uygunluğu ile ilişkili olduğundan sonuçta sanki gruplar arası bir oyun oynanıyormuş gibi bir sonuç çıkıyor. İşin matematiksel detayına bu aşamada girmek istemiyorum. Belki ileride bu modelleri olduğu gibi programlayıp gerçekten de sonuçtaki modelin oluştuğunu gösterebilirim. Şimdilik sadece makalede verilen sonuç kazanç matrisini vereceğim ve bu matrise uygun programla yaptığım simülasyon sonuçlarına bakacağım.

\(M = \begin{bmatrix} (n+m)\cdot R&&n \cdot S + m \cdot R \\n \cdot T + m \cdot P &&(n + m) \cdot P \end{bmatrix} \)

Aşağıdaki programda önce kazanç matrisini tutsak ikilemi şartlarına uygun bir şekilde kurdum. Sonra grup sayısı ve grup büyüklüğü parametreleriyle oynayarak bu oyunun işbirlikçiler için avantajlı hale getirilebileceğini gösterdim.

import numpy as np
import matplotlib.pyplot as plt

strategies = np.array([[1, 0], [0, 1]])

m = 20 #number_of_groups
n = 10 #size of groups

R = 3
S = 0
T = 5
P = 1
payoff = np.array([[(n+m)*R, (n*S + m*R)], [(n*T + m*P), (n + m)*P]])

number_of_iterations = 100

increment = 0.01
steady_states = np.array([[0, 0]])

for s1 in np.arange(0, 1.0, increment):
    s2 = 1 - s1
    species = np.array([s1, s2])

    for i in range(0, number_of_iterations):

        difference = (payoff.dot(species) - species.dot(payoff).dot(species))*species
        species = species + difference
        species = np.clip(species, 0, 1)

        
    steady_states = np.append(steady_states, np.array([[s1, species[0]]]), axis = 0)

plt.plot(steady_states[:, 0], steady_states[:, 1])
plt.ylabel("işbirlikçi türün sondaki oranı")
plt.xlabel("işbirlikçi türün başlangıçtaki oranı")
plt.show()
m = 20 ve n = 10 için işbirlikçilik kazanan bir strateji oluyor

Doğrudan karşılıklılık

Evrimsel oyun teorisi kitabında bir sonraki konu karşılıklılık konusuydu. Cevap aranan soru anladığım kadarıyla şöyleydi: Oyun teorisi karşımızdakine ihanet etmemiz gerekir derken, toplum için daha iyi çözümler olan kooperasyon stratejilerine gerçek hayatta hangi mekanizmalarla erişebiliyoruz?

Martin Nowak bu soruya cevap veren beş adet mekanizma bulmuş. Kitap bu konuya kısaca değindiğinden bu konuyu Christine Taylor ve Martin Nowak’ın “Transforming the dilemma” başlıklı makalesinden öğrenmeye karar verdim.

Makalede ilk mekanizma olarak doğrudan karşılıklılık mekanizmasından bahsediliyor. Toplumlarda ikilemlere dayalı oyunlar oynanırken sonsuz büyüklükte bir toplumda herkesin rastgele kişilerle oyunlar oynadığını varsaymıştık. Gerçek hayatta ise bu oyunlar değişik şekilde oynanıyor. Örneğin bu mekanizmada olduğu gibi aynı kişiler aynı oyunu birbirleriyle defalarca oynayabiliyor. Bu durumda ikisi de diğer oyuncunun daha önce hangi stratejileri kullandığını biliyor ve kendi stratejilerini buna göre güncelleyebiliyor.

Bu mekanizmada iki oyuncu arasındaki karşılaşmadan sonra \(\omega \) olasılıkla tekrar karşılaşma oluyor. Bu durumda iki oyuncu arasında ortalama karşılaşma sayısı da \(\frac{1}{1 – \omega} \) formülüyle hesaplanabilir. İki oyuncunun karşılaşma kuralları şöyle:

İki oyuncu da işbirlikçiyse o zaman ikisi de işbirlikçi kalır. İhanet stratejisini seçen her zaman ihanet stratejisini uygular. İşbirlikçi ihanet stratejisi kullanan oyuncuyla karşılaştığında ikinci karşılaşmadan itibaren ihanet stratejisi kullanmaya başlar.

Bu durumda payoff matrisi aşağıdaki şekle dönüşür:

\(M = \begin{bmatrix} \frac {R}{1-\omega}&&S+\frac{\omega \cdot P}{1 – \omega}\\T+\frac{\omega \cdot P}{1 – \omega}&&\frac{P}{1-\omega} \end{bmatrix} \)

Aşağıdaki programla \(\omega \) değişkeninin etkisini denemeye çalıştım. Aslında bu değişkenle matrisin elemanları ve dolayısıyla elemanlar arasındaki ilişkiler değişeceğinden bu ikilemin başka bir oyun tipine dönüşmesini beklemek çok normal. Aslında bunu daha iyi görmek için oyuncuların karşılaşmalarını tek tek simüle edip aynı sonuca yaklaştığını göstermek lazım ama bu denemeleri ilerideki yazılarda yapmak istiyorum. Makalede bu dönüşüm için bir eşitsizlik veriliyor:

\(\omega > \frac{T-R}{T-P} \)

Eğer eşitsizlik sağlanmıyorsa ihanet eden strateji her durumda popülasyonu domine ediyor. Eğer eşitsizlik sağlanıyorsa işbirlikçi strateji de evrimsel açıdan kararlı bir durum olabiliyor. \(\omega \) değeri büyüdükçe, yani oyuncular arasındaki karşılaşma sayısı arttıkça işbirlikçi strateji de daha dominant olmaya başlıyor. Bu sonuçları denemek için aşağıdaki python programını kullandım:

import numpy as np
import matplotlib.pyplot as plt

strategies = np.array([[1, 0], [0, 1]])

R = 5
S = 1
T = 7
P = 3
omega = 0.60
prisoner_dilemma_payoff = np.array([[R/(1-omega), S + (omega*P)/(1 - omega)], [T + (omega*P)/(1-omega), P/(1-omega)]])

number_of_iterations = 100

payoff = prisoner_dilemma_payoff

increment = 0.01
steady_states = np.array([[0, 0]])


for s1 in np.arange(0, 1.0, increment):
    s2 = 1 - s1
    species = np.array([s1, s2])

    for i in range(0, number_of_iterations):

        difference = (strategies.dot(payoff).dot(species) - species.dot(payoff).dot(species))*species
        species = species + difference
        species = np.clip(species, 0, 1)
        
    steady_states = np.append(steady_states, np.array([[s1, species[0]]]), axis = 0)

plt.plot(steady_states[:, 0], steady_states[:, 1])
plt.ylabel("1. türün sondaki oranı")
plt.xlabel("1. türün başlangıçtaki oranı")
plt.show()

\(\omega = 0.6 \) değeri için aşağıdaki sonucu aldım

\(\omega = 0.6 \) değeri için işbirlikçi strateji belli bir yoğunluktan sonra kararlı bir strateji oluyor.

Programdaki R, S, T ve P değerlerini eşitsizlikte yerlerine koyduğumda

\(\omega > \frac{7 – 5}{7 – 3} = \frac {2}{4} = 0.5 \)

eşitsizliğini buluyoruz. Bu eşitsizliği sınıra yakın bir yerde denemek istedim.

\(\omega = 0.51\) değeri için şu grafiği elde ettim.

\(\omega = 0.51 \)

Eşitsizliğin söylediğinin aksine hiçbir durumda işbirlikçi stratejisi kararlı olamadı. Belki de programda küçük bir hata yapmışımdır. Bunu da incelemem lazım.

Eğer \(\omega \) değeri bire yaklaşırsa oyuncular arasında karşılaşma sayısı da artar. Bu durumda grafiğin nasıl olduğuna da bakmak istedim.

\(\omega = 0.99 \)

Gerçekten de hemen hemen her başlangıç durumu için işbirlikçi strateji kararlı bir strateji oldu.