<jupyter_start><jupyter_text>JOCO DATA TEST<jupyter_code>import pandas as pd

FILE_PATH = '/Volumes/dbs/triage-working/matrices'

label_col = 'booking_view_warr_bw_1y'
demo_col = 'race_nonwhite'
exclude_cols = ['entity_id', 'as_of_date', label_col, demo_col]

def load_matrix(matrix_id, non_white):
    df = pd.read_csv('%s/%s.csv' % (FILE_PATH, matrix_id))
    df[demo_col] = non_white
    df[label_col] = 2*(df[label_col] - 0.5)
    return df

# training data
df_b_train = load_matrix('bf8f725453aebbcf3decbb72fcf95e6e', 1)
df_h_train = load_matrix('ee4a84ff473f0af6acf7d35432dcddfa', 1)
df_w_train = load_matrix('f6baf93649b912647017bb15b87131e0', 0)

df_train = pd.concat([df_b_train, df_h_train, df_w_train], axis=0)

x_train = df_train[[c for c in df_train.columns if c not in exclude_cols]].values
y_train = df_train[label_col].values
x_control_train = {demo_col: df_train[demo_col].values}


# test data
df_b_test = load_matrix('b6e936186bec0885640f5a0bb8ab787b', 1)
df_h_test = load_matrix('6b616fa0db246fc6222049e709efd430', 1)
df_w_test = load_matrix('0db1a017699f3d3142d7688128f844f1', 0)

df_test = pd.concat([df_b_test, df_h_test, df_w_test], axis=0)

x_test = df_test[[c for c in df_test.columns if c not in exclude_cols]].values
y_test = df_test[label_col].values
x_control_test = {demo_col: df_test[demo_col].values}


x_train.shape
df_train.shape
# TODO for JOCO Data:
# 1. load a training and test set
# 2. add columns to feature matrices for constant term (assumed to be in the X matrix here)
# 3. scale matrices as in scaled logit
# 4. read in race_3way and modify to race_2way for x_control
# 5. run a lasso logit as a means of feature selection
# 6. set up parameters for disparate mistreatment logit (cons_type=2 for FNR <=> recall)
# 7. run regressions, test on test set
# 8. iterate over full set of matrices/as_of_dates


# MAYBE modify the package with...
# - ability to use a ridge or lasso loss function
# - allow for multiple sensitive features (e.g., one-hot enconded categorical?)


loss_function = "logreg"
EPS = 1e-4
cons_params = None

def train_test_classifier():
    w = fdm.train_model_disp_mist(x_train, y_train, x_control_train, loss_function, EPS, cons_params)

    train_score, test_score, cov_all_train, cov_all_test, s_attr_to_fp_fn_train, s_attr_to_fp_fn_test = fdm.get_clf_stats(w, x_train, y_train, x_control_train, x_test, y_test, x_control_test, sensitive_attrs)

    # accuracy and FPR are for the test because we need of for plotting
    return w, test_score, s_attr_to_fp_fn_test

# Unconstrained classifier

cons_params = None

w_uncons, acc_uncons, s_attr_to_fp_fn_test_uncons = train_test_classifier()
# FNR fairness-constrained classifier

cons_type = 2 # FNR constraint -- same as recall
tau = 5.0
mu = 1.2
# note thresh for constraints will be cov_thresh[1] - cov_thresh[0]
# had trouble satisfying exact 0s here in synthetic data tests, so go with small epsilon, 1e-6...
sensitive_attrs_to_cov_thresh = {"s1": {0:{0:0, 1:1e-6}, 1:{0:0, 1:1e-6}, 2:{0:0, 1:1e-6}}} # zero covariance threshold, means try to get the fairest solution
cons_params = {"cons_type": cons_type, 
                "tau": tau, 
                "mu": mu, 
                "sensitive_attrs_to_cov_thresh": sensitive_attrs_to_cov_thresh}

w_cons, acc_cons, s_attr_to_fp_fn_test_cons = train_test_classifier()
x = x_train
y = y_train
x_control = x_control_train

max_iters = 150
max_iter_dccp = 75

num_points, num_features = x.shape

import cvxpy

w = cvxpy.Variable(num_features)

np.random.seed(112233)

w.value = np.random.rand(x.shape[1])

