0

コード一覧

2
0
$$$$
      from collections import defaultdict
import matplotlib.pyplot as plt
import numpy as np
import random
from sklearn.datasets import load_digits
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler
# =========================================================
# 1. 環境とデータの準備(手書き数字 64次元データ)
# =========================================================
np.random.seed(42)
digits = load_digits()
X_raw, y_raw = digits.data, digits.target
# 生態系の変化を見やすくするため、400サンプルを抽出
total_indices = np.random.choice(len(X_raw), 400, replace=False)
X_400, y_400 = X_raw[total_indices], y_raw[total_indices]
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_400)
# 1枚サンプルを訓練用に(残りをテスト環境に)
train_idx = []
for c in range(10):
    c_idxs = np.where(y_400 == c)[0]
    train_idx.extend(c_idxs[:1]) # 各クラス1枚ずつ
test_idx = [i for i in range(400) if i not in train_idx]
X_train, y_train = X_scaled[train_idx], y_400[train_idx]
X_test, y_test = X_scaled[test_idx], y_400[test_idx]
NUM_CLASSES = 10
INPUT_DIM = 64
# --- ハイパーパラメータ ---
INIT_CELLS = 1000
MAX_CELLS = 10000
MIN_CELLS = 80
THOUGHT_STEPS = 25
lr_base = 0.20
lr_link_base = 0.10
DIVISION_THRESHOLD = 0.85     # 構造過学習を防ぐため、少し厳格化
DEATH_THRESHOLD = 0.08        # 淘汰圧を少し強める
GRAPH_DECAY = 0.90
EDGE_PRUNE_THRESHOLD = 0.02
epochs = 3        
# =========================================================
# 1.5 カリキュラム学習用のインデックスグループ作成 (追加)
# =========================================================
# 各クラスに対応する訓練データのインデックスを整理
class_to_train_idxs = {c: [] for c in range(NUM_CLASSES)}
for idx, c in enumerate(y_train):
    class_to_train_idxs[c].append(idx)
# ユーザー指定のカリキュラム順序を定義
curriculum_phases = [
    [1],          # フェーズ1: はじめに
    [7, 4],       # フェーズ2: 集中的に最初
    [9, 3, 8],    # フェーズ3: 展開
    [0, 2, 5, 6]  # フェーズ4: 残り (0, 2, 5, 6)
]# 現象を観察するため、5サイクル回します
# =========================================================
# 2. 視覚記憶細胞(VisionMemoryCell)の定義
# =========================================================
class VisionMemoryCell:
    def __init__(self, cell_id):
        self.id = cell_id
        self.w_in = np.random.normal(0, 1.0, INPUT_DIM)  # 蜘蛛の巣初期化で再上書きされます
        self.A = np.random.normal(0, 0.1, NUM_CLASSES)
        self.links = {}
        self.visits = 0.0
        self.energy = 0.5
        self.recent_activity = 0.0
def cosine_similarity(w, x):
    return np.dot(w, x) / (np.linalg.norm(w) * np.linalg.norm(x) + 1e-9)
def softmax(x, temp=1.0):
    x = np.array(x) / temp
    x = x - np.max(x)
    e = np.exp(x)
    return e / (np.sum(e) + 1e-12)
# =========================================================
# 🧬 3. 代謝・新陳代謝コア(分裂コストの厳格化)
# =========================================================
def evolve_and_prune_space(memory_space, outer_graph):
    # 睡眠・自然減衰
    for cell in memory_space:
        cell.energy *= 0.92
        cell.recent_activity *= 0.50
        if cell.energy < 0.01: cell.energy = 0.01
        
    # --- 分裂フェーズ(ユーザーさんの提案によるコスト厳格化) ---
    new_cells = []
    current_max_id = max([c.id for c in memory_space]) if memory_space else 0
    for cell in memory_space:
        if len(memory_space) + len(new_cells) >= MAX_CELLS: break
        
        # 分裂条件をエネルギーかつ、 visits累積に設定
        if cell.energy > DIVISION_THRESHOLD and cell.visits > 15:
            child = VisionMemoryCell(current_max_id + len(new_cells) + 1)
            
            # 子は親の特性に少し変異(ノイズ)を乗せる
            child.w_in = np.clip(cell.w_in + np.random.normal(0, 0.05, INPUT_DIM), -3.0, 3.0)
            child.A = cell.A.copy()
            for k, v in cell.links.items(): child.links[k] = v * 0.5
            
            # 【ユーザーさんの設計思想】親の資源を大きく削り、子を未熟な状態からスタートさせる
            cell.energy *= 0.3
            cell.visits *= 0.5
            child.energy = 0.2  # 子の初期エネルギーは低い
            
            new_cells.append(child)
            
    memory_space.extend(new_cells)
    
    # --- 淘汰 & 外側マクログラフの再マッピング ---
    survived_space = []
    id_map = {}
    for cell in memory_space:
        if len(survived_space) > MIN_CELLS:
            if cell.energy < DEATH_THRESHOLD and cell.visits < 1.5:
                continue
        new_id = len(survived_space)
        id_map[cell.id] = new_id
        cell.id = new_id
        survived_space.append(cell)
        
    for cell in survived_space:
        cell.links = {id_map[old_id]: w * 0.85 for old_id, w in cell.links.items() if old_id in id_map and w > 0.05}
        
    new_graph = defaultdict(dict)
    for u, neighbors in outer_graph.items():
        new_u = ("cell", id_map[u[1]]) if u[0] == "cell" and u[1] in id_map else u
        if u[0] == "cell" and u[1] not in id_map: continue
        
        for v, weight in neighbors.items():
            decayed_weight = weight * GRAPH_DECAY
            if decayed_weight < EDGE_PRUNE_THRESHOLD: continue
            
            new_v = ("cell", id_map[v[1]]) if v[0] == "cell" and v[1] in id_map else v
            if v[0] == "cell" and v[1] not in id_map: continue
            
            new_graph[new_u][new_v] = decayed_weight
            
    return survived_space, new_graph
