|

How to Build a Neuro-Symbolic Hybrid Agent that Combines Logical Planning with Neural Perception for Robust Autonomous Decision-Making

🧠

In this tutorial, we show how to mix the strengths of symbolic reasoning with neural studying to construct a highly effective hybrid agent. We deal with creating a neuro-symbolic structure that makes use of classical planning for construction, guidelines, and goal-directed habits, whereas neural networks deal with notion and motion refinement. As we stroll by the code, we see how each layers work together in actual time, permitting us to navigate an surroundings, overcome uncertainty, and adapt intelligently. At final, we perceive how neuro-symbolic techniques convey interpretability, robustness, and suppleness collectively in a single agentic framework. Check out the FULL CODES here.

import numpy as np
import matplotlib.pyplot as plt
from dataclasses import dataclass, area
from typing import List, Dict, Tuple, Set, Optional
from collections import deque
import warnings
warnings.filterwarnings('ignore')


@dataclass
class State:
   robot_pos: Tuple[int, int]
   holding: Optional[str] = None
   visited: Set[Tuple[int, int]] = area(default_factory=set)
   objects_collected: Set[str] = area(default_factory=set)
   def __hash__(self):
       return hash((self.robot_pos, self.holding))


class SymbolicPlanner:
   def __init__(self, grid_size: int = 8):
       self.grid_size = grid_size
       self.actions = ['up', 'down', 'left', 'right', 'pickup', 'drop']
   def get_successors(self, state: State, obstacles: Set[Tuple[int, int]], objects: Dict[str, Tuple[int, int]]) -> List[Tuple[str, State]]:
       successors = []
       x, y = state.robot_pos
       strikes = {'up': (x, y-1), 'down': (x, y+1), 'left': (x-1, y), 'proper': (x+1, y)}
       for motion, new_pos in strikes.gadgets():
           nx, ny = new_pos
           if (0 <= nx < self.grid_size and 0 <= ny < self.grid_size and new_pos not in obstacles):
               new_state = State(new_pos, state.holding, state.visited | {new_pos}, state.objects_collected.copy())
               successors.append((motion, new_state))
       if state.holding is None:
           for obj_name, obj_pos in objects.gadgets():
               if state.robot_pos == obj_pos and obj_name not in state.objects_collected:
                   new_state = State(state.robot_pos, obj_name, state.visited.copy(), state.objects_collected.copy())
                   successors.append(('pickup', new_state))
       if state.holding just isn't None:
           new_state = State(state.robot_pos, None, state.visited.copy(), state.objects_collected | {state.holding})
           successors.append(('drop', new_state))
       return successors
   def heuristic(self, state: State, aim: Tuple[int, int]) -> float:
       return abs(state.robot_pos[0] - aim[0]) + abs(state.robot_pos[1] - aim[1])
   def a_star_plan(self, start_state: State, aim: Tuple[int, int], obstacles: Set[Tuple[int, int]], objects: Dict[str, Tuple[int, int]]) -> List[str]:
       counter = 0
       frontier = [(self.heuristic(start_state, goal), counter, 0, start_state, [])]
       visited = set()
       whereas frontier:
           frontier.kind()
           _, _, value, state, plan = frontier.pop(0)
           counter += 1
           if state.robot_pos == aim and len(state.objects_collected) >= len(objects):
               return plan
           state_key = (state.robot_pos, state.holding)
           if state_key in visited:
               proceed
           visited.add(state_key)
           for motion, next_state in self.get_successors(state, obstacles, objects):
               new_cost = value + 1
               new_plan = plan + [action]
               precedence = new_cost + self.heuristic(next_state, aim)
               frontier.append((precedence, counter, new_cost, next_state, new_plan))
               counter += 1
       return []

We lay the inspiration for our symbolic reasoning system and outline how states, actions, and transitions work. We implement classical planning logic utilizing A* search to generate goal-directed, interpretable motion sequences. As we construct this half, we set up the rule-based spine that guides the agent’s high-level choices. Check out the FULL CODES here.

class NeuralPerception:
   def __init__(self, grid_size: int = 8):
       self.grid_size = grid_size
       self.W1 = np.random.randn(grid_size * grid_size, 64) * 0.1
       self.b1 = np.zeros(64)
       self.W2 = np.random.randn(64, 32) * 0.1
       self.b2 = np.zeros(32)
       self.W3 = np.random.randn(32, grid_size * grid_size) * 0.1
       self.b3 = np.zeros(grid_size * grid_size)
   def relu(self, x):
       return np.most(0, x)
   def sigmoid(self, x):
       return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
   def understand(self, noisy_grid: np.ndarray) -> np.ndarray:
       x = noisy_grid.flatten()
       h1 = self.relu(x @ self.W1 + self.b1)
       h2 = self.relu(h1 @ self.W2 + self.b2)
       out = self.sigmoid(h2 @ self.W3 + self.b3)
       return out.reshape(self.grid_size, self.grid_size)