constraints = []
loss = cvxpy.sum(  cvxpy.logistic( cvxpy.multiply(-y, x*w) )  ) / num_points
prob = cvxpy.Problem(cvxpy.Minimize(loss), constraints)

tau, mu = 0.005, 1.2
prob.solve(method='dccp', tau=tau, mu=mu, tau_max=1e10, solver=cvxpy.ECOS, verbose=True, 
           feastol=EPS, abstol=EPS, reltol=EPS, feastol_inacc=EPS, abstol_inacc=EPS, reltol_inacc=EPS,
          max_iters=max_iters, max_iter=max_iter_dccp)
ret_w = np.array(w.value).flatten()
x_control_train = {demo_col: x_control_train}
x_control_test = {demo_col: x_control_test}
sensitive_attrs = list(x_control_train.keys())
train_score, test_score, cov_all_train, cov_all_test, s_attr_to_fp_fn_train, s_attr_to_fp_fn_test = fdm.get_clf_stats(ret_w, x_train, y_train, x_control_train, x_test, y_test, x_control_test, list(sensitive_attrs))
s_attr = sensitive_attrs[0]
distances_boundary_test = fdm.get_distance_boundary(ret_w, x_test, x_control_test[s_attr])

def label_top_k(distances, k):
    sort_df = pd.DataFrame({'dist': distances})
    sort_df.sort_values('dist', ascending=False, inplace=True)
    sort_df['pred_label'] = -1
    sort_df['orig_idx'] = sort_df.index
    sort_df.reset_index(inplace=True)
    i = k-1
    sort_df.loc[:i,'pred_label'] = 1
    sort_df.sort_values('orig_idx', inplace=True)
    return sort_df['pred_label'].values

def calc_prec(pred_label, actual_label):
    label_pos = sum((pred_label == 1).astype(int))
    true_pos = sum(np.logical_and(pred_label == actual_label, pred_label == 1).astype(int))
    return float(true_pos/label_pos)

k = 100

all_class_labels_assigned_test = label_top_k(distances_boundary_test, k)
#all_class_labels_assigned_test = np.sign(distances_boundary_test)

prec_k = calc_prec(all_class_labels_assigned_test, y_test)

print('prec@%s_abs: %.5f' % (k, prec_k))

s_attr_to_fp_fn_test = fdm.get_fpr_fnr_sensitive_features(y_test, all_class_labels_assigned_test, x_control_test, sensitive_attrs, False)

for s_attr in s_attr_to_fp_fn_test.keys():
    for s_val in s_attr_to_fp_fn_test[s_attr].keys():
        s_attr_to_fp_fn_test[s_attr][s_val]['recall'] = 1.000-s_attr_to_fp_fn_test[s_attr][s_val]['fnr']


recall_nonwhite = s_attr_to_fp_fn_test['race_nonwhite'][1]['recall']
recall_white = s_attr_to_fp_fn_test['race_nonwhite'][0]['recall']

print('recall non-white: %.6f' % recall_nonwhite)
print('recall white: %.6f' % recall_white)
print('recall ratio: %.6f' % float(recall_white/recall_nonwhite))

# 35% precision is incredibly low here -- likely because the logistic loss is totally unregularized,
# so presumably we're getting a massively overfit model...

# let's take a look at training set precision as an indication

k = 100
distances_boundary_train = fdm.get_distance_boundary(ret_w, x_train, x_control_train[s_attr])

all_class_labels_assigned_train = label_top_k(distances_boundary_train, k)
#all_class_labels_assigned_test = np.sign(distances_boundary_test)

prec_k = calc_prec(all_class_labels_assigned_train, y_train)

print('prec@%s_abs: %.5f' % (k, prec_k))

s_attr_to_fp_fn_train = fdm.get_fpr_fnr_sensitive_features(y_train, all_class_labels_assigned_train, x_control_train, sensitive_attrs, False)

for s_attr in s_attr_to_fp_fn_train.keys():
    for s_val in s_attr_to_fp_fn_train[s_attr].keys():
        s_attr_to_fp_fn_train[s_attr][s_val]['recall'] = 1.000-s_attr_to_fp_fn_train[s_attr][s_val]['fnr']


