Loading content...
Not all recommendations can be learned from data alone. When recommending a mortgage product, you need to understand income requirements, credit scoring, and regulatory constraints. When suggesting a medical treatment, clinical guidelines and contraindications must be respected. When configuring a complex industrial system, engineering specifications constrain what's possible.
Knowledge-based recommendation systems encode explicit domain expertise—rules, constraints, ontologies, and reasoning mechanisms—to provide recommendations that are not just statistically likely, but technically correct and domain-appropriate.
By the end of this page, you will understand constraint-based and case-based recommendation paradigms, knowledge representation techniques (ontologies, rules), when to use knowledge-based approaches, and how to integrate domain knowledge with data-driven methods.
Content-based and collaborative filtering learn patterns from historical data. But some domains pose unique challenges where pure learning falls short:
High-Stakes Domains:
Infrequent Purchase Domains:
Complex Configuration:
Explainability Requirements:
| Characteristic | Data-Driven Suitable | Knowledge-Based Suitable |
|---|---|---|
| Purchase frequency | High (daily/weekly) | Low (yearly or less) |
| User history depth | Extensive | Sparse or none |
| Domain constraints | Few/soft | Many/hard |
| Explainability need | Nice-to-have | Essential |
| Stakes of recommendation | Low | High |
| Product complexity | Simple attributes | Complex configuration |
Constraint-based recommenders use explicit rules to filter and rank items based on user requirements and domain constraints.
Components:
User Requirements (REQ): Explicitly stated preferences
Product/Filter Constraints (FC): Domain rules on what's valid
Product Catalog (CAT): Items with their attributes
Recommendation: Items satisfying REQ ∩ FC
Formal Model:
$$\text{Recommendations} = {i \in \text{CAT} : \text{SAT}(\text{REQ}_u \cup \text{FC})}$$
Where SAT tests satisfiability of combined constraints.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
from typing import List, Dict, Any, Callable, Optionalfrom dataclasses import dataclassfrom enum import Enum class ConstraintType(Enum): HARD = "hard" # Must be satisfied SOFT = "soft" # Prefer if possible @dataclassclass Constraint: name: str condition: Callable[[Dict[str, Any], Dict[str, Any]], bool] type: ConstraintType = ConstraintType.HARD weight: float = 1.0 explanation: str = "" class ConstraintBasedRecommender: """ Constraint-based recommendation system. Filters items based on user requirements and domain constraints, returning only items that satisfy all hard constraints and ranking by soft constraint satisfaction. """ def __init__(self): self.filter_constraints: List[Constraint] = [] self.product_constraints: List[Constraint] = [] def add_filter_constraint(self, constraint: Constraint): """Add a constraint based on user requirements.""" self.filter_constraints.append(constraint) def add_product_constraint(self, constraint: Constraint): """Add a domain/product constraint.""" self.product_constraints.append(constraint) def check_constraint( self, constraint: Constraint, item: Dict[str, Any], requirements: Dict[str, Any] ) -> bool: """Check if constraint is satisfied.""" try: return constraint.condition(item, requirements) except Exception: return False # Constraint cannot be evaluated def recommend( self, items: List[Dict[str, Any]], requirements: Dict[str, Any], max_results: int = 10 ) -> List[Dict[str, Any]]: """Generate recommendations satisfying constraints.""" all_constraints = self.filter_constraints + self.product_constraints hard_constraints = [c for c in all_constraints if c.type == ConstraintType.HARD] soft_constraints = [c for c in all_constraints if c.type == ConstraintType.SOFT] valid_items = [] for item in items: # Check all hard constraints hard_satisfied = all( self.check_constraint(c, item, requirements) for c in hard_constraints ) if not hard_satisfied: continue # Score by soft constraint satisfaction soft_score = sum( c.weight for c in soft_constraints if self.check_constraint(c, item, requirements) ) valid_items.append({ 'item': item, 'score': soft_score, 'explanations': self._generate_explanations( item, requirements, all_constraints ) }) # Sort by soft constraint score valid_items.sort(key=lambda x: x['score'], reverse=True) return valid_items[:max_results] def _generate_explanations( self, item: Dict[str, Any], requirements: Dict[str, Any], constraints: List[Constraint] ) -> List[str]: """Generate explanations for satisfied constraints.""" explanations = [] for c in constraints: if c.explanation and self.check_constraint(c, item, requirements): explanations.append(c.explanation.format(**item, **requirements)) return explanations # Example: Real Estate Recommendationdef create_real_estate_recommender(): rec = ConstraintBasedRecommender() # User-based filter constraints rec.add_filter_constraint(Constraint( name="budget", condition=lambda item, req: item['price'] <= req.get('max_price', float('inf')), type=ConstraintType.HARD, explanation="Within your budget of ${max_price:, }" )) rec.add_filter_constraint(Constraint( name = "bedrooms", condition = lambda item, req: item['bedrooms'] >= req.get('min_bedrooms', 0), type = ConstraintType.HARD, explanation = "Has {bedrooms} bedrooms (you need {min_bedrooms}+)" )) rec.add_filter_constraint(Constraint( name = "location", condition = lambda item, req: item['city'] in req.get('preferred_cities', [item['city']]), type = ConstraintType.HARD )) # Soft preference constraints rec.add_filter_constraint(Constraint( name = "garage", condition = lambda item, req: item.get('has_garage', False) if req.get('wants_garage') else True, type = ConstraintType.SOFT, weight = 2.0, explanation = "Has a garage as preferred" )) rec.add_filter_constraint(Constraint( name = "pool", condition = lambda item, req: item.get('has_pool', False) if req.get('wants_pool') else True, type = ConstraintType.SOFT, weight = 1.5, explanation = "Has a pool as preferred" )) return recWhen no items satisfy all constraints, smart systems can: (1) Identify minimal constraint relaxation to find solutions, (2) Explain why requirements are unsatisfiable, (3) Suggest alternative requirements. This requires constraint solving techniques beyond simple filtering.
Case-based reasoning (CBR) recommends items similar to cases that previously satisfied similar requirements. It's based on the principle: similar problems have similar solutions.
CBR Cycle:
In Recommendation:
Similarity in CBR:
Domain-specific similarity functions: $$\text{sim}(\text{req}_1, \text{req}_2) = \sum_i w_i \cdot \text{sim}_i(\text{req}_1[i], \text{req}_2[i])$$
Where attribute similarities may use:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
import numpy as npfrom typing import List, Dict, Any, Tuplefrom dataclasses import dataclass @dataclass class Case: requirements: Dict[str, Any] recommended_item_id: str outcome: float # Success score(e.g., purchase, satisfaction) class CaseBasedRecommender: """ Case - based recommendation using past requirement-solution pairs. """ def __init__(self): self.case_base: List[Case] = [] self.attribute_weights: Dict[str, float] = {} self.items: Dict[str, Dict[str, Any]] = {} def add_case(self, case: Case): """Add a case to the case base.""" self.case_base.append(case) def set_attribute_weights(self, weights: Dict[str, float]): """Set importance weights for requirement attributes.""" self.attribute_weights = weights def _attribute_similarity( self, attr: str, val1: Any, val2: Any ) -> float: """Compute similarity for a single attribute.""" if val1 is None or val2 is None: return 0.5 # Neutral for missing values if isinstance(val1, (int, float)) and isinstance(val2, (int, float)): # Numeric: normalized distance max_val = max(abs(val1), abs(val2), 1) return 1.0 - abs(val1 - val2) / max_val elif isinstance(val1, str) and isinstance(val2, str): # Categorical: exact match or use ontology return 1.0 if val1 == val2 else 0.0 elif isinstance(val1, (list, set)) and isinstance(val2, (list, set)): # Set: Jaccard similarity set1, set2 = set(val1), set(val2) if not set1 and not set2: return 1.0 return len(set1 & set2) / len(set1 | set2) return 0.0 def case_similarity( self, req1: Dict[str, Any], req2: Dict[str, Any] ) -> float: """Compute weighted similarity between requirement profiles.""" all_attrs = set(req1.keys()) | set(req2.keys()) total_weight = 0.0 weighted_sim = 0.0 for attr in all_attrs: weight = self.attribute_weights.get(attr, 1.0) sim = self._attribute_similarity( attr, req1.get(attr), req2.get(attr) ) weighted_sim += weight * sim total_weight += weight return weighted_sim / total_weight if total_weight > 0 else 0.0 def retrieve( self, requirements: Dict[str, Any], k: int = 10 ) -> List[Tuple[Case, float]]: """Retrieve k most similar cases.""" similarities = [ (case, self.case_similarity(requirements, case.requirements)) for case in self.case_base ] # Sort by similarity descending similarities.sort(key = lambda x: x[1], reverse = True) return similarities[:k] def recommend( self, requirements: Dict[str, Any], n_items: int = 5, min_similarity: float = 0.5 ) -> List[Dict[str, Any]]: """Recommend items based on similar cases.""" similar_cases = self.retrieve(requirements, k = 50) # Aggregate recommendations from similar cases item_scores = {} for case, similarity in similar_cases: if similarity < min_similarity: continue item_id = case.recommended_item_id weight = similarity * case.outcome if item_id not in item_scores: item_scores[item_id] = { 'score': 0, 'count': 0 } item_scores[item_id]['score'] += weight item_scores[item_id]['count'] += 1 # Rank items recommendations = [] for item_id, data in item_scores.items(): avg_score = data['score'] / data['count'] recommendations.append({ 'item_id': item_id, 'item': self.items.get(item_id, {}), 'score': avg_score, 'support': data['count'] }) recommendations.sort(key = lambda x: x['score'], reverse = True) return recommendations[:n_items]Ontologies:
Formal representations of domain concepts and their relationships:
Ontologies enable:
Rule Systems:
Explicit IF-THEN rules encoding domain knowledge:
IF user.experience = 'beginner' AND user.budget < 1000
THEN recommend entry_level_cameras WITH explanation
'These are great starter cameras within your budget'
IF camera.sensor_size = 'full_frame' AND user.use_case = 'travel'
THEN add_concern 'Full-frame cameras are typically heavier'
Knowledge Graphs:
Graph structures linking entities with typed relationships:
Standard ontology languages include OWL (Web Ontology Language) and RDF (Resource Description Framework). Tools like Protégé help build ontologies. For recommendation, lighter-weight taxonomies or graph databases often suffice.
Knowledge-based systems often operate through dialogue, eliciting requirements through conversation:
System: What type of photography do you primarily do? User: Mostly landscape and travel. System: Do you prioritize image quality or portability? User: Image quality, but I can't carry more than 1kg. System: Based on your needs, I recommend mirrorless cameras with APS-C sensors. Here are three options that excel in landscape photography under 1kg...
Critique-Based Refinement:
Users refine recommendations through critiques:
The system incorporates critiques as additional constraints.
Modern LLM Integration:
Large language models enable natural language understanding of requirements and natural language generation of explanations, while structured knowledge ensures constraint satisfaction and factual accuracy.
The most powerful systems combine domain knowledge with learned patterns:
Knowledge-Guided Learning:
Learning Knowledge:
Practical Patterns:
The field is moving toward neuro-symbolic systems that combine the pattern recognition power of neural networks with the reasoning capabilities of symbolic AI. For recommendations, this means learning from data while respecting encoded domain expertise.
Knowledge Acquisition Bottleneck:
The biggest challenge is capturing domain knowledge:
Maintenance:
Knowledge bases require ongoing maintenance:
Scalability:
Testing:
| Challenge | Mitigation Strategy |
|---|---|
| Knowledge acquisition | Expert workshops, data mining, iterative refinement |
| Knowledge maintenance | Version control, change tracking, automated testing |
| Scalability | Pre-computation, indexing, constraint propagation |
| Brittleness | Soft constraints, graceful degradation, fallbacks |
| User requirement elicitation | Smart defaults, critique-based, conversational UI |
Module Complete:
You've now mastered content-based recommendation methods—from item representations and user profiles through TF-IDF and embeddings, hybrid approaches, and knowledge-based systems. These techniques form the foundation for building recommendation systems that truly understand what items offer and what users want.
Congratulations! You've completed Module 4: Content-Based Methods. You now have comprehensive knowledge of item representation, user profiling, text encoding (TF-IDF to embeddings), hybrid systems, and knowledge-based approaches. Next, explore Deep Learning for Recommendations to see how neural networks are transforming the field.