romanbredehoft-zama
commited on
Commit
•
8e0d56d
1
Parent(s):
61cd73f
Rename to applicant and credit bureau
Browse files- app.py +27 -26
- backend.py +34 -33
- deployment_files/model/{pre_processor_user.pkl → pre_processor_applicant.pkl} +0 -0
- deployment_files/model/{pre_processor_cs_agency.pkl → pre_processor_credit_bureau.pkl} +0 -0
- deployment_files/{pre_processor_user.pkl → pre_processor_applicant.pkl} +0 -0
- deployment_files/{pre_processor_cs_agency.pkl → pre_processor_credit_bureau.pkl} +0 -0
- development.py +16 -16
- server.py +2 -2
- settings.py +10 -10
- utils/client_server_interface.py +2 -2
- utils/pre_processing.py +3 -3
app.py
CHANGED
@@ -22,9 +22,9 @@ from settings import (
|
|
22 |
)
|
23 |
from backend import (
|
24 |
keygen_send,
|
25 |
-
|
26 |
pre_process_encrypt_send_bank,
|
27 |
-
|
28 |
run_fhe,
|
29 |
get_output_and_decrypt,
|
30 |
explain_encrypt_run_decrypt,
|
@@ -61,7 +61,7 @@ with demo:
|
|
61 |
"""
|
62 |
)
|
63 |
|
64 |
-
gr.Markdown("#
|
65 |
|
66 |
gr.Markdown("## Step 1: Generate the keys.")
|
67 |
gr.Markdown(
|
@@ -90,10 +90,11 @@ with demo:
|
|
90 |
"""
|
91 |
Select the information that corresponds to the profile you want to evaluate. Three sources
|
92 |
of information are represented in this model:
|
93 |
-
-
|
94 |
-
- the
|
95 |
-
banking information relevant to the decision (here, we consider duration of
|
96 |
-
|
|
|
97 |
employment history) that could provide additional insight relevant to the decision.
|
98 |
|
99 |
Please always encrypt and send the values (through the buttons on the right) once updated
|
@@ -103,7 +104,7 @@ with demo:
|
|
103 |
|
104 |
with gr.Row():
|
105 |
with gr.Column():
|
106 |
-
gr.Markdown("###
|
107 |
bool_inputs = gr.CheckboxGroup(
|
108 |
["Car", "Property", "Mobile phone"],
|
109 |
label="Which of the following do you actively hold or own?"
|
@@ -167,15 +168,15 @@ with demo:
|
|
167 |
)
|
168 |
|
169 |
with gr.Column():
|
170 |
-
|
171 |
|
172 |
-
|
173 |
label="Encrypted input representation:", max_lines=2, interactive=False
|
174 |
)
|
175 |
|
176 |
with gr.Row():
|
177 |
with gr.Column(scale=2):
|
178 |
-
gr.Markdown("### Bank ")
|
179 |
account_age = gr.Slider(
|
180 |
**ACCOUNT_MIN_MAX,
|
181 |
step=1,
|
@@ -192,7 +193,7 @@ with demo:
|
|
192 |
|
193 |
with gr.Row():
|
194 |
with gr.Column(scale=2):
|
195 |
-
gr.Markdown("### Credit
|
196 |
employed = gr.Radio(["Yes", "No"], label="Is the person employed ?", value="Yes")
|
197 |
years_employed = gr.Dropdown(
|
198 |
choices=YEARS_EMPLOYED_BINS,
|
@@ -202,19 +203,19 @@ with demo:
|
|
202 |
)
|
203 |
|
204 |
with gr.Column():
|
205 |
-
|
206 |
|
207 |
-
|
208 |
label="Encrypted input representation:", max_lines=2, interactive=False
|
209 |
)
|
210 |
|
211 |
-
# Button to pre-process, generate the key, encrypt and send the
|
212 |
# side to the server
|
213 |
-
|
214 |
-
|
215 |
inputs=[client_id, bool_inputs, num_children, household_size, total_income, age, \
|
216 |
income_type, education_type, family_status, occupation_type, housing_type],
|
217 |
-
outputs=[
|
218 |
)
|
219 |
|
220 |
# Button to pre-process, generate the key, encrypt and send the bank inputs from the client
|
@@ -225,12 +226,12 @@ with demo:
|
|
225 |
outputs=[encrypted_input_bank],
|
226 |
)
|
227 |
|
228 |
-
# Button to pre-process, generate the key, encrypt and send the credit
|
229 |
# client side to the server
|
230 |
-
|
231 |
-
|
232 |
inputs=[client_id, years_employed, employed],
|
233 |
-
outputs=[
|
234 |
)
|
235 |
|
236 |
gr.Markdown("# Server side")
|
@@ -253,10 +254,10 @@ with demo:
|
|
253 |
# Button to send the encodings to the server using post method
|
254 |
execute_fhe_button.click(run_fhe, inputs=[client_id], outputs=[fhe_execution_time])
|
255 |
|
256 |
-
gr.Markdown("#
|
257 |
gr.Markdown(
|
258 |
"""
|
259 |
-
Once the server completed the inference, the encrypted output is returned to the
|
260 |
|
261 |
The three entities that provide the information to compute the credit score are the only
|
262 |
ones that can decrypt the result. They take part in a decryption protocol that allows to
|
@@ -269,7 +270,7 @@ with demo:
|
|
269 |
"""
|
270 |
The first value displayed below is a shortened byte representation of the actual encrypted
|
271 |
output.
|
272 |
-
The
|
273 |
"""
|
274 |
)
|
275 |
|
@@ -291,7 +292,7 @@ with demo:
|
|
291 |
gr.Markdown("## Step 6 (optional): Explain the prediction.")
|
292 |
gr.Markdown(
|
293 |
"""
|
294 |
-
In case the credit card is likely to be denied, the
|
295 |
employment would most likely be required in order to increase the chance of getting a
|
296 |
credit card approval.
|
297 |
|
|
|
22 |
)
|
23 |
from backend import (
|
24 |
keygen_send,
|
25 |
+
pre_process_encrypt_send_applicant,
|
26 |
pre_process_encrypt_send_bank,
|
27 |
+
pre_process_encrypt_send_credit_bureau,
|
28 |
run_fhe,
|
29 |
get_output_and_decrypt,
|
30 |
explain_encrypt_run_decrypt,
|
|
|
61 |
"""
|
62 |
)
|
63 |
|
64 |
+
gr.Markdown("# Applicant, Bank and Credit bureau setup")
|
65 |
|
66 |
gr.Markdown("## Step 1: Generate the keys.")
|
67 |
gr.Markdown(
|
|
|
90 |
"""
|
91 |
Select the information that corresponds to the profile you want to evaluate. Three sources
|
92 |
of information are represented in this model:
|
93 |
+
- the applicant's personal information in order to evaluate his/her credit card eligibility;
|
94 |
+
- the applicant bank account history, which provides any type of information on the
|
95 |
+
applicant's banking information relevant to the decision (here, we consider duration of
|
96 |
+
account);
|
97 |
+
- and credit bureau information, which represents any other information (here,
|
98 |
employment history) that could provide additional insight relevant to the decision.
|
99 |
|
100 |
Please always encrypt and send the values (through the buttons on the right) once updated
|
|
|
104 |
|
105 |
with gr.Row():
|
106 |
with gr.Column():
|
107 |
+
gr.Markdown("### Applicant information")
|
108 |
bool_inputs = gr.CheckboxGroup(
|
109 |
["Car", "Property", "Mobile phone"],
|
110 |
label="Which of the following do you actively hold or own?"
|
|
|
168 |
)
|
169 |
|
170 |
with gr.Column():
|
171 |
+
encrypt_button_applicant = gr.Button("Encrypt the inputs and send to server.")
|
172 |
|
173 |
+
encrypted_input_applicant = gr.Textbox(
|
174 |
label="Encrypted input representation:", max_lines=2, interactive=False
|
175 |
)
|
176 |
|
177 |
with gr.Row():
|
178 |
with gr.Column(scale=2):
|
179 |
+
gr.Markdown("### Bank information")
|
180 |
account_age = gr.Slider(
|
181 |
**ACCOUNT_MIN_MAX,
|
182 |
step=1,
|
|
|
193 |
|
194 |
with gr.Row():
|
195 |
with gr.Column(scale=2):
|
196 |
+
gr.Markdown("### Credit bureau information ")
|
197 |
employed = gr.Radio(["Yes", "No"], label="Is the person employed ?", value="Yes")
|
198 |
years_employed = gr.Dropdown(
|
199 |
choices=YEARS_EMPLOYED_BINS,
|
|
|
203 |
)
|
204 |
|
205 |
with gr.Column():
|
206 |
+
encrypt_button_credit_bureau = gr.Button("Encrypt the inputs and send to server.")
|
207 |
|
208 |
+
encrypted_input_credit_bureau = gr.Textbox(
|
209 |
label="Encrypted input representation:", max_lines=2, interactive=False
|
210 |
)
|
211 |
|
212 |
+
# Button to pre-process, generate the key, encrypt and send the applicant inputs from the client
|
213 |
# side to the server
|
214 |
+
encrypt_button_applicant.click(
|
215 |
+
pre_process_encrypt_send_applicant,
|
216 |
inputs=[client_id, bool_inputs, num_children, household_size, total_income, age, \
|
217 |
income_type, education_type, family_status, occupation_type, housing_type],
|
218 |
+
outputs=[encrypted_input_applicant],
|
219 |
)
|
220 |
|
221 |
# Button to pre-process, generate the key, encrypt and send the bank inputs from the client
|
|
|
226 |
outputs=[encrypted_input_bank],
|
227 |
)
|
228 |
|
229 |
+
# Button to pre-process, generate the key, encrypt and send the credit bureau inputs from the
|
230 |
# client side to the server
|
231 |
+
encrypt_button_credit_bureau.click(
|
232 |
+
pre_process_encrypt_send_credit_bureau,
|
233 |
inputs=[client_id, years_employed, employed],
|
234 |
+
outputs=[encrypted_input_credit_bureau],
|
235 |
)
|
236 |
|
237 |
gr.Markdown("# Server side")
|
|
|
254 |
# Button to send the encodings to the server using post method
|
255 |
execute_fhe_button.click(run_fhe, inputs=[client_id], outputs=[fhe_execution_time])
|
256 |
|
257 |
+
gr.Markdown("# Applicant, Bank and Credit bureau decryption")
|
258 |
gr.Markdown(
|
259 |
"""
|
260 |
+
Once the server completed the inference, the encrypted output is returned to the applicant.
|
261 |
|
262 |
The three entities that provide the information to compute the credit score are the only
|
263 |
ones that can decrypt the result. They take part in a decryption protocol that allows to
|
|
|
270 |
"""
|
271 |
The first value displayed below is a shortened byte representation of the actual encrypted
|
272 |
output.
|
273 |
+
The applicant is then able to decrypt the value using its private key.
|
274 |
"""
|
275 |
)
|
276 |
|
|
|
292 |
gr.Markdown("## Step 6 (optional): Explain the prediction.")
|
293 |
gr.Markdown(
|
294 |
"""
|
295 |
+
In case the credit card is likely to be denied, the applicant can ask for how many years of
|
296 |
employment would most likely be required in order to increase the chance of getting a
|
297 |
credit card approval.
|
298 |
|
backend.py
CHANGED
@@ -18,13 +18,13 @@ from settings import (
|
|
18 |
PROCESSED_INPUT_SHAPE,
|
19 |
INPUT_INDEXES,
|
20 |
INPUT_SLICES,
|
21 |
-
|
22 |
PRE_PROCESSOR_BANK_PATH,
|
23 |
-
|
24 |
CLIENT_TYPES,
|
25 |
-
|
26 |
BANK_COLUMNS,
|
27 |
-
|
28 |
YEARS_EMPLOYED_BINS,
|
29 |
YEARS_EMPLOYED_BIN_NAME_TO_INDEX,
|
30 |
)
|
@@ -37,13 +37,13 @@ DENIED_MESSAGE = "Credit card is likely to be denied ❌"
|
|
37 |
|
38 |
# Load pre-processor instances
|
39 |
with (
|
40 |
-
|
41 |
PRE_PROCESSOR_BANK_PATH.open('rb') as file_bank,
|
42 |
-
|
43 |
):
|
44 |
-
|
45 |
PRE_PROCESSOR_BANK = pickle.load(file_bank)
|
46 |
-
|
47 |
|
48 |
|
49 |
def shorten_bytes_object(bytes_object, limit=500):
|
@@ -114,8 +114,8 @@ def _get_client_file_path(name, client_id, client_type=None):
|
|
114 |
name (str): The desired file name (either 'evaluation_key', 'encrypted_inputs' or
|
115 |
'encrypted_outputs').
|
116 |
client_id (int): The client ID to consider.
|
117 |
-
client_type (Optional[str]): The type of
|
118 |
-
'
|
119 |
|
120 |
Returns:
|
121 |
pathlib.Path: The file path.
|
@@ -135,8 +135,8 @@ def _send_to_server(client_id, client_type, file_name):
|
|
135 |
|
136 |
Args:
|
137 |
client_id (int): The client ID to consider.
|
138 |
-
client_type (Optional[str]): The type of client to consider (either '
|
139 |
-
'
|
140 |
file_name (str): File name to send (either 'evaluation_key' or 'encrypted_inputs').
|
141 |
"""
|
142 |
# Get the paths to the encrypted inputs
|
@@ -208,7 +208,8 @@ def _encrypt_send(client_id, inputs, client_type):
|
|
208 |
Args:
|
209 |
client_id (str): The current client ID to consider.
|
210 |
inputs (numpy.ndarray): The inputs to encrypt.
|
211 |
-
client_type (str): The type of client to consider (either '
|
|
|
212 |
|
213 |
Returns:
|
214 |
encrypted_inputs_short (str): A short representation of the encrypted input to send in hex.
|
@@ -244,8 +245,8 @@ def _encrypt_send(client_id, inputs, client_type):
|
|
244 |
return encrypted_inputs_short
|
245 |
|
246 |
|
247 |
-
def
|
248 |
-
"""Pre-process, encrypt and send the
|
249 |
|
250 |
Args:
|
251 |
client_id (str): The current client ID to consider.
|
@@ -262,7 +263,7 @@ def pre_process_encrypt_send_user(client_id, *inputs):
|
|
262 |
own_property = "Property" in bool_inputs
|
263 |
mobile_phone = "Mobile phone" in bool_inputs
|
264 |
|
265 |
-
|
266 |
"Own_car": [own_car],
|
267 |
"Own_property": [own_property],
|
268 |
"Mobile_phone": [mobile_phone],
|
@@ -277,11 +278,11 @@ def pre_process_encrypt_send_user(client_id, *inputs):
|
|
277 |
"Housing_type": [housing_type],
|
278 |
})
|
279 |
|
280 |
-
|
281 |
|
282 |
-
|
283 |
|
284 |
-
return _encrypt_send(client_id,
|
285 |
|
286 |
|
287 |
def pre_process_encrypt_send_bank(client_id, *inputs):
|
@@ -307,8 +308,8 @@ def pre_process_encrypt_send_bank(client_id, *inputs):
|
|
307 |
return _encrypt_send(client_id, preprocessed_bank_inputs, "bank")
|
308 |
|
309 |
|
310 |
-
def
|
311 |
-
"""Pre-process, encrypt and send the credit
|
312 |
|
313 |
Args:
|
314 |
client_id (str): The current client ID to consider.
|
@@ -322,15 +323,15 @@ def pre_process_encrypt_send_cs_agency(client_id, *inputs):
|
|
322 |
years_employed = YEARS_EMPLOYED_BIN_NAME_TO_INDEX[years_employed_bin]
|
323 |
is_employed = employed == "Yes"
|
324 |
|
325 |
-
|
326 |
"Years_employed": [years_employed],
|
327 |
"Employed": [is_employed],
|
328 |
})
|
329 |
|
330 |
-
|
331 |
-
|
332 |
|
333 |
-
return _encrypt_send(client_id,
|
334 |
|
335 |
|
336 |
def run_fhe(client_id):
|
@@ -426,7 +427,7 @@ def explain_encrypt_run_decrypt(client_id, prediction_output, *inputs):
|
|
426 |
"Explaining the prediction can only be done if the credit card is likely to be denied."
|
427 |
)
|
428 |
|
429 |
-
# Retrieve the credit
|
430 |
years_employed, employed = inputs
|
431 |
|
432 |
# Years_employed is divided into several ordered bins. Here, we retrieve the index representing
|
@@ -435,14 +436,14 @@ def explain_encrypt_run_decrypt(client_id, prediction_output, *inputs):
|
|
435 |
|
436 |
# If the bin is not the last (representing the most years of employment), we run the model in
|
437 |
# FHE for each bins "older" or equal to the given bin, in order. Then, we retrieve the first
|
438 |
-
# bin that changes the model's prediction to "approval" and display it to the
|
439 |
if bin_index != len(YEARS_EMPLOYED_BINS) - 1:
|
440 |
|
441 |
# Loop over the bins starting with "older" or equal to the given bin
|
442 |
for years_employed_bin in YEARS_EMPLOYED_BINS[bin_index:]:
|
443 |
|
444 |
# Send the new encrypted input
|
445 |
-
|
446 |
|
447 |
# Run the model in FHE
|
448 |
run_fhe(client_id)
|
@@ -450,16 +451,16 @@ def explain_encrypt_run_decrypt(client_id, prediction_output, *inputs):
|
|
450 |
# Retrieve the new prediction
|
451 |
output_prediction = get_output_and_decrypt(client_id)
|
452 |
|
453 |
-
# If the bin made the model predict an approval, share it to the
|
454 |
if "approved" in output_prediction[0]:
|
455 |
|
456 |
-
# If the approval was made using the given input, that means the
|
457 |
-
# tried the bin suggested in a previous explainability run. In that case, we
|
458 |
# confirm that the credit card is likely to be approved
|
459 |
if years_employed_bin == years_employed:
|
460 |
return APPROVED_MESSAGE
|
461 |
|
462 |
-
# Else, that means the
|
463 |
# suggest to try the obtained bin
|
464 |
return (
|
465 |
DENIED_MESSAGE + f" However, having at least {years_employed_bin} years of "
|
@@ -474,7 +475,7 @@ def explain_encrypt_run_decrypt(client_id, prediction_output, *inputs):
|
|
474 |
"bigger impact in this particular case."
|
475 |
)
|
476 |
|
477 |
-
# In case the
|
478 |
return (
|
479 |
DENIED_MESSAGE + " Unfortunately, you already have the maximum amount of years of "
|
480 |
f"employment ({years_employed} years). Other inputs like the income or the account's age "
|
|
|
18 |
PROCESSED_INPUT_SHAPE,
|
19 |
INPUT_INDEXES,
|
20 |
INPUT_SLICES,
|
21 |
+
PRE_PROCESSOR_APPLICANT_PATH,
|
22 |
PRE_PROCESSOR_BANK_PATH,
|
23 |
+
PRE_PROCESSOR_CREDIT_BUREAU_PATH,
|
24 |
CLIENT_TYPES,
|
25 |
+
APPLICANT_COLUMNS,
|
26 |
BANK_COLUMNS,
|
27 |
+
CREDIT_BUREAU_COLUMNS,
|
28 |
YEARS_EMPLOYED_BINS,
|
29 |
YEARS_EMPLOYED_BIN_NAME_TO_INDEX,
|
30 |
)
|
|
|
37 |
|
38 |
# Load pre-processor instances
|
39 |
with (
|
40 |
+
PRE_PROCESSOR_APPLICANT_PATH.open('rb') as file_applicant,
|
41 |
PRE_PROCESSOR_BANK_PATH.open('rb') as file_bank,
|
42 |
+
PRE_PROCESSOR_CREDIT_BUREAU_PATH.open('rb') as file_credit_bureau,
|
43 |
):
|
44 |
+
PRE_PROCESSOR_APPLICANT = pickle.load(file_applicant)
|
45 |
PRE_PROCESSOR_BANK = pickle.load(file_bank)
|
46 |
+
PRE_PROCESSOR_CREDIT_BUREAU = pickle.load(file_credit_bureau)
|
47 |
|
48 |
|
49 |
def shorten_bytes_object(bytes_object, limit=500):
|
|
|
114 |
name (str): The desired file name (either 'evaluation_key', 'encrypted_inputs' or
|
115 |
'encrypted_outputs').
|
116 |
client_id (int): The client ID to consider.
|
117 |
+
client_type (Optional[str]): The type of client to consider (either 'applicant', 'bank',
|
118 |
+
'credit_bureau' or None). Default to None, which is used for evaluation key and output.
|
119 |
|
120 |
Returns:
|
121 |
pathlib.Path: The file path.
|
|
|
135 |
|
136 |
Args:
|
137 |
client_id (int): The client ID to consider.
|
138 |
+
client_type (Optional[str]): The type of client to consider (either 'applicant', 'bank',
|
139 |
+
'credit_bureau' or None).
|
140 |
file_name (str): File name to send (either 'evaluation_key' or 'encrypted_inputs').
|
141 |
"""
|
142 |
# Get the paths to the encrypted inputs
|
|
|
208 |
Args:
|
209 |
client_id (str): The current client ID to consider.
|
210 |
inputs (numpy.ndarray): The inputs to encrypt.
|
211 |
+
client_type (str): The type of client to consider (either 'applicant', 'bank' or
|
212 |
+
'credit_bureau').
|
213 |
|
214 |
Returns:
|
215 |
encrypted_inputs_short (str): A short representation of the encrypted input to send in hex.
|
|
|
245 |
return encrypted_inputs_short
|
246 |
|
247 |
|
248 |
+
def pre_process_encrypt_send_applicant(client_id, *inputs):
|
249 |
+
"""Pre-process, encrypt and send the applicant inputs for a specific client to the server.
|
250 |
|
251 |
Args:
|
252 |
client_id (str): The current client ID to consider.
|
|
|
263 |
own_property = "Property" in bool_inputs
|
264 |
mobile_phone = "Mobile phone" in bool_inputs
|
265 |
|
266 |
+
applicant_inputs = pandas.DataFrame({
|
267 |
"Own_car": [own_car],
|
268 |
"Own_property": [own_property],
|
269 |
"Mobile_phone": [mobile_phone],
|
|
|
278 |
"Housing_type": [housing_type],
|
279 |
})
|
280 |
|
281 |
+
applicant_inputs = applicant_inputs.reindex(APPLICANT_COLUMNS, axis=1)
|
282 |
|
283 |
+
preprocessed_applicant_inputs = PRE_PROCESSOR_APPLICANT.transform(applicant_inputs)
|
284 |
|
285 |
+
return _encrypt_send(client_id, preprocessed_applicant_inputs, "applicant")
|
286 |
|
287 |
|
288 |
def pre_process_encrypt_send_bank(client_id, *inputs):
|
|
|
308 |
return _encrypt_send(client_id, preprocessed_bank_inputs, "bank")
|
309 |
|
310 |
|
311 |
+
def pre_process_encrypt_send_credit_bureau(client_id, *inputs):
|
312 |
+
"""Pre-process, encrypt and send the credit bureau inputs for a specific client to the server.
|
313 |
|
314 |
Args:
|
315 |
client_id (str): The current client ID to consider.
|
|
|
323 |
years_employed = YEARS_EMPLOYED_BIN_NAME_TO_INDEX[years_employed_bin]
|
324 |
is_employed = employed == "Yes"
|
325 |
|
326 |
+
credit_bureau_inputs = pandas.DataFrame({
|
327 |
"Years_employed": [years_employed],
|
328 |
"Employed": [is_employed],
|
329 |
})
|
330 |
|
331 |
+
credit_bureau_inputs = credit_bureau_inputs.reindex(CREDIT_BUREAU_COLUMNS, axis=1)
|
332 |
+
preprocessed_credit_bureau_inputs = PRE_PROCESSOR_CREDIT_BUREAU.transform(credit_bureau_inputs)
|
333 |
|
334 |
+
return _encrypt_send(client_id, preprocessed_credit_bureau_inputs, "credit_bureau")
|
335 |
|
336 |
|
337 |
def run_fhe(client_id):
|
|
|
427 |
"Explaining the prediction can only be done if the credit card is likely to be denied."
|
428 |
)
|
429 |
|
430 |
+
# Retrieve the credit bureau inputs
|
431 |
years_employed, employed = inputs
|
432 |
|
433 |
# Years_employed is divided into several ordered bins. Here, we retrieve the index representing
|
|
|
436 |
|
437 |
# If the bin is not the last (representing the most years of employment), we run the model in
|
438 |
# FHE for each bins "older" or equal to the given bin, in order. Then, we retrieve the first
|
439 |
+
# bin that changes the model's prediction to "approval" and display it to the applicant.
|
440 |
if bin_index != len(YEARS_EMPLOYED_BINS) - 1:
|
441 |
|
442 |
# Loop over the bins starting with "older" or equal to the given bin
|
443 |
for years_employed_bin in YEARS_EMPLOYED_BINS[bin_index:]:
|
444 |
|
445 |
# Send the new encrypted input
|
446 |
+
pre_process_encrypt_send_credit_bureau(client_id, years_employed_bin, employed)
|
447 |
|
448 |
# Run the model in FHE
|
449 |
run_fhe(client_id)
|
|
|
451 |
# Retrieve the new prediction
|
452 |
output_prediction = get_output_and_decrypt(client_id)
|
453 |
|
454 |
+
# If the bin made the model predict an approval, share it to the applicant
|
455 |
if "approved" in output_prediction[0]:
|
456 |
|
457 |
+
# If the approval was made using the given input, that means the applicant most
|
458 |
+
# likely tried the bin suggested in a previous explainability run. In that case, we
|
459 |
# confirm that the credit card is likely to be approved
|
460 |
if years_employed_bin == years_employed:
|
461 |
return APPROVED_MESSAGE
|
462 |
|
463 |
+
# Else, that means the applicant is looking for some explainability. We therefore
|
464 |
# suggest to try the obtained bin
|
465 |
return (
|
466 |
DENIED_MESSAGE + f" However, having at least {years_employed_bin} years of "
|
|
|
475 |
"bigger impact in this particular case."
|
476 |
)
|
477 |
|
478 |
+
# In case the applicant tried the "oldest" bin (but still got denied), explain why
|
479 |
return (
|
480 |
DENIED_MESSAGE + " Unfortunately, you already have the maximum amount of years of "
|
481 |
f"employment ({years_employed} years). Other inputs like the income or the account's age "
|
deployment_files/model/{pre_processor_user.pkl → pre_processor_applicant.pkl}
RENAMED
File without changes
|
deployment_files/model/{pre_processor_cs_agency.pkl → pre_processor_credit_bureau.pkl}
RENAMED
File without changes
|
deployment_files/{pre_processor_user.pkl → pre_processor_applicant.pkl}
RENAMED
File without changes
|
deployment_files/{pre_processor_cs_agency.pkl → pre_processor_credit_bureau.pkl}
RENAMED
File without changes
|
development.py
CHANGED
@@ -9,12 +9,12 @@ from settings import (
|
|
9 |
DEPLOYMENT_PATH,
|
10 |
DATA_PATH,
|
11 |
INPUT_SLICES,
|
12 |
-
|
13 |
PRE_PROCESSOR_BANK_PATH,
|
14 |
-
|
15 |
-
|
16 |
BANK_COLUMNS,
|
17 |
-
|
18 |
)
|
19 |
from utils.client_server_interface import MultiInputsFHEModelDev
|
20 |
from utils.model import MultiInputDecisionTreeClassifier, MultiInputDecisionTreeRegressor
|
@@ -31,9 +31,9 @@ def get_multi_inputs(data):
|
|
31 |
(Tuple[numpy.ndarray]): The inputs for all three parties.
|
32 |
"""
|
33 |
return (
|
34 |
-
data[:, INPUT_SLICES["
|
35 |
data[:, INPUT_SLICES["bank"]],
|
36 |
-
data[:, INPUT_SLICES["
|
37 |
)
|
38 |
|
39 |
|
@@ -47,18 +47,18 @@ data_x = data.copy()
|
|
47 |
data_y = data_x.pop("Target").copy().to_frame()
|
48 |
|
49 |
# Get data from all parties
|
50 |
-
|
51 |
data_bank = data_x[BANK_COLUMNS].copy()
|
52 |
-
|
53 |
|
54 |
# Feature engineer the data
|
55 |
-
|
56 |
|
57 |
-
|
58 |
preprocessed_data_bank = pre_processor_bank.fit_transform(data_bank)
|
59 |
-
|
60 |
|
61 |
-
preprocessed_data_x = numpy.concatenate((
|
62 |
|
63 |
|
64 |
print("\nTrain and compile the model")
|
@@ -83,12 +83,12 @@ fhe_model_dev.save(via_mlir=True)
|
|
83 |
|
84 |
# Save pre-processors
|
85 |
with (
|
86 |
-
|
87 |
PRE_PROCESSOR_BANK_PATH.open('wb') as file_bank,
|
88 |
-
|
89 |
):
|
90 |
-
pickle.dump(
|
91 |
pickle.dump(pre_processor_bank, file_bank)
|
92 |
-
pickle.dump(
|
93 |
|
94 |
print("\nDone !")
|
|
|
9 |
DEPLOYMENT_PATH,
|
10 |
DATA_PATH,
|
11 |
INPUT_SLICES,
|
12 |
+
PRE_PROCESSOR_APPLICANT_PATH,
|
13 |
PRE_PROCESSOR_BANK_PATH,
|
14 |
+
PRE_PROCESSOR_CREDIT_BUREAU_PATH,
|
15 |
+
APPLICANT_COLUMNS,
|
16 |
BANK_COLUMNS,
|
17 |
+
CREDIT_BUREAU_COLUMNS,
|
18 |
)
|
19 |
from utils.client_server_interface import MultiInputsFHEModelDev
|
20 |
from utils.model import MultiInputDecisionTreeClassifier, MultiInputDecisionTreeRegressor
|
|
|
31 |
(Tuple[numpy.ndarray]): The inputs for all three parties.
|
32 |
"""
|
33 |
return (
|
34 |
+
data[:, INPUT_SLICES["applicant"]],
|
35 |
data[:, INPUT_SLICES["bank"]],
|
36 |
+
data[:, INPUT_SLICES["credit_bureau"]]
|
37 |
)
|
38 |
|
39 |
|
|
|
47 |
data_y = data_x.pop("Target").copy().to_frame()
|
48 |
|
49 |
# Get data from all parties
|
50 |
+
data_applicant = data_x[APPLICANT_COLUMNS].copy()
|
51 |
data_bank = data_x[BANK_COLUMNS].copy()
|
52 |
+
data_credit_bureau = data_x[CREDIT_BUREAU_COLUMNS].copy()
|
53 |
|
54 |
# Feature engineer the data
|
55 |
+
pre_processor_applicant, pre_processor_bank, pre_processor_credit_bureau = get_pre_processors()
|
56 |
|
57 |
+
preprocessed_data_applicant = pre_processor_applicant.fit_transform(data_applicant)
|
58 |
preprocessed_data_bank = pre_processor_bank.fit_transform(data_bank)
|
59 |
+
preprocessed_data_credit_bureau = pre_processor_credit_bureau.fit_transform(data_credit_bureau)
|
60 |
|
61 |
+
preprocessed_data_x = numpy.concatenate((preprocessed_data_applicant, preprocessed_data_bank, preprocessed_data_credit_bureau), axis=1)
|
62 |
|
63 |
|
64 |
print("\nTrain and compile the model")
|
|
|
83 |
|
84 |
# Save pre-processors
|
85 |
with (
|
86 |
+
PRE_PROCESSOR_APPLICANT_PATH.open('wb') as file_applicant,
|
87 |
PRE_PROCESSOR_BANK_PATH.open('wb') as file_bank,
|
88 |
+
PRE_PROCESSOR_CREDIT_BUREAU_PATH.open('wb') as file_credit_bureau,
|
89 |
):
|
90 |
+
pickle.dump(pre_processor_applicant, file_applicant)
|
91 |
pickle.dump(pre_processor_bank, file_bank)
|
92 |
+
pickle.dump(pre_processor_credit_bureau, file_credit_bureau)
|
93 |
|
94 |
print("\nDone !")
|
server.py
CHANGED
@@ -19,8 +19,8 @@ def _get_server_file_path(name, client_id, client_type=None):
|
|
19 |
name (str): The desired file name (either 'evaluation_key', 'encrypted_inputs' or
|
20 |
'encrypted_outputs').
|
21 |
client_id (int): The client ID to consider.
|
22 |
-
client_type (Optional[str]): The type of
|
23 |
-
'
|
24 |
|
25 |
Returns:
|
26 |
pathlib.Path: The file path.
|
|
|
19 |
name (str): The desired file name (either 'evaluation_key', 'encrypted_inputs' or
|
20 |
'encrypted_outputs').
|
21 |
client_id (int): The client ID to consider.
|
22 |
+
client_type (Optional[str]): The type of client to consider (either 'applicant', 'bank',
|
23 |
+
'credit_bureau' or None). Default to None, which is used for evaluation key and output.
|
24 |
|
25 |
Returns:
|
26 |
pathlib.Path: The file path.
|
settings.py
CHANGED
@@ -16,9 +16,9 @@ SERVER_FILES = REPO_DIR / "server_files"
|
|
16 |
DEPLOYMENT_PATH = DEPLOYMENT_PATH / "model"
|
17 |
|
18 |
# Path targeting pre-processor saved files
|
19 |
-
|
20 |
PRE_PROCESSOR_BANK_PATH = DEPLOYMENT_PATH / 'pre_processor_bank.pkl'
|
21 |
-
|
22 |
|
23 |
# Create the necessary directories
|
24 |
FHE_KEYS.mkdir(exist_ok=True)
|
@@ -34,26 +34,26 @@ DATA_PATH = "data/data.csv"
|
|
34 |
# Development settings
|
35 |
PROCESSED_INPUT_SHAPE = (1, 39)
|
36 |
|
37 |
-
CLIENT_TYPES = ["
|
38 |
INPUT_INDEXES = {
|
39 |
-
"
|
40 |
"bank": 1,
|
41 |
-
"
|
42 |
}
|
43 |
INPUT_SLICES = {
|
44 |
-
"
|
45 |
-
"bank": slice(36, 37), # Second position: start from
|
46 |
-
"
|
47 |
}
|
48 |
|
49 |
# Fix column order for pre-processing steps
|
50 |
-
|
51 |
'Own_car', 'Own_property', 'Mobile_phone', 'Num_children', 'Household_size',
|
52 |
'Total_income', 'Age', 'Income_type', 'Education_type', 'Family_status', 'Housing_type',
|
53 |
'Occupation_type',
|
54 |
]
|
55 |
BANK_COLUMNS = ["Account_age"]
|
56 |
-
|
57 |
|
58 |
_data = pandas.read_csv(DATA_PATH, encoding="utf-8")
|
59 |
|
|
|
16 |
DEPLOYMENT_PATH = DEPLOYMENT_PATH / "model"
|
17 |
|
18 |
# Path targeting pre-processor saved files
|
19 |
+
PRE_PROCESSOR_APPLICANT_PATH = DEPLOYMENT_PATH / 'pre_processor_applicant.pkl'
|
20 |
PRE_PROCESSOR_BANK_PATH = DEPLOYMENT_PATH / 'pre_processor_bank.pkl'
|
21 |
+
PRE_PROCESSOR_CREDIT_BUREAU_PATH = DEPLOYMENT_PATH / 'pre_processor_credit_bureau.pkl'
|
22 |
|
23 |
# Create the necessary directories
|
24 |
FHE_KEYS.mkdir(exist_ok=True)
|
|
|
34 |
# Development settings
|
35 |
PROCESSED_INPUT_SHAPE = (1, 39)
|
36 |
|
37 |
+
CLIENT_TYPES = ["applicant", "bank", "credit_bureau"]
|
38 |
INPUT_INDEXES = {
|
39 |
+
"applicant": 0,
|
40 |
"bank": 1,
|
41 |
+
"credit_bureau": 2,
|
42 |
}
|
43 |
INPUT_SLICES = {
|
44 |
+
"applicant": slice(0, 36), # First position: start from 0
|
45 |
+
"bank": slice(36, 37), # Second position: start from n_feature_applicant
|
46 |
+
"credit_bureau": slice(37, 39), # Third position: start from n_feature_applicant + n_feature_bank
|
47 |
}
|
48 |
|
49 |
# Fix column order for pre-processing steps
|
50 |
+
APPLICANT_COLUMNS = [
|
51 |
'Own_car', 'Own_property', 'Mobile_phone', 'Num_children', 'Household_size',
|
52 |
'Total_income', 'Age', 'Income_type', 'Education_type', 'Family_status', 'Housing_type',
|
53 |
'Occupation_type',
|
54 |
]
|
55 |
BANK_COLUMNS = ["Account_age"]
|
56 |
+
CREDIT_BUREAU_COLUMNS = ["Years_employed", "Employed"]
|
57 |
|
58 |
_data = pandas.read_csv(DATA_PATH, encoding="utf-8")
|
59 |
|
utils/client_server_interface.py
CHANGED
@@ -46,8 +46,8 @@ class MultiInputsFHEModelClient(FHEModelClient):
|
|
46 |
Args:
|
47 |
x (numpy.ndarray): The input to consider. Here, the input should only represent a
|
48 |
single party.
|
49 |
-
input_index (int): The index representing the type of model (0: "
|
50 |
-
2: "
|
51 |
processed_input_shape (Tuple[int]): The total input shape (all parties combined) after
|
52 |
pre-processing.
|
53 |
input_slice (slice): The slices to consider for the given party.
|
|
|
46 |
Args:
|
47 |
x (numpy.ndarray): The input to consider. Here, the input should only represent a
|
48 |
single party.
|
49 |
+
input_index (int): The index representing the type of model (0: "applicant", 1: "bank",
|
50 |
+
2: "credit_bureau")
|
51 |
processed_input_shape (Tuple[int]): The total input shape (all parties combined) after
|
52 |
pre-processing.
|
53 |
input_slice (slice): The slices to consider for the given party.
|
utils/pre_processing.py
CHANGED
@@ -22,7 +22,7 @@ def _replace_values_eq(column, value):
|
|
22 |
return column
|
23 |
|
24 |
def get_pre_processors():
|
25 |
-
|
26 |
transformers=[
|
27 |
(
|
28 |
"replace_occupation_type_labor",
|
@@ -55,10 +55,10 @@ def get_pre_processors():
|
|
55 |
verbose_feature_names_out=False,
|
56 |
)
|
57 |
|
58 |
-
|
59 |
transformers=[],
|
60 |
remainder='passthrough',
|
61 |
verbose_feature_names_out=False,
|
62 |
)
|
63 |
|
64 |
-
return
|
|
|
22 |
return column
|
23 |
|
24 |
def get_pre_processors():
|
25 |
+
pre_processor_applicant = ColumnTransformer(
|
26 |
transformers=[
|
27 |
(
|
28 |
"replace_occupation_type_labor",
|
|
|
55 |
verbose_feature_names_out=False,
|
56 |
)
|
57 |
|
58 |
+
pre_processor_credit_bureau = ColumnTransformer(
|
59 |
transformers=[],
|
60 |
remainder='passthrough',
|
61 |
verbose_feature_names_out=False,
|
62 |
)
|
63 |
|
64 |
+
return pre_processor_applicant, pre_processor_bank, pre_processor_credit_bureau
|