recall_nonwhite = s_attr_to_fp_fn_train['race_nonwhite'][1]['recall']
recall_white = s_attr_to_fp_fn_train['race_nonwhite'][0]['recall']

print('recall non-white: %.6f' % recall_nonwhite)
print('recall white: %.6f' % recall_white)
print('recall ratio: %.6f' % float(recall_white/recall_nonwhite))<jupyter_output>prec@100_abs: 1.00000
recall non-white: 0.015428
recall white: 0.036568
recall ratio: 2.370285<jupyter_text>yup -- training set precision at 100 is 100%, compared to 35% on the test set, so we're massively overfit.Unfortunately, the best bet here is probably to scale the data and use a lasso to do feature selection, though perhaps we could simply pull the existing feature importances from the triage database as a first step here (scaling isn't strictly necessary since there's no regularization term here)<jupyter_code># load columns with non-zero lasso coefficients from one of the old models

from sklearn.externals import joblib

old_mod_hash = '/Users/kit/Documents/joco_working/5270794c51b1de17745de599be68e951'
old_mat_hash = '/Users/kit/Documents/joco_working/510a0fbff9a6f649ef881fe07b12b08e'
old_mod = joblib.load(open(old_mod_hash, 'rb'))
old_mat = pd.read_csv('%s.csv' % old_mat_hash, nrows=1)
old_exclude_cols = ['entity_id', 'as_of_date', 'booking_view_warr_bw_1y']

all_cols = [c for c in old_mat.columns if c not in old_exclude_cols]
keep_cols = []
for i, col in enumerate(all_cols):
    if old_mod.coef_[0][i] != 0:
        keep_cols.append(col)

keep_cols = keep_cols + ['intercept']
# forgot to do this above...
df_train['intercept'] = 1
df_test['intercept'] = 1

x_train = df_train[[c for c in df_train.columns if c in keep_cols]].values
y_train = df_train[label_col].values
x_control_train = {demo_col: df_train[demo_col].values}

x_test = df_test[[c for c in df_test.columns if c in keep_cols]].values
y_test = df_test[label_col].values
x_control_test = {demo_col: df_test[demo_col].values}
x = x_train
y = y_train
x_control = x_control_train

max_iters = 150
max_iter_dccp = 75

num_points, num_features = x.shape

import cvxpy

w = cvxpy.Variable(num_features)

np.random.seed(112233)

w.value = np.random.rand(x.shape[1])

constraints = []
loss = cvxpy.sum(  cvxpy.logistic( cvxpy.multiply(-y, x*w) )  ) / num_points
prob = cvxpy.Problem(cvxpy.Minimize(loss), constraints)

tau, mu = 0.005, 1.2


prob.solve(method='dccp', tau=tau, mu=mu, tau_max=1e10, solver=cvxpy.ECOS, verbose=True, 
           feastol=EPS, abstol=EPS, reltol=EPS, feastol_inacc=EPS, abstol_inacc=EPS, reltol_inacc=EPS,
          max_iters=max_iters, max_iter=max_iter_dccp)
ret_w_uncons = np.array(w.value).flatten()

train_score, test_score, cov_all_train, cov_all_test, s_attr_to_fp_fn_train, s_attr_to_fp_fn_test = fdm.get_clf_stats(ret_w_uncons, x_train, y_train, x_control_train, x_test, y_test, x_control_test, list(sensitive_attrs))
s_attr = sensitive_attrs[0]
distances_boundary_test = fdm.get_distance_boundary(ret_w_uncons, x_test, x_control_test[s_attr])

k = 100

all_class_labels_assigned_test = label_top_k(distances_boundary_test, k)
#all_class_labels_assigned_test = np.sign(distances_boundary_test)

prec_k = calc_prec(all_class_labels_assigned_test, y_test)

print('prec@%s_abs: %.5f' % (k, prec_k))

s_attr_to_fp_fn_test = fdm.get_fpr_fnr_sensitive_features(y_test, all_class_labels_assigned_test, x_control_test, sensitive_attrs, False)

for s_attr in s_attr_to_fp_fn_test.keys():
    for s_val in s_attr_to_fp_fn_test[s_attr].keys():
        s_attr_to_fp_fn_test[s_attr][s_val]['recall'] = 1.000-s_attr_to_fp_fn_test[s_attr][s_val]['fnr']


