Spaces:
Running
on
Zero
Running
on
Zero
| # %%writefile dynamic_weight_calculator.py | |
| import numpy as np | |
| from typing import Dict, List, Tuple, Set, Optional, Any | |
| from dataclasses import dataclass, field | |
| import traceback | |
| class WeightAllocationResult: | |
| """權重分配結果""" | |
| dynamic_weights: Dict[str, float] = field(default_factory=dict) | |
| allocation_method: str = 'balanced' | |
| high_priority_count: int = 0 | |
| mentioned_dimensions: Set[str] = field(default_factory=set) | |
| weight_sum: float = 1.0 | |
| allocation_notes: List[str] = field(default_factory=list) | |
| class DynamicWeightCalculator: | |
| """ | |
| 動態權重計算器 | |
| 根據使用者優先級動態調整維度權重 | |
| 策略: | |
| - 1個高優先級 → 固定預留 40% | |
| - 2個高優先級 → 固定預留 40% + 25% | |
| - 3個高優先級 → 固定預留 30% + 27% + 23% | |
| - 4+個高優先級 → 倍數正規化法 | |
| """ | |
| def __init__(self): | |
| """初始化動態權重計算器""" | |
| self.default_weights = self._initialize_default_weights() | |
| self.dimension_name_mapping = self._initialize_dimension_mapping() | |
| self.high_priority_threshold = 1.4 # Balanced threshold (not too high, not too low) | |
| self.min_weight_floor = 0.05 | |
| self.contextual_weight_distribution = { | |
| 'critical_dimensions_weight': 0.50, # Moderate emphasis on critical dimensions | |
| 'mentioned_dimensions_weight': 0.35, # Good weight for mentioned dimensions | |
| 'other_dimensions_weight': 0.15 # Reasonable baseline for other dimensions | |
| } | |
| def _initialize_default_weights(self) -> Dict[str, float]: | |
| """初始化預設權重(平衡配置)""" | |
| return { | |
| 'activity_compatibility': 0.18, | |
| 'noise_compatibility': 0.16, | |
| 'spatial_compatibility': 0.13, | |
| 'family_compatibility': 0.13, | |
| 'maintenance_compatibility': 0.13, | |
| 'experience_compatibility': 0.15, # 新增獨立的experience維度 | |
| 'health_compatibility': 0.12 | |
| } | |
| def _initialize_dimension_mapping(self) -> Dict[str, str]: | |
| """初始化維度名稱映射""" | |
| return { | |
| 'noise': 'noise_compatibility', | |
| 'size': 'spatial_compatibility', # size更適合映射到spatial | |
| 'exercise': 'activity_compatibility', | |
| 'activity': 'activity_compatibility', | |
| 'grooming': 'maintenance_compatibility', | |
| 'maintenance': 'maintenance_compatibility', | |
| 'family': 'family_compatibility', | |
| 'experience': 'experience_compatibility', # 獨立映射 | |
| 'health': 'health_compatibility', # 獨立映射 | |
| 'spatial': 'spatial_compatibility', | |
| 'space': 'spatial_compatibility' | |
| } | |
| def calculate_dynamic_weights(self, | |
| dimension_priorities: Dict[str, float], | |
| user_mentions: Optional[Set[str]] = None, | |
| use_contextual: bool = True) -> WeightAllocationResult: | |
| """ | |
| 計算動態權重 | |
| Args: | |
| dimension_priorities: 維度優先級 {dimension: priority_score} | |
| user_mentions: 使用者明確提到的維度 | |
| use_contextual: 是否使用情境相對評分(關鍵維度80%) | |
| Returns: | |
| WeightAllocationResult: 權重分配結果 | |
| """ | |
| try: | |
| if user_mentions is None: | |
| user_mentions = set() | |
| # Step 1: 標準化維度名稱 | |
| normalized_priorities = self._normalize_dimension_names(dimension_priorities) | |
| # Step 2: 分類維度 | |
| high_priority_dims = { | |
| dim: score for dim, score in normalized_priorities.items() | |
| if score >= self.high_priority_threshold | |
| } | |
| high_count = len(high_priority_dims) | |
| # Step 3: 根據高優先級數量選擇策略 | |
| if high_count == 0: | |
| # 無優先級 → 使用預設權重 | |
| result = self._allocate_default_weights(user_mentions) | |
| result.allocation_method = 'default_balanced' | |
| elif use_contextual: | |
| # 使用情境相對評分(關鍵維度80%) | |
| result = self._allocate_contextual_weights( | |
| normalized_priorities, user_mentions | |
| ) | |
| result.allocation_method = 'contextual_relative' | |
| elif high_count == 1: | |
| # 單一高優先級 → 固定預留40% | |
| result = self._allocate_single_priority( | |
| normalized_priorities, user_mentions | |
| ) | |
| result.allocation_method = 'single_fixed' | |
| elif high_count <= 3: | |
| # 2-3個高優先級 → 階梯固定預留法 | |
| result = self._allocate_multiple_priorities_fixed( | |
| normalized_priorities, user_mentions, high_count | |
| ) | |
| result.allocation_method = f'multiple_fixed_{high_count}' | |
| else: | |
| # 4+個高優先級 → 倍數正規化法 | |
| result = self._allocate_multiple_priorities_proportional( | |
| normalized_priorities, user_mentions | |
| ) | |
| result.allocation_method = 'proportional' | |
| # Step 4: 應用最低權重保護 | |
| result.dynamic_weights = self._apply_weight_floor(result.dynamic_weights) | |
| # Step 5: 正規化確保總和為1.0 | |
| result.dynamic_weights = self._normalize_weights(result.dynamic_weights) | |
| result.weight_sum = sum(result.dynamic_weights.values()) | |
| result.high_priority_count = high_count | |
| result.mentioned_dimensions = user_mentions | |
| return result | |
| except Exception as e: | |
| print(f"Error calculating dynamic weights: {str(e)}") | |
| print(traceback.format_exc()) | |
| return WeightAllocationResult( | |
| dynamic_weights=self.default_weights.copy(), | |
| allocation_method='fallback' | |
| ) | |
| def _normalize_dimension_names(self, | |
| priorities: Dict[str, float]) -> Dict[str, float]: | |
| """標準化維度名稱""" | |
| normalized = {} | |
| for dim, score in priorities.items(): | |
| mapped_dim = self.dimension_name_mapping.get(dim, dim) | |
| # 如果mapped_dim不在default_weights中,保留原維度名 | |
| if mapped_dim not in self.default_weights: | |
| mapped_dim = dim | |
| normalized[mapped_dim] = max(normalized.get(mapped_dim, 1.0), score) | |
| return normalized | |
| def _allocate_default_weights(self, | |
| user_mentions: Set[str]) -> WeightAllocationResult: | |
| """分配預設平衡權重""" | |
| weights = self.default_weights.copy() | |
| notes = ["Using default balanced weights (no priorities detected)"] | |
| return WeightAllocationResult( | |
| dynamic_weights=weights, | |
| allocation_notes=notes | |
| ) | |
| def _allocate_contextual_weights(self, | |
| priorities: Dict[str, float], | |
| user_mentions: Set[str]) -> WeightAllocationResult: | |
| """ | |
| 情境相對權重分配(關鍵維度50%) | |
| """ | |
| weights = {} | |
| notes = [] | |
| # 標準化user_mentions維度名稱 | |
| normalized_mentions = set() | |
| for mention in user_mentions: | |
| normalized_name = self.dimension_name_mapping.get(mention, mention) | |
| if normalized_name in self.default_weights: | |
| normalized_mentions.add(normalized_name) | |
| # 分類維度 | |
| critical_dims = [d for d, s in priorities.items() if s >= self.high_priority_threshold] | |
| mentioned_dims = [d for d in normalized_mentions if d not in critical_dims] | |
| other_dims = [d for d in self.default_weights.keys() | |
| if d not in critical_dims and d not in mentioned_dims] | |
| # 權重分配 | |
| total_critical = self.contextual_weight_distribution['critical_dimensions_weight'] | |
| total_mentioned = self.contextual_weight_distribution['mentioned_dimensions_weight'] | |
| total_other = self.contextual_weight_distribution['other_dimensions_weight'] | |
| # 關鍵維度:按優先級比例分配50% | |
| if critical_dims: | |
| critical_priority_sum = sum(priorities.get(d, 1.0) for d in critical_dims) | |
| for dim in critical_dims: | |
| weight = (priorities.get(dim, 1.0) / critical_priority_sum) * total_critical | |
| weights[dim] = weight | |
| notes.append(f"Critical dimensions ({len(critical_dims)}): {total_critical:.0%} weight") | |
| # 提及維度:平均分配35% | |
| if mentioned_dims: | |
| for dim in mentioned_dims: | |
| weights[dim] = total_mentioned / len(mentioned_dims) | |
| notes.append(f"Mentioned dimensions ({len(mentioned_dims)}): {total_mentioned:.0%} weight") | |
| # 其他維度:平均分配15% | |
| if other_dims: | |
| for dim in other_dims: | |
| weights[dim] = total_other / len(other_dims) | |
| notes.append(f"Other dimensions ({len(other_dims)}): {total_other:.0%} weight") | |
| # 填充未覆蓋的維度 | |
| for dim in self.default_weights.keys(): | |
| if dim not in weights: | |
| weights[dim] = 0.05 | |
| return WeightAllocationResult( | |
| dynamic_weights=weights, | |
| allocation_notes=notes | |
| ) | |
| def _allocate_single_priority(self, | |
| priorities: Dict[str, float], | |
| user_mentions: Set[str]) -> WeightAllocationResult: | |
| """單一高優先級:固定預留40%""" | |
| weights = {} | |
| notes = [] | |
| # 找到高優先級維度 | |
| high_priority_dim = None | |
| max_priority = 0 | |
| for dim, score in priorities.items(): | |
| if score >= self.high_priority_threshold and score > max_priority: | |
| high_priority_dim = dim | |
| max_priority = score | |
| if high_priority_dim: | |
| # 高優先級維度:40% | |
| weights[high_priority_dim] = 0.40 | |
| notes.append(f"{high_priority_dim}: 40% (high priority)") | |
| # 其他維度:平均分配剩餘60% | |
| other_dims = [d for d in self.default_weights.keys() if d != high_priority_dim] | |
| remaining_weight = 0.60 | |
| for dim in other_dims: | |
| weights[dim] = remaining_weight / len(other_dims) | |
| else: | |
| weights = self.default_weights.copy() | |
| return WeightAllocationResult( | |
| dynamic_weights=weights, | |
| allocation_notes=notes | |
| ) | |
| def _allocate_multiple_priorities_fixed(self, | |
| priorities: Dict[str, float], | |
| user_mentions: Set[str], | |
| high_count: int) -> WeightAllocationResult: | |
| """2-3個高優先級:階梯固定預留法""" | |
| weights = {} | |
| notes = [] | |
| # 排序高優先級維度 | |
| high_priority_dims = sorted( | |
| [(dim, score) for dim, score in priorities.items() | |
| if score >= self.high_priority_threshold], | |
| key=lambda x: x[1], | |
| reverse=True | |
| ) | |
| # 根據數量分配固定權重 | |
| if high_count == 2: | |
| # 2個高優先級:40% + 25% | |
| fixed_weights = [0.40, 0.25] | |
| remaining = 0.35 | |
| notes.append("2 high priorities: 40% + 25%, others share 35%") | |
| elif high_count == 3: | |
| # 3個高優先級:30% + 27% + 23% | |
| fixed_weights = [0.30, 0.27, 0.23] | |
| remaining = 0.20 | |
| notes.append("3 high priorities: 30% + 27% + 23%, others share 20%") | |
| else: | |
| # 降級處理 | |
| return self._allocate_multiple_priorities_proportional( | |
| priorities, user_mentions | |
| ) | |
| # 分配固定權重 | |
| for i, (dim, score) in enumerate(high_priority_dims[:high_count]): | |
| weights[dim] = fixed_weights[i] | |
| # 其他維度:平均分配剩餘 | |
| other_dims = [d for d in self.default_weights.keys() | |
| if d not in [dim for dim, _ in high_priority_dims[:high_count]]] | |
| if other_dims: | |
| for dim in other_dims: | |
| weights[dim] = remaining / len(other_dims) | |
| return WeightAllocationResult( | |
| dynamic_weights=weights, | |
| allocation_notes=notes | |
| ) | |
| def _allocate_multiple_priorities_proportional(self, | |
| priorities: Dict[str, float], | |
| user_mentions: Set[str]) -> WeightAllocationResult: | |
| """4+個高優先級:倍數正規化法""" | |
| weights = {} | |
| notes = [] | |
| # 計算原始權重(基於優先級倍數) | |
| raw_weights = {} | |
| for dim in self.default_weights.keys(): | |
| priority_score = priorities.get(dim, 1.0) | |
| raw_weights[dim] = 1.0 * priority_score | |
| # 正規化 | |
| total_raw = sum(raw_weights.values()) | |
| for dim, raw_weight in raw_weights.items(): | |
| weights[dim] = raw_weight / total_raw | |
| notes.append(f"Proportional allocation for {len([d for d, s in priorities.items() if s >= self.high_priority_threshold])} high priorities") | |
| return WeightAllocationResult( | |
| dynamic_weights=weights, | |
| allocation_notes=notes | |
| ) | |
| def _apply_weight_floor(self, weights: Dict[str, float]) -> Dict[str, float]: | |
| """應用最低權重保護""" | |
| protected_weights = {} | |
| for dim, weight in weights.items(): | |
| protected_weights[dim] = max(self.min_weight_floor, weight) | |
| return protected_weights | |
| def _normalize_weights(self, weights: Dict[str, float]) -> Dict[str, float]: | |
| """正規化權重確保總和為1.0""" | |
| total = sum(weights.values()) | |
| if total == 0: | |
| return self.default_weights.copy() | |
| normalized = {dim: weight / total for dim, weight in weights.items()} | |
| return normalized | |
| def get_weight_summary(self, result: WeightAllocationResult) -> Dict[str, Any]: | |
| """ | |
| 獲取權重分配摘要 | |
| Args: | |
| result: 權重分配結果 | |
| Returns: | |
| Dict[str, Any]: 權重摘要 | |
| """ | |
| return { | |
| 'allocation_method': result.allocation_method, | |
| 'high_priority_count': result.high_priority_count, | |
| 'weight_sum': result.weight_sum, | |
| 'weights': result.dynamic_weights, | |
| 'top_3_dimensions': sorted( | |
| result.dynamic_weights.items(), | |
| key=lambda x: x[1], | |
| reverse=True | |
| )[:3], | |
| 'allocation_notes': result.allocation_notes | |
| } | |
| def calculate_weights_from_priorities(dimension_priorities: Dict[str, float], | |
| user_mentions: Optional[Set[str]] = None, | |
| use_contextual: bool = True) -> WeightAllocationResult: | |
| """ | |
| 便利函數: 從優先級計算權重 | |
| Args: | |
| dimension_priorities: 維度優先級 | |
| user_mentions: 使用者提及的維度 | |
| use_contextual: 使用情境相對評分 | |
| Returns: | |
| WeightAllocationResult: 權重分配結果 | |
| """ | |
| calculator = DynamicWeightCalculator() | |
| return calculator.calculate_dynamic_weights( | |
| dimension_priorities, user_mentions, use_contextual | |
| ) | |
| def get_weight_summary(dimension_priorities: Dict[str, float], | |
| user_mentions: Optional[Set[str]] = None) -> Dict[str, Any]: | |
| """ | |
| 便利函數: 獲取權重摘要 | |
| Args: | |
| dimension_priorities: 維度優先級 | |
| user_mentions: 使用者提及的維度 | |
| Returns: | |
| Dict[str, Any]: 權重摘要 | |
| """ | |
| calculator = DynamicWeightCalculator() | |
| result = calculator.calculate_dynamic_weights(dimension_priorities, user_mentions) | |
| return calculator.get_weight_summary(result) | |