示例#1
0
def test_single_text_to_text():
    """ Just make sure the test_plot function doesn't crash.
    """

    test_values = np.array([[10.61284012, 3.28389317],
                            [-3.77245945, 10.76889759], [0., 0.]])

    test_base_values = np.array([-6.12535715, -12.87049389])

    test_data = np.array(['▁Hello ', '▁world ', ' '], dtype='<U7')

    test_output_names = np.array(['▁Hola', '▁mundo'], dtype='<U6')

    test_clustering = np.array([[0., 1., 12., 2.], [3., 2., 13., 3.]])

    test_hierarchical_values = np.array([[13.91739416, 7.09603131],
                                         [-0.4679054, 14.58103573], [0., 0.],
                                         [-6.60910809, -7.62427628], [0., 0.]])

    shap_values_test = shap.Explanation(
        values=[test_values],
        base_values=[test_base_values],
        data=[test_data],
        output_names=test_output_names,
        feature_names=test_base_values,
        clustering=[test_clustering],
        hierarchical_values=[test_hierarchical_values])
    shap.plots.text(shap_values_test)
示例#2
0
 def _build_txt_shap_values(shap_values, original_tokens):
     assert (
         shap_values.shape[0] == original_tokens.shape[0]
     ), "need same number of shap values and tokens"
     return shap.Explanation(
         values=shap_values.values,
         base_values=shap_values.base_values,
         data=original_tokens,
     )
示例#3
0
文件: ml.py 项目: soerendip/toolbox
 def waterfall(self, i, **kwargs):
     shap_values = self.shap_values
     self._base_values = shap_values[i][0].base_values
     self._values = shap_values[i].values
     shap_object = shap.Explanation(
         base_values=self._base_values,
         values=self._values,
         feature_names=self.feature_names,
         #instance_names = self._instance_names,
         data=shap_values[i].data,
     )
     shap.plots.waterfall(shap_object, **kwargs)
示例#4
0
    def visualize_text_plot(self, shap_values, input_ids):

        # SHAP force plot

        sv, new_tweet_fancy = self.aggregate_tweet_tokens(
            shap_values, input_ids)
        sv = sv[0:len(new_tweet_fancy)]
        avg = np.mean(np.sqrt(
            np.abs(sv)))  # this was just chosen empirically for now
        sv = shap.Explanation(values=sv, data=new_tweet_fancy, base_values=0)

        splt.text(sv, cmax=avg)
示例#5
0
    def _concat_shap_values(shap_values: List):
        """ Build an explanation object with all shapley values concatenated
        """
        values = np.concatenate([s.values for s in shap_values], axis=0)
        data = np.concatenate([s.data for s in shap_values], axis=0)
        base_values = np.concatenate([s.base_values for s in shap_values],
                                     axis=0)
        clustering = np.concatenate(
            [s.clustering for s in shap_values],
            axis=0) if shap_values[0].clustering is not None else None
        hierarchical = np.concatenate(
            [s.hierarchical_values for s in shap_values],
            axis=0) if shap_values[0].hierarchical_values is not None else None

        return shap.Explanation(values=values,
                                base_values=base_values,
                                data=data,
                                clustering=clustering,
                                hierarchical_values=hierarchical)
示例#6
0
 def get_shapley_value_data(train, replication=True, dict_map_result={}):
     dataset_type = ''
     shap_values = np.concatenate(
         [train[0]['shap_values_train'], train[0]['shap_values_test']],
         axis=0)
     # shap_values = train[0]['shap_values_train']
     X = pd.concat([train[1]['X_train'], train[1]['X_valid']], axis=0)
     exval = train[2]['explainer_train']
     auc_train = train[3]['AUC_train']
     auc_test = train[3]['AUC_test']
     ids = list(train[3]['ID_train'.format(dataset_type)]) + list(
         train[3]['ID_test'.format(dataset_type)])
     labels_pred = list(
         train[3]['y_pred_train'.format(dataset_type)]) + list(
             train[3]['y_pred_test'.format(dataset_type)])
     labels_actual = list(train[3]['y_train'.format(dataset_type)]) + list(
         train[3]['y_test'.format(dataset_type)])
     shap_values_updated = shap.Explanation(values=np.copy(shap_values),
                                            base_values=np.array([exval] *
                                                                 len(X)),
                                            data=np.copy(X.values),
                                            feature_names=X.columns)
     train_samples = len(train[1]['X_train'])
     test_samples = len(train[1]['X_valid'])
     X.columns = [
         '{}'.format(dict_map_result[col]) if dict_map_result.get(
             col, None) is not None else col for col in list(X.columns)
     ]
     # X.columns = ['({}) {}'.format(dict_map_result[col], col) if dict_map_result.get(col, None) is not None else col for col in list(X.columns)]
     shap_values_updated = copy.deepcopy(shap_values_updated)
     patient_index = [
         hashlib.md5(str(s).encode()).hexdigest() for e, s in enumerate(ids)
     ]
     return (X, shap_values, exval, patient_index, auc_train, auc_test,
             labels_actual, labels_pred, shap_values_updated, train_samples,
             test_samples)
示例#7
0
    def _build_img_shap_values(
        shap_values: shap.Explanation, segment_map: np.ndarray, image: np.ndarray
    ):
        """TODO: docs

        Args:
            shap_values: shap values for 1 sample, shape = (#segments, #labels)
            segment_map: an 2-d array, each element is the segment number for the pixel in
                the original image
            image: original image to explain, shape (H, W, C)

        Returns:
            shap.Explanation object, shape (1, 3D_image_shape, #labels)
            image shap values where data is the original image and the shap value
            of each pixel is the average across the entire segment

        """
        assert (
            len(shap_values.shape) == 2
        ), "shap_values should have shape (#segments, #labels)"
        assert len(image.shape) == 3, "image should have shape (1, H, W, C)"
        # build output array
        out = np.zeros(image.shape + (shap_values.shape[-1],))
        num_channels = 3
        # loop through each segment's shap values - vals.shape = #labels
        for seg, vals in enumerate(shap_values):
            # calculate the average for each pixel in each channel
            condition = segment_map == seg  # filter by segment
            shaps_per_pixel = vals.values / (np.sum(condition) * num_channels)
            out[condition] = shaps_per_pixel
        # explanation object
        shap_val = shap.Explanation(
            values=out, base_values=shap_values.base_values, data=image.astype(float)
        )
        shap_val.segment_map = segment_map  # store segment map for current image
        return shap_val
