2026年2月23日月曜日

Atron Script -2-

 ◆◇-------------動かなかった!!----------------◇◆

scene: LionEncounter_v1 cues: { vision: lion, sound: shout, distance: near } outer: rule: { danger_if: { vision: lion } } inner: pain: { add: 3.0, when: { distance: near, vision: lion } } arousal:{ add: 2.0, when: { sound: shout } } # 推奨:内輪の減衰(興奮しっぱなし防止) inner_decay: pain: 0.2 arousal: 0.3 memory: links: - { from: lion, to: A, weight: 0.20 } - { from: A, to: danger, weight: 0.10 } - { from: danger, to: flee, weight: 0.60 } update: { hebb: 0.05, decay: 0.01, clamp: [0.0, 1.0] } competition: rule: winner_take_top_1 activate_threshold: 0.15 attractor: stable_if: { link: [A, danger], ge: 0.80, for_scenes: 5 } policy: if_winner_is: flee act: run_away personality: auto_learn: 0.85 caution: 0.60


----- スクリプト側を↑これに修正 -------



それと、Python側の
Hebb の active セットが 「ずるい」
これはアソシアトロンじゃなかった。

active = [] if cues.get("vision") == "lion": active.append("lion") if rt.winner: active.append(rt.winner) # also include implied nodes if present if ("lion", "A") in mem: active.append("A") if ("A", "danger") in mem: active.append("danger")


これって

A が実際に活性化してなくても
danger が勝ってなくても
リンクが存在するだけで A と danger を “active 扱い” にして Hebb 更新する
シーンが何も起きてなくても、構造だけで w(A→danger) が育つ
三人称の設計を内輪に混ぜ込んでしまってる
うっかりすると、直ぐにこうなってしまう。


しかも、これじゃ新しいリンクが生まれない

for (a, b), w in mem.items(): if a in aset and b in aset: mem[(a, b)] = clamp(w + hebb, lo, hi)



lionを何度も見たら、勝手に lion→A ができる
叫びと逃走が結びついて、sound→flee ができる
みたいな挙動になっていない。


本来の Atron(経験→競合→想起→学習)に寄せる。

---------------Python  正しいcode-----------------