recall_nonwhite = s_attr_to_fp_fn_test['race_nonwhite'][1]['recall']
recall_white = s_attr_to_fp_fn_test['race_nonwhite'][0]['recall']

print('recall non-white: %.6f' % recall_nonwhite)
print('recall white: %.6f' % recall_white)
print('recall ratio: %.6f' % float(recall_white/recall_nonwhite))

s_attr = sensitive_attrs[0]
distances_boundary_test = fdm.get_distance_boundary(ret_w_uncons, x_test, x_control_test[s_attr])

k = 500

all_class_labels_assigned_test = label_top_k(distances_boundary_test, k)
#all_class_labels_assigned_test = np.sign(distances_boundary_test)

prec_k = calc_prec(all_class_labels_assigned_test, y_test)

print('prec@%s_abs: %.5f' % (k, prec_k))

s_attr_to_fp_fn_test = fdm.get_fpr_fnr_sensitive_features(y_test, all_class_labels_assigned_test, x_control_test, sensitive_attrs, False)

for s_attr in s_attr_to_fp_fn_test.keys():
    for s_val in s_attr_to_fp_fn_test[s_attr].keys():
        s_attr_to_fp_fn_test[s_attr][s_val]['recall'] = 1.000-s_attr_to_fp_fn_test[s_attr][s_val]['fnr']


recall_nonwhite = s_attr_to_fp_fn_test['race_nonwhite'][1]['recall']
recall_white = s_attr_to_fp_fn_test['race_nonwhite'][0]['recall']

print('recall non-white: %.6f' % recall_nonwhite)
print('recall white: %.6f' % recall_white)
print('recall ratio: %.6f' % float(recall_white/recall_nonwhite))<jupyter_output>prec@500_abs: 0.52600
recall non-white: 0.048032
recall white: 0.086858
recall ratio: 1.808328<jupyter_text>Ok -- that's at least a bit more in line with what we've seen with the regularized logistic regressions in general, so at least this makes a little more sense.<jupyter_code># let's take a look at training set precision as an indication

k = 100
distances_boundary_train = fdm.get_distance_boundary(ret_w_uncons, x_train, x_control_train[s_attr])

all_class_labels_assigned_train = label_top_k(distances_boundary_train, k)
#all_class_labels_assigned_test = np.sign(distances_boundary_test)

prec_k = calc_prec(all_class_labels_assigned_train, y_train)

print('prec@%s_abs: %.5f' % (k, prec_k))

s_attr_to_fp_fn_train = fdm.get_fpr_fnr_sensitive_features(y_train, all_class_labels_assigned_train, x_control_train, sensitive_attrs, False)

for s_attr in s_attr_to_fp_fn_train.keys():
    for s_val in s_attr_to_fp_fn_train[s_attr].keys():
        s_attr_to_fp_fn_train[s_attr][s_val]['recall'] = 1.000-s_attr_to_fp_fn_train[s_attr][s_val]['fnr']


recall_nonwhite = s_attr_to_fp_fn_train['race_nonwhite'][1]['recall']
recall_white = s_attr_to_fp_fn_train['race_nonwhite'][0]['recall']

print('recall non-white: %.6f' % recall_nonwhite)
print('recall white: %.6f' % recall_white)
print('recall ratio: %.6f' % float(recall_white/recall_nonwhite))<jupyter_output>prec@100_abs: 0.97000
recall non-white: 0.018233
recall white: 0.033286
recall ratio: 1.825634<jupyter_text>Admittedly, still seems a bit overfit and we could consider stronger regularization for the feature selection, but given the test set error in line with other models, I think we can forge ahead with this for now and see how it performs including the FNR fairness constraints.<jupyter_code>loss_function = "logreg"
EPS = 1e-4
cons_params = None

def train_test_classifier():
    w = fdm.train_model_disp_mist(x_train, y_train, x_control_train, loss_function, EPS, cons_params)

    train_score, test_score, cov_all_train, cov_all_test, s_attr_to_fp_fn_train, s_attr_to_fp_fn_test = fdm.get_clf_stats(w, x_train, y_train, x_control_train, x_test, y_test, x_control_test, sensitive_attrs)

    # accuracy and FPR are for the test because we need of for plotting
    return w, test_score, s_attr_to_fp_fn_test