# =========================================================
# 🔄 4. 融合生命ダイナミクス(思考の歩行・側頭抑制)
# =========================================================
def live_vision_cycle(memory_space, outer_graph, x_64d=None, target_class=None, target_recall_class=None,
                      sensory_drive=1.0, intrinsic_drive=0.0, top_down_drive=0.0, learning_gate=1.0):
    if len(memory_space) == 0: return np.zeros(NUM_CLASSES), np.zeros(INPUT_DIM)
    id_to_cell = {c.id: c for c in memory_space}
    
    start_probabilities = np.zeros(len(memory_space))
    
    if x_64d is not None and sensory_drive > 0.0:
        sims = np.array([cosine_similarity(cell.w_in, x_64d) for cell in memory_space])
        start_probabilities += softmax(sims, temp=0.1) * sensory_drive
        
    if intrinsic_drive > 0.0:
        activity_weights = np.array([cell.energy * (cell.recent_activity + 0.05) for cell in memory_space])
        start_probabilities += (activity_weights / (np.sum(activity_weights) + 1e-12)) * intrinsic_drive
        
    if target_recall_class is not None and top_down_drive > 0.0:
        c_node = ("class", target_recall_class)
        for next_node, weight in outer_graph.get(c_node, {}).items():
            if next_node[0] == "cell" and next_node[1] in id_to_cell:
                start_probabilities[next_node[1]] += weight * top_down_drive
                
    if np.sum(start_probabilities) == 0: 
        start_probabilities = np.ones(len(memory_space)) / len(memory_space)
    start_probabilities /= np.sum(start_probabilities)
    
    current_cell_id = np.random.choice(list(id_to_cell.keys()), p=start_probabilities)
    path = [current_cell_id]
    
    collected_signals = [id_to_cell[current_cell_id].A.copy()]
    imagined_pixels = [id_to_cell[current_cell_id].w_in.copy()]
    
    # --- 思考がグラフを歩く(Graph Walk) ---
    for step in range(THOUGHT_STEPS - 1):
        current_cell = id_to_cell[current_cell_id]
        current_cell.recent_activity += sensory_drive * 1.0
        
        all_ids = list(id_to_cell.keys())
        candidates = set(np.random.choice(all_ids, min(25, len(all_ids)), replace=False))
        candidates.update(current_cell.links.keys())
        
        scores = []
        c_ids = []
        for cid in candidates:
            if cid in path or cid not in id_to_cell: continue
            cell = id_to_cell[cid]
            
            score = current_cell.links.get(cid, 0.0) * 2.5
            if x_64d is not None:
                score += 2.0 * cosine_similarity(cell.w_in, x_64d) * sensory_drive
            score += 0.5 * cosine_similarity(current_cell.A, cell.A)
            
            scores.append(score)
            c_ids.append(cid)
            
        if not scores: break
        probs = softmax(scores, temp=0.3)
        current_cell_id = np.random.choice(c_ids, p=probs)
        path.append(current_cell_id)
        collected_signals.append(id_to_cell[current_cell_id].A.copy())
        imagined_pixels.append(id_to_cell[current_cell_id].w_in.copy())
        
    final_perception = np.mean(np.stack(collected_signals), axis=0) if collected_signals else np.zeros(NUM_CLASSES)
    final_image = np.mean(np.stack(imagined_pixels), axis=0) if imagined_pixels else np.zeros(INPUT_DIM)
    
    sensory_factor = sensory_drive * learning_gate
    intrinsic_factor = intrinsic_drive * learning_gate
    
    inferred_class = np.argmax(final_perception)
    target = target_class if target_class is not None else inferred_class
    
    target_vec = np.zeros(NUM_CLASSES)
    target_vec[target] = 1.0
    
    # 報酬の決定(厳格化:専門化を促す)
    reward = min(1.0 / (np.linalg.norm(target_vec - final_perception) + 0.15), 2.5)
    
    # --- ヘブ則的な学習と、専門化のための側頭抑制(Lateral Inhibition) ---
    if x_64d is not None and sensory_factor > 0.0:
        # 1. 経路上の細胞の学習
        for cid in path:
            if cid in id_to_cell:
                cell = id_to_cell[cid]
                cell.visits += sensory_factor
                cell.energy = min(1.0, cell.energy + reward * 0.15 * sensory_factor)
                cell.w_in += (lr_base * 0.5 * sensory_factor) * (x_64d - cell.w_in)
                cell.A = (1.0 - lr_base * sensory_factor) * cell.A + (lr_base * sensory_factor) * target_vec
                
        # 2. 側頭抑制(同じ情報への過剰適合・重複増殖を防ぐ)
        # 歩いた細胞の近隣リンクにありながら、今回選ばれなかった細胞のエネルギーを少し削る
        for cid in path:
            if cid in id_to_cell:
                for neighbor_id in id_to_cell[cid].links.keys():
                    if neighbor_id not in path and neighbor_id in id_to_cell:
                        id_to_cell[neighbor_id].energy *= 0.95 # 側頭抑制によるエネルギー減衰
                
    total_learning_power = (sensory_factor * reward + intrinsic_factor * 0.2)
    if total_learning_power > 0.0:
        for i in range(len(path) - 1):
            u, v = path[i], path[i+1]
            if u in id_to_cell and v in id_to_cell:
                id_to_cell[u].links[v] = id_to_cell[u].links.get(v, 0.1) + (lr_link_base * total_learning_power)
                
        c_node = ("class", int(target))
        first_cell_node = ("cell", path[0])
        last_cell_node = ("cell", path[-1])
        
        for i in range(len(path) - 1):
            f_node = ("cell", path[i])
            t_node = ("cell", path[i+1])
            outer_graph[f_node][t_node] = outer_graph[f_node].get(t_node, 0.0) + total_learning_power
            outer_graph[t_node][f_node] = outer_graph[t_node].get(f_node, 0.0) + total_learning_power
            
        # クラスから細胞、細胞からクラスへの「双方向エッジ」を正確にマッピング
        outer_graph[first_cell_node][c_node] = outer_graph[first_cell_node].get(c_node, 0.0) + total_learning_power
        outer_graph[c_node][first_cell_node] = outer_graph[c_node].get(first_cell_node, 0.0) + total_learning_power
        
        outer_graph[last_cell_node][c_node] = outer_graph[last_cell_node].get(c_node, 0.0) + total_learning_power
        outer_graph[c_node][last_cell_node] = outer_graph[c_node].get(last_cell_node, 0.0) + total_learning_power
        
    return final_perception, final_image
