Abdallah4z commited on
Commit
b1410eb
Β·
1 Parent(s): 60c3ccb

Refactor explainer methods and enhance evaluation template with detailed CF approach explanations

Browse files
app.py CHANGED
@@ -41,11 +41,6 @@ for _, u in users.iterrows():
41
  CATEGORIES = sorted(products["category"].unique().tolist())
42
  BRANDS = sorted(products["brand"].unique().tolist())
43
 
44
- for _ in cf_train.train_svd_generator():
45
- pass
46
- for _ in cf_train.compute_slope_one_dev_generator():
47
- pass
48
-
49
  APPROACHES = {
50
  "cf": {
51
  "label": "Collaborative Filtering",
 
41
  CATEGORIES = sorted(products["category"].unique().tolist())
42
  BRANDS = sorted(products["brand"].unique().tolist())
43
 
 
 
 
 
 
44
  APPROACHES = {
45
  "cf": {
46
  "label": "Collaborative Filtering",
recommender/__pycache__/explainer.cpython-310.pyc CHANGED
Binary files a/recommender/__pycache__/explainer.cpython-310.pyc and b/recommender/__pycache__/explainer.cpython-310.pyc differ
 
recommender/explainer.py CHANGED
@@ -15,45 +15,78 @@ class Explainer:
15
  def _get_user(self, user_id):
16
  return self._user_cache.get(user_id, {})
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  def explain_cf(self, method, user_id, product_id, details=None):
19
  product = self._get_product(product_id)
20
- product_name = product.get("name", f"Item #{product_id}")
 
 
 
 
21
  details = details or {}
22
-
23
  sim = min(details.get('sim_score', 0), 1.0)
 
 
24
  templates = {
25
- "user_based": f"Recommended because {details.get('count', 'several')} similar users (avg {sim*100:.0f}% match) also purchased {product_name}",
26
- "item_based": f"Recommended because it is {sim*100:.0f}% similar to items you liked",
27
- "svd": f"Recommended based on your taste profile β€” {product_name} matches your latent preferences",
28
- "knn": f"Recommended because {details.get('count', 'several')} nearest neighbors with similar taste purchased this",
29
- "slope_one": f"Recommended based on average preference patterns for {product_name}",
30
  }
31
  return templates.get(method, f"Recommended based on collaborative filtering")
32
 
33
  def explain_content(self, method, user_id, product_id, details=None):
34
  product = self._get_product(product_id)
35
- product_name = product.get("name", f"Item #{product_id}")
36
- category = product.get("category", "")
 
 
 
37
  details = details or {}
38
-
39
  score = min(details.get('score', 0), 1.0)
 
 
40
  templates = {
41
- "tfidf": f"Recommended because {product_name} matches items you've liked before (content similarity: {score*100:.0f}%)",
42
- "feature_match": f"Recommended because it matches your preferred category: {category}",
43
  }
44
  return templates.get(method, f"Recommended based on item features")
45
 
46
  def explain_knowledge(self, method, user_id, product_id, details=None):
47
  product = self._get_product(product_id)
48
- product_name = product.get("name", f"Item #{product_id}")
 
 
49
  brand = product.get("brand", "")
50
  price = product.get("price", 0)
 
51
  details = details or {}
 
52
 
53
  templates = {
54
- "constraint": f"Recommended because {product_name} meets your criteria: budget ${details.get('budget_max', 'N/A')}, brand: {brand}",
55
- "rule": f"Recommended because customers who bought {details.get('trigger_item', 'similar items')} also purchased {product_name}",
56
- "utility": f"Recommended with {details.get('score', 0)*100:.1f}% utility match based on your preference weights",
57
  }
58
  return templates.get(method, f"Recommended based on your requirements")
59
 
 
15
  def _get_user(self, user_id):
16
  return self._user_cache.get(user_id, {})
17
 
18
+ def _fmt_score(self, score):
19
+ if score >= 0.9:
20
+ return "Excellent match"
21
+ elif score >= 0.7:
22
+ return "Strong match"
23
+ elif score >= 0.5:
24
+ return "Good match"
25
+ elif score >= 0.3:
26
+ return "Moderate match"
27
+ else:
28
+ return "Partial match"
29
+
30
+ def _pref_list(self, user, key):
31
+ raw = user.get(key, "")
32
+ if raw is None or isinstance(raw, float):
33
+ return []
34
+ if isinstance(raw, str):
35
+ return [p.strip() for p in raw.split(",") if p.strip()][:2]
36
+ return list(raw)[:2] if raw else []
37
+
38
  def explain_cf(self, method, user_id, product_id, details=None):
39
  product = self._get_product(product_id)
40
+ user = self._get_user(user_id)
41
+ pname = product.get("name", f"Item #{product_id}")
42
+ cat = product.get("category", "")
43
+ brand = product.get("brand", "")
44
+ price = product.get("price", 0)
45
  details = details or {}
 
46
  sim = min(details.get('sim_score', 0), 1.0)
47
+ pref_cats = self._pref_list(user, "preferred_categories")
48
+
49
  templates = {
50
+ "user_based": f"Users with {cat} taste also liked {pname}",
51
+ "item_based": f"Matches your {cat} preferences β€” similar to items you've rated highly",
52
+ "svd": f"Fits your profile: {brand}'s {cat} β€” top latent factor match" if brand and cat else f"Fits your profile: {cat} β€” top latent factor match",
53
+ "knn": f"Popular among peers who also like {cat}" if cat else f"Popular among users with similar taste",
54
+ "slope_one": f"Frequently chosen by users who liked the same {cat} products" if cat else f"Frequently chosen by users with your taste",
55
  }
56
  return templates.get(method, f"Recommended based on collaborative filtering")
57
 
58
  def explain_content(self, method, user_id, product_id, details=None):
59
  product = self._get_product(product_id)
60
+ user = self._get_user(user_id)
61
+ pname = product.get("name", f"Item #{product_id}")
62
+ cat = product.get("category", "")
63
+ brand = product.get("brand", "")
64
+ sub = product.get("subcategory", "")
65
  details = details or {}
 
66
  score = min(details.get('score', 0), 1.0)
67
+ pref_cats = self._pref_list(user, "preferred_categories")
68
+
69
  templates = {
70
+ "tfidf": f"Content matches: {cat} / {sub} β€” strongly aligns with your past likes" if sub else f"Content matches your preferred {cat} items",
71
+ "feature_match": f"Matches your preferences: {cat}, {brand}" if brand else f"Matches your preferred category: {cat}",
72
  }
73
  return templates.get(method, f"Recommended based on item features")
74
 
75
  def explain_knowledge(self, method, user_id, product_id, details=None):
76
  product = self._get_product(product_id)
77
+ user = self._get_user(user_id)
78
+ pname = product.get("name", f"Item #{product_id}")
79
+ cat = product.get("category", "")
80
  brand = product.get("brand", "")
81
  price = product.get("price", 0)
82
+ budget_max = details.get("budget_max", 0)
83
  details = details or {}
84
+ pref_brands = self._pref_list(user, "favorite_brands")
85
 
86
  templates = {
87
+ "constraint": f"Fits your criteria: ${price:.0f} within budget Β· {cat} Β· {brand}" if brand else f"Fits your criteria: ${price:.0f} within budget Β· {cat}",
88
+ "rule": f"{cat} buyers commonly add {pname} β€” cross-sell match" if cat else f"Customers also purchased {pname}",
89
+ "utility": f"Top utility score: {cat} Γ— {brand} matches your preference weights" if brand else f"Top utility score: {cat} matches your preference weights",
90
  }
91
  return templates.get(method, f"Recommended based on your requirements")
92
 
templates/evaluation.html CHANGED
@@ -405,14 +405,40 @@
405
  buildBestMethodAnalysis() {
406
  const best = this.cfData.find(r => r.method === this.bestCfMethod);
407
  if (!best) return 'Run evaluation to find out.';
408
- return `<strong>${methodLabels[this.bestCfMethod] || this.bestCfMethod}</strong> achieves the lowest RMSE (${best.RMSE.toFixed(4)}) among all CF methods. SVD (Matrix Factorization) typically performs best because it captures latent factors in the user-item interaction matrix, handling sparsity better than memory-based methods.`;
 
 
 
 
 
 
 
 
 
 
 
409
  },
410
 
411
  buildBestApproachAnalysis(data) {
412
  if (!data.best_approach) return 'Run evaluation to find out.';
413
  const bestApp = data.approaches?.find(a => a.approach === data.best_approach);
414
  const precVal = bestApp ? bestApp['Precision@5'].toFixed(4) : 'β€”';
415
- return `<strong>${data.best_approach}</strong> achieves the highest Precision@5 (${precVal}) on this dataset. Collaborative Filtering generally performs best when sufficient rating data exists. Content-Based works well for new items but suffers from overspecialization. Knowledge-Based excels in cold-start scenarios and when users have explicit constraints.`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  },
417
 
418
  animateCounter(obj, key, target, decimals = 4, duration = 800) {
 
405
  buildBestMethodAnalysis() {
406
  const best = this.cfData.find(r => r.method === this.bestCfMethod);
407
  if (!best) return 'Run evaluation to find out.';
408
+
409
+ const methodExplanations = {
410
+ user_based: 'simplicity and direct peer comparison β€” works well when the target user has enough similar neighbors.',
411
+ item_based: 'direct item-item similarity β€” effective on dense data where co-rated item pairs are plentiful.',
412
+ svd: 'latent factor modeling β€” captures hidden patterns in the user-item matrix, excelling on sparse, large-scale data.',
413
+ knn: 'nearest-neighbor aggregation β€” robust to noise, but performance depends on the quality of the neighbor set.',
414
+ slope_one: 'simplicity and speed of prediction β€” performs well when rating deviations are consistent across users.',
415
+ };
416
+ const why = methodExplanations[this.bestCfMethod] || 'strong performance on this dataset.';
417
+
418
+ return `<strong>${methodLabels[this.bestCfMethod] || this.bestCfMethod}</strong> achieves the lowest RMSE (${best.RMSE.toFixed(4)}) among all CF methods. ` +
419
+ `${methodLabels[this.bestCfMethod] || this.bestCfMethod} excels because of ${why}`;
420
  },
421
 
422
  buildBestApproachAnalysis(data) {
423
  if (!data.best_approach) return 'Run evaluation to find out.';
424
  const bestApp = data.approaches?.find(a => a.approach === data.best_approach);
425
  const precVal = bestApp ? bestApp['Precision@5'].toFixed(4) : 'β€”';
426
+ const second = [...(data.approaches || [])].sort((a, b) => (b['Precision@5'] || 0) - (a['Precision@5'] || 0));
427
+ const runnerUp = second.length > 1 ? second[1].approach : null;
428
+
429
+ const explanations = {
430
+ 'Collaborative Filtering': `strongest when rating data is dense (${this.cfData.length} methods averaged). Works by leveraging peer behavior patterns.`,
431
+ 'Content-Based': `best when item features are informative (${data.approaches?.length || 0} approaches compared). Shines for cold-start items and niche categories where collaborative signals are weak.`,
432
+ 'Knowledge-Based': `ideal for cold-start users with explicit constraints. Deterministic and transparent, but limited by the quality of hand-crafted rules.`,
433
+ };
434
+ const why = explanations[data.best_approach] || 'strong performance on this dataset.';
435
+
436
+ let text = `<strong>${data.best_approach}</strong> achieves the highest Precision@5 (${precVal}) on this dataset. ` +
437
+ `${data.best_approach} performs ${why}`;
438
+ if (runnerUp) {
439
+ text += ` <strong>${runnerUp}</strong> follows as the runner-up.`;
440
+ }
441
+ return text;
442
  },
443
 
444
  animateCounter(obj, key, target, decimals = 4, duration = 800) {