Skip to content

Commit 1691650

Browse files
fix: correct baseline indentation errors from main
1 parent 394527f commit 1691650

2 files changed

Lines changed: 27 additions & 132 deletions

File tree

celery_app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,4 @@ def dispatch_task_safely(task, *args, **kwargs):
6666
)
6767
# .apply() tells Celery to run the function right now on the main execution thread
6868
return task.apply(args=args, kwargs=kwargs)
69-
app.conf.worker_max_tasks_per_child = 5
69+
celery_app.conf.worker_max_tasks_per_child = 5

src/model/hybrid_model.py

Lines changed: 26 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333

3434
from src.model.causal_config import CausalConfig
3535
from src.model.causal_model import CausalDebiaser
36-
from src.model.recommendation_history import history_tracker
3736

3837
logger = logging.getLogger(__name__)
3938

@@ -53,29 +52,6 @@ def bayesian_rating(
5352

5453

5554
class HybridRecommender:
56-
def __init__(self, content_model, collab_model=None, item_df=None,
57-
alpha=0.4, beta=0.35, gamma=0.25,
58-
normalization='minmax', weight_matrix=None,
59-
use_causal_debiasing=False, causal_lambda=0.5, causal_clip=5.0,
60-
causal_config=None, model_kwargs=None,
61-
kg_model=None, delta=0.05):
62-
"""
63-
content_model: ContentRecommender instance
64-
collab_model: CollaborativeRecommender instance (optional)
65-
item_df: DataFrame with 'avg_sentiment', 'rating', 'review_count' columns
66-
alpha: weight for content-based score
67-
beta: weight for collaborative score
68-
gamma: weight for sentiment score
69-
use_causal_debiasing: Enable IPS-based causal debiasing on the final hybrid score.
70-
When True, a CausalDebiaser is built from item_df and applied
71-
after the weighted blend, before final ranking.
72-
causal_lambda: Blend factor λ for causal correction (0.0–1.0).
73-
0.0 = no debiasing, 1.0 = full IPS reweighting. Default 0.5.
74-
causal_clip: Max IPS weight cap to prevent variance explosion. Default 5.0.
75-
causal_config: Optional CausalConfig instance. When provided, takes precedence
76-
over use_causal_debiasing / causal_lambda / causal_clip.
77-
Use this for structured configuration management.
78-
"""
7955
"""Hybrid recommender combining content + collaborative + sentiment."""
8056

8157
def __init__(
@@ -148,12 +124,6 @@ def __init__(
148124

149125
self.online_updater = None
150126

151-
# Bandit exploration
152-
self.epsilon = 0.1
153-
self.bandit_arms = [(self.alpha, self.beta, self.gamma)]
154-
self.arm_rewards = {0: 0.0}
155-
self.arm_counts = {0: 0}
156-
157127
if item_df is not None:
158128
global_avg = float(item_df["rating"].mean()) if "rating" in item_df.columns else 3.0
159129

@@ -209,58 +179,14 @@ def get_weights(self):
209179
'beta': self.beta,
210180
'gamma': self.gamma,
211181
'delta': self.delta,
212-
}
213-
182+
}
214183

215184
def select_bandit_arm(self):
216185
import random
217186

218187
if random.random() < self.epsilon:
219188
return random.randint(0, len(self.bandit_arms) - 1)
220189

221-
review_count = int(review_count)
222-
self._review_count_map[title] = review_count
223-
self._rating_map[title] = bayesian_rating(
224-
raw_rating, review_count, global_avg
225-
)
226-
self._category_map[title] = row.get('category', '')
227-
self._catalog_map[title] = row.get('catalog', '')
228-
229-
# Popularity rank (0-1 scale, higher = more popular)
230-
if 'review_count' in item_df.columns:
231-
max_reviews = item_df['review_count'].max()
232-
if max_reviews > 0:
233-
for _, row in item_df.iterrows():
234-
self._popularity_map[row['title']] = (
235-
row['review_count'] / max_reviews
236-
)
237-
238-
# Optional runtime hook for online updates (attachable)
239-
self.online_updater = None
240-
241-
def set_weights(self, alpha, beta, gamma, delta=0.05):
242-
"""Update the scoring weights. Normalized to sum to 1.
243-
244-
Args:
245-
alpha: weight for content_score
246-
beta: weight for collab_score
247-
gamma: weight for sentiment_score
248-
delta: weight for popularity (default 0.05). All four weights are
249-
normalized to sum to 1.0, guaranteeing hybrid_score in [0, 1].
250-
"""
251-
if any(math.isnan(w) for w in [alpha, beta, gamma, delta]):
252-
raise ValueError("Weights must be finite numbers")
253-
if any(w < 0 for w in [alpha, beta, gamma, delta]):
254-
raise ValueError("Weights must be non-negative")
255-
total = alpha + beta + gamma + delta
256-
if total == 0:
257-
total = 1
258-
self.alpha = alpha / total
259-
self.beta = beta / total
260-
self.gamma = gamma / total
261-
self.delta = delta / total
262-
def get_weights(self):
263-
return {'alpha': self.alpha, 'beta': self.beta, 'gamma': self.gamma, 'delta': self.delta}
264190
best_arm = max(
265191
self.arm_rewards,
266192
key=lambda x: self.arm_rewards[x] / max(self.arm_counts[x], 1)
@@ -271,7 +197,7 @@ def get_weights(self):
271197
def update_bandit_reward(self, arm_id, reward):
272198
self.arm_counts[arm_id] += 1
273199
self.arm_rewards[arm_id] += reward
274-
200+
return {'alpha': self.alpha, 'beta': self.beta, 'gamma': self.gamma, 'delta': self.delta}
275201

276202
# ------------------------- fairness helpers -------------------------
277203
def set_fairness(self, enabled=None, key=None, max_share=None):
@@ -288,7 +214,6 @@ def set_fairness(self, enabled=None, key=None, max_share=None):
288214
def _fair_rerank(self, results: list[dict[str, Any]], top_n: int, key: str, max_share: float):
289215
if not results or top_n <= 1:
290216
return results[:top_n]
291-
292217
try:
293218
max_share = float(max_share)
294219
except Exception:
@@ -343,31 +268,25 @@ def _normalize_scores(self, scores: list[float]) -> list[float]:
343268

344269
def _get_active_weights(
345270
self,
346-
base_a: float,
347-
base_b: float,
348-
base_g: float,
349-
base_d: float = 0.0,
350-
user_id: str | None = None,
351271
candidate_titles: list[str] | None = None,
352-
) -> tuple[float, float, float, float]:
272+
user_id: str | None = None,
273+
) -> tuple[float, float, float]:
353274
"""Resolve active weights using weight_matrix and runtime signals."""
354275

355-
a, b, g, d = float(base_a), float(base_b), float(base_g), float(base_d)
276+
a, b, g = float(self.alpha), float(self.beta), float(self.gamma)
356277

357-
def unpack_weights(val, default_d=0.0):
278+
def unpack_weights(val):
358279
if isinstance(val, (list, tuple)):
359-
if len(val) >= 4:
360-
return float(val[0]), float(val[1]), float(val[2]), float(val[3])
361-
if len(val) == 3:
362-
return float(val[0]), float(val[1]), float(val[2]), default_d
280+
if len(val) >= 3:
281+
return float(val[0]), float(val[1]), float(val[2])
363282
if len(val) == 2:
364-
return float(val[0]), float(val[1]), 0.0, default_d
283+
return float(val[0]), float(val[1]), 0.0
365284
return None
366285

367286
if "default" in self.weight_matrix:
368-
w = unpack_weights(self.weight_matrix["default"], d)
287+
w = unpack_weights(self.weight_matrix["default"])
369288
if w is not None:
370-
a, b, g, d = w
289+
a, b, g = w
371290

372291
# category override
373292
if candidate_titles and self.item_df is not None and {"title", "category"}.issubset(self.item_df.columns):
@@ -382,42 +301,31 @@ def unpack_weights(val, default_d=0.0):
382301
top_cat = Counter(cats).most_common(1)[0][0]
383302
key = f"category:{top_cat}"
384303
if key in self.weight_matrix:
385-
w = unpack_weights(self.weight_matrix[key], d)
304+
w = unpack_weights(self.weight_matrix[key])
386305
if w is not None:
387-
a, b, g, d = w
306+
a, b, g = w
388307
except Exception:
389308
logger.warning("weight_matrix category override failed", exc_info=True)
390309

391-
# user signals
392-
if user_id and self.collab_model and hasattr(self.collab_model, 'df'):
393-
try:
394-
user_interacts = int(len(self.collab_model.df[self.collab_model.df['user_id'] == user_id]))
395-
if 'warm_user' in self.weight_matrix and user_interacts > 10:
396-
w = unpack_weights(self.weight_matrix['warm_user'], d)
397-
if w is not None:
398-
a, b, g, d = w
399-
if 'cold_user' in self.weight_matrix and user_interacts < 3:
400-
w = unpack_weights(self.weight_matrix['cold_user'], d)
401-
if w is not None:
402-
a, b, g, d = w
403-
except Exception:
404-
pass
405-
406310
# feature absence overrides
407311
if self.collab_model is None and "no_collab" in self.weight_matrix:
408-
w = unpack_weights(self.weight_matrix["no_collab"], d)
312+
w = unpack_weights(self.weight_matrix["no_collab"])
409313
if w is not None:
410-
a, b, g, d = w
314+
a, b, g = w
411315

412316
if not self._sentiment_map and "no_sentiment" in self.weight_matrix:
413-
w = unpack_weights(self.weight_matrix["no_sentiment"], d)
317+
w = unpack_weights(self.weight_matrix["no_sentiment"])
414318
if w is not None:
415-
a, b, g, d = w
319+
a, b, g = w
416320

417-
total = a + b + g + d
321+
if self.kg_model is None and "no_kg" in self.weight_matrix:
322+
# KG weight handled via delta; keep legacy keys but ignore in 3-way blend
323+
pass
324+
325+
total = a + b + g
418326
if total <= 0:
419-
return base_a, base_b, base_g, base_d
420-
return a / total, b / total, g / total, d / total
327+
return float(self.alpha), float(self.beta), float(self.gamma)
328+
return a / total, b / total, g / total
421329

422330
# ------------------------- main recommend -------------------------
423331
def recommend(
@@ -523,7 +431,7 @@ def recommend(
523431
a, b, g = getattr(self, 'bandit_arms', [(self.alpha, self.beta, self.gamma)])[arm_id]
524432

525433
a, b, g, d = self._get_active_weights(
526-
a, b, g, getattr(self, 'delta', 0.0),
434+
a, b, g, getattr(self, 'delta', 0),
527435
user_id=user_id,
528436
)
529437
d = self.delta if self.kg_model else 0.0
@@ -554,19 +462,6 @@ def recommend(
554462
avg_rating = float(self._rating_map.get(item["title"], 0.0) or 0.0)
555463
category = self._category_map.get(item["title"], "")
556464

557-
# Popularity as a proper fourth component weighted by delta.
558-
# Weights a, b, g are re-normalized here to leave room for delta,
559-
# guaranteeing hybrid_score in [0, 1].
560-
popularity = self._popularity_map.get(item['title'], 0.5)
561-
weight_sum = a + b + g + self.delta
562-
if weight_sum <= 0:
563-
weight_sum = 1.0
564-
hybrid = (
565-
(a * content_scores[i] +
566-
b * collab_scores[i] +
567-
g * sentiment_scores[i] +
568-
self.delta * popularity) / weight_sum
569-
)
570465
popularity_bonus = 0.05 * popularity
571466

572467
# Enforce strict upper bound limit check
@@ -722,7 +617,7 @@ def _build_explanation(
722617
'content': round(alpha * content_score, 4),
723618
'collaborative': round(beta * collab_score, 4),
724619
'sentiment': round(gamma * sentiment_score, 4),
725-
'popularity_bonus': round(self.delta * popularity, 4),
620+
'popularity_bonus': round(0.05 * popularity, 4),
726621
}
727622
strongest = max(weighted_components, key=weighted_components.get)
728623

0 commit comments

Comments
 (0)