# run_assoc_v1_correct.py from __future__ import annotations from dataclasses import dataclass from typing import Dict, Tuple, Any, List, Optional, Set import yaml # pip install pyyaml LinkKey = Tuple[str, str] @dataclass class Runtime: inner: Dict[str, float] outer: Dict[str, Any] winner: Optional[str] stable_counter: int locked: bool # ---------------- utilities ---------------- def match_when(cues: Dict[str, Any], when: Dict[str, Any]) -> bool: """All key-value pairs in 'when' must match cues exactly (tiny v1).""" for k, v in when.items(): if cues.get(k) != v: return False return True def clamp(x: float, lo: float, hi: float) -> float: return max(lo, min(hi, x)) def build_memory(script: Dict[str, Any]) -> Dict[LinkKey, float]: mem: Dict[LinkKey, float] = {} for lk in script.get("memory", {}).get("links", []): a = str(lk["from"]) b = str(lk["to"]) mem[(a, b)] = float(lk["weight"]) return mem def apply_outer(cues: Dict[str, Any], outer_spec: Dict[str, Any]) -> Dict[str, Any]: """ OUTER v1: compute only minimal flags from cues. IMPORTANT: do NOT force inner meaning here. It's just 'environment signal'. """ rule = outer_spec.get("rule", {}) danger_if = rule.get("danger_if", {}) danger = all(cues.get(k) == v for k, v in danger_if.items()) return {"danger": bool(danger)} def apply_inner(cues: Dict[str, Any], inner_spec: Dict[str, Any], inner: Dict[str, float]) -> None: """Add pain/arousal/etc if conditions match.""" for name, spec in inner_spec.items(): add = float(spec.get("add", 0.0)) when = spec.get("when", {}) if match_when(cues, when): inner[name] = inner.get(name, 0.0) + add def decay_inner(inner: Dict[str, float], inner_decay: Dict[str, float]) -> None: """Optional: let inner states relax, so the system doesn't 'forever excite'.""" for k, d in inner_decay.items(): inner[k] = max(0.0, inner.get(k, 0.0) - float(d)) def decay_memory(mem: Dict[LinkKey, float], decay: float, lo: float, hi: float) -> None: for k in list(mem.keys()): mem[k] = clamp(mem[k] - decay, lo, hi) def hebb_update_existing(mem: Dict[LinkKey, float], active: Set[str], hebb: float, lo: float, hi: float) -> None: """ v1: strengthen ONLY existing links whose endpoints are both active. (No link creation in v1 by default.) """ for (a, b), w in list(mem.items()): if a in active and b in active: mem[(a, b)] = clamp(w + hebb, lo, hi) # ---------------- competition ---------------- def competition_v1( script: Dict[str, Any], cues: Dict[str, Any], rt: Runtime, mem: Dict[LinkKey, float] ) -> Tuple[Optional[str], Dict[str, float], Set[str]]: """ v1 competition: 1) Build base active from cues (e.g., vision==lion -> 'lion') 2) Score candidates by propagating through existing links: base -> 1-hop -> 2-hop -> ... (We keep it tiny but general-ish.) 3) Choose winner by rule. Returns: (winner, candidates, active_nodes_used) """ # 1) base active from cues (tiny mapping) base: Set[str] = set() if cues.get("vision") == "lion": base.add("lion") if cues.get("sound") == "shout": base.add("shout") # Optional: allow outer flags to become base nodes (still not "meaning", just signal) # If you dislike this, comment out. if rt.outer.get("danger") is True: base.add("danger_signal") # 2) propagation (tiny, bounded depth) candidates: Dict[str, float] = {} def push_from(active_sources: Set[str], scale: float) -> Set[str]: newly: Set[str] = set() for (a, b), w in mem.items(): if a in active_sources: score = scale * w if score > candidates.get(b, float("-inf")): candidates[b] = score newly.add(b) return newly # Depth 1 frontier = push_from(base, 1.0) # Depth 2 (chain effect) # (Small depth is enough for v1; make it 3 if you want more richness.) frontier2 = push_from(frontier, 0.85) # Depth 3 (optional, tiny) _ = push_from(frontier2, 0.70) # 3) inner contribution (arousal makes everything slightly more "ready") arousal = rt.inner.get("arousal", 0.0) if arousal: for k in list(candidates.keys()): candidates[k] += arousal * 0.01 # personality bias: caution can bias specific nodes but should be light persona = script.get("personality", {}) or {} caution = float(persona.get("caution", 0.0)) # Example bias (keep very small; otherwise it becomes "policy in disguise") for node, bonus in (("danger", 0.05), ("flee", 0.08)): if node in candidates: candidates[node] += caution * bonus if not candidates: return None, candidates, base # competition rule comp = script.get("competition", {}) or {} rule = comp.get("rule", "winner_take_top_1") if rule == "winner_take_top_1": winner = max(candidates.items(), key=lambda kv: kv[1])[0] else: # fallback to top_1 for v1 winner = max(candidates.items(), key=lambda kv: kv[1])[0] # Active nodes used for learning: base + winner + top candidates above a threshold # This is critical: we do NOT add nodes just because a link exists. used: Set[str] = set(base) used.add(winner) # "Activated" candidates threshold th = float(comp.get("activate_threshold", 0.15)) for n, s in candidates.items(): if s >= th: used.add(n) return winner, candidates, used # ---------------- step ---------------- def step(script: Dict[str, Any], mem: Dict[LinkKey, float], rt: Runtime) -> None: cues = dict(script.get("cues", {})) # OUTER + INNER rt.outer = apply_outer(cues, script.get("outer", {}) or {}) apply_inner(cues, script.get("inner", {}) or {}, rt.inner) # Optional: inner relaxation (recommended) inner_decay_spec = (script.get("inner_decay", {}) or {}) if inner_decay_spec: decay_inner(rt.inner, inner_decay_spec) # MEMORY UPDATE parameters upd = (script.get("memory", {}) or {}).get("update", {}) or {} hebb = float(upd.get("hebb", 0.0)) decay = float(upd.get("decay", 0.0)) lo, hi = map(float, upd.get("clamp", [0.0, 1.0])) # COMPETITION (winner + activated nodes) winner, candidates, active_nodes = competition_v1(script, cues, rt, mem) rt.winner = winner # ATTRACTOR check st = (script.get("attractor", {}) or {}).get("stable_if", {}) or {} a, b = st.get("link", ["A", "danger"]) ge = float(st.get("ge", 0.8)) for_scenes = int(st.get("for_scenes", 5)) w_link = mem.get((str(a), str(b)), 0.0) if w_link >= ge: rt.stable_counter += 1 else: rt.stable_counter = 0 rt.locked = rt.stable_counter >= for_scenes # MEMORY decay + Hebb # IMPORTANT: lock means "this has become trait-like", so freeze learning. if not rt.locked: if decay > 0: decay_memory(mem, decay, lo, hi) persona = script.get("personality", {}) or {} auto_learn = float(persona.get("auto_learn", 0.5)) # gentle multiplier: 0.0..1.0 -> 0.7..1.3 hebb_eff = hebb * (0.7 + 0.6 * clamp(auto_learn, 0.0, 1.0)) if hebb_eff > 0 and active_nodes: hebb_update_existing(mem, active_nodes, hebb_eff, lo, hi) # POLICY: translate winner to action (thin!) pol = script.get("policy", {}) or {} act = "none" if rt.winner is not None and rt.winner == pol.get("if_winner_is"): act = str(pol.get("act", "none")) pain = rt.inner.get("pain", 0.0) arousal = rt.inner.get("arousal", 0.0) # Show top few candidates for debugging top = sorted(candidates.items(), key=lambda kv: kv[1], reverse=True)[:3] top_s = ", ".join([f"{k}:{v:.2f}" for k, v in top]) if top else "-" print( f"winner={str(rt.winner):>8} act={act:>8} " f"pain={pain:>5.1f} arousal={arousal:>5.1f} " f"w({a}->{b})={w_link:.2f} stable={rt.stable_counter}/{for_scenes} locked={rt.locked} " f"top=[{top_s}] active={sorted(active_nodes)}" ) def main() -> None: import argparse ap = argparse.ArgumentParser() ap.add_argument("yaml_path", help="Associatron Script v1 YAML path") ap.add_argument("--scenes", type=int, default=20) args = ap.parse_args() with open(args.yaml_path, "r", encoding="utf-8") as f: script = yaml.safe_load(f) or {} mem = build_memory(script) rt = Runtime(inner={}, outer={}, winner=None, stable_counter=0, locked=False) print(f"=== Scene: {script.get('scene','(unnamed)')} ===") for i in range(args.scenes): print(f"[{i+1:02d}] ", end="") step(script, mem, rt) if __name__ == "__main__": main()

