fairlearn: ENH Add Reject Object Classification and Discrimination-Aware Ensemble based on Kamiran, Karim, Zhang

The paper Decision Theory for Discrimination-Aware Classification by Kamiran, Karim and Zhang presents two solutions for discrimination-aware classification that neither require data modification nor classifier tweaking; Reject Option based Classification and Discrimination-Aware Ensemble (DAE) . The goal of this task is to implement them.

Completing this item requires:

  • code for the technique in ?? (@fairlearn/fairlearn-maintainers I’m not entirely sure where this should go?)
  • unit tests in test.unit.??
  • descriptive API reference (directly in the docstring)
  • ideally a short user guide in docs.user_guide.mitigation.rst

A fully fledged example notebook is not required.

PS. I think I covered all the stuff I’m required to put in here, if not please let me know 😃.

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 37 (37 by maintainers)

Most upvoted comments

so you may end up trying lots and lots of combinations until you’re finally (somewhat) happy.

Sounds like the typical ML process to me!

@koaning

Ah makes sense, I get what you mean now 😃

Multiple sensitive features are not handled for each dimension (thresholds for all groups in SF1, thresholds for all groups in SF2, etc.) but we rather generate all possible overlap groups (AC, AD, BC, BD in your example) and collapse them into a single sensitive feature internally before working on it further. That way it’s no different from a single sensitive feature column. This code may be of interest to you. It’s packaged within validate_and_reformat_input. You may have noticed that it may happen that there’s AC, AD, BC, but no member of BD. In that case that group doesn’t exist as far as the thresholder is concerned. Which directly leads me to the next point…

Groups that aren’t part of sensitive_features during training should not be part of sensitive_features at predict time. We should throw a sensible error in that case, because we have never seen anyone from that group before and shouldn’t make predictions. [Sidenote: Arguably, we should even detect and notify the model builder if a group is disproportionately small (which is often a contributing factor in ML systems causing harms). But we shouldn’t club this into this issue and leave it for a future issue if it’s of interest. I could imagine that we want to tackle this at a different point in the ML lifecycle, potentially significantly before one considers building a model.]

I concur 100% with @adrinjalali’s assessment of predict_method. He introduced that for the particular reason he mentioned. In your PR you seem to have used the existing predict_method functionality which is what I’d expect to see. If you want us to elaborate more please don’t hesitate to ask!

For Thresholder I would suggest the following logic for the class. Please let me know if this makes sense 😃


def __init__(self, estimator, threshold_dict, prefit=False, predict_method='deprecated'):
        self.estimator = estimator
        self.threshold_dict = threshold_dict
        self.prefit = prefit
        self.predict_method = predict_method

def fit(self, X, y, sensitive_features?, **kwargs):
        *

def predict(self, X, *, sensitive_features):
        check_is_fitted(self)
        #get soft predictions
        #validate and reformat input
        **
        #for the groups mentioned in threshold_dict -> predict by comparing the soft predictions to the mentioned thresholds

*It makes sense to me to use the exact same fit method as implemented in InterpolatedThresholder, where sensitive_features isn’t used in the fit method, am I missing something why we should include it in fit here?

**I’m not sure what to do with the ‘normal’ groups. How would we decide on the standard threshold? Is that an attribute we can get from estimator, or will we have to provide thresholds in threshold_dict for all groups?

I think you will need sensitive_features in .fit as well, but other than that this looks like a good place to start from!

(Tagging @romanlutz @MiroDudik)

Am I correct in my interpretation that we will now have two different classes (RejectOptionClassifier and Thresholder) with the same API, but with the difference that RejectOptionClassifier accepts binary classifiers and Thresholder accepts regressors?

I realize now that the above is not the case, however the two API’s will not differ greatly I suspect:

fairlearn.postprocessing.Thresholder( estimator, threshold_dict, prefit, predict_method)

.fit( X , y)

.predict( X , sensitive_features )

and

fairlearn.postprocessing.RejectOptionClassifier( estimator, theta , prefit, predict_method)

.fit( X , y)

.predict( X , sensitive_features )

Where the only difference in API lies in multiple thresholds for Thresholder and a single one for RejectOptionClassifier.

I plan to start working on Thresholder now.

I agree with everything you wrote @hildeweerts. I think @MiroDudik just meant that you don’t have an algorithm that computes the thresholds for you, so you may end up trying lots and lots of combinations until you’re finally (somewhat) happy. I personally like the interactive visualization with sliders that Google provided (can’t find the link at the moment) for such a purpose, but that’s a bit out of scope for this. I’m still a fan of providing the generic version and making the Reject Option Classifier a special case of it with its own class name so that people know what it is.

100% agree re. naming, @romanlutz!

I have no objection. But let’s not use ROC as an acronym here. It will 100% get confused with the more commonly used receiver operating characteristic 🙂