# FNR fairness-constrained classifier

cons_type = 2 # FNR constraint -- same as recall
tau = 5.0
mu = 1.2
# note thresh for constraints will be cov_thresh[1] - cov_thresh[0]
# had trouble satisfying exact 0s here in synthetic data tests, so go with small epsilon, 1e-6...
sensitive_attrs_to_cov_thresh = {"race_nonwhite": {0:{0:0, 1:1e-6}, 1:{0:0, 1:1e-6}, 2:{0:0, 1:1e-6}}} # zero covariance threshold, means try to get the fairest solution
cons_params = {"cons_type": cons_type, 
                "tau": tau, 
                "mu": mu, 
                "sensitive_attrs_to_cov_thresh": sensitive_attrs_to_cov_thresh}

ret_w, acc_cons, s_attr_to_fp_fn_test_cons = train_test_classifier()<jupyter_output>Accuracy: 0.855
||  s  || FPR. || FNR. ||
||  0  || 0.04 || 0.81 ||
||  1  || 0.03 || 0.84 ||<jupyter_text>A little surprising that this doesn't yield perfectly-balanced FNR at the original decision boundary, or even particularly close, but let's see what happens at the top 100...<jupyter_code>s_attr = sensitive_attrs[0]
distances_boundary_test = fdm.get_distance_boundary(ret_w, x_test, x_control_test[s_attr])

k = 100

all_class_labels_assigned_test = label_top_k(distances_boundary_test, k)
#all_class_labels_assigned_test = np.sign(distances_boundary_test)

prec_k = calc_prec(all_class_labels_assigned_test, y_test)

print('prec@%s_abs: %.5f' % (k, prec_k))

s_attr_to_fp_fn_test = fdm.get_fpr_fnr_sensitive_features(y_test, all_class_labels_assigned_test, x_control_test, sensitive_attrs, False)

for s_attr in s_attr_to_fp_fn_test.keys():
    for s_val in s_attr_to_fp_fn_test[s_attr].keys():
        s_attr_to_fp_fn_test[s_attr][s_val]['recall'] = 1.000-s_attr_to_fp_fn_test[s_attr][s_val]['fnr']


recall_nonwhite = s_attr_to_fp_fn_test['race_nonwhite'][1]['recall']
recall_white = s_attr_to_fp_fn_test['race_nonwhite'][0]['recall']

print('recall non-white: %.6f' % recall_nonwhite)
print('recall white: %.6f' % recall_white)
print('recall ratio: %.6f' % float(recall_white/recall_nonwhite))<jupyter_output>prec@100_abs: 0.53000
recall non-white: 0.010674
recall white: 0.016826
recall ratio: 1.576370<jupyter_text>Not much change from the unconstrained model (in fact, a little worse) and the raw ratio is very high compared to what we get after choosing separate thresholds, while the precision is much lower that we can achieve with random forests.<jupyter_code>s_attr = sensitive_attrs[0]
distances_boundary_test = fdm.get_distance_boundary(ret_w, x_test, x_control_test[s_attr])

k = 500

all_class_labels_assigned_test = label_top_k(distances_boundary_test, k)
#all_class_labels_assigned_test = np.sign(distances_boundary_test)

prec_k = calc_prec(all_class_labels_assigned_test, y_test)

print('prec@%s_abs: %.5f' % (k, prec_k))

s_attr_to_fp_fn_test = fdm.get_fpr_fnr_sensitive_features(y_test, all_class_labels_assigned_test, x_control_test, sensitive_attrs, False)

for s_attr in s_attr_to_fp_fn_test.keys():
    for s_val in s_attr_to_fp_fn_test[s_attr].keys():
        s_attr_to_fp_fn_test[s_attr][s_val]['recall'] = 1.000-s_attr_to_fp_fn_test[s_attr][s_val]['fnr']


recall_nonwhite = s_attr_to_fp_fn_test['race_nonwhite'][1]['recall']
recall_white = s_attr_to_fp_fn_test['race_nonwhite'][0]['recall']