------------------------------- -------------------------------



さっきのところ。
新しいリンクが生まれるところ
さっきまでは

def hebb_update_existing(mem, active, hebb, lo, hi): for (a, b), w in list(mem.items()): if a in active and b in active: mem[(a, b)] = clamp(w + hebb, lo, hi)

すでに存在する (a, b) だけを見る
両方 active なら重みを増やす


状態
lion と flee が同時に active

結果
既存の lion→flee があれば強化

状態
lion→flee が存在しない

結果
何も起きない
      ↓



hebb_update_existingを変える。



def hebb_update(mem, active, hebb, lo, hi, create_th=0.02): active = list(active) # 1) 既存リンクの強化 for (a, b), w in list(mem.items()): if a in active and b in active: mem[(a, b)] = clamp(w + hebb, lo, hi) # 2) 新規リンク生成 for a in active: for b in active: if a == b: continue key = (a, b) if key not in mem: # 小さな初期値で生成 mem[key] = clamp(create_th, lo, hi)



active = {lion, flee}
なら
(lion, flee) が無ければ生成
(flee, lion) も生成

active_nodes が、そのシーンで「同時に起きた経験」になる。


新しいリンク生成に対して

A. 常に行う(連想が爆発するタイプ)
B. 閾値付き(安定型)
C. 高arousal時のみ(トラウマ型)

この調整で、性格のタイプが変わってしまう。


ぼーっとしてると、直ぐに3人称になってしまうから、
Atron Scriptで気づくようにしておかないとマズイ感じ。




0 件のコメント:

コメントを投稿

outlawだってさ。ありがとよ。 - Associatronと一人称自律

 オランダからメールが来たよ。 「Atraもいいけど、outlawだろ、」ってさ 最高だよ。 outlaw architecture ってのは間違いないよねw 実際、僕は、流れや制度・分類・学派・評価体系の外にいる者だし、そういうのあまり大切にしていない。今の大学の事は分からない...