"""
Pattern 56: Offline Learning
Purpose: Learn from complete datasets in batch mode before deployment.
Trains on static datasets and evaluates comprehensively before production use.
"""
from typing import Dict, Any, List, Optional, Tuple
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
import random
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
class BatchStrategy(Enum):
"""Strategies for batch learning."""
FULL_BATCH = "full_batch" # Train on entire dataset at once
MINI_BATCH = "mini_batch" # Train on small batches with multiple epochs
STOCHASTIC = "stochastic" # Train on random samples
STRATIFIED = "stratified" # Ensure balanced class distribution
@dataclass
class TrainingExample:
"""A training example."""
id: str
features: Dict[str, Any]
label: str
weight: float = 1.0
fold: Optional[int] = None # For cross-validation
metadata: Dict[str, Any] = field(default_factory=dict)
@dataclass
class EpochMetrics:
"""Metrics for a training epoch."""
epoch: int
train_loss: float
train_accuracy: float
val_loss: float
val_accuracy: float
learning_rate: float
timestamp: datetime = field(default_factory=datetime.now)
@dataclass
class ModelCheckpoint:
"""Saved model checkpoint."""
epoch: int
accuracy: float
loss: float
model_state: Dict[str, Any]
timestamp: datetime = field(default_factory=datetime.now)
class OfflineLearningAgent:
"""
Offline Learning Agent - Learn from complete datasets in batch mode.
Trains on static datasets with multiple epochs, proper train/val/test
splits, and comprehensive evaluation before deployment.
"""
def __init__(self,
name: str,
task_description: str,
strategy: BatchStrategy = BatchStrategy.MINI_BATCH,
batch_size: int = 32,
epochs: int = 10,
learning_rate: float = 0.01):
self.name = name
self.task_description = task_description
self.strategy = strategy
self.batch_size = batch_size
self.epochs = epochs
self.learning_rate = learning_rate
self.llm = ChatOpenAI(model="gpt-4", temperature=0.3)
# Datasets
self.train_set: List[TrainingExample] = []
self.val_set: List[TrainingExample] = []
self.test_set: List[TrainingExample] = []
# Training state
self.model_patterns: Dict[str, Any] = {}
self.trained: bool = False
self.current_epoch: int = 0
# Training history
self.epoch_history: List[EpochMetrics] = []
self.checkpoints: List[ModelCheckpoint] = []
# Statistics
self.statistics: Dict[str, Any] = {
"total_examples": 0,
"training_time": 0.0,
"best_val_accuracy": 0.0,
"final_test_accuracy": 0.0
}
def prepare_data(self,
examples: List[TrainingExample],
train_ratio: float = 0.7,
val_ratio: float = 0.15):
"""Split data into train/val/test sets."""
# Shuffle data
shuffled = examples.copy()
random.shuffle(shuffled)
# Calculate split points
n = len(shuffled)
train_end = int(n * train_ratio)
val_end = int(n * (train_ratio + val_ratio))
# Split data
self.train_set = shuffled[:train_end]
self.val_set = shuffled[train_end:val_end]
self.test_set = shuffled[val_end:]
self.statistics["total_examples"] = n
print(f" {self.name}: Data prepared - "
f"Train: {len(self.train_set)}, Val: {len(self.val_set)}, Test: {len(self.test_set)}")
def train(self):
"""Train model on complete dataset."""
if not self.train_set:
print(f" {self.name}: No training data!")
return
print(f"\n {self.name}: Training with {self.strategy.value} strategy...")
start_time = datetime.now()
for epoch in range(self.epochs):
self.current_epoch = epoch + 1
# Train epoch
train_loss, train_acc = self._train_epoch()
# Validate
val_loss, val_acc = self._validate()
# Record metrics
metrics = EpochMetrics(
epoch=self.current_epoch,
train_loss=train_loss,
train_accuracy=train_acc,
val_loss=val_loss,
val_accuracy=val_acc,
learning_rate=self.learning_rate
)
self.epoch_history.append(metrics)
# Save checkpoint if best so far
if val_acc > self.statistics["best_val_accuracy"]:
self.statistics["best_val_accuracy"] = val_acc
self._save_checkpoint(val_acc, val_loss)
# Progress update
if (epoch + 1) % max(1, self.epochs // 5) == 0:
print(f" Epoch {self.current_epoch}/{self.epochs}: "
f"Train Acc: {train_acc:.2%}, Val Acc: {val_acc:.2%}")
self.trained = True
self.statistics["training_time"] = (datetime.now() - start_time).total_seconds()
print(f" Training complete! Best val accuracy: {self.statistics['best_val_accuracy']:.2%}")
def _train_epoch(self) -> Tuple[float, float]:
"""Train for one epoch."""
# Prepare batches based on strategy
if self.strategy == BatchStrategy.FULL_BATCH:
batches = [self.train_set]
elif self.strategy == BatchStrategy.MINI_BATCH:
batches = self._create_mini_batches(self.train_set, self.batch_size)
elif self.strategy == BatchStrategy.STOCHASTIC:
batches = [[ex] for ex in self.train_set] # One example per batch
else: # STRATIFIED
batches = self._create_stratified_batches(self.train_set, self.batch_size)
# Train on batches
total_loss = 0.0
correct = 0
total = 0
for batch in batches:
# Extract patterns from batch using LLM
patterns = self._extract_batch_patterns(batch)
# Update model patterns
for pattern_id, pattern_data in patterns.items():
if pattern_id in self.model_patterns:
# Average with existing pattern
self.model_patterns[pattern_id]["confidence"] = (
0.9 * self.model_patterns[pattern_id]["confidence"] +
0.1 * pattern_data["confidence"]
)
else:
self.model_patterns[pattern_id] = pattern_data
# Simulate training metrics
batch_loss = random.uniform(0.1, 0.5) * (1.0 - self.current_epoch / self.epochs)
total_loss += batch_loss * len(batch)
# Predict on batch
for ex in batch:
pred = self._predict_internal(ex)
if pred == ex.label:
correct += 1
total += 1
avg_loss = total_loss / total if total > 0 else 0.0
accuracy = correct / total if total > 0 else 0.0
return avg_loss, accuracy
def _validate(self) -> Tuple[float, float]:
"""Validate on validation set."""
if not self.val_set:
return 0.0, 0.0
correct = 0
total_loss = 0.0
for ex in self.val_set:
pred = self._predict_internal(ex)
if pred == ex.label:
correct += 1
# Simulate loss
total_loss += random.uniform(0.1, 0.4)
accuracy = correct / len(self.val_set)
avg_loss = total_loss / len(self.val_set)
return avg_loss, accuracy
def test(self) -> Dict[str, float]:
"""Test on test set."""
if not self.test_set:
print(f" {self.name}: No test data!")
return {"accuracy": 0.0}
if not self.trained:
print(f" {self.name}: Model not trained yet!")
return {"accuracy": 0.0}
print(f"\n {self.name}: Testing on {len(self.test_set)} examples...")
correct = 0
predictions = []
for ex in self.test_set:
pred = self.predict(ex)
predictions.append(pred)
if pred["predicted_label"] == ex.label:
correct += 1
accuracy = correct / len(self.test_set)
self.statistics["final_test_accuracy"] = accuracy
print(f" Test Accuracy: {accuracy:.2%}")
return {
"accuracy": accuracy,
"correct": correct,
"total": len(self.test_set),
"predictions": predictions
}
def _predict_internal(self, example: TrainingExample) -> str:
"""Internal prediction without full response."""
if not self.model_patterns:
return "unknown"
# Simple prediction based on learned patterns
# Use most confident pattern
if self.model_patterns:
best_pattern = max(self.model_patterns.values(),
key=lambda p: p.get("confidence", 0))
# Extract label from pattern description if possible
if "label" in best_pattern:
return best_pattern["label"]
return "unknown"
def predict(self, example: TrainingExample) -> Dict[str, Any]:
"""Make prediction on example."""
if not self.trained:
return {
"example_id": example.id,
"predicted_label": "untrained",
"confidence": 0.0
}
# Create prompt with learned patterns
patterns_text = ""
for pattern_id, pattern_data in list(self.model_patterns.items())[:5]:
patterns_text += f" - {pattern_data.get('description', pattern_id)}\n"
prompt = ChatPromptTemplate.from_messages([
("system", f"You are performing {self.task_description}. "
f"You have been trained on a dataset and learned these patterns:\n{patterns_text}"),
("human", f"Example: {example.features}\n\n"
f"Predict the label and confidence (0.0-1.0).\n"
f"Format: LABEL|CONFIDENCE")
])
chain = prompt | self.llm
try:
response = chain.invoke({})
content = response.content if isinstance(response.content, str) else str(response.content)
parts = content.strip().split('|')
predicted_label = parts[0].strip()
confidence = float(parts[1].strip()) if len(parts) > 1 else 0.7
except:
predicted_label = self._predict_internal(example)
confidence = 0.6
return {
"example_id": example.id,
"predicted_label": predicted_label,
"actual_label": example.label,
"confidence": confidence,
"correct": predicted_label == example.label
}
def _extract_batch_patterns(self, batch: List[TrainingExample]) -> Dict[str, Any]:
"""Extract patterns from a batch of examples."""
if not batch:
return {}
# Group by label
label_groups = {}
for ex in batch:
if ex.label not in label_groups:
label_groups[ex.label] = []
label_groups[ex.label].append(ex)
patterns = {}
for label, examples in label_groups.items():
pattern_id = f"label_{label}"
patterns[pattern_id] = {
"description": f"Pattern for label {label}",
"label": label,
"confidence": len(examples) / len(batch),
"support": len(examples)
}
return patterns
def _create_mini_batches(self,
examples: List[TrainingExample],
batch_size: int) -> List[List[TrainingExample]]:
"""Create mini-batches."""
batches = []
for i in range(0, len(examples), batch_size):
batches.append(examples[i:i + batch_size])
return batches
def _create_stratified_batches(self,
examples: List[TrainingExample],
batch_size: int) -> List[List[TrainingExample]]:
"""Create stratified batches with balanced class distribution."""
# Group by label
label_groups = {}
for ex in examples:
if ex.label not in label_groups:
label_groups[ex.label] = []
label_groups[ex.label].append(ex)
# Create balanced batches
batches = []
max_batches = len(examples) // batch_size
for batch_idx in range(max_batches):
batch = []
for label, examples in label_groups.items():
# Add examples from each class
start_idx = (batch_idx * batch_size) // len(label_groups)
end_idx = start_idx + batch_size // len(label_groups)
batch.extend(examples[start_idx:end_idx])
if batch:
batches.append(batch[:batch_size])
return batches
def _save_checkpoint(self, accuracy: float, loss: float):
"""Save model checkpoint."""
checkpoint = ModelCheckpoint(
epoch=self.current_epoch,
accuracy=accuracy,
loss=loss,
model_state={
"patterns": self.model_patterns.copy(),
"epoch": self.current_epoch
}
)
self.checkpoints.append(checkpoint)
def get_statistics(self) -> Dict[str, Any]:
"""Get comprehensive training statistics."""
return {
"name": self.name,
"strategy": self.strategy.value,
"trained": self.trained,
"epochs_completed": self.current_epoch,
"train_size": len(self.train_set),
"val_size": len(self.val_set),
"test_size": len(self.test_set),
"patterns_learned": len(self.model_patterns),
"checkpoints_saved": len(self.checkpoints),
**self.statistics
}
class OfflineLearningPattern:
"""
Offline Learning Pattern - Batch learning from complete datasets.
Coordinates multiple agents training on static datasets with
different batch strategies.
"""
def __init__(self):
self.agents: Dict[str, OfflineLearningAgent] = {}
self.dataset: List[TrainingExample] = []
def register_agent(self, agent: OfflineLearningAgent):
"""Register offline learning agent."""
self.agents[agent.name] = agent
print(f" Registered agent: {agent.name} ({agent.strategy.value})")
def load_dataset(self, examples: List[TrainingExample]):
"""Load dataset for all agents."""
self.dataset = examples
print(f" Loaded dataset: {len(examples)} examples")
# Prepare data for each agent
for agent in self.agents.values():
agent.prepare_data(examples)
def train_all(self):
"""Train all agents."""
print("\n Training all agents...")
for agent in self.agents.values():
agent.train()
def test_all(self) -> Dict[str, Dict[str, float]]:
"""Test all agents."""
print("\n Testing all agents...")
results = {}
for name, agent in self.agents.items():
results[name] = agent.test()
return results
def run(self, examples: List[TrainingExample]) -> Dict[str, Any]:
"""Run offline learning pipeline."""
print("=== Offline Learning Pattern Demo ===\n")
# Load dataset
print("1. Loading dataset...")
self.load_dataset(examples)
# Train all agents
print("\n2. Training agents...")
self.train_all()
# Test all agents
print("\n3. Testing agents...")
test_results = self.test_all()
# Get statistics
stats = {name: agent.get_statistics()
for name, agent in self.agents.items()}
return {
"pattern": "OfflineLearning",
"agents": len(self.agents),
"dataset_size": len(examples),
"test_results": test_results,
"statistics": stats
}
# Example usage
if __name__ == "__main__":
print("=== Offline Learning Pattern Demo ===\n")
# Create pattern
pattern = OfflineLearningPattern()
# Create agents with different strategies
print("1. Creating agents...")
full_batch_agent = OfflineLearningAgent(
name="FullBatchAgent",
task_description="classify sentiment as positive or negative",
strategy=BatchStrategy.FULL_BATCH,
epochs=5
)
pattern.register_agent(full_batch_agent)
mini_batch_agent = OfflineLearningAgent(
name="MiniBatchAgent",
task_description="classify sentiment as positive or negative",
strategy=BatchStrategy.MINI_BATCH,
batch_size=16,
epochs=10
)
pattern.register_agent(mini_batch_agent)
# Generate dataset
print("\n2. Generating dataset...")
dataset = []
# Positive examples
for i in range(80):
dataset.append(TrainingExample(
id=f"pos_{i}",
features={"text": f"positive_example_{i}", "score": random.uniform(0.6, 1.0)},
label="positive"
))
# Negative examples
for i in range(80):
dataset.append(TrainingExample(
id=f"neg_{i}",
features={"text": f"negative_example_{i}", "score": random.uniform(0.0, 0.4)},
label="negative"
))
# Run offline learning
print("\n3. Running offline learning...")
summary = pattern.run(dataset)
print(f"\n4. Results:")
print(f" Agents: {summary['agents']}")
print(f" Dataset Size: {summary['dataset_size']}")
print(f"\n5. Test Results:")
for agent_name, results in summary['test_results'].items():
print(f"\n {agent_name}:")
print(f" Accuracy: {results['accuracy']:.2%}")
print(f" Correct: {results['correct']}/{results['total']}")
print(f"\n6. Training Statistics:")
for agent_name, stats in summary['statistics'].items():
print(f"\n {agent_name}:")
for key, value in stats.items():
if key != "name":
if isinstance(value, float):
print(f" {key}: {value:.4f}")
else:
print(f" {key}: {value}")
print("\nā Offline learning completed comprehensive training and evaluation!")
Agentic Architecture Pattern: Offline Learning
Pattern 56: 56 Offline Learning – Line-by-Line Explanation
Overview
Pattern 56: Offline Learning
Purpose: Learn from complete datasets in batch mode before deployment. Trains on static datasets and evaluates comprehensively before production use.
Code Structure Analysis
File Information
- Pattern Number: 56
- Pattern Name: 56 Offline Learning
- File Path:
patterns\learning_adaptation\56_offline_learning.py - Total Lines: 562
Detailed Code Explanation
Module Docstring
"""
Pattern 56: Offline Learning
Purpose: Learn from complete datasets in batch mode before deployment.
Trains on static datasets and evaluates comprehensively before production use.
"""
Explanation: This module docstring describes the purpose and key features of the 56 Offline Learning pattern.
Imports Section
from typing import Dict, Any, List, Optional, Tuple
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
import random
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI # First 20 imports shown
Explanation: The module imports necessary dependencies:
– from typing import Dict, Any, List, Optional, Tuple: Type hints for better code documentation
– from dataclasses import dataclass, field: Data class decorators for clean data structures
– from datetime import datetime: Date and time utilities
– from enum import Enum: Enumeration support for fixed sets of values
– from langchain_core.prompts import ChatPromptTemplate: LangChain framework integration
– from langchain_openai import ChatOpenAI: LangChain framework integration
Enumerations
BatchStrategy (Line 17)
class BatchStrategy(Enum):
FULL_BATCH
MINI_BATCH
STOCHASTIC
STRATIFIED
Purpose: Strategies for batch learning.
Values:
– FULL_BATCH: Represents full batch
– MINI_BATCH: Represents mini batch
– STOCHASTIC: Represents stochastic
– STRATIFIED: Represents stratified
Classes
BatchStrategy (Line 17)
Description: Strategies for batch learning.
TrainingExample (Line 26)
Description: A training example.
EpochMetrics (Line 37)
Description: Metrics for a training epoch.
ModelCheckpoint (Line 49)
Description: Saved model checkpoint.
OfflineLearningAgent (Line 58)
Description: Offline Learning Agent – Learn from complete datasets in batch mode.
Trains on static datasets with multiple epochs, proper train/val/test splits, and comprehensive evaluation before deployment.
Methods:
__init__ (Line 66)
def __init__(self, name, task_description, strategy, batch_size, epochs, learning_rate):
"""No method docstring."""
Purpose: No method docstring.
Parameters:
– self: Instance reference
– name: Identifier or label
– task_description: Parameter for task_description
– strategy: Parameter for strategy
– batch_size: Parameter for batch_size
– epochs: Parameter for epochs
– learning_rate: Parameter for learning_rate
prepare_data (Line 103)
def prepare_data(self, examples, train_ratio, val_ratio):
"""Split data into train/val/test sets."""
Purpose: Split data into train/val/test sets.
Parameters:
– self: Instance reference
– examples: Parameter for examples
– train_ratio: Parameter for train_ratio
– val_ratio: Parameter for val_ratio
train (Line 127)
def train(self):
"""Train model on complete dataset."""
Purpose: Train model on complete dataset.
Parameters:
– self: Instance reference
_train_epoch (Line 171)
def _train_epoch(self):
"""Train for one epoch."""
Purpose: Train for one epoch.
Parameters:
– self: Instance reference
_validate (Line 219)
def _validate(self):
"""Validate on validation set."""
Purpose: Validate on validation set.
Parameters:
– self: Instance reference
test (Line 240)
def test(self):
"""Test on test set."""
Purpose: Test on test set.
Parameters:
– self: Instance reference
_predict_internal (Line 273)
def _predict_internal(self, example):
"""Internal prediction without full response."""
Purpose: Internal prediction without full response.
Parameters:
– self: Instance reference
– example: Parameter for example
predict (Line 289)
def predict(self, example):
"""Make prediction on example."""
Purpose: Make prediction on example.
Parameters:
– self: Instance reference
– example: Parameter for example
_extract_batch_patterns (Line 332)
def _extract_batch_patterns(self, batch):
"""Extract patterns from a batch of examples."""
Purpose: Extract patterns from a batch of examples.
Parameters:
– self: Instance reference
– batch: Parameter for batch
_create_mini_batches (Line 356)
def _create_mini_batches(self, examples, batch_size):
"""Create mini-batches."""
Purpose: Create mini-batches.
Parameters:
– self: Instance reference
– examples: Parameter for examples
– batch_size: Parameter for batch_size
_create_stratified_batches (Line 365)
def _create_stratified_batches(self, examples, batch_size):
"""Create stratified batches with balanced class distribution."""
Purpose: Create stratified batches with balanced class distribution.
Parameters:
– self: Instance reference
– examples: Parameter for examples
– batch_size: Parameter for batch_size
_save_checkpoint (Line 393)
def _save_checkpoint(self, accuracy, loss):
"""Save model checkpoint."""
Purpose: Save model checkpoint.
Parameters:
– self: Instance reference
– accuracy: Parameter for accuracy
– loss: Parameter for loss
get_statistics (Line 406)
def get_statistics(self):
"""Get comprehensive training statistics."""
Purpose: Get comprehensive training statistics.
Parameters:
– self: Instance reference
OfflineLearningPattern (Line 422)
Description: Offline Learning Pattern – Batch learning from complete datasets.
Coordinates multiple agents training on static datasets with different batch strategies.
Methods:
__init__ (Line 430)
def __init__(self):
"""No method docstring."""
Purpose: No method docstring.
Parameters:
– self: Instance reference
register_agent (Line 434)
def register_agent(self, agent):
"""Register offline learning agent."""
Purpose: Register offline learning agent.
Parameters:
– self: Instance reference
– agent: Parameter for agent
load_dataset (Line 439)
def load_dataset(self, examples):
"""Load dataset for all agents."""
Purpose: Load dataset for all agents.
Parameters:
– self: Instance reference
– examples: Parameter for examples
train_all (Line 448)
def train_all(self):
"""Train all agents."""
Purpose: Train all agents.
Parameters:
– self: Instance reference
test_all (Line 454)
def test_all(self):
"""Test all agents."""
Purpose: Test all agents.
Parameters:
– self: Instance reference
run (Line 462)
def run(self, examples):
"""Run offline learning pipeline."""
Purpose: Run offline learning pipeline.
Parameters:
– self: Instance reference
– examples: Parameter for examples
Design Patterns Used
- Strategy Pattern: Different execution strategies for various scenarios
- State Pattern: Managing different states in the workflow
- Chain of Responsibility: Sequential processing through workflow nodes
- Observer Pattern: Tracking and monitoring pattern execution
- Factory Pattern: Creating instances with proper configuration
Usage Example
# Initialize the pattern
pattern = BatchStrategy(name="example", model_name="gpt-4")
# Configure and execute
result = pattern.execute(data={"key": "value"})
# Check results
print(result)
Key Concepts
- LLM Integration: Uses LangChain/OpenAI for intelligent decision-making
- Workflow Orchestration: LangGraph manages execution flow through nodes
- State Management: TypedDict defines workflow state structure
- Error Handling: Comprehensive error handling and fallback mechanisms
- Extensibility: Designed for easy customization and extension
Extension Points
This pattern can be extended by: 1. Adding custom workflow nodes 2. Implementing additional validation logic 3. Integrating with external services 4. Customizing LLM prompts and responses 5. Adding monitoring and logging capabilities
Leave a Reply