class NeuralPolicy:
   def __init__(self, state_dim: int = 4, action_dim: int = 4):
       self.W = np.random.randn(state_dim, action_dim) * 0.1
       self.b = np.zeros(action_dim)
       self.action_map = ['up', 'down', 'left', 'right']
   def softmax(self, x):
       exp_x = np.exp(x - np.max(x))
       return exp_x / exp_x.sum()
   def get_action_probs(self, state_features: np.ndarray) -> np.ndarray:
       logits = state_features @ self.W + self.b
       return self.softmax(logits)
   def select_action(self, state_features: np.ndarray, symbolic_action: str) -> str:
       probs = self.get_action_probs(state_features)
       if symbolic_action in self.action_map:
           sym_idx = self.action_map.index(symbolic_action)
           probs[sym_idx] += 0.7
           probs = probs / probs.sum()
       return np.random.alternative(self.action_map, p=probs)

We introduce the neural parts that enable our agent to sense and adapt. We design a light-weight neural community to denoise the surroundings and a easy coverage community to refine actions primarily based on options. As we combine these components, we guarantee that our agent can deal with uncertainty and regulate habits dynamically. Check out the FULL CODES here.

class NeuroSymbolicAgent:
   def __init__(self, grid_size: int = 8):
       self.grid_size = grid_size
       self.planner = SymbolicPlanner(grid_size)
       self.notion = NeuralPerception(grid_size)
       self.coverage = NeuralPolicy()
       self.obstacles = {(3, 3), (3, 4), (4, 3), (5, 5), (6, 2)}
       self.objects = {'key': (2, 6), 'gem': (6, 6)}
       self.aim = (7, 7)
   def create_noisy_observation(self, true_grid: np.ndarray) -> np.ndarray:
       noise = np.random.randn(*true_grid.form) * 0.2
       return np.clip(true_grid + noise, 0, 1)
   def extract_state_features(self, pos: Tuple[int, int], aim: Tuple[int, int]) -> np.ndarray:
       return np.array([pos[0]/self.grid_size, pos[1]/self.grid_size, aim[0]/self.grid_size, aim[1]/self.grid_size])
   def execute_mission(self, verbose: bool = True) -> Tuple[List, List]:
       start_state = State(robot_pos=(0, 0), visited={(0, 0)})
       symbolic_plan = self.planner.a_star_plan(start_state, self.aim, self.obstacles, self.objects)
       if verbose:
           print(f"🧠 Symbolic Plan Generated: {len(symbolic_plan)} steps")
           print(f"   Plan: {symbolic_plan[:10]}{'...' if len(symbolic_plan) > 10 else ''}n")
       true_grid = np.zeros((self.grid_size, self.grid_size))
       for obs in self.obstacles:
           true_grid[obs[1], obs[0]] = 1.0
       noisy_obs = self.create_noisy_observation(true_grid)
       perceived_grid = self.notion.understand(noisy_obs)
       if verbose:
           print(f"👁  Neural Perception: Denoised impediment map")
           print(f"   Perception accuracy: {np.imply((perceived_grid > 0.5) == true_grid):.2%}n")
       trajectory = [(0, 0)]
       current_pos = (0, 0)
       actions_taken = []
       for i, sym_action in enumerate(symbolic_plan[:30]):
           options = self.extract_state_features(current_pos, self.aim)
           refined_action = self.coverage.select_action(options, sym_action) if sym_action in ['up','down','left','right'] else sym_action
           actions_taken.append(refined_action)
           if refined_action == 'up': current_pos = (current_pos[0], max(0, current_pos[1]-1))
           elif refined_action == 'down': current_pos = (current_pos[0], min(self.grid_size-1, current_pos[1]+1))
           elif refined_action == 'left': current_pos = (max(0, current_pos[0]-1), current_pos[1])
           elif refined_action == 'proper': current_pos = (min(self.grid_size-1, current_pos[0]+1), current_pos[1])
           if current_pos not in self.obstacles:
               trajectory.append(current_pos)
       return trajectory, actions_taken

We convey the symbolic and neural layers collectively into a unified agent. We generate a symbolic plan, understand the surroundings by neural processing, and refine every deliberate motion utilizing the neural coverage. As we execute the mission loop, we observe how each techniques work together seamlessly to produce strong habits. Check out the FULL CODES here.