# =========================================================
# 👥 5. アンサンブル・蜘蛛の巣トポロジー管理(ミーム交配の実装)
# =========================================================
class EnsembleVisionEcosystem:
    def __init__(self, n_estimators=5, total_cells=1000):
        self.n_estimators = n_estimators
        self.models = []
        NUM_RINGS = 5  # 蜘蛛の巣の階層数
        
        for _ in range(n_estimators):
            space = []
            graph = defaultdict(dict)
            
            # 密度の調整:外側(感覚層)ほど細胞を多く、内側(概念層)ほど少なく
            cells_per_ring = []
            remaining = total_cells
            for r in range(NUM_RINGS):
                if r == NUM_RINGS - 1:
                    cells_per_ring.append(remaining)
                else:
                    count = int(total_cells * (0.45 / (1.6**r)))
                    cells_per_ring.append(count)
                    remaining -= count
                    
            cell_id_counter = 0
            ring_nodes = {}
            
            # --- 1. 蜘蛛の巣の細胞配置と受容野初期化 ---
            for ring_idx, n_cells in enumerate(cells_per_ring):
                ring_nodes[ring_idx] = []
                for i in range(n_cells):
                    cell = VisionMemoryCell(cell_id_counter)
                    
                    # 外周(感覚層)ほど実際の数字に近い初期受容野を持たせる
                    sensory_proximity = 1.0 - (ring_idx / NUM_RINGS)
                    if sensory_proximity > 0.2:
                        ref_idx = np.random.choice(len(X_train))
                        base_pattern = X_train[ref_idx]
                        cell.w_in = base_pattern * sensory_proximity + np.random.normal(0, 0.3, INPUT_DIM) * (1.0 - sensory_proximity)
                        cell.A[y_train[ref_idx]] = 0.5 * sensory_proximity
                    
                    space.append(cell)
                    ring_nodes[ring_idx].append(cell_id_counter)
                    cell_id_counter += 1
                    
            # --- 2. 蜘蛛の巣の糸(リンク)の構築 ---
            id_to_cell = {c.id: c for c in space}
            for ring_idx in range(NUM_RINGS):
                nodes = ring_nodes[ring_idx]
                num_nodes = len(nodes)
                if num_nodes == 0: continue
                
                for i in range(num_nodes):
                    curr_id = nodes[i]
                    
                    # (a) 同心円の横の糸(横方向リンク)
                    next_id = nodes[(i + 1) % num_nodes]
                    id_to_cell[curr_id].links[next_id] = 0.4
                    id_to_cell[next_id].links[curr_id] = 0.4
                    
                    # (b) 放射方向の縦の糸(抽象化への階層リンク)
                    if ring_idx < NUM_RINGS - 1:
                        inner_nodes = ring_nodes[ring_idx + 1]
                        if len(inner_nodes) > 0:
                            inner_idx = int(i * len(inner_nodes) / num_nodes)
                            inner_id = inner_nodes[inner_idx]
                            
                            id_to_cell[curr_id].links[inner_id] = 0.6
                            id_to_cell[inner_id].links[curr_id] = 0.6
            self.models.append({'space': space, 'graph': graph})
    def train_cycle(self, X, y, phase="day"):
        for model in self.models:
            # 脳の個性を引き出すブートストラップサンプリング
            indices = np.random.choice(len(X), len(X), replace=True)
            for idx in indices:
                if phase == "day":
                    live_vision_cycle(model['space'], model['graph'], x_64d=X[idx], 
                                      target_class=y[idx], sensory_drive=1.0, intrinsic_drive=0.0)
                elif phase == "evening":
                    live_vision_cycle(model['space'], model['graph'], x_64d=X[idx], 
                                      target_class=y[idx], sensory_drive=0.3, intrinsic_drive=0.7)
    def sleep_cycle(self, steps=60):
        for model in self.models:
            for _ in range(steps):
                live_vision_cycle(model['space'], model['graph'], x_64d=None, 
                                  target_class=None, sensory_drive=0.0, intrinsic_drive=1.0)
    def cultural_crossover(self, crossover_rate=0.2):
        """異なる脳の間で、最も洗練されたイデア(ミーム)を交換する"""
        elite_cells_per_model = []
        
        for m_idx, model in enumerate(self.models):
            space = model['space']
            if not space: continue
            
            # 最深部(Ring 4に相当する後半10%のコア層)からエリートを選別
            core_start_idx = int(len(space) * 0.9)
            core_cells = space[core_start_idx:]
            if not core_cells: continue
            
            # エネルギーが高く、キャラクター(特定のクラスへの確信)が立っている細胞をスカウト
            elite_cell = max(core_cells, key=lambda c: (c.energy * np.max(c.A)))
            elite_cells_per_model.append((m_idx, elite_cell))
            
        # 集団間での文化伝播(シナプス感染)
        for src_idx, src_cell in elite_cells_per_model:
            target_indices = [i for i in range(self.n_estimators) if i != src_idx]
            if not target_indices: continue
            dst_idx = random.choice(target_indices)
            dst_model = self.models[dst_idx]
            
            dst_core_cells = dst_model['space'][int(len(dst_model['space']) * 0.9):]
            if not dst_core_cells: continue
            
            # 伝播先の脳の中で、最も迷っている(エネルギーの低い)細胞にミームをブレンドする
            weak_cell = min(dst_core_cells, key=lambda c: c.energy)
            
            # 知識の混ざり合い
            weak_cell.w_in = (1.0 - crossover_rate) * weak_cell.w_in + crossover_rate * src_cell.w_in
            weak_cell.A = (1.0 - crossover_rate) * weak_cell.A + crossover_rate * src_cell.A
            weak_cell.energy = max(weak_cell.energy, src_cell.energy * 0.8) # 文化刺激による活性化
    def evolve(self):
        for model in self.models:
            model['space'], model['graph'] = evolve_and_prune_space(model['space'], model['graph'])
    def predict(self, X):
        y_pred = []
        for idx in range(len(X)):
            ensemble_perceptions = []
            for model in self.models:
                perception, _ = live_vision_cycle(model['space'], model['graph'], x_64d=X[idx], 
                                                  sensory_drive=1.0, intrinsic_drive=0.0, learning_gate=0.0)
                ensemble_perceptions.append(perception)
            
            avg_perception = np.mean(ensemble_perceptions, axis=0)
            y_pred.append(np.argmax(avg_perception))
        return y_pred
    def get_average_cell_count(self):
        return int(np.mean([len(m['space']) for m in self.models]))
# =========================================================
# 🚀 6. カリキュラム巡礼ループの実行(修正版)
# =========================================================
print("=== 蜘蛛の巣ウェブ型・カリキュラム学習システム 起動 ===")
ensemble = EnsembleVisionEcosystem(n_estimators=5, total_cells=INIT_CELLS)
# 各フェーズを順番に学習させていく
for phase_idx, classes_in_phase in enumerate(curriculum_phases):
    print(f"\n--- 【フェーズ {phase_idx + 1}】対象クラス: {classes_in_phase} の学習を開始 ---")
    
    # このフェーズの対象クラスだけの訓練データインデックスを抽出
    phase_train_indices = []
    for c in classes_in_phase:
        phase_train_indices.extend(class_to_train_idxs[c])
        
    if not phase_train_indices:
        print(f"警告: クラス {classes_in_phase} の訓練データが不足しています。スキップします。")
        continue
        
    X_phase = X_train[phase_train_indices]
    y_phase = y_train[phase_train_indices]
    
    # 各フェーズで、生態系が形作られるまで数サイクル回す(例: 3サイクル)
    phase_epochs = 3 
    for cycle in range(phase_epochs):
        # ☀️ 昼(現在のフェーズの数字のみに触れる)
        ensemble.train_cycle(X_phase, y_phase, phase="day")
        
        # 🌆 夕
        ensemble.train_cycle(X_phase, y_phase, phase="evening")
        
        # 🌙 夜(夢:これまでに培った全ネットワークの自己組織化)
        ensemble.sleep_cycle(steps=60)
        
        # 🌌 ミーム文化的交配
        ensemble.cultural_crossover(crossover_rate=0.2)
        
        # 🧬 新陳代謝(分裂・淘汰)
        ensemble.evolve()
        
        # 📊 テスト(評価は常に 0~9 全体のテストデータで行う)
        y_pred = ensemble.predict(X_test)
        acc = accuracy_score(y_test, y_pred)
        
        avg_cells = ensemble.get_average_cell_count()
       
# =========================================================
# 7. ダッシュボード:【 空想状態(Daydream) 】の可視化
# =========================================================
print("\n[Generating Daydream Images from the First Evolved Ecosystem...]")
fig, axes = plt.subplots(2, 5, figsize=(12, 5))
fig.suptitle("Daydreaming Mode (Top-Down Drive via Double Edges) - Model #1\nVisualizing the 'Ideals' of digits polished by Structural Control", fontsize=14)
first_model = ensemble.models[0]
for cls in range(10):
    _, daydream_image = live_vision_cycle(first_model['space'], first_model['graph'], 
                                          sensory_drive=0.0, intrinsic_drive=0.2, top_down_drive=1.0, 
                                          target_recall_class=cls, learning_gate=0.0)
    ax = axes[cls // 5, cls % 5]
    ax.imshow(daydream_image.reshape(8, 8), cmap="bone")
    ax.set_title(f"Concept: {cls}")
    ax.axis("off")
plt.tight_layout()
plt.show()
    
      from collections import defaultdict
import matplotlib.pyplot as plt
import numpy as np
import random
from sklearn.datasets import load_digits
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import StandardScaler
# =========================================================
# 1. 環境とデータの準備(手書き数字 64次元データ)
# =========================================================
np.random.seed(42)
digits = load_digits()
X_raw, y_raw = digits.data, digits.target
# 生態系の変化を見やすくするため、400サンプルを抽出
total_indices = np.random.choice(len(X_raw), 400, replace=False)
X_400, y_400 = X_raw[total_indices], y_raw[total_indices]
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_400)
# 少数サンプルを訓練用に(残りをテスト環境に)
train_idx = []
for c in range(10):
    c_idxs = np.where(y_400 == c)[0]
    train_idx.extend(c_idxs[:1]) # 各クラス1枚ずつ
test_idx = [i for i in range(400) if i not in train_idx]
X_train, y_train = X_scaled[train_idx], y_400[train_idx]
X_test, y_test = X_scaled[test_idx], y_400[test_idx]
NUM_CLASSES = 10
INPUT_DIM = 64
# --- ハイパーパラメータ ---
INIT_CELLS = 1000
MAX_CELLS = 10000
MIN_CELLS = 80
THOUGHT_STEPS = 25
lr_base = 0.20
lr_link_base = 0.10
DIVISION_THRESHOLD = 0.85     # 構造過学習を防ぐため、少し厳格化
DEATH_THRESHOLD = 0.08        # 淘汰圧を少し強める
GRAPH_DECAY = 0.90
EDGE_PRUNE_THRESHOLD = 0.02
# =========================================================
# 1.5 カリキュラム学習用のインデックスグループ作成
# =========================================================
class_to_train_idxs = {c: [] for c in range(NUM_CLASSES)}
for idx, c in enumerate(y_train):
    class_to_train_idxs[c].append(idx)
