Skip to content

MDTerp module

MDTerp.final_analysis.py – Final MDTerp round for implementing forward feature selection and attributing feature importance.

charac_theta(d_U, d_S)

Function for computing change in unfaithfulness per unit change in interpretation entropy as the number of features used to build a linear model increases by 1.

Parameters:

Name Type Description Default
d_U np.ndarray

Change in unfaithfulness with increasing features in linear models.

required
d_S np.ndarray

Change in interpretation entropy with increasing features in linear models.

required

Returns:

Type Description
np.ndarray

Change in unfaithfulness per unit change in interpretation entropy.

Source code in MDTerp/final_analysis.py
def charac_theta(d_U: np.ndarray,d_S: np.ndarray) -> np.ndarray:
  """
    Function for computing change in unfaithfulness per unit change in interpretation entropy as the number of features used to build a linear model increases by 1.

    Args:
        d_U (np.ndarray): Change in unfaithfulness with increasing features in linear models.
        d_S (np.ndarray): Change in interpretation entropy with increasing features in linear models.

    Returns:
        np.ndarray: Change in unfaithfulness per unit change in interpretation entropy.
  """
  return -d_U/d_S

final_model(neighborhood_data, pred_proba, unf_threshold, feature_type_indices, selected_features, seed, use_all_cutoff_features=False)

Function for computing final feature importance by implementing forward feature selection.

Parameters:

Name Type Description Default
neighborhood_data np.ndarray

Perturbed data generated by MDTerp.neighborhood.py.

required
pred_proba np.ndarray

Metastable state probabilities obtained from the black-box.

required
unf_threshold float

Hyperparameter setting a lower limit on model unfaithafulness. Forward feature selection ends when unfaithfulness reaches lower than this threshold.

required
feature_type_indices np.ndarray

Indices of the features to perform the final round of MDTerp on.

required
selected_features np.ndarray

Indices of the features selected for detailed analysis.

required
seed int

Random seed.

required
use_all_cutoff_features bool

If True, use all cutoff features for importance instead of selecting optimal k via interpretation entropy. This avoids discarding features that may be irrelevant for one sample but relevant for another in the same transition ensemble. The final importance is computed using all selected features. Default: False.

False

Returns:

Type Description
np.ndarray

Normalized feature importance.

Source code in MDTerp/final_analysis.py
def final_model(neighborhood_data: np.ndarray, pred_proba: np.ndarray, unf_threshold: float, feature_type_indices: np.ndarray, selected_features: np.ndarray, seed:int, use_all_cutoff_features: bool = False) -> np.ndarray:
    """
    Function for computing final feature importance by implementing forward feature selection.

    Args:
        neighborhood_data (np.ndarray): Perturbed data generated by MDTerp.neighborhood.py.
        pred_proba (np.ndarray): Metastable state probabilities obtained from the black-box.
        unf_threshold (float): Hyperparameter setting a lower limit on model unfaithafulness. Forward feature selection ends when unfaithfulness reaches lower than this threshold.
        feature_type_indices (np.ndarray): Indices of the features to perform the final round of MDTerp on.
        selected_features (np.ndarray): Indices of the features selected for detailed analysis.
        seed (int): Random seed.
        use_all_cutoff_features (bool): If True, use all cutoff features for
            importance instead of selecting optimal k via interpretation entropy.
            This avoids discarding features that may be irrelevant for one sample
            but relevant for another in the same transition ensemble. The final
            importance is computed using all selected features. Default: False.

    Returns:
        np.ndarray: Normalized feature importance.
    """
    tot_feat = feature_type_indices[0].shape[0] + feature_type_indices[1].shape[0] + feature_type_indices[2].shape[0] + feature_type_indices[3].shape[0]
    k_max = neighborhood_data.shape[1]

    explain_class = np.argmax(pred_proba[0,:])

    target = pred_proba[:,explain_class]

    threshold, upper, lower = 0.5, 1, 0
    target_binarized = np.where(target>threshold, upper, lower)

    clf = lda()
    clf.fit(neighborhood_data,target_binarized)
    projected_data = clf.transform(neighborhood_data)
    weights = similarity_kernel(projected_data.reshape(-1,1), 1)

    predict_proba = pred_proba[:,explain_class]
    data = neighborhood_data*(weights**0.5).reshape(-1,1)
    labels = target.reshape(-1,1)*(weights.reshape(-1,1)**0.5)

    best_parameters_master = []
    best_parameters_converted = []
    best_unfaithfulness_master = []
    best_interp_master = []

    N = data.shape[1]
    k_array = np.arange(1,k_max+1)

    for k in k_array:
      unfaithfulness_calc(k, N, data, predict_proba, best_parameters_master, labels, best_interp_master, best_parameters_converted, best_unfaithfulness_master, tot_feat, selected_features, seed)


    if use_all_cutoff_features:
      # Use all cutoff features: return importance from the model with all
      # selected features (last entry), avoiding entropy-based pruning.
      prime_model = len(best_parameters_converted) - 1
    else:
      optimal_k = 1

      if N<=3:
        prime_model = 0
        for i in range(1,N):
          if best_unfaithfulness_master[i]<=best_unfaithfulness_master[i-1] - unf_threshold:
            prime_model = i
            continue
          else:
            break

      else:
        charac_theta_mast = []

        d_U_lst = []
        d_S_lst = []
        for i in range(1, len(selected_features)):
          d_U_lst.append(best_unfaithfulness_master[i] - best_unfaithfulness_master[i-1])
          d_S_lst.append(best_interp_master[i] - best_interp_master[i-1])

        for i in range(len(selected_features)-1):
          charac_theta_mast.append(charac_theta(d_U_lst[i], d_S_lst[i]))

        range_theta_mast = []
        for i in range(1,len(charac_theta_mast)):
          range_theta_mast.append(np.array(charac_theta_mast)[i]-np.array(charac_theta_mast)[i-1])

        prime_model = np.argmin(np.array(range_theta_mast))
        prime_model = prime_model + 1
    return np.absolute(np.array(best_parameters_converted)[prime_model]), np.absolute(np.array(best_parameters_converted)), np.array(best_unfaithfulness_master)

