◆◇-------------動かなかった!!----------------◇◆
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-----------------
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, Tuple, Any, List, Optional, Set
import yaml
LinkKey = Tuple[str, str]
@dataclass
class Runtime:
inner: Dict[str, float]
outer: Dict[str, Any]
winner: Optional[str]
stable_counter: int
locked: bool
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)
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)
"""
base: Set[str] = set()
if cues.get("vision") == "lion":
base.add("lion")
if cues.get("sound") == "shout":
base.add("shout")
if rt.outer.get("danger") is True:
base.add("danger_signal")
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
frontier = push_from(base, 1.0)
frontier2 = push_from(frontier, 0.85)
_ = push_from(frontier2, 0.70)
arousal = rt.inner.get("arousal", 0.0)
if arousal:
for k in list(candidates.keys()):
candidates[k] += arousal * 0.01
persona = script.get("personality", {}) or {}
caution = float(persona.get("caution", 0.0))
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
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:
winner = max(candidates.items(), key=lambda kv: kv[1])[0]
used: Set[str] = set(base)
used.add(winner)
th = float(comp.get("activate_threshold", 0.15))
for n, s in candidates.items():
if s >= th:
used.add(n)
return winner, candidates, used
def step(script: Dict[str, Any], mem: Dict[LinkKey, float], rt: Runtime) -> None:
cues = dict(script.get("cues", {}))
rt.outer = apply_outer(cues, script.get("outer", {}) or {})
apply_inner(cues, script.get("inner", {}) or {}, rt.inner)
inner_decay_spec = (script.get("inner_decay", {}) or {})
if inner_decay_spec:
decay_inner(rt.inner, inner_decay_spec)
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]))
winner, candidates, active_nodes = competition_v1(script, cues, rt, mem)
rt.winner = winner
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
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))
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)
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)
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)
for (a, b), w in list(mem.items()):
if a in active and b in active:
mem[(a, b)] = clamp(w + hebb, lo, hi)
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 件のコメント:
コメントを投稿