# カリキュラム順序の定義
curriculum_phases = [
    [1],          # フェーズ1: はじめに
    [7, 4],       # フェーズ2: 集中的に最初
    [9, 3, 8],    # フェーズ3: 展開
    [0, 2, 5, 6]  # フェーズ4: 残り
]
# =========================================================
# 2. 視覚記憶細胞(VisionMemoryCell)の定義
# =========================================================
class VisionMemoryCell:
    def __init__(self, cell_id):
        self.id = cell_id
        self.w_in = np.random.normal(0, 1.0, INPUT_DIM)
        self.A = np.random.normal(0, 0.1, NUM_CLASSES)
        self.links = {}
        self.visits = 0.0
        self.energy = 0.5
        self.recent_activity = 0.0
def cosine_similarity(w, x):
    return np.dot(w, x) / (np.linalg.norm(w) * np.linalg.norm(x) + 1e-9)
def softmax(x, temp=1.0):
    x = np.array(x) / temp
    x = x - np.max(x)
    e = np.exp(x)
    return e / (np.sum(e) + 1e-12)
# =========================================================
# 🧬 3. 代謝・新陳代謝コア
# =========================================================
def evolve_and_prune_space(memory_space, outer_graph):
    # 睡眠・自然減衰
    for cell in memory_space:
        cell.energy *= 0.92
        cell.recent_activity *= 0.50
        if cell.energy < 0.01: cell.energy = 0.01
        
    # --- 分裂フェーズ ---
    new_cells = []
    current_max_id = max([c.id for c in memory_space]) if memory_space else 0
    for cell in memory_space:
        if len(memory_space) + len(new_cells) >= MAX_CELLS: break
        
        if cell.energy > DIVISION_THRESHOLD and cell.visits > 15:
            child = VisionMemoryCell(current_max_id + len(new_cells) + 1)
            child.w_in = np.clip(cell.w_in + np.random.normal(0, 0.05, INPUT_DIM), -3.0, 3.0)
            child.A = cell.A.copy()
            for k, v in cell.links.items(): child.links[k] = v * 0.5
            
            cell.energy *= 0.3
            cell.visits *= 0.5
            child.energy = 0.2
            
            new_cells.append(child)
            
    memory_space.extend(new_cells)
    
    # --- 淘汰 & 外側マクログラフの再マッピング ---
    survived_space = []
    id_map = {}
    for cell in memory_space:
        if len(survived_space) > MIN_CELLS:
            if cell.energy < DEATH_THRESHOLD and cell.visits < 1.5:
                continue
        new_id = len(survived_space)
        id_map[cell.id] = new_id
        cell.id = new_id
        survived_space.append(cell)
        
    for cell in survived_space:
        cell.links = {id_map[old_id]: w * 0.85 for old_id, w in cell.links.items() if old_id in id_map and w > 0.05}
        
    new_graph = defaultdict(dict)
    for u, neighbors in outer_graph.items():
        new_u = ("cell", id_map[u[1]]) if u[0] == "cell" and u[1] in id_map else u
        if u[0] == "cell" and u[1] not in id_map: continue
        
        for v, weight in neighbors.items():
            decayed_weight = weight * GRAPH_DECAY
            if decayed_weight < EDGE_PRUNE_THRESHOLD: continue
            
            new_v = ("cell", id_map[v[1]]) if v[0] == "cell" and v[1] in id_map else v
            if v[0] == "cell" and v[1] not in id_map: continue
            
            new_graph[new_u][new_v] = decayed_weight
            
    return survived_space, new_graph
# =========================================================
# 🔄 4. 融合生命ダイナミクス(思考の歩行・側頭抑制)
# =========================================================
def live_vision_cycle(memory_space, outer_graph, x_64d=None, target_class=None, target_recall_class=None,
                      sensory_drive=1.0, intrinsic_drive=0.0, top_down_drive=0.0, learning_gate=1.0):
    if len(memory_space) == 0: return np.zeros(NUM_CLASSES), np.zeros(INPUT_DIM)
    id_to_cell = {c.id: c for c in memory_space}
    
    start_probabilities = np.zeros(len(memory_space))
    
    if x_64d is not None and sensory_drive > 0.0:
        sims = np.array([cosine_similarity(cell.w_in, x_64d) for cell in memory_space])
        start_probabilities += softmax(sims, temp=0.1) * sensory_drive
        
    if intrinsic_drive > 0.0:
        activity_weights = np.array([cell.energy * (cell.recent_activity + 0.05) for cell in memory_space])
        start_probabilities += (activity_weights / (np.sum(activity_weights) + 1e-12)) * intrinsic_drive
        
    if target_recall_class is not None and top_down_drive > 0.0:
        c_node = ("class", target_recall_class)
        for next_node, weight in outer_graph.get(c_node, {}).items():
            if next_node[0] == "cell" and next_node[1] in id_to_cell:
                start_probabilities[next_node[1]] += weight * top_down_drive
                
    if np.sum(start_probabilities) == 0: 
        start_probabilities = np.ones(len(memory_space)) / len(memory_space)
    start_probabilities /= np.sum(start_probabilities)
    
    # 開始ノードの選択(ここも推論時は最大確率の細胞を選ぶようにガード)
    if learning_gate == 0.0:
        current_cell_id = list(id_to_cell.keys())[np.argmax(start_probabilities)]
    else:
        current_cell_id = np.random.choice(list(id_to_cell.keys()), p=start_probabilities)
        
    path = [current_cell_id]
    collected_signals = [id_to_cell[current_cell_id].A.copy()]
    imagined_pixels = [id_to_cell[current_cell_id].w_in.copy()]
    
    # --- 思考がグラフを歩く(Graph Walk) ---
    for step in range(THOUGHT_STEPS - 1):
        current_cell = id_to_cell[current_cell_id]
        current_cell.recent_activity += sensory_drive * 1.0
        
        all_ids = list(id_to_cell.keys())
        candidates = set(np.random.choice(all_ids, min(25, len(all_ids)), replace=False))
        candidates.update(current_cell.links.keys())
        
        scores = []
        c_ids = []
        for cid in candidates:
            if cid in path or cid not in id_to_cell: continue
            cell = id_to_cell[cid]
            
            score = current_cell.links.get(cid, 0.0) * 2.5
            if x_64d is not None:
                score += 2.0 * cosine_similarity(cell.w_in, x_64d) * sensory_drive
            score += 0.5 * cosine_similarity(current_cell.A, cell.A)
            
            scores.append(score)
            c_ids.append(cid)
            
        if not scores: break
        
        # 【改善①】learning_gateによる探索ロジックの分離
        if learning_gate == 0.0:
            # 推論モード:最もスコアの高いルートへ確実に進む(決定論的)
            current_cell_id = c_ids[np.argmax(scores)]
        else:
            # 学習モード:柔軟な開拓のため確率的に進む(ランダムウォーク)
            probs = softmax(scores, temp=0.3)
            current_cell_id = np.random.choice(c_ids, p=probs)
            
        path.append(current_cell_id)
        collected_signals.append(id_to_cell[current_cell_id].A.copy())
        imagined_pixels.append(id_to_cell[current_cell_id].w_in.copy())
        
    final_perception = np.mean(np.stack(collected_signals), axis=0) if collected_signals else np.zeros(NUM_CLASSES)
    final_image = np.mean(np.stack(imagined_pixels), axis=0) if imagined_pixels else np.zeros(INPUT_DIM)
    
    sensory_factor = sensory_drive * learning_gate
    intrinsic_factor = intrinsic_drive * learning_gate
    
    inferred_class = np.argmax(final_perception)
    target = target_class if target_class is not None else inferred_class
    
    target_vec = np.zeros(NUM_CLASSES)
    target_vec[target] = 1.0
    
    # 報酬のベース計算
    reward = min(1.0 / (np.linalg.norm(target_vec - final_perception) + 0.15), 2.5)
    
    # 【改善②】正誤による報酬のブースト & ペナルティ
    if inferred_class == target:
        reward *= 2.0   # 正解ルートを爆発的に強固にする
    else:
        reward *= 0.2   # 不正解ルートを餓死(淘汰)へ追い込む
        
    # --- ヘブ則的な学習と側頭抑制 ---
    if x_64d is not None and sensory_factor > 0.0:
        # 1. 経路上の細胞の学習
        for cid in path:
            if cid in id_to_cell:
                cell = id_to_cell[cid]
                cell.visits += sensory_factor
                cell.energy = min(1.0, cell.energy + reward * 0.15 * sensory_factor)
                cell.w_in += (lr_base * 0.5 * sensory_factor) * (x_64d - cell.w_in)
                cell.A = (1.0 - lr_base * sensory_factor) * cell.A + (lr_base * sensory_factor) * target_vec
                
        # 2. 側頭抑制
        for cid in path:
            if cid in id_to_cell:
                for neighbor_id in id_to_cell[cid].links.keys():
                    if neighbor_id not in path and neighbor_id in id_to_cell:
                        id_to_cell[neighbor_id].energy *= 0.95
                        
    total_learning_power = (sensory_factor * reward + intrinsic_factor * 0.2)
    if total_learning_power > 0.0:
        for i in range(len(path) - 1):
            u, v = path[i], path[i+1]
            if u in id_to_cell and v in id_to_cell:
                id_to_cell[u].links[v] = id_to_cell[u].links.get(v, 0.1) + (lr_link_base * total_learning_power)
                
        c_node = ("class", int(target))
        first_cell_node = ("cell", path[0])
        last_cell_node = ("cell", path[-1])
        
        for i in range(len(path) - 1):
            f_node = ("cell", path[i])
            t_node = ("cell", path[i+1])
            outer_graph[f_node][t_node] = outer_graph[f_node].get(t_node, 0.0) + total_learning_power
            outer_graph[t_node][f_node] = outer_graph[t_node].get(f_node, 0.0) + total_learning_power
            
        outer_graph[first_cell_node][c_node] = outer_graph[first_cell_node].get(c_node, 0.0) + total_learning_power
        outer_graph[c_node][first_cell_node] = outer_graph[c_node].get(first_cell_node, 0.0) + total_learning_power
        
        outer_graph[last_cell_node][c_node] = outer_graph[last_cell_node].get(c_node, 0.0) + total_learning_power
        outer_graph[c_node][last_cell_node] = outer_graph[c_node].get(last_cell_node, 0.0) + total_learning_power
        
    return final_perception, final_image