示例#8
0
def _shap_explain_multitask_model_cate(cme_model,
                                       multitask_model_cate,
                                       X,
                                       d_t,
                                       d_y,
                                       featurizer=None,
                                       feature_names=None,
                                       treatment_names=None,
                                       output_names=None,
                                       input_names=None,
                                       background_samples=100):
    """
    Method to explain a final cate model that is represented in a multi-task manner, i.e. the prediction
    of the method is of dimension equal to the number of treatments and represents the const_marginal_effect
    vector for all treatments.

    Parameters
    ----------
    cme_model: function
        const_marginal_effect function.
    multitask_model_cate: a single estimator or a list of estimators of length d_y if d_y > 1
        the model's final stage model whose predict represents the const_marginal_effect for
        all treatments (or list of models, one for each outcome)
    X: (m, d_x) matrix
        Features for each sample.
    d_t: tuple of int
        Tuple of number of treatment (exclude control in discrete treatment scenario).
    d_y: tuple of int
        Tuple of number of outcome.
    featurizer: optional None or instance of featurizer
        Fitted Featurizer of feature X.
    feature_names: optional None or list of strings of length X.shape[1] (Default=None)
        The names of input features.
    treatment_names: optional None or list (Default=None)
        The name of treatment. In discrete treatment scenario, the name should not include the name of
        the baseline treatment (i.e. the control treatment, which by default is the alphabetically smaller)
    output_names:  optional None or list (Default=None)
        The name of the outcome.
    input_names: dictionary or None
        The parsed names of variables at fit input time of cate estimators
    background_samples: int or None, (Default=100)
        How many samples to use to compute the baseline effect. If None then all samples are used.

    Returns
    -------
    shap_outs: nested dictionary of Explanation object
        A nested dictionary by using each output name (e.g. "Y0" when `output_names=None`) and
        each treatment name (e.g. "T0" when `treatment_names=None`) as key
        and the shap_values explanation object as value.
    """
    d_t_, d_y_ = d_t, d_y
    feature_names_, treatment_names_ = feature_names, treatment_names,
    output_names_, input_names_ = output_names, input_names
    (dt, dy, treatment_names, output_names,
     feature_names) = _define_names(d_t, d_y, treatment_names, output_names,
                                    feature_names, input_names)
    if featurizer is not None:
        F = featurizer.transform(X)
    else:
        F = X
    if dy == 1 and (not isinstance(multitask_model_cate, list)):
        multitask_model_cate = [multitask_model_cate]

    # define masker by using entire dataset, otherwise Explainer will only sample 100 obs by default.
    bg_samples = F.shape[0] if background_samples is None else min(
        background_samples, F.shape[0])
    background = shap.maskers.Independent(F, max_samples=bg_samples)
    shap_outs = defaultdict(dict)
    for j in range(dy):
        try:
            explainer = shap.Explainer(multitask_model_cate[j],
                                       background,
                                       feature_names=feature_names)
        except Exception as e:
            print(
                "Final model can't be parsed, explain const_marginal_effect() instead!",
                repr(e))
            return _shap_explain_cme(cme_model,
                                     X,
                                     d_t_,
                                     d_y_,
                                     feature_names=None,
                                     treatment_names=treatment_names_,
                                     output_names=output_names_,
                                     input_names=input_names_,
                                     background_samples=background_samples)

        shap_out = explainer(F)
        if dt > 1:
            for i in range(dt):
                base_values = shap_out.base_values[..., i]
                values = shap_out.values[..., i]
                main_effects = None if shap_out.main_effects is None else shap_out.main_effects[
                    ..., i]
                shap_out_new = shap.Explanation(
                    values,
                    base_values=base_values,
                    data=shap_out.data,
                    main_effects=main_effects,
                    feature_names=shap_out.feature_names)
                shap_outs[output_names[j]][treatment_names[i]] = shap_out_new
        else:
            shap_outs[output_names[j]][treatment_names[0]] = shap_out
    return shap_outs
