The 4 types of rule-based agents are similar but have differing advantages relative to certain problems. Let’s find out what these are.
Not long ago I outlined four ways to build AI agents. Rule-based agents were the first and most rudimentary agent. Being rudimentary does not mean such agents are not useful or without value. On the contrary, these agents are common solutions to a variety of problems. Let’s see how to get a grip on rule-based agents using a compare-contrast analysis of reasoning capabilities and code examples.
Within the realm of rule-based agents, there are variations and different types that can be considered based on their characteristics and rule structures. Here are a few types of rule-based agents. Note: I say few because there are more types as well as hybrid combinations available. I’m highlighting what I have observed to be the most common or most interesting.
1. Condition-Action Rules
This is the most common type of rule-based agent. Rules consist of conditions (i.e., if statements) and corresponding actions (then statements). The agent evaluates the conditions and performs the associated action when the conditions are satisfied. Each rule typically operates independently and the agent selects the first rule whose conditions are met.
Here’s an example of a Condition-Action Rules-based agent. This example emphasizes:
Inference and decision-making: The ability to select the most appropriate rule(s) whose conditions are satisfied and execute the associated actions.
Adaptability and learning: The ability to update or modify rules based on feedback, new data, or learning from experience.
class LumberjackAgent:
def __init__(self):
self.rules = [
{'condition': 'tree_nearby', 'action': 'chop_tree'},
{'condition': 'no_tree_nearby', 'action': 'explore'},
{'condition': 'inventory_full', 'action': 'return_to_base'}
]
self.inventory = []
def make_decision(self, game_state):
for rule in self.rules:
if self.evaluate_condition(rule['condition'], game_state):
action = rule['action']
self.perform_action(action)
break
def evaluate_condition(self, condition, game_state):
if condition == 'tree_nearby':
return game_state['tree_nearby']
elif condition == 'no_tree_nearby':
return not game_state['tree_nearby']
elif condition == 'inventory_full':
return len(self.inventory) >= game_state['max_inventory']
else:
return False
def perform_action(self, action):
if action == 'chop_tree':
self.chop_tree()
elif action == 'explore':
self.explore()
elif action == 'return_to_base':
self.return_to_base()
def chop_tree(self):
print("Chopping down the tree!")
# Code for chopping down the tree goes here
# Update inventory, score, or other relevant game state
def explore(self):
print("Exploring the area!")
# Code for exploring the area goes here
# Move to a new location, search for trees, etc.
def return_to_base(self):
print("Returning to base!")
# Code for returning to the base goes here
# Reset or perform necessary actions when inventory is full
# Simulation/Game State
game_state = {
'tree_nearby': True,
'max_inventory': 10
}
# Lumberjack Agent
lumberjack_agent = LumberjackAgent()
lumberjack_agent.make_decision(game_state)
In this example, the LumberjackAgent class represents the game agent who is a lumberjack. The agent’s behavior is governed by a set of rules defined in the rules list. Each rule has a condition and an associated action.
The make_decision method takes the current game state as input and evaluates each rule’s condition. If a condition is satisfied, the associated action is performed by calling the perform_action method.
The evaluate_condition method compares the condition with the game state and returns a Boolean value. The perform_action method executes the appropriate action based on the input.
The agent provides basic actions such as chopping down a tree, exploring the area, or returning to the base, which can be customized with the relevant game logic.
Here’s an example usage of the LumberjackAgent:
game_state = {
'tree_nearby': True,
'max_inventory': 10
}
lumberjack_agent = LumberjackAgent()
lumberjack_agent.make_decision(game_state)
This example showcases how a Condition-Action Rule based agent can make decisions based on the current game state and perform actions accordingly. The agent evaluates the conditions specified in the rules and selects the action associated with the first satisfied condition. Such rules are rather inflexible however. If we need flexibility, we need a different kind of rule-based agent.
2. Production Systems
Production systems are a more complex type of rule-based agent. These agents incorporate of a set of rules organized as production rules. Think of production in the computer science context free grammar sense.
Anyway, each rule consists of a condition part (called the left-hand side or LHS) and an action part (called the right-hand side or RHS). Production systems often employ an inference engine to match conditions against the agent’s current state and execute rules whose conditions are satisfied.
Production systems emphasize:
Rule Matching: Production systems excel at matching rules against the current state of the system or problem domain.
Conflict Resolution: In situations where multiple rules are eligible for execution due to overlapping conditions, production systems employ conflict resolution strategies to determine which rule(s) should take precedence.
Rule Ordering and Control Flow: Rule ordering and control flow mechanisms enable the system to enforce specific sequences of rule firing and perform actions in a desired order.
class LumberjackAgent:
def __init__(self):
self.production_rules = [
{'condition': ['tree_nearby'], 'action': 'chop_tree'},
{'condition': ['no_tree_nearby'], 'action': 'explore'},
{'condition': ['inventory_full'], 'action': 'return_to_base'}
]
self.inventory = []
def make_decision(self, game_state):
for rule in self.production_rules:
if self.evaluate_conditions(rule['condition'], game_state):
action = rule['action']
self.perform_action(action)
break
def evaluate_conditions(self, conditions, game_state):
for condition in conditions:
if condition not in game_state or not game_state[condition]:
return False
return True
def perform_action(self, action):
if action == 'chop_tree':
self.chop_tree()
elif action == 'explore':
self.explore()
elif action == 'return_to_base':
self.return_to_base()
def chop_tree(self):
print("Chopping down the tree!")
# Code for chopping down the tree goes here
# Update inventory, score, or other relevant game state
def explore(self):
print("Exploring the area!")
# Code for exploring the area goes here
# Move to a new location, search for trees, etc.
def return_to_base(self):
print("Returning to base!")
# Code for returning to the base goes here
# Reset or perform necessary actions when inventory is full
# Simulation/Game State
game_state = {
'tree_nearby': True,
'max_inventory': 10
}
# Lumberjack Agent
lumberjack_agent = LumberjackAgent()
lumberjack_agent.make_decision(game_state)
In this example, the LumberjackAgent class represents the lumberjack agent implemented as a Production System. The agent’s behavior is governed by a set of production rules defined in the production_rules list. Each production rule has a condition and an associated action.
The make_decision method takes the current game state as input and evaluates each production rule’s conditions. If the conditions of a rule are satisfied, the associated action is performed by calling the perform_action method.
The evaluate_conditions method checks whether all the conditions in a rule are met based on the game state. If any condition is not met, the evaluation returns False. Otherwise, it returns True.
The perform_action method executes the appropriate action based on the input action string.
Here’s an example usage of the LumberjackAgent:
game_state = {
'tree_nearby': True,
'max_inventory': 10
}
lumberjack_agent = LumberjackAgent()
lumberjack_agent.make_decision(game_state)
In this example, the agent receives the game state with a nearby tree, and it performs the action of chopping down the tree based on the production rule that matches the condition.
The Production System for the lumberjack agent is actually a specific implementation of a Condition-Action Rule based agent. The main difference lies in the representation and organization of the rules.
Both Condition-Action and Production System agents share the same limitations of course. Chiefly, neither can robustly handle uncertainty in their environments. Simply put, if there is no rule to handle an input, there is no action. The Fuzzy Rule-Based agent seeks to address this limitation.
3. Fuzzy Rule-Based Systems
Fuzzy rule-based systems utilize fuzzy logic to handle imprecise or uncertain information. Fuzzy rule-based systems use fuzzy sets and linguistic variables to capture and reason with degrees of truth. The rules in these systems contain fuzzy membership functions and employ fuzzy inference mechanisms to make decisions.
The reasoning emphasis is on:
Fuzzy Inference: Fuzzy logic allows for gradual truth values between zero and one. Fuzzy inference mechanisms, such as fuzzy matching and fuzzy reasoning, are used to derive fuzzy conclusions based on fuzzy rules and fuzzy sets.
Linguistic Variables and Fuzzy Sets: Fuzzy Rule-Based Systems use linguistic variables process and express qualitative concepts (e.g., temperature: cold, warm, hot), while fuzzy sets represent the membership function that assigns degrees of membership to each linguistic term.
Fuzzy Rule Aggregation: Fuzzy Rule-Based Systems employ rule aggregation methods to combine the outputs of multiple rules. Aggregation methods, such as max-min or weighted averaging, aggregate the fuzzy conclusions of applicable rules to obtain an overall fuzzy output.
Defuzzification: After the fuzzy rule aggregation, defuzzification is performed to obtain a crisp output or decision. Defuzzification methods, such as centroid or height-based methods, convert the fuzzy output into a crisp value based on the aggregated fuzzy set.
Again, Fuzzy Rule-Based Systems can handle uncertainty and imprecision inherent in real-world problems. They model and reason with vague or uncertain information which enables decision-making in situations where precise data may be unavailable or difficult to obtain. Here’s an example:
import numpy as np
import skfuzzy as fuzz
from skfuzzy import control as ctrl
class FuzzyLumberjackAgent:
def __init__(self):
# Fuzzy input variables
self.temperature = ctrl.Antecedent(np.arange(0, 101, 1), 'temperature')
self.humidity = ctrl.Antecedent(np.arange(0, 101, 1), 'humidity')
# Fuzzy output variable
self.action = ctrl.Consequent(np.arange(0, 11, 1), 'action')
# Fuzzy membership functions
self.temperature['cold'] = fuzz.trimf(self.temperature.universe, [0, 0, 50])
self.temperature['moderate'] = fuzz.trimf(self.temperature.universe, [0, 50, 100])
self.temperature['hot'] = fuzz.trimf(self.temperature.universe, [50, 100, 100])
self.humidity['low'] = fuzz.trimf(self.humidity.universe, [0, 0, 50])
self.humidity['moderate'] = fuzz.trimf(self.humidity.universe, [0, 50, 100])
self.humidity['high'] = fuzz.trimf(self.humidity.universe, [50, 100, 100])
self.action['explore'] = fuzz.trimf(self.action.universe, [0, 0, 5])
self.action['chop_tree'] = fuzz.trimf(self.action.universe, [0, 5, 10])
# Fuzzy rules
self.rules = [
ctrl.Rule(self.temperature['cold'] | self.humidity['low'], self.action['explore']),
ctrl.Rule(self.temperature['moderate'], self.action['chop_tree']),
ctrl.Rule(self.temperature['hot'] | self.humidity['high'], self.action['explore'])
]
# Fuzzy control system
self.lumberjack_ctrl = ctrl.ControlSystem(self.rules)
self.lumberjack_sim = ctrl.ControlSystemSimulation(self.lumberjack_ctrl)
def make_decision(self, temperature_input, humidity_input):
self.lumberjack_sim.input['temperature'] = temperature_input
self.lumberjack_sim.input['humidity'] = humidity_input
self.lumberjack_sim.compute()
action_output = self.lumberjack_sim.output['action']
return action_output
# Simulation
lumberjack_agent = FuzzyLumberjackAgent()
temperature_input = 70
humidity_input = 30
action_output = lumberjack_agent.make_decision(temperature_input, humidity_input)
print("Action output:", action_output)
In this example, the FuzzyLumberjackAgent class represents the lumberjack agent implemented as a Fuzzy Rule-Based System. The agent takes fuzzy inputs for temperature and humidity and provides a fuzzy output for the action to be taken.
The fuzzy inputs (temperature and humidity) and fuzzy output (action) are defined using the Antecedent and Consequent classes from the skfuzzy library. Fuzzy membership functions are defined for each input and output variable using fuzz.trimf, representing linguistic terms such as ‘cold’, ‘moderate’, ‘hot’, ‘low’, ‘moderate’, ‘high’, ‘explore’, and ‘chop_tree’.
The fuzzy rules are defined using ctrl.Rule and represent the linguistic relationships between the inputsand output. In this example, the rules specify conditions and actions based on the temperature and humidity inputs, such as “If temperature is cold or humidity is low, then the action is explore.”
The fuzzy control system is created using ctrl.ControlSystem, and a simulation is initialized with ctrl.ControlSystemSimulation. The make_decision method takes the temperature and humidity inputs, sets them in the simulation, computes the output action, and returns the fuzzy action output.
Here’s an example usage of the FuzzyLumberjackAgent:
lumberjack_agent = FuzzyLumberjackAgent()
temperature_input = 70
humidity_input = 30
action_output = lumberjack_agent.make_decision(temperature_input, humidity_input)
print("Action output:", action_output)
In this example, the agent receives the temperature input of 70 and humidity input of 30. Based on the fuzzy rules defined, the agent computes the fuzzy output action as 7.5, which can be interpreted as a moderate level of “chop_tree” action.
In Fuzzy Rule-Based Systems, the conditions and conclusions of the rules are expressed using fuzzy sets and linguistic variables. The conditions evaluate the degree of membership of input variables to determine rule applicability, while the conclusions specify the fuzzy output or action to be taken based on the degree of satisfaction of the rule’s conditions. This is in contrast to the binary input-action structure of the prior two rule-based agents.
A drawback is the overhead of fuzzy decision-making. Each interaction passing through the Fuzzy Rule-Based System is new, thus incurs the same processing overhead. Further, these agents can become stochastic in certain circumstances which might be undesirable. Fortunately, we have one type of rule-based agent left to discuss that addresses these situations.
4. Case-Based Reasoning Systems
Case-based reasoning agents store and reason based on a collection of previously solved cases. The rules in this type of agent are typically in the form of matching a current situation or problem to similar cases in the database. The agent then applies the corresponding solution or action. This is achieved through reasoning capabilities such as:
Case Retrieval: CBR systems excel at retrieving relevant cases from a case library or knowledge base. Given a new problem or situation, CBR systems search for similar cases that match the current problem’s features or context. The retrieval process involves matching the problem’s attributes against the stored cases, using techniques like similarity metrics or pattern matching.
Case Adaptation: Once a relevant case is retrieved, CBR systems adapt or modify the action from the retrieved case to fit the current problem. Adaptation mechanisms analyze the differences between the retrieved case and the current problem and apply appropriate modifications to make the solution more suitable or effective.
Similarity Assessment: Similarity assessment techniques consider various features of the problem and solution. These include numeric values, categorical variables, or even complex structures. This allows the system to identify cases that are most relevant to the current problem. Think Levenshtein Distance.
Explanation: Critically, CBR systems provide explanations for their decision-making process by linking the current problem to relevant retrieved cases. Explanations help users understand why a particular action was chosen.
These desired reasoning capabilities make Case-Based Reasoning (CBR) systems valuable in problem domains where past experiences and solutions can guide decision-making. Let’s see how our lumberjack might benefit from this type of agent architecture.
class Case:
def __init__(self, temperature, humidity, action):
self.temperature = temperature
self.humidity = humidity
self.action = action
class CBRlumberjackAgent:
def __init__(self):
self.case_library = [
Case(65, 40, 'chop_tree'),
Case(70, 35, 'chop_tree'),
Case(80, 50, 'explore'),
Case(60, 45, 'chop_tree')
]
def retrieve_similar_cases(self, temperature, humidity):
similar_cases = []
for case in self.case_library:
if self.similarity_measure(temperature, humidity, case.temperature, case.humidity):
similar_cases.append(case)
return similar_cases
def similarity_measure(self, temperature1, humidity1, temperature2, humidity2):
# Implement your similarity measure here
# This can be based on distance metrics, fuzzy similarity, or any other relevant measure
# Return True if similar, False otherwise
return abs(temperature1 - temperature2) < 5 and abs(humidity1 - humidity2) < 5
def adapt_solution(self, similar_cases):
if similar_cases:
# Select a similar case (you can use more sophisticated selection strategies)
selected_case = similar_cases[0]
return selected_case.action
else:
return 'explore' # Default action if no similar case is found
def make_decision(self, temperature, humidity):
similar_cases = self.retrieve_similar_cases(temperature, humidity)
action = self.adapt_solution(similar_cases)
return action
# Simulation
lumberjack_agent = CBRlumberjackAgent()
temperature = 67
humidity = 42
action = lumberjack_agent.make_decision(temperature, humidity)
print("Action:", action)
In this example, the Case class represents a case in the case library. Each case has attributes such as temperature, humidity, and action. The CBRlumberjackAgent class represents the lumberjack agent implemented as a Case-Based Reasoning (CBR) system.
The case library consists of instances of the Case class, representing past cases with associated temperature, humidity, and action. The retrieve_similar_cases method searches the case library for similar cases based on a similarity measure. In this example, the similarity measure is based on a simple difference threshold for temperature and humidity.
The adapt_solution method adapts the solution or action from the retrieved similar cases. In this example, the simplest strategy is used, where the action from the first similar case is selected as the adapted solution.
The make_decision method takes the current temperature and humidity as input. It retrieves similar cases, adapts the solution, and returns the adapted action for the current problem.
Here’s an example usage of the CBRlumberjackAgent:
lumberjack_agent = CBRlumberjackAgent()
temperature = 67
humidity = 42
action = lumberjack_agent.make_decision(temperature, humidity)
print("Action:", action)
In this example, the agent receives a temperature of 67 and humidity of 42. It retrieves similar cases from the case library, finds a similar case with temperature 65 and humidity 40, and adapts the action from that case, resulting in the decision to “chop_tree”.
Overall, the Case-Based Reasoning agent emphasizes the retrieval and adaptation of past cases to solve current problems. It leverages the case library to retrieve similar cases, adapt their solutions, and provide customized recommendations or actions. The focus on reusing past experiences and adapting them to the current context differentiates CBR systems from other types of agents that rely on predefined rules, utility functions, or search algorithms.
There we have it, the common types of rule-based agents. Remember, each type is capable of solving problems. Condition-Action Rule-Based and Production Rule agents rely on explicit if-then rules to make decisions or perform actions based on the current state or inputs. Fuzzy Rule-Based Systems employ fuzzy logic to handle imprecise or uncertain information, using fuzzy conditions and conclusions to reason and make decisions. Case-Based Reasoning Systems retrieve and adapt solutions from past cases stored in a case library to solve current problems, leveraging similarities between cases.
However, there are some problems where a different type of agent is necessary. We’ll explore search agents in the near future. Until then, happy coding and be kind to your agents!
How to Get a Grip on Rule-based Agents was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.