done
This commit is contained in:
134
Tsetlin_in_Operation.py
Normal file
134
Tsetlin_in_Operation.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
"""
|
||||||
|
Tsetlin Automata team experiment
|
||||||
|
--------------------------------
|
||||||
|
We have 5 independent learning automata. Each one can only choose "No" or "Yes".
|
||||||
|
Together they form a team. The environment looks at how many said "Yes" (called M)
|
||||||
|
and then rewards or penalizes them. Over many rounds they learn to keep M near 3,
|
||||||
|
because that is where the reward probability is highest (0.6).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
NUM_AUTOMATA = 5 # team size
|
||||||
|
STATES_PER_ACTION = 3 # states per action (No / Yes)
|
||||||
|
ITERATIONS = 10000 # learning rounds
|
||||||
|
|
||||||
|
|
||||||
|
class Environment:
|
||||||
|
"""Gives reward/penalty based only on M = how many automata said Yes."""
|
||||||
|
|
||||||
|
def __init__(self, c_1, c_2):
|
||||||
|
"""c_1 = 0.2 (step), c_2 = 0.6 (peak reward at M=3)."""
|
||||||
|
self.c_1 = c_1
|
||||||
|
self.c_2 = c_2
|
||||||
|
|
||||||
|
def give_penalty(self, M):
|
||||||
|
"""
|
||||||
|
Sample reward/penalty for one automaton from M.
|
||||||
|
M = 0..3: P(reward) = M * 0.2
|
||||||
|
M = 4..5: P(reward) = 0.6 - (M-3)*0.2
|
||||||
|
Returns True = penalty, False = reward.
|
||||||
|
Best case is M=3 (P=0.6); too few or too many Yes votes get worse odds.
|
||||||
|
"""
|
||||||
|
if M == 0 or M == 1 or M == 2 or M == 3:
|
||||||
|
if random.random() <= M * self.c_1:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
elif M == 4 or M == 5:
|
||||||
|
if random.random() <= self.c_2 - (M - 3) * self.c_1:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def reward_probability(self, M):
|
||||||
|
"""Same formulas without randomness — used to print the payoff curve."""
|
||||||
|
if M == 0 or M == 1 or M == 2 or M == 3:
|
||||||
|
return M * self.c_1
|
||||||
|
elif M == 4 or M == 5:
|
||||||
|
return self.c_2 - (M - 3) * self.c_1
|
||||||
|
|
||||||
|
|
||||||
|
class Tsetlin:
|
||||||
|
"""
|
||||||
|
One automaton with actions No/Yes.
|
||||||
|
With n=3: states 1-3 = No, 4-6 = Yes.
|
||||||
|
Lower No / higher Yes = more confident. Does not see M, only reward/penalty.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, n):
|
||||||
|
"""Start at the weak boundary (state n or n+1)."""
|
||||||
|
self.n = n
|
||||||
|
self.state = random.choice([self.n, self.n + 1])
|
||||||
|
|
||||||
|
def reward(self):
|
||||||
|
"""Current action was good — move deeper into it (more confident)."""
|
||||||
|
if self.state <= self.n and self.state > 1:
|
||||||
|
self.state -= 1
|
||||||
|
elif self.state > self.n and self.state < 2 * self.n:
|
||||||
|
self.state += 1
|
||||||
|
|
||||||
|
def penalize(self):
|
||||||
|
"""Current action was bad — move toward the other action (may flip)."""
|
||||||
|
if self.state <= self.n:
|
||||||
|
self.state += 1
|
||||||
|
else:
|
||||||
|
self.state -= 1
|
||||||
|
|
||||||
|
def make_decision(self):
|
||||||
|
"""Return 'No' or 'Yes' from the current state."""
|
||||||
|
if self.state <= self.n:
|
||||||
|
return "No"
|
||||||
|
return "Yes"
|
||||||
|
|
||||||
|
|
||||||
|
# Build environment and create 5 independent automata
|
||||||
|
env = Environment(0.2, 0.6)
|
||||||
|
automata = [Tsetlin(STATES_PER_ACTION) for _ in range(NUM_AUTOMATA)]
|
||||||
|
m_count = [0] * (NUM_AUTOMATA + 1) # how often each M occurs
|
||||||
|
|
||||||
|
# Learning loop: decide -> count Yes (M) -> reward/penalize everyone from M
|
||||||
|
for _ in range(ITERATIONS):
|
||||||
|
actions = [la.make_decision() for la in automata]
|
||||||
|
M = actions.count("Yes")
|
||||||
|
m_count[M] += 1
|
||||||
|
|
||||||
|
print("M:", M, end=" ")
|
||||||
|
|
||||||
|
# Shared team feedback: probability depends on M, not on this automaton alone
|
||||||
|
for j, la in enumerate(automata):
|
||||||
|
print(f"TA{j + 1} State: {la.state} Action: {actions[j]}", end=" ")
|
||||||
|
|
||||||
|
if env.give_penalty(M):
|
||||||
|
print("Penalty", end=" ")
|
||||||
|
la.penalize()
|
||||||
|
else:
|
||||||
|
print("Reward", end=" ")
|
||||||
|
la.reward()
|
||||||
|
|
||||||
|
print("New State:", la.state, end=" ")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Results
|
||||||
|
print("Reward probability P(reward | M):") # theory: peak at M=3
|
||||||
|
for m in range(NUM_AUTOMATA + 1):
|
||||||
|
print(f" M={m}: {env.reward_probability(m):.1f}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("Final automaton states:") # usually about 3 Yes and 2 No
|
||||||
|
for i, ta in enumerate(automata, start=1):
|
||||||
|
print(f" Automaton {i}: state={ta.state} action={ta.make_decision()}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f"Distribution of M over all {ITERATIONS} iterations:") # M=3 should dominate
|
||||||
|
for m in range(NUM_AUTOMATA + 1):
|
||||||
|
share = 100.0 * m_count[m] / ITERATIONS
|
||||||
|
print(f" M={m}: {m_count[m]:6d} ({share:6.2f}%)")
|
||||||
|
|
||||||
|
print()
|
||||||
|
average_m = sum(m * m_count[m] for m in range(NUM_AUTOMATA + 1)) / ITERATIONS
|
||||||
|
print("Average M over all iterations:") # should be close to 3
|
||||||
|
print(f" {average_m:.3f}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("Optimum of the payoff function is M=3 (P(reward)=0.6).")
|
||||||
Reference in New Issue
Block a user