# =========================================================
# 👥 5. アンサンブル・蜘蛛の巣トポロジー管理
# =========================================================
class EnsembleVisionEcosystem:
    def __init__(self, n_estimators=5, total_cells=1000):
        self.n_estimators = n_estimators
        self.models = []
        NUM_RINGS = 5
        
        for _ in range(n_estimators):
            space = []
            graph = defaultdict(dict)
            
            cells_per_ring = []
            remaining = total_cells
            for r in range(NUM_RINGS):
                if r == NUM_RINGS - 1:
                    cells_per_ring.append(remaining)
                else:
                    count = int(total_cells * (0.45 / (1.6**r)))
                    cells_per_ring.append(count)
                    remaining -= count
                    
            cell_id_counter = 0
            ring_nodes = {}
            
            for ring_idx, n_cells in enumerate(cells_per_ring):
                ring_nodes[ring_idx] = []
                for i in range(n_cells):
                    cell = VisionMemoryCell(cell_id_counter)
                    sensory_proximity = 1.0 - (ring_idx / NUM_RINGS)
                    if sensory_proximity > 0.2:
                        ref_idx = np.random.choice(len(X_train))
                        base_pattern = X_train[ref_idx]
                        cell.w_in = base_pattern * sensory_proximity + np.random.normal(0, 0.3, INPUT_DIM) * (1.0 - sensory_proximity)
                        cell.A[y_train[ref_idx]] = 0.5 * sensory_proximity
                        
                    space.append(cell)
                    ring_nodes[ring_idx].append(cell_id_counter)
                    cell_id_counter += 1
                    
            id_to_cell = {c.id: c for c in space}
            for ring_idx in range(NUM_RINGS):
                nodes = ring_nodes[ring_idx]
                num_nodes = len(nodes)
                if num_nodes == 0: continue
                
                for i in range(num_nodes):
                    curr_id = nodes[i]
                    next_id = nodes[(i + 1) % num_nodes]
                    id_to_cell[curr_id].links[next_id] = 0.4
                    id_to_cell[next_id].links[curr_id] = 0.4
                    
                    if ring_idx < NUM_RINGS - 1:
                        inner_nodes = ring_nodes[ring_idx + 1]
                        if len(inner_nodes) > 0:
                            inner_idx = int(i * len(inner_nodes) / num_nodes)
                            inner_id = inner_nodes[inner_idx]
                            id_to_cell[curr_id].links[inner_id] = 0.6
                            id_to_cell[inner_id].links[curr_id] = 0.6
            self.models.append({'space': space, 'graph': graph})
    def train_cycle(self, X, y, phase="day"):
        for model in self.models:
            indices = np.random.choice(len(X), len(X), replace=True)
            for idx in indices:
                if phase == "day":
                    live_vision_cycle(model['space'], model['graph'], x_64d=X[idx], 
                                      target_class=y[idx], sensory_drive=1.0, intrinsic_drive=0.0, learning_gate=1.0)
                elif phase == "evening":
                    live_vision_cycle(model['space'], model['graph'], x_64d=X[idx], 
                                      target_class=y[idx], sensory_drive=0.3, intrinsic_drive=0.7, learning_gate=1.0)
    def sleep_cycle(self, steps=60):
        for model in self.models:
            for _ in range(steps):
                live_vision_cycle(model['space'], model['graph'], x_64d=None, 
                                  target_class=None, sensory_drive=0.0, intrinsic_drive=1.0, learning_gate=1.0)
    def cultural_crossover(self, crossover_rate=0.2):
        elite_cells_per_model = []
        for m_idx, model in enumerate(self.models):
            space = model['space']
            if not space: continue
            core_start_idx = int(len(space) * 0.9)
            core_cells = space[core_start_idx:]
            if not core_cells: continue
            
            elite_cell = max(core_cells, key=lambda c: (c.energy * np.max(c.A)))
            elite_cells_per_model.append((m_idx, elite_cell))
            
        for src_idx, src_cell in elite_cells_per_model:
            target_indices = [i for i in range(self.n_estimators) if i != src_idx]
            if not target_indices: continue
            dst_idx = random.choice(target_indices)
            dst_model = self.models[dst_idx]
            
            dst_core_cells = dst_model['space'][int(len(dst_model['space']) * 0.9):]
            if not dst_core_cells: continue
            
            weak_cell = min(dst_core_cells, key=lambda c: c.energy)
            weak_cell.w_in = (1.0 - crossover_rate) * weak_cell.w_in + crossover_rate * src_cell.w_in
            weak_cell.A = (1.0 - crossover_rate) * weak_cell.A + crossover_rate * src_cell.A
            weak_cell.energy = max(weak_cell.energy, src_cell.energy * 0.8)
    def evolve(self):
        for model in self.models:
            model['space'], model['graph'] = evolve_and_prune_space(model['space'], model['graph'])
    def predict(self, X):
        y_pred = []
        for idx in range(len(X)):
            ensemble_perceptions = []
            for model in self.models:
                # 推論時は完全に学習ゲートを0にしてargmaxを走らせる
                perception, _ = live_vision_cycle(model['space'], model['graph'], x_64d=X[idx], 
                                                  sensory_drive=1.0, intrinsic_drive=0.0, learning_gate=0.0)
                ensemble_perceptions.append(perception)
            avg_perception = np.mean(ensemble_perceptions, axis=0)
            y_pred.append(np.argmax(avg_perception))
        return y_pred
    def get_average_cell_count(self):
        return int(np.mean([len(m['space']) for m in self.models]))
