意識の5層モデルをPythonで実装する試み25
〜v2.0 実装解剖編:コードを見せろの精神〜
今回は能書きなし。コードを見せろの精神で、v2.0実装のL5周辺を解剖する。
理論の背景は v24 を参照。
リポジトリ:predictive-agency-simulator
ファイル構成
predictive-agency-simulator/
├── Hida_unified_V2/
│ └── hida_unified_v2.py ← 今回見るやつ(メイン)
├── HIDA_theory_paper_v2_0.md ← 論文
├── spec_v2.md ← AI向け仕様書
└── README_ja_v2.md
hida_unified_v2.py を解剖していく。
1. L5クラスの全体像
class L5Consciousness:
"""意識(主導・追随同期、自認、言語化)"""
# 初期DNA(クラス定数)
LEADER_THRESHOLD = 0.3 # 主導層判定の閾値
EMA_ALPHA = 0.35 # EMA平滑化係数
ON_THRESHOLD = 0.03 # 意識ON判定
OFF_THRESHOLD = 0.02 # 意識OFF判定(ヒステリシス)
def __init__(self, threshold=None):
if threshold is not None:
self.ON_THRESHOLD = threshold
self.OFF_THRESHOLD = max(0.0, threshold - 0.05)
# 意識状態
self.is_conscious = False
self.sync_score = 0.0 # 強度の軸(v2.0)
self.sync_type = None # 主導タイプの軸(v2.0)
self.sync_level = 0.0 # 後方互換(= sync_score)
# EMA平滑化
self._ema = 0.0
# 前ステップの状態保持(活動量=変化量の計算用)
self._prev = {
'l1_energy': 1.0,
'l1_fatigue': 0.0,
'l2_valence': 0.0,
'l2_arousal': 0.0,
'l2_qualia': {},
'l3_error_count': 0,
'l4_found_count': 0, # ※ v2.0では実質activation_countの前回値
}
ポイント:
- 閾値はクラス定数として固定(初期DNA)
-
sync_scoreとsync_typeの二軸出力 - 変化量計算のため、前ステップの状態を
_prevに保持 -
l4_found_countというキー名は後方互換のため残してあるが、v2.0では実質的にactivation_countの前回値として使っている
2. 各層の活動量を「変化量」として測る
def _measure_activity(self, l1, l2, l3, l4):
"""各層の活動量を「前ステップからの変化量」として測定"""
# L1: 身体状態の変化量
l1_change = abs(l1.energy - self._prev['l1_energy']) + \
abs(l1.fatigue - self._prev['l1_fatigue'])
l1_act = self._clip01(l1_change / 1.0)
# L2: 価値変化量
l2_change = abs(l2.valence - self._prev['l2_valence']) + \
abs(l2.arousal - self._prev['l2_arousal']) + \
sum(abs(l2.qualia.get(k, 0) - self._prev['l2_qualia'].get(k, 0))
for k in l2.qualia)
l2_act = self._clip01(l2_change / 3.0)
# L3: 予測誤差の強度合計
l3_magnitude_sum = sum(e.get('magnitude', 0.0) for e in l3.errors) if l3.errors else 0.0
l3_act = self._clip01(l3_magnitude_sum / 3.0)
# L4: 記憶活性化の増分
l4_current_count = l4.activation_count if hasattr(l4, 'activation_count') else 0
l4_change = max(0, l4_current_count - self._prev['l4_found_count'])
l4_act = self._clip01(l4_change / 3.0)
return {'L1': l1_act, 'L2': l2_act, 'L3': l3_act, 'L4': l4_act}
ポイント:
- 絶対値ではなく変化量(「今、同期信号を出してるか」を測る)
- L3だけ件数差分ではなく magnitude合計(毎ステップ更新されるリストなので)
- 正規化定数(1.0, 3.0, 3.0, 3.0)も初期DNA
3. 主導層を特定する
def _detect_leader(self, activities):
"""主導層の特定: 閾値超過層のうち最大活動量を持つ層"""
candidates = {k: v for k, v in activities.items()
if v >= self.LEADER_THRESHOLD}
if not candidates:
return None, 0.0 # 同期信号なし
leader = max(candidates, key=candidates.get)
return leader, candidates[leader]
シンプル。閾値超えた層のうち最大値が主導層。なければ None。
4. 追随整合性を評価する
def _measure_follower_coherence(self, activities, leader, leader_strength):
"""追随整合性の評価(簡易版:同時応答指標)"""
follower_activities = [v for k, v in activities.items() if k != leader]
follower_mean = sum(follower_activities) / 3
coherence = self._clip01(follower_mean / leader_strength)
return coherence
主導層を除いた3層の活動量平均を、主導強度で正規化。1に近いほど追随層が応答してる。
これは簡易版。本来は時間遅れ相関や位相同期で測るべきだが、v2.0初期実装では同時応答で近似してる。
5. 統合:check_sync 関数
def check_sync(self, l1, l2, l3, l4):
"""第5層の同期判定(v2.0: 主導・追随モデル)"""
# Step 1: 各層の活動量を測定
activities = self._measure_activity(l1, l2, l3, l4)
# Step 2: 主導層の特定
leader, leader_strength = self._detect_leader(activities)
if leader is None:
raw_sync = 0.0
self.sync_type = None
else:
# Step 3: 追随整合性の評価
follower_coherence = self._measure_follower_coherence(
activities, leader, leader_strength
)
# Step 4: 同期スコアの算出
raw_sync = leader_strength * follower_coherence
self.sync_type = leader
# Step 5: EMA平滑化とヒステリシス
self._ema = (1 - self.EMA_ALPHA) * self._ema + self.EMA_ALPHA * raw_sync
self.sync_score = self._ema
self.sync_level = self._ema # 後方互換
if not self.is_conscious:
self.is_conscious = (self._ema >= self.ON_THRESHOLD)
else:
self.is_conscious = (self._ema >= self.OFF_THRESHOLD)
# Step 6: 次ステップ用の状態保存
self._update_prev(l1, l2, l3, l4)
return self.is_conscious
sync_score = leader_strength × follower_coherence。これが v2.0 の中核。
EMAとヒステリシスで点滅を抑制。
なお、ここで決まった sync_score・sync_type を含むL1〜L4の状態は、L5の self_recognize() → format_for_llm() → LLMVerbalizer.verbalize() を経由してLLMに渡し、1人称で語らせることができる(第23回の内省の話の延長)。詳細は別の回で扱う。
6. step() の処理順序
def step(self, world, verbose=True):
self.step_count += 1
# 1. 変調値適用、L2更新
self.l2.apply_modulation(self.l4.get_modulation())
self.l2.update_from_body(self.l1.get_state())
# 2. 感覚・思考・行動
errors = self.sense(world)
action, details = self.think(world)
event = self.act(world, action, details) # "goal_reached"/"grabbed_ball"/None
goal_reached = (event == "goal_reached")
# 3. 訪問記録
self.l4.mark_visited(tuple(self.l1.position))
# 4. イベント別処理(check_syncの前に!)
if event == "grabbed_ball":
self.l4.remember_consciously(...)
if goal_reached:
self.l2.qualia['joy'] = min(1.0, self.l2.qualia['joy'] + 0.5)
self.l2.valence += 0.3
self.l4.remember_consciously(...)
# 5. クオリア減衰
self.l2.decay_qualia()
# 6. 同期判定(毎ステップ!)
is_conscious = self.l5.check_sync(self.l1, self.l2, self.l3, self.l4)
# 7. 必要なら言語化
if verbose and is_conscious and self.step_count % 5 == 0:
reflection = self.reflect(action, details)
順序のポイント(GPT指摘で修正済み):
- 感情イベント処理は check_sync の前(同期判定に反映させるため)
- check_sync は毎ステップ呼ぶ(5ステップに1回ではない)
- reflect は check_sync の後、意識ON時のみ
-
act()の戻り値はboolではなくイベント名(ボール取得とゴール到達を区別)
7. L4Memory への追加
class L4Memory:
def __init__(self):
# ... 既存属性 ...
self.activation_count = 0 # v2.0追加:記憶活性化カウンタ
def update_from_sense(self, sense_data):
for pos, data in sense_data.items():
if pos not in self.internal_map:
self.activation_count += 1 # 新規セル
self.internal_map[pos] = data['cell']
if data['object']:
if pos not in self.found_objects:
self.activation_count += 1 # 新規オブジェクト
else:
self.activation_count += 0.3 # 再観測
self.found_objects[pos] = data['object']
def update_from_errors(self, errors, qualia_intensity):
for error in errors:
pos = error.get('pos')
if pos and error.get('object'):
self.found_objects[pos] = {
**error['object'],
'memory_strength': qualia_intensity
}
self.activation_count += 1 + qualia_intensity
def mark_visited(self, pos):
if pos not in self.visited:
self.activation_count += 0.5
self.visited.add(pos)
増加ルールも初期DNA。新規発見・再観測・予測誤差由来でそれぞれ加算。
8. 動作確認スクリプト
# 1. リセット
python hida_unified_v2.py reset
記憶と変調値をリセットしました
# 2. 通常実行(赤好き、エネルギー満タン)
python hida_unified_v2.py
=== HIDA統合版テスト ===
【テスト1】赤好き、通常エネルギー
Step 1: go_to_red
○ sync_score=0.021 leader=L4
Step 2: go_to_red
○ sync_score=0.013 leader=-
...
○ が意識OFF、● が意識ON。leader が主導タイプ(L1/L2/L3/L4/-)。leader=- の表示は内部的には sync_type=None(主導層なし)を表す。
9. 出力例:sync_typeの切り替え観察
実際のシミュレーションログから抜粋:
=== テスト1:赤好き、通常エネルギー ===
Step 1: go_to_red
○ sync_score=0.021 leader=L4 ← 探索開始、新規発見でL4主導
Step 3: go_to_red
○ sync_score=0.015 leader=L4
Step 4: go_to_red
○ sync_score=0.010 leader=- ← 活動量低下、主導層なし
Step 7: go_to_red
○ sync_score=0.008 leader=L4
Step 8: go_to_red ← ボール取得
● sync_score=0.038 leader=L2 ← 感情変化でL2主導、意識ON
Step 17: go_to_goal ← ゴール到達
● sync_score=0.024 leader=L2 ← joy上昇でL2主導
🏆 ゴール到達!
=== テスト2:低エネルギー ===
Step 1: ● sync_score=0.062 leader=L1 ← 低エネで身体変化、L1主導
Step 2: ● sync_score=0.057 leader=L4
Step 4: ● sync_score=0.024 leader=-
🏆 ゴール到達!
観察できる挙動:
- 探索中:L4主導⇄主導層なし の切り替え
- ボール取得:L2主導(取得イベントに伴う価値変化)
- ゴール到達:L2主導(joy上昇)
- 低エネルギー:L1主導(身体危機)
同じエージェント内で、同じシミュレーション中に複数種類の意識状態がログに記録される。
10. sync_type分布の測定スクリプト
実験用:
import sys
sys.path.insert(0, '.')
from hida_unified_v2 import HIDA, create_test_world
world = create_test_world()
hida = HIDA(color_preference={'red': 1.0, 'blue': 0.3, 'green': 0.3})
hida.l1.position = [3, 6]
hida.l1.energy = 1.0
type_counts = {'L1': 0, 'L2': 0, 'L3': 0, 'L4': 0, None: 0}
conscious_count = 0
transitions = 0
prev_type = None
for step in range(50):
result = hida.step(world, verbose=False)
type_counts[result['sync_type']] += 1
if result['conscious']:
conscious_count += 1
if result['sync_type'] != prev_type and prev_type is not None:
transitions += 1
prev_type = result['sync_type']
if result.get('goal_reached'):
break
print(f'意識ON率: {100*conscious_count/(step+1):.1f}%')
print(f'sync_type切り替え回数: {transitions}')
print(f'sync_type分布: {type_counts}')
このスクリプトで「主導タイプの切り替え頻度」と「意識ON率」を測定できる。
論文§6 予測9「健康な意識では sync_type が状況に応じて頻繁に切り替わる」の検証材料になる。
まとめ
- v2.0実装の中核は
check_sync()関数 - 活動量を「変化量」として測る
-
leader_strength × follower_coherenceで同期スコア - 各層の正規化定数・閾値・増加ルールは初期DNAとして固定
- 閾値キャリブレーションはコミュニティ検証に開かれている
コードを動かして、sync_typeが切り替わるログを見るのが一番早い。
理論の背景は第24回、論文はHIDA_theory_paper_v2_0.mdを参照。
関連リンク
- 実装:hida_unified_v2.py
- 論文 v2.0:HIDA_theory_paper_v2_0.md
- AI向け仕様書:spec_v2.md