示例#9
0
def _shap_explain_joint_linear_model_cate(model_final,
                                          X,
                                          d_t,
                                          d_y,
                                          fit_cate_intercept,
                                          feature_names=None,
                                          treatment_names=None,
                                          output_names=None,
                                          input_names=None,
                                          background_samples=100):
    """
    Method to explain `model_cate` of parametric final stage that was fitted on the cross product of
    `featurizer(X)` and T.

    Parameters
    ----------
    model_final: a single estimator
        the model's final stage model.
    X: matrix
        Featurized X
    d_t: tuple of int
        Tuple of number of treatment (exclude control in discrete treatment scenario).
    d_y: tuple of int
        Tuple of number of outcome.
    fit_cate_intercept: bool
        Whether the first entry of the coefficient of the joint linear model associated with
        each treatment, is an intercept.
    feature_names: optional None or list of strings of length X.shape[1] (Default=None)
        The names of input features.
    treatment_names: optional None or list (Default=None)
        The name of treatment. In discrete treatment scenario, the name should not include the name of
        the baseline treatment (i.e. the control treatment, which by default is the alphabetically smaller)
    output_names:  optional None or list (Default=None)
        The name of the outcome.
    input_names: dictionary or None
        The parsed names of variables at fit input time of cate estimators
    background_samples: int or None, (Default=100)
        How many samples to use to compute the baseline effect. If None then all samples are used.

    Returns
    -------
    shap_outs: nested dictionary of Explanation object
        A nested dictionary by using each output name (e.g. "Y0" when `output_names=None`) and
        each treatment name (e.g. "T0" when `treatment_names=None`) as key
        and the shap_values explanation object as value.
    """
    (d_t, d_y, treatment_names, output_names,
     feature_names) = _define_names(d_t, d_y, treatment_names, output_names,
                                    feature_names, input_names)
    X, T = broadcast_unit_treatments(X, d_t)
    X = cross_product(X, T)
    d_x = X.shape[1]
    # define the index of d_x to filter for each given T
    ind_x = np.arange(d_x).reshape(d_t, -1)
    if fit_cate_intercept:  # skip intercept
        ind_x = ind_x[:, 1:]
    shap_outs = defaultdict(dict)
    for i in range(d_t):
        # filter X after broadcast with T for each given T
        X_sub = X[T[:, i] == 1]
        # define masker by using entire dataset, otherwise Explainer will only sample 100 obs by default.
        bg_samples = X_sub.shape[0] if background_samples is None else min(
            background_samples, X_sub.shape[0])
        background = shap.maskers.Independent(X_sub, max_samples=bg_samples)
        explainer = shap.Explainer(model_final, background)
        shap_out = explainer(X_sub)

        data = shap_out.data[:, ind_x[i]]
        if d_y > 1:
            for j in range(d_y):
                base_values = shap_out.base_values[..., j]
                main_effects = None if shap_out.main_effects is None else shap_out.main_effects[
                    ..., ind_x[i], j]
                values = shap_out.values[..., ind_x[i], j]
                shap_out_new = shap.Explanation(values,
                                                base_values=base_values,
                                                data=data,
                                                main_effects=main_effects,
                                                feature_names=feature_names)
                shap_outs[output_names[j]][treatment_names[i]] = shap_out_new
        else:
            values = shap_out.values[..., ind_x[i]]
            main_effects = shap_out.main_effects[..., ind_x[i], 0]
            shap_out_new = shap.Explanation(values,
                                            base_values=shap_out.base_values,
                                            data=data,
                                            main_effects=main_effects,
                                            feature_names=feature_names)
            shap_outs[output_names[0]][treatment_names[i]] = shap_out_new

    return shap_outs
示例#10
0
def _shap_explain_cme(cme_model,
                      X,
                      d_t,
                      d_y,
                      feature_names=None,
                      treatment_names=None,
                      output_names=None,
                      input_names=None,
                      background_samples=100):
    """
    Method to explain `const_marginal_effect` function using shap Explainer().

    Parameters
    ----------
    cme_models: function
        const_marginal_effect function.
    X: (m, d_x) matrix
        Features for each sample. Should be in the same shape of fitted X in final stage.
    d_t: tuple of int
        Tuple of number of treatment (exclude control in discrete treatment scenario).
    d_y: tuple of int
        Tuple of number of outcome.
    feature_names: optional None or list of strings of length X.shape[1] (Default=None)
        The names of input features.
    treatment_names: optional None or list (Default=None)
        The name of treatment. In discrete treatment scenario, the name should not include the name of
        the baseline treatment (i.e. the control treatment, which by default is the alphabetically smaller)
    output_names:  optional None or list (Default=None)
        The name of the outcome.
    input_names: dictionary or None
        The parsed names of variables at fit input time of cate estimators
    background_samples: int or None, (Default=100)
        How many samples to use to compute the baseline effect. If None then all samples are used.

    Returns
    -------
    shap_outs: nested dictionary of Explanation object
        A nested dictionary by using each output name (e.g. "Y0" when `output_names=None`) and
        each treatment name (e.g. "T0" when `treatment_names=None`) as key
        and the shap_values explanation object as value.

    """
    (dt, dy, treatment_names, output_names,
     feature_names) = _define_names(d_t, d_y, treatment_names, output_names,
                                    feature_names, input_names)
    # define masker by using entire dataset, otherwise Explainer will only sample 100 obs by default.
    bg_samples = X.shape[0] if background_samples is None else min(
        background_samples, X.shape[0])
    background = shap.maskers.Independent(X, max_samples=bg_samples)
    shap_outs = defaultdict(dict)
    for i in range(dy):

        def cmd_func(X):
            return cme_model(X).reshape(-1, dy, dt)[:, i, :]

        explainer = shap.Explainer(cmd_func,
                                   background,
                                   feature_names=feature_names)
        shap_out = explainer(X)
        if dt > 1:
            for j in range(dt):
                base_values = shap_out.base_values[..., j]
                values = shap_out.values[..., j]
                main_effects = None if shap_out.main_effects is None else shap_out.main_effects[
                    ..., j]
                shap_out_new = shap.Explanation(
                    values,
                    base_values=base_values,
                    data=shap_out.data,
                    main_effects=main_effects,
                    feature_names=shap_out.feature_names)
                shap_outs[output_names[i]][treatment_names[j]] = shap_out_new
        else:
            base_values = shap_out.base_values[..., 0]
            shap_out_new = shap.Explanation(
                shap_out.values,
                base_values=base_values,
                data=shap_out.data,
                main_effects=shap_out.main_effects,
                feature_names=shap_out.feature_names)
            shap_outs[output_names[i]][treatment_names[0]] = shap_out_new
    return shap_outs
