romanbredehoft-zama commited on
Commit
61cd73f
1 Parent(s): 316f8e9

Improve explainability step

Browse files
Files changed (2) hide show
  1. app.py +15 -9
  2. backend.py +32 -31
app.py CHANGED
@@ -93,8 +93,11 @@ with demo:
93
  - a user's personal information in order to evaluate his/her credit card eligibility;
94
  - the user’s bank account history, which provides any type of information on the user's
95
  banking information relevant to the decision (here, we consider duration of account);
96
- - and credit scoring agency information, which represents any other information (here, employment
97
- history) that could provide additional insight relevant to the decision.
 
 
 
98
  """
99
  )
100
 
@@ -250,7 +253,7 @@ with demo:
250
  # Button to send the encodings to the server using post method
251
  execute_fhe_button.click(run_fhe, inputs=[client_id], outputs=[fhe_execution_time])
252
 
253
- gr.Markdown("# Client, Bank and Credit Scoring Agency setup")
254
  gr.Markdown(
255
  """
256
  Once the server completed the inference, the encrypted output is returned to the user.
@@ -288,20 +291,23 @@ with demo:
288
  gr.Markdown("## Step 6 (optional): Explain the prediction.")
289
  gr.Markdown(
290
  """
291
- In case the credit card is likely to be denied, the user can run a second model in order to
292
- Explain the prediction better. More specifically, this new model indicates the number of
293
- additional years of employment that could be required in order to increase the chance of
294
  credit card approval.
 
295
  All of the above steps are combined into a single button for simplicity. The following
296
- button therefore encrypts the same inputs (except the years of employment) from all three
297
- parties, runs the new prediction in FHE and decrypts the output.
 
 
 
298
  """
299
  )
300
  explain_button = gr.Button(
301
  "Encrypt the inputs, compute in FHE and decrypt the output."
302
  )
303
  explain_prediction = gr.Textbox(
304
- label="Additional years of employed required.", max_lines=1, interactive=False
305
  )
306
 
307
  # Button to explain the prediction
 
93
  - a user's personal information in order to evaluate his/her credit card eligibility;
94
  - the user’s bank account history, which provides any type of information on the user's
95
  banking information relevant to the decision (here, we consider duration of account);
