Herkese merhaba,
Bu yazıda Python ile mini bir bayesian network motoru nasıl tasarlanabilir bundan bahsedeceğim.
Varsayalım ki bir metnin robot üretimi mi veya insan üretimi mi olduğunu ölçmek istiyoruz. Elimizde bir metnin robot tarafından mı yoksa insan tarafından mı üretildiğini test eden üç araç olsun.
Bu araçlara metin yüklediğimizde bize + veya - sonuçlar veriyorlar.
Metinlerle ilgili şunlar biliniyor olsun:
-
P(R) = 0.25 : Robotlar tarafından üretilen metin oranı.
-
P(H) = 0.75 : İnsanlar tarafından üretilen metin oranı.
-
P(R) + P(H) = 1
Araçlarla ilgili olarak da aşağıdakiler bilinmektedir.
-
P(D_1^+|R) = 0.90 : Bir robot tarafından üretilmiş bir metni D_1 aracının + olarak işaretleme olasılığı.
-
P(D_1^-|H) = 0.85 : Bir insan tarafından üretilmiş bir metni D_1 aracının - olarak işaretleme olasılığı.
-
P(D_2^+|R) = 0.84 : Bir robot tarafından üretilmiş bir metni D_2 aracının + olarak işaretleme olasılığı.
-
P(D_2^-|H) = 0.78 : Bir insan tarafından üretilmiş bir metni D_2 aracının - olarak işaretleme olasılığı.
-
P(D_3^+|R) = 0.95 : Bir robot tarafından üretilmiş bir metni D_3 aracının + olarak işaretleme olasılığı.
-
P(D_3^-|H) = 0.93 : Bir insan tarafından üretilmiş bir metni D_3 aracının - olarak işaretleme olasılığı.
Bir metnin robot üretimi olup olmadığını test etmek için birden çok araç kullanabiliriz. Bu araçların birbirine bağımlı veya bağımsız olmasına göre farklı olasılık modellerini hesaplamamız gerekir.
Birazdan sırayla tek bir araç için, iki araç için ve üç araç için farklı senaryolara göre olasılık modelleri oluşturacağız. Ama ondan önce iki veya üç araç kullanarak modelleme yaptığımızda bilmemiz gereken koşullu bağımlılık ilişkilerini tanımlayalım:
-
D_2, D_1'e bağımlı ise:
-
P(D_2^+|R,D_1^+) = 0.95 : Bir robot tarafından üretilmiş bir metni D_1 aracı + olarak işaretlemiş ise D_2 aracının metni + olarak işaretleme olasılığı.
-
P(D_2^+|R,D_1^-) = 0.35 : Bir robot tarafından üretilmiş bir metni D_1 aracı - olarak işaretlemiş ise D_2 aracının metni + olarak işaretleme olasılığı.
-
P(D_2^+|H,D_1^+) = 0.56 : Bir insan tarafından üretilmiş bir metni D_1 aracı + olarak işaretlemiş ise D_2 aracının metni + olarak işaretleme olasılığı.
-
P(D_2^+|H,D_1^-) = 0.45 : Bir insan tarafından üretilmiş bir metni D_1 aracı - olarak işaretlemiş ise D_2 aracının metni + olarak işaretleme olasılığı.
-
-
D_3, D_1'e bağımlı ise:
-
P(D_3^+|R,D_1^+) = 0.92 : Bir robot tarafından üretilmiş bir metni D_1 aracı + olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|R,D_1^-) = 0.13 : Bir robot tarafından üretilmiş bir metni D_1 aracı - olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|H,D_1^+) = 0.16 : Bir insan tarafından üretilmiş bir metni D_1 aracı + olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|H,D_1^-) = 0.23 : Bir insan tarafından üretilmiş bir metni D_1 aracı - olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
-
D_3, D_2'ye bağımlı ise:
-
P(D_3^+|R,D_2^+) = 0.94 : Bir robot tarafından üretilmiş bir metni D_2 aracı + olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|R,D_2^-) = 0.24 : Bir robot tarafından üretilmiş bir metni D_2 aracı - olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|H,D_2^+) = 0.14 : Bir insan tarafından üretilmiş bir metni D_2 aracı + olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|H,D_2^-) = 0.18 : Bir insan tarafından üretilmiş bir metni D_2 aracı - olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
-
D_3, D_1 ve D_2'ye bağımlı ise:
-
P(D_3^+|R,D_1^+,D_2^+) = 0.98 : Bir robot tarafından üretilmiş bir metni D_1 aracı +, D_2 aracı da + olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|R,D_1^+,D_2^-) = 0.67 : Bir robot tarafından üretilmiş bir metni D_1 aracı +, D_2 aracı da - olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|R,D_1^-,D_2^+) = 0.53 : Bir robot tarafından üretilmiş bir metni D_1 aracı -, D_2 aracı da + olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|R,D_1^-,D_2^-) = 0.49 : Bir robot tarafından üretilmiş bir metni D_1 aracı -, D_2 aracı da - olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|H,D_1^+,D_2^+) = 0.65 : Bir insan tarafından üretilmiş bir metni D_1 aracı +, D_2 aracı da + olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|H,D_1^+,D_2^-) = 0.54 : Bir insan tarafından üretilmiş bir metni D_1 aracı +, D_2 aracı da - olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|H,D_1^-,D_2^+) = 0.41 : Bir insan tarafından üretilmiş bir metni D_1 aracı -, D_2 aracı da + olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
-
P(D_3^+|H,D_1^-,D_2^-) = 0.03 : Bir insan tarafından üretilmiş bir metni D_1 aracı -, D_2 aracı da - olarak işaretlemiş ise D_3 aracının metni + olarak işaretleme olasılığı.
Şimdi oluşturacağımız bütün modellerde geçerli olacak basit bir Python uygulaması oluşturalım.
-
from math import log2
from itertools import product
from functools import lru_cache
CPT = dict[tuple[bool, ...], dict[bool, float]]
class Node:
def __init__(
self,
cpt: CPT,
parents: list['Node', ...] = None,
name: str = None
):
if not self.is_cpt(cpt):
raise ValueError("Invalid CPT")
self.__cpt = cpt
self.__parents = tuple(parents) if parents else ()
self.__name = name if name else str(id(self))
@property
def name(self):
return self.__name
@property
def cpt(self):
return self.__cpt
@property
def parents(self):
return self.__parents
def is_cpt(self, cpt: CPT) -> bool:
if not isinstance(cpt, dict):
return False
repeat = int(log2(len(cpt)))
keys = set(product((True, False), repeat=repeat))
if set(cpt.keys()) != keys:
return False
for entry in cpt.values():
if set(entry.keys()) != {True, False}:
return False
if not abs(sum(entry.values()) - 1) < 1e-9:
return False
return True
def posterior_probability(
p_prior: dict[bool, float],
nodes: list[Node],
evidence: dict[str: bool]
) -> float:
def marginalize(
cpt: CPT,
parents: list[Node],
key: tuple[bool, ...]
) -> dict[bool, float]:
if not parents:
return cpt[key]
parent, *rest = parents
if parent.name in evidence:
return marginalize(cpt, rest, key + (evidence[parent.name],))
else:
left = marginalize(cpt, rest, key + (True, ))
right = marginalize(cpt, rest, key + (False, ))
return {k: left[k] + right[k] for k in left}
@lru_cache(maxsize=None)
def joint_probability(hypothesis: bool) -> float:
p_joint = p_prior[hypothesis]
for node in nodes:
cpt_entry = marginalize(node.cpt, node.parents, (hypothesis, ))
if node.name in evidence:
p_joint *= cpt_entry[evidence[node.name]]
else:
p_joint *= sum(cpt_entry.values())
return p_joint
p_true, p_false = tuple(map(joint_probability, (True, False)))
return p_true / (p_true + p_false)
Node sınıfı, bağımlı veya bağımsız gözlemleri temsil etmek için kullanacağımız sınıftır. Bu sınıftan örnek oluştururken cpt adında sözlük tipinde geçerli bir argüman yazılmalı. parents argümanı ise list tipinde olup, aracın bağlı olduğu başka bir araç varsa kullanılır.
-
Geçerli bir
cptverisinin yapısı,dict[tuple[bool, ...], dict[bool, float]]veri yapısına uyar. -
parentsniteliği ise, bir aracın bağımlı olduğu diğer araçları içerir. -
nameniteliği ise,evidencesözlüğünde anahtar olarak kullanılır.
posterior_probability() fonksiyonu ise üç argüman alır:
-
p_prior:dict[bool: float]veri yapısında olan bir ön olasılık. Örneğin popülasyonda robotlar tarafından yazılmış metin oranı. -
nodes:list[Node]tipinde,p_priorolasılığı güncellerken kullanacağımız diğer koşullu durumları tutan liste. -
evidence:p_priorolasılığı güncellerken kullanacağımız gözlemlenmiş koşullu durumları ve bunlarınbooleansonuçlarını içerendict[str: bool]yapısında bir sözlüktür.
posterior_probability() fonksiyonu ise kendi içinde iki başlıkta incelenebilir:
-
joint_probability()fonksiyonunda,p_priorolasılığının güncellenmesinde kullanılmak üzere her bir düğümmarginalize()fonksiyonuna gönderilir. -
marginalize()fonksiyonuna gönderilen bir düğümün, başka düğümlere bağımlı olup olmadığıif parentssatırı ile sorgulanır. Şayet düğümünparentsniteliği boşsacpt[key]fonksiyondan döndürülür. Eğer düğümünparentsniteliği doluysa, listedeki ilkparentdüğümünevidenceiçinde olup olmadığı kontrol edilir. Eğerparentdüğümünnameniteliğievidencesözlüğüne eklenmişse,(key, ) + (evidence[parent.name],)ifadesi yoluylakeygüncellenir vemarginalize()fonksiyonu güncellenmişkeyile tekrar çağrılır. Her bir öz-yinelemeden önceparentslistesinden sırası gelenparentdüşürülür.parentslistesi boşaldığında fonksiyondancpt[key]döner. Eğerparentdüğümünnameniteliği,evidencesözlüğüne eklenmemişse; hem sol, hem de sağ dal ayrı ayrı marjinalize edilir ve son olarak da sol ve sağ dallardaki değerler toplanarak tek birdict[bool, float]nesnesi döndürülür. Özetle her koşulda fonksiyondandict[bool, float]yapısında bir veri döner.