print('recall non-white: %.6f' % recall_nonwhite)
print('recall white: %.6f' % recall_white)
print('recall ratio: %.6f' % float(recall_white/recall_nonwhite))<jupyter_output>prec@500_abs: 0.52400
recall non-white: 0.049366
recall white: 0.085493
recall ratio: 1.731819<jupyter_text>Almost identical story at k=500 as well.Overall, this approach doesn't seem to working particularly well in the "top k" setting, which isn't terribly surprising since it wasn't designed for that (it's attempting to balance FNR based on the decision boundary from the underlying optimization problem, so there's no good reason to believe that simply shifting this boundary to yield a smaller set would preserve the fairness constraints as well). It wouldn't be fair to entirely discount this method because it doesn't work here, but we certainly do need methods that can work with the top k/resource constrained setting, as well as the flexibility to apply to a broader array of modeling methods than simple linear models as employed here.Given this, it doesn't strike me as particularly worthwhile to clean up the code here to get this working robustly across every joco as_of_date / other datasets. Rather, I think we could provide it as a simple case study at the outset and then focus more deeply on the strategies that are better suited to the top k problem.<jupyter_code># let's pickle a couple of things for potential future use / figure creation...

import pickle

export_dict = {
    'inputs': {
        'x_train': x_train,
        'x_control_train': x_control_train,
        'y_train': y_train,
        'x_test': x_test,
        'x_control_test': x_control_test,
        'y_test': y_test,
        'sensitive_attrs': sensitive_attrs
    },
    'unconstrained': {
        'model_output': {
            'w': ret_w_uncons
        },
        'top_100_abs': {
            'precision': 0.51000,
            'recall_non_white': 0.010674,
            'recall_white': 0.015916,
            'recall_ratio': 1.491161
        },
        'top_500_abs': {
            'precision': 0.52600,
            'recall_non_white': 0.048032,
            'recall_white': 0.086858,
            'recall_ratio': 1.808328
        }
    },
    'FNR constrined': {
        'model_output': {
            'w': ret_w
        },
        'top_100_abs': {
            'precision': 0.53000,
            'recall_non_white': 0.010674,
            'recall_white': 0.016826,
            'recall_ratio': 1.576370
        },
        'top_500_abs': {
            'precision': 0.52400,
            'recall_non_white': 0.049366,
            'recall_white': 0.085493,
            'recall_ratio': 1.731819
        }
    }
}

pickle.dump(export_dict, open('zafar_fair_classification.pkl', 'wb'))

del(export_dict)<jupyter_output><empty_output><jupyter_text>DEBUGGING....<jupyter_code>data_type=1
X, y, x_control = generate_synthetic_data(data_type=data_type, plot_data=False)
train_fold_size = 0.5
x_train, y_train, x_control_train, x_test, y_test, x_control_test = ut.split_into_train_test(X, y, x_control, train_fold_size)
cons_params = None
loss_function = "logreg"
EPS = 1e-4
	def train_test_classifier():
		w = fdm.train_model_disp_mist(x_train, y_train, x_control_train, loss_function, EPS, cons_params)

		train_score, test_score, cov_all_train, cov_all_test, s_attr_to_fp_fn_train, s_attr_to_fp_fn_test = fdm.get_clf_stats(w, x_train, y_train, x_control_train, x_test, y_test, x_control_test, sensitive_attrs)

		
		# accuracy and FPR are for the test because we need of for plotting
		return w, test_score, s_attr_to_fp_fn_test
w = fdm.train_model_disp_mist(x_train, y_train, x_control_train, loss_function, EPS, cons_params)
x = x_train
y = y_train
x_control = x_control_train

max_iters = 100
max_iter_dccp = 50

num_points, num_features = x.shape
import cvxpy
w = cvxpy.Variable(num_features)
np.random.seed(112233)
w.value = np.random.rand(x.shape[1])
constraints = []
loss = cvxpy.sum(  cvxpy.logistic( cvxpy.multiply(-y, x*w) )  ) / num_points
prob = cvxpy.Problem(cvxpy.Minimize(loss), constraints)
prob.is_dcp()
from dccp.problem import is_dccp
is_dccp(prob)
tau, mu = 0.005, 1.2
prob.solve(method='dccp', tau=tau, mu=mu, tau_max=1e10, solver=cvxpy.ECOS, verbose=False, 
           feastol=EPS, abstol=EPS, reltol=EPS, feastol_inacc=EPS, abstol_inacc=EPS, reltol_inacc=EPS,
          max_iters=max_iters, max_iter=max_iter_dccp)