interpretation_entropy(coef_array)

Function for computing interpretation entropy of the coefficients of a fitted linear model.

Parameters:

Name Type Description Default
coef_array np.ndarray

Numpy array with coefficients of all the features of the fitted linear model.

required

Returns:

Type Description
float

Interpretation entropy of the coefficients of a fitted linear model.

Source code in MDTerp/final_analysis.py
def interpretation_entropy(coef_array: np.ndarray) -> float:
  """
    Function for computing interpretation entropy of the coefficients of a fitted linear model.

    Args:
        coef_array (np.ndarray): Numpy array with coefficients of all the features of the fitted linear model.

    Returns:
        float: Interpretation entropy of the coefficients of a fitted linear model.
  """
  a = np.absolute(coef_array)/np.sum(np.absolute(coef_array))
  t = 0
  for i in range(a.shape[0]):
    if a[i]==0:
      continue
    else:
      t += a[i]*np.log(a[i])
  return -t/np.log(coef_array.shape[0])

unfaithfulness_calc(k, N, data, predict_proba, best_parameters_master, labels, best_interp_master, best_parameters_converted, best_unfaithfulness_master, tot_feat, selected_features, seed)

Function for implementing linear regression using stochastic gradient descent.

Parameters:

Name Type Description Default
k int

Number of features for building a surrogate, local, linear model.

required
N int

Number of features selected for detailed analysis.

required
data np.ndarray

Numpy 2D array containing the similarity-weighted training data for the black-box model. Samples along rows and features along columns.

required
predict_proba np.ndarray

Numpy array containing metastable state prediction probabilities for a perturbed neighborhood corresponding to a specific sample. Includes the state for which the original sample has the highest probability.

required
best_parameters_master list

List of lists that saves the best fit coefficients for linear models built using k=1, .., N features.

required
best_interp_master list

List that saves the interpretation entropy for the linear models built using k=1,...,N features.

required
best_parameters_converted list

List of lists that saves the best fit coefficients for linear models built using k=1, .., N features, and imputes the discarded features in the initial MDTerp round with 0 importance to preserve feature ID.

required
best_unfaithfulness_master list

List that saves the unfaithfulness of the best-fit linear models built using k=1, ..., N features.

required
tot_feat int

Total number of features in the dataset.

required
selected_features np.ndarray

Indices of the features selected for detailed MDTerp analysis.

required
seed int

Random seed.

required

Returns:

Type Description
None

None

