Arko006 commited on
Commit
e5885cb
·
verified ·
1 Parent(s): 09e6d6a

fix: SHAP explain - torch import, reduce samples, fix visualizer scale

Browse files
explain/shap_wrapper.py CHANGED
@@ -12,21 +12,17 @@ class GNNFeatureWrapper:
12
 
13
  def __call__(self, binary_coalitions: np.ndarray) -> np.ndarray:
14
  predictions = []
 
 
 
 
 
15
  with torch.no_grad():
16
  for coalition in binary_coalitions:
17
- x_perturbed = self.data_obj.x.clone().to(self.device)
18
- mask_tensor = torch.tensor(
19
- coalition, dtype=torch.float32, device=self.device
20
- ).unsqueeze(1)
21
- x_perturbed = x_perturbed * mask_tensor
22
-
23
- logits = self.model(
24
- x_perturbed,
25
- self.data_obj.edge_index.to(self.device),
26
- self.data_obj.edge_attr.to(self.device),
27
- torch.zeros(x_perturbed.shape[0], dtype=torch.long, device=self.device),
28
- )
29
- probs = torch.sigmoid(logits)
30
- predictions.append(probs[0, self.target_task_idx].cpu().item())
31
 
32
  return np.array(predictions)
 
12
 
13
  def __call__(self, binary_coalitions: np.ndarray) -> np.ndarray:
14
  predictions = []
15
+ x = self.data_obj.x.to(self.device)
16
+ edge_index = self.data_obj.edge_index.to(self.device)
17
+ edge_attr = self.data_obj.edge_attr.to(self.device)
18
+ batch = torch.zeros(x.shape[0], dtype=torch.long, device=self.device)
19
+
20
  with torch.no_grad():
21
  for coalition in binary_coalitions:
22
+ mask = torch.tensor(coalition, dtype=torch.float32, device=self.device).unsqueeze(1)
23
+ x_perturbed = x * mask
24
+ logits = self.model(x_perturbed, edge_index, edge_attr, batch)
25
+ prob = torch.sigmoid(logits[0, self.target_task_idx]).cpu().item()
26
+ predictions.append(prob)
 
 
 
 
 
 
 
 
 
27
 
28
  return np.array(predictions)
explain/visualizer.py CHANGED
@@ -13,12 +13,18 @@ def generate_similarity_map(smiles: str, attributions: list, target_assay: str)
13
  if mol is None:
14
  raise ValueError(f"Invalid SMILES: {smiles}")
15
 
 
 
 
 
 
 
16
  fig, ax = plt.subplots(figsize=(8, 6))
17
  SimilarityMaps.GetSimilarityMapFromWeights(
18
  mol,
19
  attributions,
20
  colorMap='RdYlBu_r',
21
- scale=-1,
22
  alpha=0.45,
23
  )
24
  ax.set_title(f"SHAP Attributions — {target_assay}", fontsize=14, fontweight="bold")
 
13
  if mol is None:
14
  raise ValueError(f"Invalid SMILES: {smiles}")
15
 
16
+ if mol.GetNumAtoms() != len(attributions):
17
+ raise ValueError(
18
+ f"Atom count mismatch: mol has {mol.GetNumAtoms()} atoms, "
19
+ f"but {len(attributions)} attributions provided"
20
+ )
21
+
22
  fig, ax = plt.subplots(figsize=(8, 6))
23
  SimilarityMaps.GetSimilarityMapFromWeights(
24
  mol,
25
  attributions,
26
  colorMap='RdYlBu_r',
27
+ scale=1,
28
  alpha=0.45,
29
  )
30
  ax.set_title(f"SHAP Attributions — {target_assay}", fontsize=14, fontweight="bold")
routes/explain.py CHANGED
@@ -1,4 +1,5 @@
1
  import numpy as np
 
2
  from fastapi import APIRouter, Header, HTTPException
3
  from pydantic import BaseModel
4
  from firebase_auth import verify_token
@@ -43,20 +44,23 @@ async def explain(request: ExplainRequest, authorization: str = Header(...)):
43
  raise HTTPException(status_code=400, detail=str(e))
44
 
45
  device = "cuda" if torch.cuda.is_available() else "cpu"
46
- import torch
47
  data = data.to(device)
48
 
49
  num_nodes = data.x.shape[0]
50
  predict_fn = GNNFeatureWrapper(model, data, request.target_task, device)
51
- background_reference = np.zeros((100, num_nodes))
 
 
52
  active_state = np.ones((1, num_nodes))
53
 
54
  import shap
55
  explainer = shap.KernelExplainer(predict_fn, background_reference)
56
- shap_values = explainer.shap_values(active_state, nsamples=min(1000, 2 ** num_nodes))
 
57
 
58
- attributions = shap_values[0].tolist()
59
- high_impact = np.where(np.abs(shap_values[0]) > 0.05)[0].tolist()
 
60
 
61
  try:
62
  vis_base64 = generate_similarity_map(
 
1
  import numpy as np
2
+ import torch
3
  from fastapi import APIRouter, Header, HTTPException
4
  from pydantic import BaseModel
5
  from firebase_auth import verify_token
 
44
  raise HTTPException(status_code=400, detail=str(e))
45
 
46
  device = "cuda" if torch.cuda.is_available() else "cpu"
 
47
  data = data.to(device)
48
 
49
  num_nodes = data.x.shape[0]
50
  predict_fn = GNNFeatureWrapper(model, data, request.target_task, device)
51
+
52
+ random_states = np.random.randint(0, 2, size=(20, num_nodes))
53
+ background_reference = np.vstack([np.zeros((1, num_nodes)), random_states])
54
  active_state = np.ones((1, num_nodes))
55
 
56
  import shap
57
  explainer = shap.KernelExplainer(predict_fn, background_reference)
58
+ nsamples = min(500, 2 ** num_nodes)
59
+ shap_values = explainer.shap_values(active_state, nsamples=nsamples)
60
 
61
+ attributions = np.array(shap_values[0]) if isinstance(shap_values, list) else np.array(shap_values)
62
+ attributions = attributions.flatten().tolist()
63
+ high_impact = np.where(np.abs(np.array(attributions)) > 0.05)[0].tolist()
64
 
65
  try:
66
  vis_base64 = generate_similarity_map(