# =========================================================
# 🚀 6. カリキュラム巡礼ループの実行
# =========================================================
print("=== 蜘蛛の巣ウェブ型・カリキュラム学習システム 起動 ===")
ensemble = EnsembleVisionEcosystem(n_estimators=5, total_cells=INIT_CELLS)
for phase_idx, classes_in_phase in enumerate(curriculum_phases):
    print(f"\n--- 【フェーズ {phase_idx + 1}】対象クラス: {classes_in_phase} の学習を開始 ---")
    
    phase_train_indices = []
    for c in classes_in_phase:
        phase_train_indices.extend(class_to_train_idxs[c])
        
    if not phase_train_indices:
        print(f"警告: クラス {classes_in_phase} の訓練データが不足しています。")
        continue
        
    X_phase = X_train[phase_train_indices]
    y_phase = y_train[phase_train_indices]
    
    phase_epochs = 3 
    for cycle in range(phase_epochs):
        ensemble.train_cycle(X_phase, y_phase, phase="day")
        ensemble.train_cycle(X_phase, y_phase, phase="evening")
        ensemble.sleep_cycle(steps=60)
        ensemble.cultural_crossover(crossover_rate=0.2)
        ensemble.evolve()
        
        # 評価
        y_pred = ensemble.predict(X_test)
        acc = accuracy_score(y_test, y_pred)
        avg_cells = ensemble.get_average_cell_count()
        print(f"  [サイクル {cycle+1}] テスト精度(全体): {acc:.4f} | 平均細胞数: {avg_cells}")
# =========================================================
# 7. ダッシュボード:【 空想状態(Daydream) 】の可視化
# =========================================================
print("\n[Generating Daydream Images from the First Evolved Ecosystem...]")
fig, axes = plt.subplots(2, 5, figsize=(12, 5))
fig.suptitle("Daydreaming Mode (Determined Path via Argmax) - Model #1\nVisualizing the Clear 'Ideals' of digits polished by Reward & Argmax Control", fontsize=14)
first_model = ensemble.models[0]
for cls in range(10):
    # 空想時もlearning_gate=0.0により、最もピュアな最短経路を辿る
    _, daydream_image = live_vision_cycle(first_model['space'], first_model['graph'], 
                                          sensory_drive=0.0, intrinsic_drive=0.2, top_down_drive=1.0, 
                                          target_recall_class=cls, learning_gate=0.0)
    ax = axes[cls // 5, cls % 5]
    ax.imshow(daydream_image.reshape(8, 8), cmap="bone")
    ax.set_title(f"Concept: {cls}")
    ax.axis("off")
plt.tight_layout()
plt.show()
    
      from collections import defaultdict
import matplotlib.pyplot as plt
import numpy as np
import random
from sklearn.datasets import load_digits
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import MinMaxScaler
# =========================================================
# 1. 環境とデータの準備(手書き数字 64次元データ)
# =========================================================
np.random.seed(42)
random.seed(42)
digits = load_digits()
X_raw, y_raw = digits.data, digits.target
# 生態系の変化を見やすくするため、400サンプルを抽出
total_indices = np.random.choice(len(X_raw), 400, replace=False)
X_400, y_400 = X_raw[total_indices], y_raw[total_indices]
# コサイン類似度が正常に機能するよう、0〜1に正規化
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X_400)
# 少数サンプルを訓練用に(残りをテスト環境に)
train_idx = []
for c in range(10):
    c_idxs = np.where(y_400 == c)[0]
    train_idx.extend(c_idxs[:1]) # 各クラス1枚ずつ (極小データ)
test_idx = [i for i in range(400) if i not in train_idx]
X_train, y_train = X_scaled[train_idx], y_400[train_idx]
X_test, y_test = X_scaled[test_idx], y_400[test_idx]
NUM_CLASSES = 10
INPUT_DIM = 64
# --- ハイパーパラメータ(生態系のバランス調整) ---
INIT_CELLS = 600       
MAX_CELLS = 2000
MIN_CELLS = 50
THOUGHT_STEPS = 10      
lr_base = 0.25
lr_link_base = 0.15
DIVISION_THRESHOLD = 0.75  
DEATH_THRESHOLD = 0.05    
GRAPH_DECAY = 0.90
EDGE_PRUNE_THRESHOLD = 0.02
# =========================================================
# 1.5 カリキュラム学習用のインデックスグループ作成
# =========================================================
class_to_train_idxs = {c: [] for c in range(NUM_CLASSES)}
for idx, c in enumerate(y_train):
    class_to_train_idxs[c].append(idx)
curriculum_phases = [
    [1],          
    [7, 4],       
    [9, 3, 8],    
    [0, 2, 5, 6]  
]
# =========================================================
# 2. 視覚記憶細胞(VisionMemoryCell)の定義
# =========================================================
class VisionMemoryCell:
    def __init__(self, cell_id):
        self.id = cell_id
        self.w_in = np.random.uniform(0.1, 0.9, INPUT_DIM)
        self.A = np.ones(NUM_CLASSES) * (1.0 / NUM_CLASSES)
        self.links = {}
        self.visits = 0.0
        self.energy = 0.5
        self.recent_activity = 0.0
def cosine_similarity(w, x):
    denom = (np.linalg.norm(w) * np.linalg.norm(x) + 1e-9)
    return np.dot(w, x) / denom
def softmax(x, temp=1.0):
    x = np.array(x) / temp
    x = x - np.max(x)
    e = np.exp(x)
    return e / (np.sum(e) + 1e-12)
# =========================================================
# 🧬 3. 代謝・新陳代謝コア
# =========================================================
def evolve_and_prune_space(memory_space, outer_graph):
    for cell in memory_space:
        cell.energy *= 0.90
        cell.recent_activity *= 0.50
        if cell.energy < 0.01: cell.energy = 0.01
        
    new_cells = []
    current_max_id = max([c.id for c in memory_space]) if memory_space else 0
    for cell in memory_space:
        if len(memory_space) + len(new_cells) >= MAX_CELLS: break
        
        if cell.energy > DIVISION_THRESHOLD and cell.visits > 5:
            child = VisionMemoryCell(current_max_id + len(new_cells) + 1)
            child.w_in = np.clip(cell.w_in + np.random.normal(0, 0.05, INPUT_DIM), 0.0, 1.0)
            child.A = cell.A.copy()
            for k, v in cell.links.items(): child.links[k] = v * 0.5
            
            cell.energy *= 0.4
            cell.visits *= 0.5
            child.energy = 0.3
            new_cells.append(child)
            
    memory_space.extend(new_cells)
    
    survived_space = []
    id_map = {}
    for cell in memory_space:
        if len(survived_space) > MIN_CELLS:
            if cell.energy < DEATH_THRESHOLD and cell.visits < 0.5:
                continue
        new_id = len(survived_space)
        id_map[cell.id] = new_id
        cell.id = new_id
        survived_space.append(cell)
        
    for cell in survived_space:
        cell.links = {id_map[old_id]: w * 0.85 for old_id, w in cell.links.items() if old_id in id_map and w > 0.05}
        
    new_graph = defaultdict(dict)
    for u, neighbors in outer_graph.items():
        new_u = ("cell", id_map[u[1]]) if u[0] == "cell" and u[1] in id_map else u
        if u[0] == "cell" and u[1] not in id_map: continue
        
        for v, weight in neighbors.items():
            decayed_weight = weight * GRAPH_DECAY
            if decayed_weight < EDGE_PRUNE_THRESHOLD: continue
            
            new_v = ("cell", id_map[v[1]]) if v[0] == "cell" and v[1] in id_map else v
            if v[0] == "cell" and v[1] not in id_map: continue
            
            new_graph[new_u][new_v] = decayed_weight
            
    return survived_space, new_graph