Source code in MDTerp/final_analysis.py
def unfaithfulness_calc(k: int, N: int, data: np.ndarray, predict_proba: np.ndarray, best_parameters_master: list, labels: np.ndarray, best_interp_master: list, best_parameters_converted: list, best_unfaithfulness_master: list, tot_feat: int, selected_features: np.ndarray, seed: int) -> None:
  """
    Function for implementing linear regression using stochastic gradient descent.

    Args:
        k (int): Number of features for building a surrogate, local, linear model.
        N (int): Number of features selected for detailed analysis.
        data (np.ndarray): Numpy 2D array containing the similarity-weighted training data for the black-box model. Samples along rows and features along columns.
        predict_proba (np.ndarray): Numpy array containing metastable state prediction probabilities for a perturbed neighborhood corresponding to a specific sample. Includes the state for which the original sample has the highest probability.
        best_parameters_master (list): List of lists that saves the best fit coefficients for linear models built using k=1, .., N features.
        best_interp_master (list): List that saves the interpretation entropy for the linear models built using k=1,...,N features.
        best_parameters_converted (list): List of lists that saves the best fit coefficients for linear models built using k=1, .., N features, and imputes the discarded features in the initial MDTerp round with 0 importance to preserve feature ID.
        best_unfaithfulness_master (list): List that saves the unfaithfulness of the best-fit linear models built using k=1, ..., N features.
        tot_feat (int): Total number of features in the dataset.
        selected_features (np.ndarray): Indices of the features selected for detailed MDTerp analysis.
        seed (int): Random seed.

    Returns:
        None
  """ 
  models = []
  TERP_SGD_parameters = []
  TERP_SGD_unfaithfulness = []
  TERP_SGD_interp = []
  if k == 1:
    inherited_nonzero = np.array([],dtype=int)
    inherited_zero = np.arange(N)

  elif k > 1:
    inherited_nonzero = np.nonzero(best_parameters_master[k-2][:-1])[0]
    inherited_zero = np.where(best_parameters_master[k-2][:-1] == 0)[0]

  for i in range(N-k+1):
    models.append(np.append(inherited_nonzero, inherited_zero[i]))
    result_a, result_b = ridge_regression(data[:,models[i]], labels, seed)
    parameters = np.zeros((N+1))
    parameters[models[i]] = result_a
    parameters[-1] = result_b
    TERP_SGD_parameters.append(parameters)
    residual = np.corrcoef(labels[:,0],(np.column_stack((data, np.ones((data.shape[0]))))@parameters[:]).reshape(-1,1)[:,0])[0,1]
    TERP_SGD_unfaithfulness.append(1-np.absolute(residual))
    TERP_SGD_interp.append(interpretation_entropy(TERP_SGD_parameters[-1][:-1]))
    TERP_SGD_IFE = np.array(TERP_SGD_unfaithfulness)

  best_model = np.argsort(TERP_SGD_IFE)[0]
  best_parameters_master.append(TERP_SGD_parameters[best_model])
  best_interp_master.append(TERP_SGD_interp[best_model])

  temp_coef_1 = TERP_SGD_parameters[best_model][:-1]
  temp_coef_2 = np.zeros((tot_feat))
  temp_coef_2[selected_features] = copy.deepcopy(temp_coef_1)
  best_parameters_converted.append(temp_coef_2)
  best_unfaithfulness_master.append(TERP_SGD_unfaithfulness[best_model])

  surrogate_pred = data@TERP_SGD_parameters[best_model][:-1]

zeta(U, S, theta)

Function for computing the interpretation free energy.

Parameters:

Name Type Description Default
U np.ndarray

Numpy array with unfaithfulness of the best models for number of features, k = 1, ..., N.

required
S np.ndarray

Numpy array with interpretation entropy of the best models for number of features, k = 1, ..., N.

required
theta float

Temperature of the fitted linear model.

required

Returns:

Type Description
np.ndarray

Interpretation free energy.

Source code in MDTerp/final_analysis.py
def zeta(U: np.ndarray,S: np.ndarray,theta: float) -> np.ndarray:
  """
    Function for computing the interpretation free energy.

    Args:
        U (np.ndarray): Numpy array with unfaithfulness of the best models for number of features, k = 1, ..., N.
        S (np.ndarray): Numpy array with interpretation entropy of the best models for number of features, k = 1, ..., N.
        theta (float): Temperature of the fitted linear model.

    Returns:
        np.ndarray: Interpretation free energy.
  """
  return U + theta*S