Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import torch | |
| from fastapi import APIRouter, Header, HTTPException | |
| from pydantic import BaseModel | |
| from firebase_auth import verify_token | |
| from models.molecule_graph import smiles_to_graph | |
| from explain.shap_wrapper import GNNFeatureWrapper | |
| from explain.visualizer import generate_similarity_map | |
| from config import TASK_NAMES | |
| router = APIRouter(prefix="/api") | |
| class ExplainRequest(BaseModel): | |
| smiles: str | |
| target_task: int = 0 | |
| class ExplainResponse(BaseModel): | |
| target_task: str | |
| shap_values: list[float] | |
| attribution_map_base64: str | |
| high_impact_atoms: list[int] | |
| def get_app_state(): | |
| from app import model_loader | |
| return model_loader | |
| async def explain(request: ExplainRequest, authorization: str = Header(...)): | |
| await verify_token(authorization) | |
| if request.target_task < 0 or request.target_task >= len(TASK_NAMES): | |
| raise HTTPException(status_code=400, detail=f"Invalid target_task. Must be 0-{len(TASK_NAMES)-1}") | |
| model_loader = get_app_state() | |
| model = model_loader.load_model() | |
| try: | |
| data = smiles_to_graph(request.smiles) | |
| except ValueError as e: | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| data = data.to(device) | |
| num_nodes = data.x.shape[0] | |
| predict_fn = GNNFeatureWrapper(model, data, request.target_task, device) | |
| random_states = np.random.randint(0, 2, size=(20, num_nodes)) | |
| background_reference = np.vstack([np.zeros((1, num_nodes)), random_states]) | |
| active_state = np.ones((1, num_nodes)) | |
| import shap | |
| explainer = shap.KernelExplainer(predict_fn, background_reference) | |
| nsamples = min(500, 2 ** num_nodes) | |
| shap_values = explainer.shap_values(active_state, nsamples=nsamples) | |
| attributions = np.array(shap_values[0]) if isinstance(shap_values, list) else np.array(shap_values) | |
| attributions = attributions.flatten().tolist() | |
| high_impact = np.where(np.abs(np.array(attributions)) > 0.05)[0].tolist() | |
| try: | |
| vis_base64 = generate_similarity_map( | |
| request.smiles, attributions, TASK_NAMES[request.target_task] | |
| ) | |
| except Exception as e: | |
| vis_base64 = "" | |
| return ExplainResponse( | |
| target_task=TASK_NAMES[request.target_task], | |
| shap_values=attributions, | |
| attribution_map_base64=vis_base64, | |
| high_impact_atoms=high_impact, | |
| ) | |