prob.status
ret_w = np.array(w.value).flatten()
train_score, test_score, cov_all_train, cov_all_test, s_attr_to_fp_fn_train, s_attr_to_fp_fn_test = fdm.get_clf_stats(ret_w, x_train, y_train, x_control_train, x_test, y_test, x_control_test, list(sensitive_attrs))
w_uncons, acc_uncons, s_attr_to_fp_fn_test_uncons = ret_w, test_score, s_attr_to_fp_fn_test
psb.plot_boundaries(X, y, x_control, [w_uncons, w_uncons], [acc_uncons, acc_uncons], 
                   [s_attr_to_fp_fn_test_uncons['s1'], s_attr_to_fp_fn_test_uncons['s1']],
                   "test_output.png")
print( "\n\n=== Constraints on FPR ===")	# setting parameter for constraints
cons_type = 2 # FPR constraint -- just change the cons_type, the rest of parameters should stay the same
tau = 5.0
mu = 1.2
sensitive_attrs_to_cov_thresh = {"s1": {0:{0:0, 1:0.000001}, 1:{0:0, 1:0.000001}, 2:{0:0, 1:0.000001}}} # zero covariance threshold, means try to get the fairest solution
cons_params = {"cons_type": cons_type, 
                "tau": tau, 
                "mu": mu, 
                "sensitive_attrs_to_cov_thresh": sensitive_attrs_to_cov_thresh}
w_cons, acc_cons, s_attr_to_fp_fn_test_cons  = train_test_classifier()
import cvxpy

x = x_train
y = y_train
x_control = x_control_train

max_iters = 100
max_iter_dccp = 50

num_points, num_features = x.shape
w = cvxpy.Variable(num_features) # this is the weight vector

np.random.seed(112233)
w.value = np.random.rand(x.shape[1])

constraints = fdm.get_constraint_list_cov(x, y, x_control, cons_params["sensitive_attrs_to_cov_thresh"], cons_params["cons_type"], w)
constraints
loss = cvxpy.sum(  cvxpy.logistic( cvxpy.multiply(-y, x*w) )  ) / num_points
cons_params.get('take_initial_sol')
take_initial_sol = True
w.value
p = cvxpy.Problem(cvxpy.Minimize(loss), [])
p.solve()
w.value
prob = cvxpy.Problem(cvxpy.Minimize(loss), constraints)
# prob = cvxpy.Problem(cvxpy.Minimize(loss), [cvxpy.norm(w) >= 0.1])
cons_params.get('tau')
cons_params.get('mu')
tau = cons_params.get('tau')
mu = cons_params.get('mu')
prob.solve(method='dccp', tau=tau, mu=mu, tau_max=1e10, solver=cvxpy.ECOS, verbose=True, 
           feastol=EPS, abstol=EPS, reltol=EPS, feastol_inacc=EPS, abstol_inacc=EPS, reltol_inacc=EPS,
          max_iters=max_iters, max_iter=max_iter_dccp)
print(constraints[1])
ut.get_one_hot_encoding(x_control_train['s1'])
sum(np.logical_and(x_control_train['s1'] == 0, y_train == -1))
cvxpy.norm(w)
xx = cvxpy.Parameter(value=1)
yy = cvxpy.Variable()

pp = cvxpy.Problem(cvxpy.Minimize(yy**2 + cvxpy.abs(xx)))
pp.solve(verbose=True)
xx.value
yy.value
pp.status
constraints[0].value()
constraints[0].args[0].value
constraints[0].args[1].value<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11049701138131499, 'fraction_numerical': 0.03230164578727585, 'mean_word_length': 5.515604161109629, 'pattern_counts': {'":': 15, '<': 28, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '39497', 'license': 'MIT', 'n_tokens_mistral': 10071, 'n_tokens_neox': 9369, 'n_words': 1938, 'path': 'code/joco/Zafar Fair Classification Method.ipynb', 'repo_name': 'dssg/peeps-chili', 'size': '24426'}