# =========================================================
# 🔄 4. 融合生命ダイナミクス(思考の歩行・側頭抑制)
# =========================================================
def live_vision_cycle(memory_space, outer_graph, x_64d=None, target_class=None, target_recall_class=None,
                      sensory_drive=1.0, intrinsic_drive=0.0, top_down_drive=0.0, learning_gate=1.0):
    if len(memory_space) == 0: return np.zeros(NUM_CLASSES), np.zeros(INPUT_DIM)
    id_to_cell = {c.id: c for c in memory_space}
    
    start_probabilities = np.zeros(len(memory_space))
    
    if x_64d is not None and sensory_drive > 0.0:
        sims = np.array([cosine_similarity(cell.w_in, x_64d) for cell in memory_space])
        start_probabilities += softmax(sims, temp=0.05) * sensory_drive
        
    if intrinsic_drive > 0.0:
        activity_weights = np.array([cell.energy * (cell.recent_activity + 0.05) for cell in memory_space])
        start_probabilities += (activity_weights / (np.sum(activity_weights) + 1e-12)) * intrinsic_drive
        
    if target_recall_class is not None and top_down_drive > 0.0:
        c_node = ("class", target_recall_class)
        for next_node, weight in outer_graph.get(c_node, {}).items():
            if next_node[0] == "cell" and next_node[1] in id_to_cell:
                start_probabilities[next_node[1]] += weight * top_down_drive
                
    if np.sum(start_probabilities) == 0: 
        start_probabilities = np.ones(len(memory_space)) / len(memory_space)
    start_probabilities /= np.sum(start_probabilities)
    
    if learning_gate == 0.0:
        current_cell = memory_space[np.argmax(start_probabilities)]
    else:
        current_cell = np.random.choice(memory_space, p=start_probabilities)
        
    current_cell_id = current_cell.id
    path = [current_cell_id]
    collected_signals = [current_cell.A.copy()]
    imagined_pixels = [current_cell.w_in.copy()]
    
    for step in range(THOUGHT_STEPS - 1):
        curr_node = id_to_cell[current_cell_id]
        curr_node.recent_activity += sensory_drive * 1.0
        
        all_ids = list(id_to_cell.keys())
        candidates = set(np.random.choice(all_ids, min(25, len(all_ids)), replace=False))
        candidates.update(curr_node.links.keys())
        
        scores = []
        c_nodes = []
        for cid in candidates:
            if cid in path or cid not in id_to_cell: continue
            cell = id_to_cell[cid]
            
            score = curr_node.links.get(cid, 0.0) * 2.5
            if x_64d is not None:
                score += 2.0 * cosine_similarity(cell.w_in, x_64d) * sensory_drive
            score += 0.5 * cosine_similarity(curr_node.A, cell.A)
            
            scores.append(score)
            c_nodes.append(cell)
        
        if not scores: break
        
        if learning_gate == 0.0:
            current_cell_id = c_nodes[np.argmax(scores)].id
        else:
            probs = softmax(scores, temp=0.2)
            current_cell_id = np.random.choice(c_nodes, p=probs).id
            
        path.append(current_cell_id)
        collected_signals.append(id_to_cell[current_cell_id].A.copy())
        imagined_pixels.append(id_to_cell[current_cell_id].w_in.copy())
        
    final_perception = np.mean(np.stack(collected_signals), axis=0) if collected_signals else np.zeros(NUM_CLASSES)
    final_image = np.mean(np.stack(imagined_pixels), axis=0) if imagined_pixels else np.zeros(INPUT_DIM)
    
    sensory_factor = sensory_drive * learning_gate
    intrinsic_factor = intrinsic_drive * learning_gate
    
    inferred_class = np.argmax(final_perception)
    target = target_class if target_class is not None else inferred_class
    
    target_vec = np.zeros(NUM_CLASSES)
    target_vec[target] = 1.0
    
    reward = min(1.0 / (np.linalg.norm(target_vec - final_perception) + 0.15), 2.5)
    if inferred_class == target:
        reward *= 2.5  
    else:
        reward *= 0.1  
        
    if x_64d is not None and sensory_factor > 0.0:
        for cid in path:
            if cid in id_to_cell:
                cell = id_to_cell[cid]
                cell.visits += sensory_factor
                cell.energy = min(1.0, cell.energy + reward * 0.20 * sensory_factor)
                cell.w_in += (lr_base * sensory_factor) * (x_64d - cell.w_in)
                cell.A = (1.0 - lr_base * sensory_factor) * cell.A + (lr_base * sensory_factor) * target_vec
                
        for cid in path:
            if cid in id_to_cell:
                for neighbor_id in id_to_cell[cid].links.keys():
                    if neighbor_id not in path and neighbor_id in id_to_cell:
                        id_to_cell[neighbor_id].energy *= 0.90
                        
    total_learning_power = (sensory_factor * reward + intrinsic_factor * 0.2)
    if total_learning_power > 0.0:
        for i in range(len(path) - 1):
            u, v = path[i], path[i+1]
            if u in id_to_cell and v in id_to_cell:
                id_to_cell[u].links[v] = id_to_cell[u].links.get(v, 0.1) + (lr_link_base * total_learning_power)
                
        c_node = ("class", int(target))
        first_cell_node = ("cell", path[0])
        last_cell_node = ("cell", path[-1])
        
        for i in range(len(path) - 1):
            f_node = ("cell", path[i])
            t_node = ("cell", path[i+1])
            outer_graph[f_node][t_node] = outer_graph[f_node].get(t_node, 0.0) + total_learning_power
            outer_graph[t_node][f_node] = outer_graph[t_node].get(f_node, 0.0) + total_learning_power
            
        outer_graph[first_cell_node][c_node] = outer_graph[first_cell_node].get(c_node, 0.0) + total_learning_power
        outer_graph[c_node][first_cell_node] = outer_graph[c_node].get(first_cell_node, 0.0) + total_learning_power
        
        outer_graph[last_cell_node][c_node] = outer_graph[last_cell_node].get(c_node, 0.0) + total_learning_power
        outer_graph[c_node][last_cell_node] = outer_graph[c_node].get(last_cell_node, 0.0) + total_learning_power
        
    return final_perception, final_image