示例#11
0
def _shap_explain_model_cate(cme_model,
                             models,
                             X,
                             d_t,
                             d_y,
                             featurizer=None,
                             feature_names=None,
                             treatment_names=None,
                             output_names=None,
                             input_names=None,
                             background_samples=100):
    """
    Method to explain `model_cate` using shap Explainer(), will instead explain `const_marignal_effect`
    if `model_cate` can't be parsed. Models should be a list of length d_t. Each element in the list of
    models represents the const_marginal_effect associated with each treatments and for all outcomes, i.e.
    the outcome of the predict method of each model should be of length d_y.

    Parameters
    ----------
    cme_models: function
        const_marginal_effect function.
    models: a single estimator or a list of estimators with one estimator per treatment
        models for the model's final stage model.
    X: (m, d_x) matrix
        Features for each sample.
    d_t: tuple of int
        Tuple of number of treatment (exclude control in discrete treatment scenario.
    d_y: tuple of int
        Tuple of number of outcome.
    featurizer: optional None or instance of featurizer
        Fitted Featurizer of feature X.
    feature_names: optional None or list of strings of length X.shape[1] (Default=None)
        The names of input features.
    treatment_names: optional None or list (Default=None)
        The name of treatment. In discrete treatment scenario, the name should not include the name of
        the baseline treatment (i.e. the control treatment, which by default is the alphabetically smaller)
    output_names:  optional None or list (Default=None)
        The name of the outcome.
    input_names: dictionary or None
        The parsed names of variables at fit input time of cate estimators
    background_samples: int or None, (Default=100)
        How many samples to use to compute the baseline effect. If None then all samples are used.

    Returns
    -------
    shap_outs: nested dictionary of Explanation object
        A nested dictionary by using each output name (e.g. "Y0" when `output_names=None`) and
        each treatment name (e.g. "T0" when `treatment_names=None`) as key
        and the shap_values explanation object as value.
    """
    d_t_, d_y_ = d_t, d_y
    feature_names_, treatment_names_ = feature_names, treatment_names,
    output_names_, input_names_ = output_names, input_names
    (dt, dy, treatment_names, output_names,
     feature_names) = _define_names(d_t, d_y, treatment_names, output_names,
                                    feature_names, input_names)
    if featurizer is not None:
        F = featurizer.transform(X)
    else:
        F = X
    if not isinstance(models, list):
        models = [models]
    assert len(
        models
    ) == dt, "Number of final stage models don't equals to number of treatments!"
    # define masker by using entire dataset, otherwise Explainer will only sample 100 obs by default.
    bg_samples = F.shape[0] if background_samples is None else min(
        background_samples, F.shape[0])
    background = shap.maskers.Independent(F, max_samples=bg_samples)

    shap_outs = defaultdict(dict)
    for i in range(dt):
        try:
            explainer = shap.Explainer(models[i],
                                       background,
                                       feature_names=feature_names)
        except Exception as e:
            print(
                "Final model can't be parsed, explain const_marginal_effect() instead!",
                repr(e))
            return _shap_explain_cme(cme_model,
                                     X,
                                     d_t_,
                                     d_y_,
                                     feature_names=None,
                                     treatment_names=treatment_names_,
                                     output_names=output_names_,
                                     input_names=input_names_,
                                     background_samples=background_samples)
        shap_out = explainer(F)
        if dy > 1:
            for j in range(dy):
                base_values = shap_out.base_values[..., j]
                values = shap_out.values[..., j]
                main_effects = None if shap_out.main_effects is None else shap_out.main_effects[
                    ..., j]
                shap_out_new = shap.Explanation(
                    values,
                    base_values=base_values,
                    data=shap_out.data,
                    main_effects=main_effects,
                    feature_names=shap_out.feature_names)
                shap_outs[output_names[j]][treatment_names[i]] = shap_out_new
        else:
            shap_outs[output_names[0]][treatment_names[i]] = shap_out

    return shap_outs
}
workingday_dict = {'Yes': 1, 'No': 0}

data = {
    'season': [seasons_dict[season]],
    'workingday': [workingday_dict[workingday]],
    'weather': [int(weather)],
    'temp': [temp],
    'humidity': [humidity],
    'year': [year],
    'month': [month_dict[month]],
    'hour': [hour]
}
data = pd.DataFrame.from_dict(data, orient='columns')
model = load_model()
shap_val, explainer = prepare_shap(model, data)
explanation = shap.Explanation(values=shap_val[0],
                               base_values=explainer.expected_value,
                               data=data.iloc[0, :],
                               feature_names=data.columns.tolist())

if submit:
    pred = prediction_maker(model, data)
    st.write(f'Predicted demand is: {int(np.round(pred[0]))}')
    st.write(
        'Below, there is an explanation on how this prediction is influenced by given factors'
    )
    fig, ax = plt.subplots()
    shap.plots.waterfall(explanation, show=False)
    st.pyplot(fig)
