feat: introduction of agentinc MDPs and KL divergence of > 2

This commit is contained in:
2026-01-13 15:57:05 +01:00
parent a36973cb42
commit eea019ab3f
2 changed files with 98 additions and 11 deletions

View File

@@ -56,7 +56,27 @@ class Loader:
def get_entries(self) -> tuple[list[str], int]: def get_entries(self) -> tuple[list[str], int]:
return self.entries, len(self.entries) return self.entries, len(self.entries)
class AgentLoader(Loader):
"""Loader for agent interaction data with simplified schema (direct PayloadModel format)"""
def _is_admin_page_simple(self, interaction: PayloadModel) -> bool:
return interaction.page and interaction.page.startswith("/admin/")
def _load_sessions(self) -> dict:
sessions = {}
for entry in self.entries:
int_path = f"{self.src_dir}/{entry}/int.json"
raw = json.load(open(int_path))
ints = [PayloadModel(**i) for i in raw]
sessions[entry] = [i for i in ints if not self._is_admin_page_simple(i)]
return sessions
if __name__ == "__main__": if __name__ == "__main__":
DIR = "/home/velocitatem/Documents/Projects/PHANTOM/experiments/agents/collected_data/"
loader = AgentLoader(DIR)
_, n = loader.get_entries()
print(f"Loaded {n} sessions from {DIR}")
DIR = "/home/velocitatem/Documents/Projects/PHANTOM/experiments/collected_data/" DIR = "/home/velocitatem/Documents/Projects/PHANTOM/experiments/collected_data/"
loader = Loader(DIR) loader = Loader(DIR)
_, n = loader.get_entries() _, n = loader.get_entries()

View File