def visualize_execution(agent: NeuroSymbolicAgent, trajectory: List, title: str = "Neuro-Symbolic Agent Execution"):
   fig, axes = plt.subplots(1, 2, figsize=(14, 6))
   ax = axes[0]
   grid = np.zeros((agent.grid_size, agent.grid_size, 3))
   for obs in agent.obstacles:
       grid[obs[1], obs[0]] = [0.3, 0.3, 0.3]
   for obj_pos in agent.objects.values():
       grid[obj_pos[1], obj_pos[0]] = [1.0, 0.8, 0.0]
   grid[agent.goal[1], agent.aim[0]] = [0.0, 1.0, 0.0]
   for i, pos in enumerate(trajectory):
       depth = 0.3 + 0.7 * (i / len(trajectory))
       grid[pos[1], pos[0]] = [intensity, 0.0, 1.0]
   if trajectory:
       grid[trajectory[0][1], trajectory[0][0]] = [1.0, 0.0, 0.0]
   ax.imshow(grid)
   ax.set_title("Agent Trajectory in Environment", fontsize=14, fontweight='daring')
   ax.set_xlabel("X Position")
   ax.set_ylabel("Y Position")
   ax.grid(True, alpha=0.3)
   ax = axes[1]
   ax.axis('off')
   ax.textual content(0.5, 0.95, "Neuro-Symbolic Architecture", ha='heart', fontsize=16, fontweight='daring', remodel=ax.transAxes)
   layers = [("SYMBOLIC LAYER", 0.75, "Planning • State Logic • Rules"), ("↕ INTEGRATION", 0.60, "Feature Extraction • Action Blending"), ("NEURAL LAYER", 0.45, "Perception • Policy Learning"), ("↕ EXECUTION", 0.30, "Action Refinement • Feedback"), ("ENVIRONMENT", 0.15, "State Transitions • Observations")]
   colours = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7']
   for i, (title, y, desc) in enumerate(layers):
       ax.add_patch(plt.Rectangle((0.1, y-0.05), 0.8, 0.08, facecolor=colours[i], alpha=0.7, remodel=ax.transAxes))
       ax.textual content(0.5, y, f"{title}n{desc}", ha='heart', va='heart', fontsize=10, fontweight='daring', remodel=ax.transAxes)
   plt.tight_layout()
   plt.savefig('neurosymbolic_agent.png', dpi=150, bbox_inches='tight')
   plt.present()
   print(f"n✅ Execution full! Trajectory size: {len(trajectory)} steps")

We visualize how the agent strikes by the surroundings and the way the structure is structured. We plot obstacles, objects, the aim, and the total trajectory so that we will clearly see the agent’s determination course of. As we render the structure layers, we perceive how the hybrid design flows from planning to notion to motion. Check out the FULL CODES here.

if __name__ == "__main__":
   print("=" * 70)
   print("NEURO-SYMBOLIC HYBRID AGENT TUTORIAL")
   print("Combining Classical AI Planning with Modern Neural Networks")
   print("=" * 70)
   print()
   agent = NeuroSymbolicAgent(grid_size=8)
   trajectory, actions = agent.execute_mission(verbose=True)
   visualize_execution(agent, trajectory)
   print("n" + "=" * 70)
   print("KEY INSIGHTS:")
   print("=" * 70)
   print("✦ Symbolic Layer: Provides interpretable, verifiable plans")
   print("✦ Neural Layer: Handles noisy notion & adapts to uncertainty")
   print("✦ Integration: Combines strengths of each paradigms")
   print("✦ Benefits: Explainability + Flexibility + Robustness")
   print("=" * 70)

We run the whole neuro-symbolic pipeline from planning to execution to visualization. We instantiate the agent, execute the mission, and show key insights to summarize the system’s habits. As we run this remaining block, we see the general hybrid structure in motion and admire how every element contributes to the result.

In conclusion, we observe how easily the symbolic and neural parts work collectively to produce a extra succesful and dependable agent. We admire how the symbolic planner provides us clear, verifiable steps, whereas the neural layer provides adaptability and perceptual grounding that pure logic can not provide. Through this hybrid method, we will construct brokers that cause, understand, and act in methods that are each clever and interpretable. We finish with a deeper understanding of how neuro-symbolic AI strikes us nearer to sensible, resilient agentic techniques.


Check out the FULL CODES here. Feel free to take a look at our GitHub Page for Tutorials, Codes and Notebooks. Also, be at liberty to comply with us on Twitter and don’t neglect to be part of our 100k+ ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.

The submit How to Build a Neuro-Symbolic Hybrid Agent that Combines Logical Planning with Neural Perception for Robust Autonomous Decision-Making appeared first on MarkTechPost.

Similar Posts