示例#13
0
def app():
    @st.cache(hash_funcs={"MyUnhashableClass": lambda _: None})
    def load_model1():
        with open('saved_models/trainXGB_class_map.pkl', 'rb') as f:
            class_names = list(pickle.load(f))
        return class_names

    class_names = load_model1()

    st.write("## SHAP Model Interpretation")

    @st.cache(hash_funcs={"MyUnhashableClass": lambda _: None})
    def load_model2():
        with open('saved_models/trainXGB_gpu.aucs', 'rb') as f:
            result_aucs = pickle.load(f)
        return result_aucs

    result_aucs = load_model2()

    if len(result_aucs[class_names[0]]) == 3:
        mean_train_aucs = round(
            np.mean([result_aucs[i][0] for i in class_names]), 2)
        mean_test_aucs = round(
            np.mean([result_aucs[i][1] for i in class_names]), 2)
        df_res = pd.DataFrame({
            'class name':
            class_names + ['MEAN'],
            'Discovery Cohort AUC':
            ["{:.2f}".format(result_aucs[i][0])
             for i in class_names] + [mean_train_aucs],
            'Replication Cohort AUC':
            ["{:.2f}".format(result_aucs[i][1])
             for i in class_names] + [mean_test_aucs]
        })
        replication_avail = True
    else:
        df_res = pd.DataFrame({
            'class name':
            class_names,
            'Train AUC':
            ["{:.2f}".format(result_aucs[i][0]) for i in class_names],
            'Test AUC':
            ["{:.2f}".format(result_aucs[i][1]) for i in class_names]
        })
        replication_avail = False

    @st.cache(allow_output_mutation=True, ttl=24 * 3600)
    def get_shapley_value_data(train, replication=True, dict_map_result={}):
        dataset_type = ''
        shap_values = np.concatenate(
            [train[0]['shap_values_train'], train[0]['shap_values_test']],
            axis=0)
        # shap_values = train[0]['shap_values_train']
        X = pd.concat([train[1]['X_train'], train[1]['X_valid']], axis=0)
        exval = train[2]['explainer_train']
        auc_train = train[3]['AUC_train']
        auc_test = train[3]['AUC_test']
        ids = list(train[3]['ID_train'.format(dataset_type)]) + list(
            train[3]['ID_test'.format(dataset_type)])
        labels_pred = list(
            train[3]['y_pred_train'.format(dataset_type)]) + list(
                train[3]['y_pred_test'.format(dataset_type)])
        labels_actual = list(train[3]['y_train'.format(dataset_type)]) + list(
            train[3]['y_test'.format(dataset_type)])
        shap_values_updated = shap.Explanation(values=np.copy(shap_values),
                                               base_values=np.array([exval] *
                                                                    len(X)),
                                               data=np.copy(X.values),
                                               feature_names=X.columns)
        train_samples = len(train[1]['X_train'])
        test_samples = len(train[1]['X_valid'])
        X.columns = [
            '{}'.format(dict_map_result[col]) if dict_map_result.get(
                col, None) is not None else col for col in list(X.columns)
        ]
        # X.columns = ['({}) {}'.format(dict_map_result[col], col) if dict_map_result.get(col, None) is not None else col for col in list(X.columns)]
        shap_values_updated = copy.deepcopy(shap_values_updated)
        patient_index = [
            hashlib.md5(str(s).encode()).hexdigest() for e, s in enumerate(ids)
        ]
        return (X, shap_values, exval, patient_index, auc_train, auc_test,
                labels_actual, labels_pred, shap_values_updated, train_samples,
                test_samples)

    st.write("## Introduction")
    st.write("""
        The Shapley additive explanations (SHAP) approach was used to evaluate each feature’s influence in the ensemble learning. This approach, used in game theory, assigned an importance (Shapley) value to each feature to determine a player’s contribution to success. Shapley explanations enhance understanding by creating accurate explanations for each observation in a dataset. They bolster trust when the critical variables for specific records conform to human domain knowledge and reasonable expectations. 
        We used the one-vs-rest technique for multiclass classification. Based on that, we trained a separate binary classification model for each class.
        """)
    feature_set_my = class_names[0]

    @st.cache(hash_funcs={"MyUnhashableClass": lambda _: None})
    def load_model3():
        with open('saved_models/trainXGB_gpu_{}.data'.format(feature_set_my),
                  'rb') as f:
            train = pickle.load(f)
        return train

    train = load_model3()
    data_load_state = st.text('Loading data...')
    cloned_output = copy.deepcopy(
        get_shapley_value_data(train,
                               replication=replication_avail,
                               dict_map_result=dict_map_result))
    data_load_state.text("Done Data Loading! (using st.cache)")
    X, shap_values, exval, patient_index, auc_train, auc_test, labels_actual, labels_pred, shap_values_up, len_train, len_test = cloned_output

    st.write("## Results")
    st.write("### Performance of Surrogate Model")
    st.table(df_res.set_index('class name').astype(str))

    @st.cache(hash_funcs={"MyUnhashableClass": lambda _: None})
    def load_model3():
        shap_values_list = []
        for classname in class_names:
            with open('saved_models/trainXGB_gpu_{}.data'.format(classname),
                      'rb') as f:
                train_temp = pickle.load(f)
                shap_values_list.append(
                    np.concatenate([
                        train_temp[0]['shap_values_train'],
                        train_temp[0]['shap_values_test']
                    ],
                                   axis=0))
        shap_values = np.mean(shap_values_list, axis=0)
        return shap_values

    shap_values = load_model3()
    import sklearn
    # st.write(sum(labels_actual[:len_train]), sum(np.array(labels_pred[:len_train])>0.5))
    # st.write(sum(labels_actual[len_train:]), sum(np.array(labels_pred[len_train:])>0.5))

    st.write('## Summary Plot')
    st.write(
        """Shows top-11 features that have the most significant impact on the classification model."""
    )
    if st.checkbox("Show Summary Plot"):
        shap_type = 'trainXGB'
        col1, col2, col2111 = st.beta_columns(3)
        with col1:
            st.write('---')
            temp = shap.Explanation(values=np.copy(shap_values),
                                    base_values=np.array([exval] * len(X)),
                                    data=np.copy(X.values),
                                    feature_names=X.columns)
            fig, ax = plt.subplots(figsize=(10, 15))
            shap.plots.beeswarm(
                shap.Explanation(values=np.copy(shap_values),
                                 base_values=np.array([exval] * len(X)),
                                 data=np.copy(X.values),
                                 feature_names=X.columns),
                show=False,
                max_display=20,
                order=shap.Explanation(values=np.copy(shap_values),
                                       base_values=np.array([exval] * len(X)),
                                       data=np.copy(X.values),
                                       feature_names=X.columns).mean(0).abs,
                plot_size=0.47)  # , return_objects=True
            # shap.plots.beeswarm(temp, order=temp.mean(0).abs, show=False, max_display=20) # , return_objects=True
            st.pyplot(fig)
            st.write('---')
        with col2:
            st.write('---')
            fig, ax = plt.subplots(figsize=(10, 15))
            temp = shap.Explanation(values=np.copy(shap_values),
                                    base_values=np.array([exval] * len(X)),
                                    data=np.copy(X.values),
                                    feature_names=X.columns)
            shap.plots.bar(
                shap.Explanation(values=np.copy(shap_values),
                                 base_values=np.array([exval] * len(X)),
                                 data=np.copy(X.values),
                                 feature_names=X.columns).mean(0),
                show=False,
                max_display=20,
                order=shap.Explanation(values=np.copy(shap_values),
                                       base_values=np.array([exval] * len(X)),
                                       data=np.copy(X.values),
                                       feature_names=X.columns).mean(0).abs)
            # shap.plots.bar(temp, order=temp.mean(0).abs, show=False, max_display=20)
            st.pyplot(fig)
            st.write('---')
        with col2111:
            st.write('---')
            fig, ax = plt.subplots(figsize=(10, 15))
            temp = shap.Explanation(values=np.copy(shap_values),
                                    base_values=np.array([exval] * len(X)),
                                    data=np.copy(X.values),
                                    feature_names=X.columns)
            shap.plots.bar(
                shap.Explanation(values=np.copy(shap_values),
                                 base_values=np.array([exval] * len(X)),
                                 data=np.copy(X.values),
                                 feature_names=X.columns).abs.mean(0),
                show=False,
                max_display=20,
                order=shap.Explanation(values=np.copy(shap_values),
                                       base_values=np.array([exval] * len(X)),
                                       data=np.copy(X.values),
                                       feature_names=X.columns).mean(0).abs)
            # shap.plots.bar(temp, order=temp.mean(0).abs, show=False, max_display=20)
            st.pyplot(fig)
            st.write('---')

    st.write('## Dependence Plots')
    st.write(
        """We can observe the interaction effects of different features in for predictions. To help reveal these interactions dependence_plot automatically lists (top-3) potential features for coloring. 
    Furthermore, we can observe the relationship betweem features and SHAP values for prediction using the dependence plots, which compares the actual feature value (x-axis) against the SHAP score (y-axis). 
    It shows that the effect of feature values is not a simple relationship where increase in the feature value leads to consistent changes in model output but a complicated non-linear relationship."""
    )
    if st.checkbox("Show Dependence Plots"):
        feature_name = st.selectbox('Select a feature for dependence plot',
                                    options=list(X.columns))
        try:
            inds = shap.utils.potential_interactions(
                shap.Explanation(values=np.copy(shap_values),
                                 base_values=np.array([exval] * len(X)),
                                 data=np.copy(X.values),
                                 feature_names=X.columns)[:, feature_name],
                shap.Explanation(values=np.copy(shap_values),
                                 base_values=np.array([exval] * len(X)),
                                 data=np.copy(X.values),
                                 feature_names=X.columns))
        except:
            st.info("Select Another Feature")
        st.write(
            'Top3 Potential Interactions for ***{}***'.format(feature_name))
        col3, col4, col5 = st.beta_columns(3)
        with col3:
            shap.dependence_plot(feature_name,
                                 np.copy(shap_values),
                                 X.copy(),
                                 interaction_index=list(X.columns).index(
                                     list(X.columns)[inds[0]]))
            st.pyplot()
        with col4:
            shap.dependence_plot(feature_name,
                                 np.copy(shap_values),
                                 X.copy(),
                                 interaction_index=list(X.columns).index(
                                     list(X.columns)[inds[1]]))
            st.pyplot()
        with col5:
            shap.dependence_plot(feature_name,
                                 np.copy(shap_values),
                                 X.copy(),
                                 interaction_index=list(X.columns).index(
                                     list(X.columns)[inds[2]]))
            st.pyplot()

        # labels_pred_new = np.array(labels_pred, dtype=np.float)

    import random
    select_random_samples = np.random.choice(shap_values.shape[0], 800)

    new_X = X.iloc[select_random_samples]
    new_shap_values = shap_values[select_random_samples, :]
    new_labels_pred = np.array(labels_pred,
                               dtype=np.float64)[select_random_samples]

    st.write('## Statistics for Individual Classes')
    st.write("#### Select the class")
    feature_set_my = st.selectbox("", ['Select'] + class_names, index=0)
    if not feature_set_my == "Select":

        @st.cache(hash_funcs={"MyUnhashableClass": lambda _: None})
        def load_model9():
            with open(
                    'saved_models/trainXGB_gpu_{}.data'.format(feature_set_my),
                    'rb') as f:
                train = pickle.load(f)
            return train

        train = load_model9()
        data_load_state = st.text('Loading data...')
        cloned_output = copy.deepcopy(
            get_shapley_value_data(train,
                                   replication=replication_avail,
                                   dict_map_result=dict_map_result))
        data_load_state.text("Done Data Loading! (using st.cache)")
        X, shap_values, exval, patient_index, auc_train, auc_test, labels_actual, labels_pred, shap_values_up, len_train, len_test = cloned_output
        col0, col00 = st.beta_columns(2)
        with col0:
            st.write("### Data Statistics")
            st.info('Total Features: {}'.format(X.shape[1]))
            st.info('Total Samples: {} (Discovey: {}, Replication: {})'.format(
                X.shape[0], len_train, len_test))

        with col00:
            st.write("### ML Model Performance")
            st.info('AUC Discovery Cohort: {}'.format(round(auc_train, 2)))
            st.info('AUC Replication Cohort: {}'.format(round(auc_test, 2)))

        col01, col02 = st.beta_columns(2)
        with col01:
            st.write("### Discovery Cohort Confusion Matrix")
            Z = sklearn.metrics.confusion_matrix(
                labels_actual[:len_train],
                np.array(labels_pred[:len_train]) > 0.5)
            Z_df = pd.DataFrame(Z,
                                columns=['Predicted 0', 'Predicted 1'],
                                index=['Actual 0', 'Actual 1'])
            st.table(Z_df.astype(str))

        with col02:
            st.write("### Replication Cohort Confusion Matrix")
            Z = sklearn.metrics.confusion_matrix(
                labels_actual[len_train:],
                np.array(labels_pred[len_train:]) > 0.5)
            Z_df = pd.DataFrame(Z,
                                columns=['Predicted 0', 'Predicted 1'],
                                index=['Actual 0', 'Actual 1'])
            st.table(Z_df.astype(str))

        labels_actual_new = np.array(labels_actual, dtype=np.float64)
        y_pred = (shap_values.sum(1) + exval) > 0
        misclassified = y_pred != labels_actual_new

        st.write('### Pathways for Misclassified Samples')
        if misclassified[len_train:].sum() == 0:
            st.info('No Misclassified Examples!!!')
        elif st.checkbox("Show Misclassifies Pathways"):
            col6, col7 = st.beta_columns(2)
            with col6:
                st.info('Misclassifications (test): {}/{}'.format(
                    misclassified[len_train:].sum(), len_test))
                fig, ax = plt.subplots()
                r = shap.decision_plot(exval,
                                       shap_values[misclassified],
                                       list(X.columns),
                                       link='logit',
                                       return_objects=True,
                                       new_base_value=0)
                st.pyplot(fig)
            with col7:
                # st.info('Single Example')
                sel_patients = [
                    patient_index[e] for e, i in enumerate(misclassified)
                    if i == 1
                ]
                select_pats = st.selectbox(
                    'Select random misclassified patient',
                    options=list(sel_patients))
                id_sel_pats = sel_patients.index(select_pats)
                fig, ax = plt.subplots()
                shap.decision_plot(exval,
                                   shap_values[misclassified][id_sel_pats],
                                   X.iloc[misclassified, :].iloc[id_sel_pats],
                                   link='logit',
                                   feature_order=r.feature_idx,
                                   highlight=0,
                                   new_base_value=0)
                st.pyplot()
        st.write('## Decision Plots')
        st.write("""
        We selected 800 subsamples to understand the pathways of predictive modeling. SHAP decision plots show how complex models arrive at their predictions (i.e., how models make decisions). 
        Each observation’s prediction is represented by a colored line.
        At the top of the plot, each line strikes the x-axis at its corresponding observation’s predicted value. 
        This value determines the color of the line on a spectrum. 
        Moving from the bottom of the plot to the top, SHAP values for each feature are added to the model’s base value. 
        This shows how each feature contributes to the overall prediction.
        """)
        st.write('### Pathways for Prediction (Hierarchical Clustering)')
        if st.checkbox("Show Prediction Pathways (Feature Clustered)"):
            # col3, col4, col5 = st.beta_columns(3)
            # st.write('Typical Prediction Path: Uncertainity (0.2-0.8)')
            r = shap.decision_plot(exval,
                                   np.copy(new_shap_values),
                                   list(new_X.columns),
                                   feature_order='hclust',
                                   return_objects=True,
                                   show=False)
            T = new_X.iloc[(new_labels_pred >= 0) & (new_labels_pred <= 1)]
            import warnings
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                sh = np.copy(new_shap_values)[(new_labels_pred >= 0) &
                                              (new_labels_pred <= 1), :]
            fig, ax = plt.subplots()
            shap.decision_plot(exval,
                               sh,
                               T,
                               show=False,
                               feature_order=r.feature_idx,
                               link='logit',
                               return_objects=True,
                               new_base_value=0)
            st.pyplot(fig)
            # with col4:
            #     st.write('Typical Prediction Path: Positive Class (>=0.9)')
            #     fig, ax = plt.subplots()
            #     T = new_X.iloc[np.array(new_labels_pred, dtype=np.float64) >= 0.9]
            #     import warnings
            #     with warnings.catch_warnings():
            #         warnings.simplefilter("ignore")
            #         sh = np.copy(new_shap_values)[new_labels_pred >= 0.9, :]
            #     shap.decision_plot(exval, sh, T, show=False, link='logit',  feature_order=r.feature_idx, new_base_value=0)
            #     st.pyplot(fig)
            # with col5:
            #     st.write('Typical Prediction Path: Negative Class (<=0.1)')
            #     fig, ax = plt.subplots()
            #     T = new_X.iloc[new_labels_pred <= 0.1]
            #     import warnings
            #     with warnings.catch_warnings():
            #            warnings.simplefilter("ignore")
            #            sh = np.copy(new_shap_values)[new_labels_pred <= 0.1, :]
            #     shap.decision_plot(exval, sh, T, show=False, link='logit', feature_order=r.feature_idx, new_base_value=0)
            #     st.pyplot(fig)

        st.write('### Pathways for Prediction (Feature Importance)')
        if st.checkbox("Show Prediction Pathways (Feature Importance)"):
            # col31, col41, col51 = st.beta_columns(3)
            # with col31:
            # st.write('Typical Prediction Path: Uncertainity (0.2-0.8)')
            r = shap.decision_plot(exval,
                                   np.copy(new_shap_values),
                                   list(new_X.columns),
                                   return_objects=True,
                                   show=False)
            T = new_X.iloc[(new_labels_pred >= 0) & (new_labels_pred <= 1)]
            import warnings
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")
                sh = np.copy(new_shap_values)[(new_labels_pred >= 0) &
                                              (new_labels_pred <= 1), :]
            fig, ax = plt.subplots()
            shap.decision_plot(exval,
                               sh,
                               T,
                               show=False,
                               feature_order=r.feature_idx,
                               link='logit',
                               return_objects=True,
                               new_base_value=0)
            st.pyplot(fig)
