Spaces:
Sleeping
Sleeping
| """Standalone copy of LIME-based explainability used by the dashboard.""" | |
| import logging | |
| from typing import Dict, List, Any, Optional, Tuple | |
| import numpy as np | |
| try: | |
| from lime.lime_text import LimeTextExplainer | |
| LIME_AVAILABLE = True | |
| except ImportError: | |
| LIME_AVAILABLE = False | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| class ContractExplainer: | |
| def __init__(self, model, vectorizer, class_names: List[str], feature_selector=None, random_state: int = 42): | |
| if not LIME_AVAILABLE: | |
| raise ImportError( | |
| "LIME not available. Install with: pip install lime") | |
| self.model = model | |
| self.vectorizer = vectorizer | |
| self.feature_selector = feature_selector | |
| self.class_names = class_names | |
| self.random_state = random_state | |
| self.explainer = LimeTextExplainer( | |
| class_names=class_names, random_state=random_state) | |
| def explain_prediction(self, text: str, num_features: int = 10, num_samples: int = 500) -> Dict[str, Any]: | |
| try: | |
| def predict_proba_wrapper(texts): | |
| features = self.vectorizer.transform(texts) | |
| if self.feature_selector is not None: | |
| features = self.feature_selector.transform(features) | |
| return self.model.predict_proba(features) | |
| exp = self.explainer.explain_instance( | |
| text, | |
| predict_proba_wrapper, | |
| num_features=num_features, | |
| num_samples=num_samples, | |
| top_labels=1, | |
| ) | |
| all_probs = predict_proba_wrapper([text])[0] | |
| predicted_index = int(np.argmax(all_probs)) | |
| predicted_class = self.class_names[predicted_index] | |
| confidence = float(all_probs[predicted_index]) | |
| important_features = exp.as_list(label=predicted_index) | |
| processed_features = self._get_best_phrase_feature( | |
| important_features, text) | |
| return { | |
| "text": text[:200] + "..." if len(text) > 200 else text, | |
| "full_text": text, | |
| "prediction": predicted_class, | |
| "confidence": confidence, | |
| "important_features": processed_features, | |
| "explanation_html": exp.as_html(), | |
| "num_features": num_features, | |
| "success": True, | |
| "explanation_object": exp, | |
| } | |
| except Exception as e: | |
| logger.exception("Explain failed") | |
| return {"success": False, "error": str(e), "text": text[:200] + "..." if len(text) > 200 else text, "full_text": text} | |
| def _get_best_phrase_feature(self, important_features: List[Tuple[str, float]], text: str) -> List[Tuple[str, float]]: | |
| text_lower = text.lower() | |
| candidate_phrases: List[Tuple[str, float]] = [] | |
| for feature, score in important_features: | |
| if " " in feature and len(feature.split()) >= 3: | |
| candidate_phrases.append((feature, abs(float(score)))) | |
| else: | |
| feature_lower = feature.lower() | |
| words = text_lower.split() | |
| for i, word in enumerate(words): | |
| if feature_lower in word.lower(): | |
| start_idx = max(0, i - 2) | |
| end_idx = min(len(words), i + 4) | |
| context_phrase = " ".join( | |
| words[start_idx:end_idx]).strip('.,!?;:"()[]{}') | |
| if len(context_phrase.split()) >= 3: | |
| candidate_phrases.append( | |
| (context_phrase, abs(float(score)))) | |
| break | |
| if candidate_phrases: | |
| best = max(candidate_phrases, key=lambda x: x[1]) | |
| return [best] | |
| return [important_features[0]] if important_features else [] | |