@@ -1,10 +1,12 @@
from loader import Loader from experiments.agents.base import Agent
from loader import Loader, AgentLoader
from collections import defaultdict from collections import defaultdict
from typing import Dict, List, Tuple, Set from typing import Dict, List, Tuple, Set
import numpy as np import numpy as np
import graphviz import graphviz
DIR = "/home/velocitatem/Documents/Projects/PHANTOM/experiments/collected_data/" DIR = "/home/velocitatem/Documents/Projects/PHANTOM/experiments/collected_data/"
AGENT_DIR = "/home/velocitatem/Documents/Projects/PHANTOM/experiments/agents/collected_data/"
class BehaviorModel: class BehaviorModel:
def __init__(self, src_dir: str = DIR): def __init__(self, src_dir: str = DIR):
@@ -85,13 +87,32 @@ class BehaviorModel:
path.append(curr) path.append(curr)
return path return path
def visualize_mdp(model: BehaviorModel, threshold: float = 0.05, output: str = "mdp_graph", fmt: str = "svg", view: bool = False, export_dot: bool = False): class AgentBehaviorModel(BehaviorModel):
"""visualize MDP as directed graph using graphviz, aggregated by event type""" """behavior model for agent interaction data (simplified PayloadModel schema)"""
if not model.mdp: raise ValueError("build MDP first")
# aggregate transitions by event type def __init__(self, src_dir: str = AGENT_DIR):
self.loader = AgentLoader(src_dir)
self.data = self.loader.get_data()
self.entries, self.num_entries = self.loader.get_entries()
self.mdp = None
def _state_repr(self, evt) -> str:
# direct access to PayloadModel fields (no .value.payload nesting)
return f"{evt.page or 'unk'}|{evt.productId or 'none'}|{evt.eventName}"
def _extract_sessions(self):
trajectories = []
for sid, evts in self.data.items():
if len(evts) < 2: continue
# sort by timestamp string (ISO format sorts lexicographically)
states = [self._state_repr(e) for e in sorted(evts, key=lambda x: x.ts)]
trajectories.append(states)
return trajectories
def aggregate_event_transitions(mdp: Dict) -> Dict[str, Dict[str, float]]:
"""aggregate state transitions by event type and normalize"""
evt_trans = defaultdict(lambda: defaultdict(float)) evt_trans = defaultdict(lambda: defaultdict(float))
for s, trans in model.mdp['transitions'].items(): for s, trans in mdp['transitions'].items():
evt_src = s.split('|')[2] evt_src = s.split('|')[2]
for s_next, prob in trans.items(): for s_next, prob in trans.items():
evt_dst = s_next.split('|')[2] evt_dst = s_next.split('|')[2]
@@ -103,6 +124,13 @@ def visualize_mdp(model: BehaviorModel, threshold: float = 0.05, output: str = "
if total > 0: if total > 0:
for evt_dst in evt_trans[evt_src]: for evt_dst in evt_trans[evt_src]:
evt_trans[evt_src][evt_dst] /= total evt_trans[evt_src][evt_dst] /= total
return dict(evt_trans)
def visualize_mdp(model: BehaviorModel, threshold: float = 0.05, output: str = "mdp_graph", fmt: str = "svg", view: bool = False, export_dot: bool = False):
"""visualize MDP as directed graph using graphviz, aggregated by event type"""
if not model.mdp: raise ValueError("build MDP first")
evt_trans = aggregate_event_transitions(model.mdp)
g = graphviz.Digraph(format=fmt) g = graphviz.Digraph(format=fmt)
g.attr(rankdir='LR', size='30') g.attr(rankdir='LR', size='30')
@@ -134,11 +162,50 @@ def visualize_mdp(model: BehaviorModel, threshold: float = 0.05, output: str = "
return g return g
def kl_divergence(p: Dict[str, float], q: Dict[str, float]) -> float:
"""Compute KL divergence D_KL(P || Q) for discrete distributions P and Q."""
epsilon = 1e-10 # small constant to avoid log(0)
kl_div = 0.0
for key in p:
p_val = p[key] + epsilon
q_val = q.get(key, 0.0) + epsilon
kl_div += p_val * np.log(p_val / q_val)
return kl_div
if __name__ == "__main__": if __name__ == "__main__":
model = BehaviorModel(DIR) human_model = BehaviorModel(DIR)
mdp = model.build_MDP() human_mdp = human_model.build_MDP()
print(f"Built MDP: {mdp['num_states']} states, {sum(len(t) for t in mdp['transitions'].values())} transitions") print(f"Built MDP: {human_mdp['num_states']} states, {sum(len(t) for t in human_mdp['transitions'].values())} transitions")
if not mdp['states']: if not human_mdp['states']:
print("No states found") print("No states found")
exit(1) exit(1)
visualize_mdp(model, threshold=0.05, output="mdp_viz", fmt="pdf", export_dot=True) visualize_mdp(human_model, threshold=0.05, output="human_mdp_viz", fmt="pdf", export_dot=True)
agent_model = AgentBehaviorModel()
agent_mdp = agent_model.build_MDP()
print(f"AGENT... Built MDP: {agent_mdp['num_states']} states, {sum(len(t) for t in agent_mdp['transitions'].values())} transitions")
if not agent_mdp['states']:
print("No states found")
exit(1)
visualize_mdp(agent_model, threshold=0.05, output="agent_mdp_viz", fmt="pdf", export_dot=True)
# aggregate transitions by event type for both models
human_evt_trans = aggregate_event_transitions(human_mdp)
agent_evt_trans = aggregate_event_transitions(agent_mdp)
common_evts = set(human_evt_trans.keys()) & set(agent_evt_trans.keys())
if not common_evts: import sys; sys.exit("No common event types for KL divergence analysis")
kl_divs = []
for evt in common_evts:
kl = kl_divergence(human_evt_trans[evt], agent_evt_trans[evt])
kl_divs.append((evt, kl))
kl_divs.sort(key=lambda x: x[1], reverse=True)
avg_kl = np.mean([kl for _, kl in kl_divs])
print(f"Average KL divergence: {avg_kl:.4f}")
print(f"\nMost divergent event types:")
for evt, kl in kl_divs:
print(f" {evt}: {kl:.4f}")