示例#14
0
文件: shap.py 项目: nielsota/EconML
def _shap_explain_model_cate(cme_model,
                             models,
                             X,
                             d_t,
                             d_y,
                             feature_names=None,
                             treatment_names=None,
                             output_names=None):
    """
    Method to explain `model_cate` using shap Explainer(), will instead explain `const_marignal_effect`
    if `model_cate` can't be parsed.

    Parameters
    ----------
    cme_models: function
        const_marginal_effect function.
    models: a single estimator or a list of estimators with one estimator per treatment
        models for the model's final stage model.
    X: (m, d_x) matrix
        Features for each sample. Should be in the same shape of fitted X in final stage.
    d_t: tuple of int
        Tuple of number of treatment (exclude control in discrete treatment scenario.
    d_y: tuple of int
        Tuple of number of outcome.
    feature_names: optional None or list of strings of length X.shape[1] (Default=None)
        The names of input features.
    treatment_names: optional None or list (Default=None)
        The name of treatment. In discrete treatment scenario, the name should not include the name of
        the baseline treatment (i.e. the control treatment, which by default is the alphabetically smaller)
    output_names:  optional None or list (Default=None)
        The name of the outcome.

    Returns
    -------
    shap_outs: nested dictionary of Explanation object
        A nested dictionary by using each output name (e.g. "Y0" when `output_names=None`) and
        each treatment name (e.g. "T0" when `treatment_names=None`) as key
        and the shap_values explanation object as value.
    """

    (dt, dy, treatment_names,
     output_names) = _define_names(d_t, d_y, treatment_names, output_names)
    if not isinstance(models, list):
        models = [models]
    assert len(
        models
    ) == dt, "Number of final stage models don't equals to number of treatments!"
    # define masker by using entire dataset, otherwise Explainer will only sample 100 obs by default.
    background = shap.maskers.Independent(X, max_samples=X.shape[0])

    shap_outs = defaultdict(dict)
    for i in range(dt):
        try:
            explainer = shap.Explainer(models[i],
                                       background,
                                       feature_names=feature_names)
        except Exception as e:
            print(
                "Final model can't be parsed, explain const_marginal_effect() instead!"
            )
            return _shap_explain_cme(cme_model, X, d_t, d_y, feature_names,
                                     treatment_names, output_names)
        shap_out = explainer(X)
        if dy > 1:
            for j in range(dy):
                base_values = shap_out.base_values[..., j]
                values = shap_out.values[..., j]
                main_effects = None if shap_out.main_effects is None else shap_out.main_effects[
                    ..., j]
                shap_out_new = shap.Explanation(
                    values,
                    base_values=base_values,
                    data=shap_out.data,
                    main_effects=main_effects,
                    feature_names=shap_out.feature_names)
                shap_outs[output_names[j]][treatment_names[i]] = shap_out_new
        else:
            shap_outs[output_names[0]][treatment_names[i]] = shap_out

    return shap_outs