96
+ - and credit scoring agency information, which represents any other information (here,
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
100
+ before running the FHE inference.
101
  """
102
  )
103
 
 
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("# Client, Bank and Credit Scoring Agency decryption")
257
  gr.Markdown(
258
  """
259
  Once the server completed the inference, the encrypted output is returned to the user.
 
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 user can ask for how many years of
295
+ employment would most likely be required in order to increase the chance of getting a
 
296
  credit card approval.
297
+
298
  All of the above steps are combined into a single button for simplicity. The following
299
+ button therefore encrypts the same inputs (except the years of employment, which varies)
300
+ from all three parties, runs the new prediction in FHE and decrypts the output.
301
+
302
+ In case the following states to try a new "Years of employment" input, one can simply
303
+ update the value in Step 2 and directly run Step 6 once more.
304
  """
305
  )
306
  explain_button = gr.Button(
307
  "Encrypt the inputs, compute in FHE and decrypt the output."
308
  )
309
  explain_prediction = gr.Textbox(
310
+ label="Additional years of employed required.", interactive=False
311
  )
312
 
313
  # Button to explain the prediction
backend.py CHANGED
@@ -31,6 +31,10 @@ from settings import (
31
 
32
  from utils.client_server_interface import MultiInputsFHEModelClient
33
 
 
 
 
 
34
  # Load pre-processor instances
35
  with (
36
  PRE_PROCESSOR_USER_PATH.open('rb') as file_user,
@@ -395,8 +399,7 @@ def get_output_and_decrypt(client_id):
395
  output = numpy.argmax(output_proba, axis=1).squeeze()
396
 
397
  return (
398
- "Credit card is likely to be approved ✅" if output == 1
399
- else "Credit card is likely to be denied ❌",
400
  encrypted_output_short,
401
  )
402
 
@@ -431,14 +434,12 @@ def explain_encrypt_run_decrypt(client_id, prediction_output, *inputs):
431
  bin_index = YEARS_EMPLOYED_BIN_NAME_TO_INDEX[years_employed]
432
 
433
  # If the bin is not the last (representing the most years of employment), we run the model in
434
- # FHE for each bins "older" than the given bin, in order. Then, we retrieve the first bin that
435
- # changes the model's prediction to "approval" and display it to the user.
436
  if bin_index != len(YEARS_EMPLOYED_BINS) - 1:
437
-
438
- output_predictions = []
439
 
440
- # Loop over the bins "older" than the input one
441
- for years_employed_bin in YEARS_EMPLOYED_BINS[bin_index+1:]:
442
 
443
  # Send the new encrypted input
444
  pre_process_encrypt_send_cs_agency(client_id, years_employed_bin, employed)
@@ -449,34 +450,34 @@ def explain_encrypt_run_decrypt(client_id, prediction_output, *inputs):
449
  # Retrieve the new prediction
450
  output_prediction = get_output_and_decrypt(client_id)
451
 
452
- is_approved = "approved" in output_prediction[0]
453
- output_predictions.append(is_approved)
454
-
455
- # Re-send the initial credit scoring agency inputs in order to avoid unwanted conflict (as sending
456
- # some inputs basically re-writes the associated file on the server side)
457
- pre_process_encrypt_send_cs_agency(client_id, years_employed, employed)
458
-
459
- # In case the model predicted at least one approval
460
- if any(output_predictions):
461
 
462
- # Retrieve the first bin that made the model predict an approval
463
- first_approved_prediction_index = numpy.argmax(output_predictions)
464
- years_employed_bin_needed = YEARS_EMPLOYED_BINS[first_approved_prediction_index + bin_index + 1]
 
 
465
 
466
- return (
467
- f"Having at least {years_employed_bin_needed} more years of employment would "
468
- "increase your chance of having your credit card approved."
469
- )
 
 
470
 
 
471
  return (
472
- f"Increasing the number of years of employment up to {YEARS_EMPLOYED_BINS[-1]} years "
473
- "does not seem to be enough to get an approval based on the given inputs. Other inputs "
474
- "like the income or the account's age might have bigger impact in this particular case."
475
- )
 
476
 
 
477
  return (
478
- f"You already have the maximum amount of years of employment ({years_employed} years). "
479
- "Other inputs like the income or the account's age might have bigger impact in this "
480
- "particular case."
481
  )
482
 
 
31
 
32
  from utils.client_server_interface import MultiInputsFHEModelClient
33
 
34
+ # Define the messages associated to the predictions
35
+ APPROVED_MESSAGE = "Credit card is likely to be approved ✅"
36
+ DENIED_MESSAGE = "Credit card is likely to be denied ❌"
37
+
38
  # Load pre-processor instances
39
  with (
40
  PRE_PROCESSOR_USER_PATH.open('rb') as file_user,
 
399
  output = numpy.argmax(output_proba, axis=1).squeeze()
400
 
401
  return (
402
+ APPROVED_MESSAGE if output == 1 else DENIED_MESSAGE,
 
403
  encrypted_output_short,
404
  )
405
 
 
434
  bin_index = YEARS_EMPLOYED_BIN_NAME_TO_INDEX[years_employed]
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 user.
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
  pre_process_encrypt_send_cs_agency(client_id, years_employed_bin, employed)
 
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 user
454
+ if "approved" in output_prediction[0]:
 
 
 
 
 
 
 
455
 
456
+ # If the approval was made using the given input, that means the user most likely
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 users is looking for some explainability. We therefore
463
+ # suggest to try the obtained bin
464
+ return (
465
+ DENIED_MESSAGE + f" However, having at least {years_employed_bin} years of "
466
+ "employment would increase your chance of having your credit card approved."
467
+ )
468
 
469
+ # In case no bins made the model predict an approval, explain why
470
  return (
471
+ DENIED_MESSAGE + " Unfortunately, increasing the number of years of employment up to "
472
+ f"{YEARS_EMPLOYED_BINS[-1]} years does not seem to be enough to get an approval based "
473
+ "on the given inputs. Other inputs like the income or the account's age might have "
474
+ "bigger impact in this particular case."
475
+ )
476
 
477
+ # In case the user tried the "oldest" bin (but still got denied), explain why
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 "
481
+ "might have a bigger impact in this particular case."
482
  )
483