# =========================================================
# 👥 5. アンサンブル・蜘蛛の巣トポロジー管理
# =========================================================
class EnsembleVisionEcosystem:
    def __init__(self, n_estimators=5, total_cells=400):
        self.n_estimators = n_estimators
        self.models = []
        NUM_RINGS = 4
        
        for _ in range(n_estimators):
            space = []
            graph = defaultdict(dict)
            
            cells_per_ring = []
            remaining = total_cells
            for r in range(NUM_RINGS):
                if r == NUM_RINGS - 1:
                    cells_per_ring.append(remaining)
                else:
                    count = int(total_cells * (0.5 / (1.5**r)))
                    cells_per_ring.append(count)
                    remaining -= count
                    
            cell_id_counter = 0
            ring_nodes = {}
            
            for ring_idx, n_cells in enumerate(cells_per_ring):
                ring_nodes[ring_idx] = []
                for i in range(n_cells):
                    cell = VisionMemoryCell(cell_id_counter)
                    sensory_proximity = 1.0 - (ring_idx / NUM_RINGS)
                    
                    # 【改善版】クラスを均等に分散配置&One-hotプロトタイプ化
                    if sensory_proximity > 0.1:
                        assigned_class = i % NUM_CLASSES 
                        c_idxs = class_to_train_idxs[assigned_class]
                        ref_idx = c_idxs[0] 
                        
                        base_pattern = X_train[ref_idx]
                        # ノイズを少し抑えめ(0.1)にして原型の形をしっかり残す
                        cell.w_in = np.clip(base_pattern + np.random.normal(0, 0.1, INPUT_DIM), 0.0, 1.0)
                        
                        # クラスベクトルを対象クラスに100%確信させる
                        cell.A = np.zeros(NUM_CLASSES)
                        cell.A[assigned_class] = 1.0  
                        
                    space.append(cell)
                    ring_nodes[ring_idx].append(cell_id_counter)
                    cell_id_counter += 1
                    
            id_to_cell = {c.id: c for c in space}
            for ring_idx in range(NUM_RINGS):
                nodes = ring_nodes[ring_idx]
                num_nodes = len(nodes)
                if num_nodes == 0: continue
                
                for i in range(num_nodes):
                    curr_id = nodes[i]
                    next_id = nodes[(i + 1) % num_nodes]
                    id_to_cell[curr_id].links[next_id] = 0.5
                    id_to_cell[next_id].links[curr_id] = 0.5
                    
                    if ring_idx < NUM_RINGS - 1:
                        inner_nodes = ring_nodes[ring_idx + 1]
                        if len(inner_nodes) > 0:
                            inner_idx = int(i * len(inner_nodes) / num_nodes)
                            inner_id = inner_nodes[inner_idx]
                            id_to_cell[curr_id].links[inner_id] = 0.5
                            id_to_cell[inner_id].links[curr_id] = 0.5
            self.models.append({'space': space, 'graph': graph})
    def train_cycle(self, X, y, phase="day"):
        for model in self.models:
            indices = np.random.choice(len(X), len(X) * 3, replace=True) 
            for idx in indices:
                if phase == "day":
                    live_vision_cycle(model['space'], model['graph'], x_64d=X[idx], 
                                      target_class=y[idx], sensory_drive=1.0, intrinsic_drive=0.0, learning_gate=1.0)
                elif phase == "evening":
                    live_vision_cycle(model['space'], model['graph'], x_64d=X[idx], 
                                      target_class=y[idx], sensory_drive=0.4, intrinsic_drive=0.6, learning_gate=1.0)
    def sleep_cycle(self, steps=30):
        for model in self.models:
            for _ in range(steps):
                live_vision_cycle(model['space'], model['graph'], x_64d=None, 
                                  target_class=None, sensory_drive=0.0, intrinsic_drive=1.0, learning_gate=1.0)
    def cultural_crossover(self, crossover_rate=0.2):
        for m_idx, model in enumerate(self.models):
            space = model['space']
            if len(space) < 10: continue
            core_cells = space[int(len(space)*0.8):]
            if not core_cells: continue
            
            elite_cell = max(core_cells, key=lambda c: (c.energy * np.max(c.A)))
            
            target_indices = [i for i in range(self.n_estimators) if i != m_idx]
            dst_idx = random.choice(target_indices)
            dst_model = self.models[dst_idx]
            
            if len(dst_model['space']) > 10:
                weak_cell = min(dst_model['space'], key=lambda c: c.energy)
                weak_cell.w_in = (1.0 - crossover_rate) * weak_cell.w_in + crossover_rate * elite_cell.w_in
                weak_cell.A = (1.0 - crossover_rate) * weak_cell.A + crossover_rate * elite_cell.A
                weak_cell.energy = max(weak_cell.energy, elite_cell.energy * 0.7)
    def evolve(self):
        for model in self.models:
            model['space'], model['graph'] = evolve_and_prune_space(model['space'], model['graph'])
    def predict(self, X):
        y_pred = []
        for idx in range(len(X)):
            ensemble_perceptions = []
            for model in self.models:
                perception, _ = live_vision_cycle(model['space'], model['graph'], x_64d=X[idx], 
                                                  sensory_drive=1.0, intrinsic_drive=0.0, learning_gate=0.0)
                ensemble_perceptions.append(perception)
            avg_perception = np.mean(ensemble_perceptions, axis=0)
            y_pred.append(np.argmax(avg_perception))
        return y_pred
    def get_average_cell_count(self):
        return int(np.mean([len(m['space']) for m in self.models]))
# =========================================================
# 🚀 6. 評価(初期状態)とカリキュラム巡礼の実行
# =========================================================
print("=== 蜘蛛の巣ウェブ型・カリキュラム学習システム 起動 ===")
ensemble = EnsembleVisionEcosystem(n_estimators=5, total_cells=INIT_CELLS)
# 学習前の初期化のみの精度を測定
init_pred = ensemble.predict(X_test)
init_acc = accuracy_score(y_test, init_pred)
print(f"\n🔥 [初期状態 (学習前)] テスト精度: {init_acc:.4f} | 平均細胞数: {ensemble.get_average_cell_count()}")
print("=======================================================")
for phase_idx, classes_in_phase in enumerate(curriculum_phases):
    print(f"\n--- 【フェーズ {phase_idx + 1}】対象クラス: {classes_in_phase} の学習を開始 ---")
    
    phase_train_indices = []
    for c in classes_in_phase:
        phase_train_indices.extend(class_to_train_idxs[c])
        
    X_phase = X_train[phase_train_indices]
    y_phase = y_train[phase_train_indices]
    
    phase_epochs = 6 
    for cycle in range(phase_epochs):
        ensemble.train_cycle(X_phase, y_phase, phase="day")
        ensemble.train_cycle(X_phase, y_phase, phase="evening")
        ensemble.sleep_cycle(steps=20)
        ensemble.cultural_crossover(crossover_rate=0.2)
        ensemble.evolve()
        
        # 評価
        y_pred = ensemble.predict(X_test)
        acc = accuracy_score(y_test, y_pred)
        avg_cells = ensemble.get_average_cell_count()
        print(f"  [サイクル {cycle+1}] テスト精度(全体): {acc:.4f} | 平均細胞数: {avg_cells}")
# =========================================================
# 7. ダッシュボード:【 空想状態(Daydream) 】の可視化
# =========================================================
print("\n[Generating Daydream Images from the First Evolved Ecosystem...]")
fig, axes = plt.subplots(2, 5, figsize=(12, 5))
fig.suptitle("Daydreaming Mode (Determined Path via Argmax) - Model #1", fontsize=14)
first_model = ensemble.models[0]
for cls in range(10):
    _, daydream_image = live_vision_cycle(first_model['space'], first_model['graph'], 
                                          sensory_drive=0.0, intrinsic_drive=0.1, top_down_drive=1.5, 
                                          target_recall_class=cls, learning_gate=0.0)
    ax = axes[cls // 5, cls % 5]
    ax.imshow(daydream_image.reshape(8, 8), cmap="bone")
    ax.set_title(f"Concept: {cls}")
    ax.axis("off")
plt.tight_layout()
plt.show()
    
投稿日:19日前
更新日:19日前
数学の力で現場を変える アルゴリズムエンジニア募集 - Mathlog served by OptHub

この本を高評価した人

高評価したユーザはいません

この本に送られたバッジ

バッジはありません。

投稿者

Owl
1
288

コメント

他の人のコメント

コメントはありません。
読み込み中...
読み込み中
数学の力で現場を変える アルゴリズムエンジニア募集 - Mathlog served by OptHub

現在のページ

コード一覧
前のページへ
1 / 1
次のページへ
訓練画像10枚だけの画像認識の表紙