示例#15
0
文件: shap.py 项目: nielsota/EconML
def _shap_explain_joint_linear_model_cate(model_final,
                                          X,
                                          T,
                                          d_t,
                                          d_y,
                                          fit_cate_intercept,
                                          feature_names=None,
                                          treatment_names=None,
                                          output_names=None):
    """
    Method to explain `model_cate` of parametric final stage that was fitted on the cross product of
    `featurizer(X)` and T.

    Parameters
    ----------
    model_final: a single estimator
        the model's final stage model.
    X: matrix
        Intermediate X
    T: matrix
        Intermediate T
    d_t: tuple of int
        Tuple of number of treatment (exclude control in discrete treatment scenario).
    d_y: tuple of int
        Tuple of number of outcome.
    feature_names: optional None or list of strings of length X.shape[1] (Default=None)
        The names of input features.
    treatment_names: optional None or list (Default=None)
        The name of treatment. In discrete treatment scenario, the name should not include the name of
        the baseline treatment (i.e. the control treatment, which by default is the alphabetically smaller)
    output_names:  optional None or list (Default=None)
        The name of the outcome.

    Returns
    -------
    shap_outs: nested dictionary of Explanation object
        A nested dictionary by using each output name (e.g. "Y0" when `output_names=None`) and
        each treatment name (e.g. "T0" when `treatment_names=None`) as key
        and the shap_values explanation object as value.
    """

    d_x = X.shape[1]
    # define the index of d_x to filter for each given T
    ind_x = np.arange(d_x).reshape(d_t, -1)
    if fit_cate_intercept:  # skip intercept
        ind_x = ind_x[:, 1:]
    shap_outs = defaultdict(dict)
    for i in range(d_t):
        # filter X after broadcast with T for each given T
        X_sub = X[T[:, i] == 1]
        # define masker by using entire dataset, otherwise Explainer will only sample 100 obs by default.
        background = shap.maskers.Independent(X_sub,
                                              max_samples=X_sub.shape[0])
        explainer = shap.Explainer(model_final, background)
        shap_out = explainer(X_sub)

        data = shap_out.data[:, ind_x[i]]
        if d_y > 1:
            for j in range(d_y):
                base_values = shap_out.base_values[..., j]
                main_effects = shap_out.main_effects[..., ind_x[i], j]
                values = shap_out.values[..., ind_x[i], j]
                shap_out_new = shap.Explanation(values,
                                                base_values=base_values,
                                                data=data,
                                                main_effects=main_effects,
                                                feature_names=feature_names)
                shap_outs[output_names[j]][treatment_names[i]] = shap_out_new
        else:
            values = shap_out.values[..., ind_x[i]]
            main_effects = shap_out.main_effects[..., ind_x[i], 0]
            shap_out_new = shap.Explanation(values,
                                            base_values=shap_out.base_values,
                                            data=data,
                                            main_effects=main_effects,
                                            feature_names=feature_names)
            shap_outs[output_names[0]][treatment_names[i]] = shap_out_new

    return shap_outs