romanbredehoft-zama commited on
Commit
b47829b
β€’
1 Parent(s): 060d7fa

Improve explainability inner workings

Browse files
app.py CHANGED
@@ -10,13 +10,15 @@ from settings import (
10
  CHILDREN_MIN_MAX,
11
  INCOME_MIN_MAX,
12
  AGE_MIN_MAX,
13
- EMPLOYED_MIN_MAX,
14
  FAMILY_MIN_MAX,
15
  INCOME_TYPES,
16
  OCCUPATION_TYPES,
17
  HOUSING_TYPES,
18
  EDUCATION_TYPES,
19
  FAMILY_STATUS,
 
 
 
20
  )
21
  from backend import (
22
  keygen_send,
@@ -25,7 +27,7 @@ from backend import (
25
  pre_process_encrypt_send_third_party,
26
  run_fhe,
27
  get_output_and_decrypt,
28
- years_employed_encrypt_run_decrypt,
29
  )
30
 
31
 
@@ -97,18 +99,67 @@ with demo:
97
  with gr.Row():
98
  with gr.Column():
99
  gr.Markdown("### User")
100
- bool_inputs = gr.CheckboxGroup(["Car", "Property", "Mobile phone"], label="Which of the following do you actively hold or own?")
101
- num_children = gr.Slider(**CHILDREN_MIN_MAX, step=1, label="Number of children", info="How many children do you have ?")
102
- household_size = gr.Slider(**FAMILY_MIN_MAX, step=1, label="Household size", info="How many members does your household have ?")
103
- total_income = gr.Slider(**INCOME_MIN_MAX, label="Income", info="What's you total yearly income (in euros) ?")
104
- age = gr.Slider(**AGE_MIN_MAX, step=1, label="Age", info="How old are you ?")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
  with gr.Column():
107
- income_type = gr.Dropdown(choices=INCOME_TYPES, value=INCOME_TYPES[0], label="Income type", info="What is your main type of income ?")
108
- education_type = gr.Dropdown(choices=EDUCATION_TYPES, value=EDUCATION_TYPES[0], label="Education", info="What is your education background ?")
109
- family_status = gr.Dropdown(choices=FAMILY_STATUS, value=FAMILY_STATUS[0], label="Family", info="What is your family status ?")
110
- occupation_type = gr.Dropdown(choices=OCCUPATION_TYPES, value=OCCUPATION_TYPES[0], label="Occupation", info="What is your main occupation ?")
111
- housing_type = gr.Dropdown(choices=HOUSING_TYPES, value=HOUSING_TYPES[0], label="Housing", info="In what type of housing do you live ?")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  with gr.Column():
114
  encrypt_button_user = gr.Button("Encrypt the inputs and send to server.")
@@ -120,7 +171,12 @@ with demo:
120
  with gr.Row():
121
  with gr.Column(scale=2):
122
  gr.Markdown("### Bank ")
123
- account_age = gr.Slider(**ACCOUNT_MIN_MAX, step=1, label="Account age (months)", info="How long have this person had this bank account (in months) ?")
 
 
 
 
 
124
 
125
  with gr.Column():
126
  encrypt_button_bank = gr.Button("Encrypt the inputs and send to server.")
@@ -133,7 +189,12 @@ with demo:
133
  with gr.Column(scale=2):
134
  gr.Markdown("### Third party ")
135
  employed = gr.Radio(["Yes", "No"], label="Is the person employed ?", value="Yes")
136
- years_employed = gr.Slider(**EMPLOYED_MIN_MAX, step=1, label="Years of employment", info="How long have this person been employed (in years) ?")
 
 
 
 
 
137
 
138
  with gr.Column():
139
  encrypt_button_third_party = gr.Button("Encrypt the inputs and send to server.")
@@ -163,7 +224,7 @@ with demo:
163
  # client side to the server
164
  encrypt_button_third_party.click(
165
  pre_process_encrypt_send_third_party,
166
- inputs=[client_id, employed, years_employed],
167
  outputs=[encrypted_input_third_party],
168
  )
169
 
@@ -230,20 +291,18 @@ with demo:
230
  parties, runs the new prediction in FHE and decrypts the output.
231
  """
232
  )
233
- years_employed_prediction_button = gr.Button(
234
  "Encrypt the inputs, compute in FHE and decrypt the output."
235
  )
236
- years_employed_prediction = gr.Textbox(
237
  label="Additional years of employed required.", max_lines=1, interactive=False
238
  )
239
 
240
  # Button to explain the prediction
241
- years_employed_prediction_button.click(
242
- years_employed_encrypt_run_decrypt,
243
- inputs=[client_id, prediction_output, bool_inputs, num_children, household_size, \
244
- total_income, age, income_type, education_type, family_status, occupation_type, \
245
- housing_type, account_age, employed, years_employed],
246
- outputs=[years_employed_prediction],
247
  )
248
 
249
  gr.Markdown(
 
10
  CHILDREN_MIN_MAX,
11
  INCOME_MIN_MAX,
12
  AGE_MIN_MAX,
 
13
  FAMILY_MIN_MAX,
14
  INCOME_TYPES,
15
  OCCUPATION_TYPES,
16
  HOUSING_TYPES,
17
  EDUCATION_TYPES,
18
  FAMILY_STATUS,
19
+ YEARS_EMPLOYED_BINS,
20
+ INCOME_VALUE,
21
+ AGE_VALUE,
22
  )
23
  from backend import (
24
  keygen_send,
 
27
  pre_process_encrypt_send_third_party,
28
  run_fhe,
29
  get_output_and_decrypt,
30
+ explain_encrypt_run_decrypt,
31
  )
32
 
33
 
 
99
  with gr.Row():
100
  with gr.Column():
101
  gr.Markdown("### User")
102
+ bool_inputs = gr.CheckboxGroup(
103
+ ["Car", "Property", "Mobile phone"],
104
+ label="Which of the following do you actively hold or own?"
105
+ )
106
+ num_children = gr.Slider(
107
+ **CHILDREN_MIN_MAX,
108
+ step=1,
109
+ label="Number of children",
110
+ info="How many children do you have ?"
111
+ )
112
+ household_size = gr.Slider(
113
+ **FAMILY_MIN_MAX,
114
+ step=1,
115
+ label="Household size",
116
+ info="How many members does your household have ?"
117
+ )
118
+ total_income = gr.Slider(
119
+ **INCOME_MIN_MAX,
120
+ value=INCOME_VALUE,
121
+ label="Income",
122
+ info="What's you total yearly income (in euros) ?"
123
+ )
124
+ age = gr.Slider(
125
+ **AGE_MIN_MAX,
126
+ value=AGE_VALUE,
127
+ step=1,
128
+ label="Age",
129
+ info="How old are you ?"
130
+ )
131
 
132
  with gr.Column():
133
+ income_type = gr.Dropdown(
134
+ choices=INCOME_TYPES,
135
+ value=INCOME_TYPES[0],
136
+ label="Income type",
137
+ info="What is your main type of income ?"
138
+ )
139
+ education_type = gr.Dropdown(
140
+ choices=EDUCATION_TYPES,
141
+ value=EDUCATION_TYPES[0],
142
+ label="Education",
143
+ info="What is your education background ?"
144
+ )
145
+ family_status = gr.Dropdown(
146
+ choices=FAMILY_STATUS,
147
+ value=FAMILY_STATUS[0],
148
+ label="Family",
149
+ info="What is your family status ?"
150
+ )
151
+ occupation_type = gr.Dropdown(
152
+ choices=OCCUPATION_TYPES,
153
+ value=OCCUPATION_TYPES[0],
154
+ label="Occupation",
155
+ info="What is your main occupation ?"
156
+ )
157
+ housing_type = gr.Dropdown(
158
+ choices=HOUSING_TYPES,
159
+ value=HOUSING_TYPES[0],
160
+ label="Housing",
161
+ info="In what type of housing do you live ?"
162
+ )
163
 
164
  with gr.Column():
165
  encrypt_button_user = gr.Button("Encrypt the inputs and send to server.")
 
171
  with gr.Row():
172
  with gr.Column(scale=2):
173
  gr.Markdown("### Bank ")
174
+ account_age = gr.Slider(
175
+ **ACCOUNT_MIN_MAX,
176
+ step=1,
177
+ label="Account age (months)",
178
+ info="How long have this person had this bank account (in months) ?"
179
+ )
180
 
181
  with gr.Column():
182
  encrypt_button_bank = gr.Button("Encrypt the inputs and send to server.")
 
189
  with gr.Column(scale=2):
190
  gr.Markdown("### Third party ")
191
  employed = gr.Radio(["Yes", "No"], label="Is the person employed ?", value="Yes")
192
+ years_employed = gr.Dropdown(
193
+ choices=YEARS_EMPLOYED_BINS,
194
+ value=YEARS_EMPLOYED_BINS[0],
195
+ label="Years of employment",
196
+ info="How long have this person been employed (in years) ?"
197
+ )
198
 
199
  with gr.Column():
200
  encrypt_button_third_party = gr.Button("Encrypt the inputs and send to server.")
 
224
  # client side to the server
225
  encrypt_button_third_party.click(
226
  pre_process_encrypt_send_third_party,
227
+ inputs=[client_id, years_employed, employed],
228
  outputs=[encrypted_input_third_party],
229
  )
230
 
 
291
  parties, runs the new prediction in FHE and decrypts the output.
292
  """
293
  )
294
+ explain_button = gr.Button(
295
  "Encrypt the inputs, compute in FHE and decrypt the output."
296
  )
297
+ explain_prediction = gr.Textbox(
298
  label="Additional years of employed required.", max_lines=1, interactive=False
299
  )
300
 
301
  # Button to explain the prediction
302
+ explain_button.click(
303
+ explain_encrypt_run_decrypt,
304
+ inputs=[client_id, prediction_output, years_employed, employed],
305
+ outputs=[explain_prediction],
 
 
306
  )
307
 
308
  gr.Markdown(
backend.py CHANGED
@@ -14,26 +14,22 @@ from settings import (
14
  FHE_KEYS,
15
  CLIENT_FILES,
16
  SERVER_FILES,
17
- APPROVAL_DEPLOYMENT_PATH,
18
- EXPLAIN_DEPLOYMENT_PATH,
19
- APPROVAL_PROCESSED_INPUT_SHAPE,
20
- EXPLAIN_PROCESSED_INPUT_SHAPE,
21
  INPUT_INDEXES,
22
- APPROVAL_INPUT_SLICES,
23
- EXPLAIN_INPUT_SLICES,
24
  PRE_PROCESSOR_USER_PATH,
25
  PRE_PROCESSOR_BANK_PATH,
26
  PRE_PROCESSOR_THIRD_PARTY_PATH,
27
  CLIENT_TYPES,
28
  USER_COLUMNS,
29
  BANK_COLUMNS,
30
- APPROVAL_THIRD_PARTY_COLUMNS,
 
 
31
  )
32
 
33
- from utils.client_server_interface import MultiInputsFHEModelClient, MultiInputsFHEModelServer
34
-
35
- # Load the server used for explaining the prediction
36
- EXPLAIN_FHE_SERVER = MultiInputsFHEModelServer(EXPLAIN_DEPLOYMENT_PATH)
37
 
38
  # Load pre-processor instances
39
  with (
@@ -93,22 +89,18 @@ def clean_temporary_files(n_keys=20):
93
  shutil.rmtree(directory)
94
 
95
 
96
- def _get_client(client_id, is_approval=True):
97
  """Get the client instance.
98
 
99
  Args:
100
  client_id (int): The client ID to consider.
101
- is_approval (bool): If client is representing the 'approval' model (else, it is
102
- representing the 'explain' model). Default to True.
103
 
104
  Returns:
105
  FHEModelClient: The client instance.
106
  """
107
- key_suffix = "approval" if is_approval else "explain"
108
- key_dir = FHE_KEYS / f"{client_id}_{key_suffix}"
109
- client_dir = APPROVAL_DEPLOYMENT_PATH if is_approval else EXPLAIN_DEPLOYMENT_PATH
110
 
111
- return MultiInputsFHEModelClient(client_dir, key_dir=key_dir, nb_inputs=len(CLIENT_TYPES))
112
 
113
 
114
  def _get_client_file_path(name, client_id, client_type=None):
@@ -206,7 +198,7 @@ def keygen_send():
206
  return client_id, evaluation_key_short, gr.update(value="Keys are generated and evaluation key is sent βœ…")
207
 
208
 
209
- def _encrypt_send(client_id, inputs, client_type, app_mode=True):
210
  """Encrypt the given inputs for a specific client and send it to the server.
211
 
212
  Args:
@@ -227,8 +219,8 @@ def _encrypt_send(client_id, inputs, client_type, app_mode=True):
227
  encrypted_inputs = client.quantize_encrypt_serialize_multi_inputs(
228
  inputs,
229
  input_index=INPUT_INDEXES[client_type],
230
- processed_input_shape=APPROVAL_PROCESSED_INPUT_SHAPE,
231
- input_slice=APPROVAL_INPUT_SLICES[client_type],
232
  )
233
 
234
  file_name = "encrypted_inputs"
@@ -248,14 +240,15 @@ def _encrypt_send(client_id, inputs, client_type, app_mode=True):
248
  return encrypted_inputs_short
249
 
250
 
251
- def _pre_process_user(*inputs):
252
- """Pre-process the user inputs.
253
 
254
  Args:
 
255
  *inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
256
 
257
  Returns:
258
- (numpy.ndarray): The pre-processed inputs.
259
  """
260
  bool_inputs, num_children, household_size, total_income, age, income_type, education_type, \
261
  family_status, occupation_type, housing_type = inputs
@@ -284,32 +277,18 @@ def _pre_process_user(*inputs):
284
 
285
  preprocessed_user_inputs = PRE_PROCESSOR_USER.transform(user_inputs)
286
 
287
- return preprocessed_user_inputs
288
-
289
-
290
- def pre_process_encrypt_send_user(client_id, *inputs):
291
- """Pre-process, encrypt and send the user inputs for a specific client to the server.
292
-
293
- Args:
294
- client_id (str): The current client ID to consider.
295
- *inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
296
-
297
- Returns:
298
- (str): A short representation of the encrypted input to send in hex.
299
- """
300
- preprocessed_user_inputs = _pre_process_user(*inputs)
301
-
302
  return _encrypt_send(client_id, preprocessed_user_inputs, "user")
303
 
304
 
305
- def _pre_process_bank(*inputs):
306
- """Pre-process the bank inputs.
307
 
308
  Args:
 
309
  *inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
310
 
311
  Returns:
312
- (numpy.ndarray): The pre-processed inputs.
313
  """
314
  account_age = inputs[0]
315
 
@@ -321,52 +300,7 @@ def _pre_process_bank(*inputs):
321
 
322
  preprocessed_bank_inputs = PRE_PROCESSOR_BANK.transform(bank_inputs)
323
 
324
- return preprocessed_bank_inputs
325
-
326
-
327
- def pre_process_encrypt_send_bank(client_id, *inputs):
328
- """Pre-process, encrypt and send the bank inputs for a specific client to the server.
329
-
330
- Args:
331
- client_id (str): The current client ID to consider.
332
- *inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
333
-
334
- Returns:
335
- (str): A short representation of the encrypted input to send in hex.
336
- """
337
- preprocessed_bank_inputs = _pre_process_bank(*inputs)
338
-
339
  return _encrypt_send(client_id, preprocessed_bank_inputs, "bank")
340
-
341
-
342
- def _pre_process_third_party(*inputs):
343
- """Pre-process the third party inputs.
344
-
345
- Args:
346
- *inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
347
-
348
- Returns:
349
- (numpy.ndarray): The pre-processed inputs.
350
- """
351
- third_party_data = {}
352
- if len(inputs) == 1:
353
- employed = inputs[0]
354
- else:
355
- employed, years_employed = inputs
356
- third_party_data["Years_employed"] = [years_employed]
357
-
358
- is_employed = employed == "Yes"
359
- third_party_data["Employed"] = [is_employed]
360
-
361
- third_party_inputs = pandas.DataFrame(third_party_data)
362
-
363
- if len(inputs) == 1:
364
- preprocessed_third_party_inputs = third_party_inputs.to_numpy()
365
- else:
366
- third_party_inputs = third_party_inputs.reindex(APPROVAL_THIRD_PARTY_COLUMNS, axis=1)
367
- preprocessed_third_party_inputs = PRE_PROCESSOR_THIRD_PARTY.transform(third_party_inputs)
368
-
369
- return preprocessed_third_party_inputs
370
 
371
 
372
  def pre_process_encrypt_send_third_party(client_id, *inputs):
@@ -379,7 +313,18 @@ def pre_process_encrypt_send_third_party(client_id, *inputs):
379
  Returns:
380
  (str): A short representation of the encrypted input to send in hex.
381
  """
382
- preprocessed_third_party_inputs = _pre_process_third_party(*inputs)
 
 
 
 
 
 
 
 
 
 
 
383
 
384
  return _encrypt_send(client_id, preprocessed_third_party_inputs, "third_party")
385
 
@@ -459,7 +404,7 @@ def get_output_and_decrypt(client_id):
459
  raise gr.Error("Please run the FHE execution first and wait for it to be completed.")
460
 
461
 
462
- def years_employed_encrypt_run_decrypt(client_id, prediction_output, *inputs):
463
  """Pre-process and encrypt the inputs, run the prediction in FHE and decrypt the output.
464
 
465
  Args:
@@ -470,8 +415,7 @@ def years_employed_encrypt_run_decrypt(client_id, prediction_output, *inputs):
470
 
471
  Returns:
472
  (str): A message indicating the number of additional years of employment that could be
473
- required in order to increase the chance of
474
- credit card approval.
475
  """
476
 
477
  if "approved" in prediction_output:
@@ -479,63 +423,60 @@ def years_employed_encrypt_run_decrypt(client_id, prediction_output, *inputs):
479
  "Explaining the prediction can only be done if the credit card is likely to be denied."
480
  )
481
 
482
- # Retrieve the client instance
483
- client = _get_client(client_id, is_approval=False)
484
 
485
- # Generate the private and evaluation keys
486
- client.generate_private_and_evaluation_keys(force=False)
 
487
 
488
- # Retrieve the serialized evaluation key
489
- evaluation_key = client.get_serialized_evaluation_keys()
 
 
490
 
491
- bool_inputs, num_children, household_size, total_income, age, income_type, education_type, \
492
- family_status, occupation_type, housing_type, account_age, employed, years_employed = inputs
 
 
 
 
 
493
 
494
- preprocessed_user_inputs = _pre_process_user(
495
- bool_inputs, num_children, household_size, total_income, age, income_type, education_type,
496
- family_status, occupation_type, housing_type,
497
- )
498
- preprocessed_bank_inputs = _pre_process_bank(account_age)
499
- preprocessed_third_party_inputs = _pre_process_third_party(employed)
500
 
501
- preprocessed_inputs = [
502
- preprocessed_user_inputs,
503
- preprocessed_bank_inputs,
504
- preprocessed_third_party_inputs
505
- ]
506
 
507
- # Quantize, encrypt and serialize the inputs
508
- encrypted_inputs = []
509
- for client_type, preprocessed_input in zip(CLIENT_TYPES, preprocessed_inputs):
510
- encrypted_input = client.quantize_encrypt_serialize_multi_inputs(
511
- preprocessed_input,
512
- input_index=INPUT_INDEXES[client_type],
513
- processed_input_shape=EXPLAIN_PROCESSED_INPUT_SHAPE,
514
- input_slice=EXPLAIN_INPUT_SLICES[client_type],
515
- )
516
- encrypted_inputs.append(encrypted_input)
517
-
518
- # Run the FHE computation
519
- encrypted_output = EXPLAIN_FHE_SERVER.run(
520
- *encrypted_inputs,
521
- serialized_evaluation_keys=evaluation_key
522
- )
523
 
524
- # Decrypt the output
525
- output_prediction = client.deserialize_decrypt_dequantize(encrypted_output)
 
526
 
527
- # Get the difference with the initial 'years of employment' input
528
- years_employed_diff = int(numpy.ceil(output_prediction.squeeze() - years_employed))
 
 
529
 
530
- if years_employed_diff > 0:
531
  return (
532
- f"Having at least {years_employed_diff} more years of employment would increase "
533
- "your chance of having your credit card approved."
 
534
  )
535
 
536
  return (
537
- "The number of years of employment you provided seems to be enough. The negative prediction "
538
- "might come from other inputs."
 
539
  )
540
-
541
 
 
14
  FHE_KEYS,
15
  CLIENT_FILES,
16
  SERVER_FILES,
17
+ DEPLOYMENT_PATH,
18
+ PROCESSED_INPUT_SHAPE,
 
 
19
  INPUT_INDEXES,
20
+ INPUT_SLICES,
 
21
  PRE_PROCESSOR_USER_PATH,
22
  PRE_PROCESSOR_BANK_PATH,
23
  PRE_PROCESSOR_THIRD_PARTY_PATH,
24
  CLIENT_TYPES,
25
  USER_COLUMNS,
26
  BANK_COLUMNS,
27
+ THIRD_PARTY_COLUMNS,
28
+ YEARS_EMPLOYED_BINS,
29
+ YEARS_EMPLOYED_BIN_NAME_TO_INDEX,
30
  )
31
 
32
+ from utils.client_server_interface import MultiInputsFHEModelClient
 
 
 
33
 
34
  # Load pre-processor instances
35
  with (
 
89
  shutil.rmtree(directory)
90
 
91
 
92
+ def _get_client(client_id):
93
  """Get the client instance.
94
 
95
  Args:
96
  client_id (int): The client ID to consider.
 
 
97
 
98
  Returns:
99
  FHEModelClient: The client instance.
100
  """
101
+ key_dir = FHE_KEYS / f"{client_id}"
 
 
102
 
103
+ return MultiInputsFHEModelClient(DEPLOYMENT_PATH, key_dir=key_dir, nb_inputs=len(CLIENT_TYPES))
104
 
105
 
106
  def _get_client_file_path(name, client_id, client_type=None):
 
198
  return client_id, evaluation_key_short, gr.update(value="Keys are generated and evaluation key is sent βœ…")
199
 
200
 
201
+ def _encrypt_send(client_id, inputs, client_type):
202
  """Encrypt the given inputs for a specific client and send it to the server.
203
 
204
  Args:
 
219
  encrypted_inputs = client.quantize_encrypt_serialize_multi_inputs(
220
  inputs,
221
  input_index=INPUT_INDEXES[client_type],
222
+ processed_input_shape=PROCESSED_INPUT_SHAPE,
223
+ input_slice=INPUT_SLICES[client_type],
224
  )
225
 
226
  file_name = "encrypted_inputs"
 
240
  return encrypted_inputs_short
241
 
242
 
243
+ def pre_process_encrypt_send_user(client_id, *inputs):
244
+ """Pre-process, encrypt and send the user inputs for a specific client to the server.
245
 
246
  Args:
247
+ client_id (str): The current client ID to consider.
248
  *inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
249
 
250
  Returns:
251
+ (str): A short representation of the encrypted input to send in hex.
252
  """
253
  bool_inputs, num_children, household_size, total_income, age, income_type, education_type, \
254
  family_status, occupation_type, housing_type = inputs
 
277
 
278
  preprocessed_user_inputs = PRE_PROCESSOR_USER.transform(user_inputs)
279
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  return _encrypt_send(client_id, preprocessed_user_inputs, "user")
281
 
282
 
283
+ def pre_process_encrypt_send_bank(client_id, *inputs):
284
+ """Pre-process, encrypt and send the bank inputs for a specific client to the server.
285
 
286
  Args:
287
+ client_id (str): The current client ID to consider.
288
  *inputs (Tuple[numpy.ndarray]): The inputs to pre-process.
289
 
290
  Returns:
291
+ (str): A short representation of the encrypted input to send in hex.
292
  """
293
  account_age = inputs[0]
294
 
 
300
 
301
  preprocessed_bank_inputs = PRE_PROCESSOR_BANK.transform(bank_inputs)
302
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  return _encrypt_send(client_id, preprocessed_bank_inputs, "bank")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
 
306
  def pre_process_encrypt_send_third_party(client_id, *inputs):
 
313
  Returns:
314
  (str): A short representation of the encrypted input to send in hex.
315
  """
316
+ years_employed_bin, employed = inputs
317
+
318
+ years_employed = YEARS_EMPLOYED_BIN_NAME_TO_INDEX[years_employed_bin]
319
+ is_employed = employed == "Yes"
320
+
321
+ third_party_inputs = pandas.DataFrame({
322
+ "Years_employed": [years_employed],
323
+ "Employed": [is_employed],
324
+ })
325
+
326
+ third_party_inputs = third_party_inputs.reindex(THIRD_PARTY_COLUMNS, axis=1)
327
+ preprocessed_third_party_inputs = PRE_PROCESSOR_THIRD_PARTY.transform(third_party_inputs)
328
 
329
  return _encrypt_send(client_id, preprocessed_third_party_inputs, "third_party")
330
 
 
404
  raise gr.Error("Please run the FHE execution first and wait for it to be completed.")
405
 
406
 
407
+ def explain_encrypt_run_decrypt(client_id, prediction_output, *inputs):
408
  """Pre-process and encrypt the inputs, run the prediction in FHE and decrypt the output.
409
 
410
  Args:
 
415
 
416
  Returns:
417
  (str): A message indicating the number of additional years of employment that could be
418
+ required in order to increase the chance of credit card approval.
 
419
  """
420
 
421
  if "approved" in prediction_output:
 
423
  "Explaining the prediction can only be done if the credit card is likely to be denied."
424
  )
425
 
426
+ # Retrieve the third party inputs
427
+ years_employed, employed = inputs
428
 
429
+ # Years_employed is divided into several ordered bins. Here, we retrieve the index representing
430
+ # the bin from the input
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_third_party(client_id, years_employed_bin, employed)
445
 
446
+ # Run the model in FHE
447
+ run_fhe(client_id)
 
 
 
 
448
 
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 third party 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_third_party(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
 
data/data.csv CHANGED
@@ -1,57 +1,57 @@
1
  Own_car,Own_property,Mobile_phone,Num_children,Household_size,Total_income,Age,Income_type,Education_type,Family_status,Occupation_type,Housing_type,Account_age,Employed,Years_employed,Target
2
- 1,1,1,2,4,27500.000000000004,35,Salaried,Higher education,Married,Engineer,House / apartment,36,1,5,1
3
  0,0,1,0,1,11000.0,50,Pensioner,Secondary,Widow,Retired,Rented apartment,12,0,0,0
4
- 1,1,0,1,3,41250.0,40,Self-employed,Higher education,Married,Business Owner,House / apartment,48,1,10,1
5
- 0,1,0,1,2,16500.0,28,Salaried,Secondary,Single,Teacher,With parents,24,1,3,1
6
- 1,0,1,0,1,24750.000000000004,32,Self-employed,Higher education,Divorced,Entrepreneur,Rented apartment,60,1,7,1
7
- 0,1,1,3,5,19250.0,45,Salaried,Incomplete higher,Married,Office Worker,House / apartment,30,1,10,1
8
  1,0,0,0,1,13750.000000000002,55,Pensioner,Lower secondary,Widow,Retired,House / apartment,120,0,0,0
9
- 0,1,1,2,3,33000.0,37,Salaried,Higher education,Married,Manager,House / apartment,36,1,12,1
10
- 1,1,1,0,1,44000.0,29,Self-employed,Higher education,Single,Entrepreneur,House / apartment,48,1,6,1
11
- 0,1,1,4,6,22000.0,38,Salaried,Secondary,Married,Salesperson,With parents,60,1,15,1
12
- 1,0,0,1,2,30250.000000000004,26,Self-employed,Higher education,Married,Designer,Rented apartment,12,1,3,1
13
  0,0,0,0,1,16500.0,60,Pensioner,Lower secondary,Widow,Retired,House / apartment,180,0,0,0
14
- 1,1,1,2,4,38500.0,45,Salaried,Higher education,Married,Manager,House / apartment,72,1,20,1
15
- 0,1,1,3,5,35750.0,50,Salaried,Incomplete higher,Divorced,Teacher,House / apartment,96,1,25,1
16
- 1,0,1,0,1,46750.00000000001,33,Self-employed,Higher education,Single,Consultant,Rented apartment,36,1,10,1
17
  0,1,0,1,3,17600.0,55,Pensioner,Secondary,Widow,Retired,House / apartment,150,0,0,0
18
- 1,1,1,2,4,49500.00000000001,41,Salaried,Higher education,Married,Doctor,House / apartment,60,1,15,1
19
  0,0,1,0,1,12650.000000000002,24,Student,Incomplete higher,Single,Student,With parents,6,0,0,0
20
- 1,0,0,1,3,26400.000000000004,35,Self-employed,Secondary,Divorced,Cooking Staff,Rented apartment,24,1,5,1
21
- 0,1,1,3,5,30250.000000000004,50,Salaried,Higher education,Widow,Engineer,House / apartment,120,1,20,1
22
- 1,0,1,0,2,38500.0,30,Salaried,Higher education,Married,Lawyer,Rented apartment,36,1,8,1
23
- 0,1,0,2,4,17600.0,40,Self-employed,Secondary,Married,Carpenter,House / apartment,48,1,12,1
24
  1,0,1,1,2,14300.000000000002,29,Student,Higher education,Single,Student,With parents,12,0,0,0
25
  0,1,1,0,1,24750.000000000004,55,Pensioner,Lower secondary,Divorced,Retired,House / apartment,180,0,0,0
26
- 1,1,1,1,3,44000.0,46,Salaried,Higher education,Married,Architect,House / apartment,72,1,18,1
27
- 0,0,0,2,5,20350.0,33,Self-employed,Secondary,Single,Plumber,With parents,24,1,6,1
28
- 1,1,1,0,1,35750.0,28,Self-employed,Higher education,Single,Designer,Rented apartment,36,1,4,1
29
  0,0,0,1,2,15950.000000000002,53,Pensioner,Incomplete higher,Widow,Retired,House / apartment,144,0,0,0
30
- 1,1,0,3,6,31900.000000000004,39,Salaried,Secondary,Divorced,Teacher,House / apartment,60,1,10,1
31
- 0,1,1,0,1,22000.0,31,Salaried,Higher education,Single,Nurse,Rented apartment,48,1,7,1
32
- 1,0,1,2,4,28050.000000000004,42,Self-employed,Higher education,Married,Business Owner,House / apartment,60,1,14,1
33
- 0,1,0,1,3,18700.0,47,Salaried,Secondary,Married,Factory Worker,With parents,72,1,9,0
34
- 0,0,1,0,1,46750.00000000001,25,Self-employed,Higher education,Single,Entrepreneur,Rented apartment,12,1,2,1
35
- 1,1,0,3,5,16500.0,55,Salaried,Secondary,Married,Teacher,House / apartment,240,1,30,1
36
- 0,0,1,1,2,24750.000000000004,40,Salaried,Higher education,Divorced,Nurse,With parents,36,1,10,1
37
  1,0,1,0,1,11000.0,50,Pensioner,Lower secondary,Widow,Retired,Rented apartment,60,0,0,0
38
- 0,1,0,2,4,19250.0,30,Salaried,Higher education,Married,Office Worker,House / apartment,24,1,5,1
39
- 1,0,1,1,3,33000.0,33,Self-employed,Secondary,Single,Entrepreneur,Rented apartment,48,1,7,0
40
- 0,0,0,0,1,41250.0,28,Salaried,Higher education,Single,Manager,With parents,12,1,3,1
41
- 1,1,1,3,6,22000.0,45,Salaried,Incomplete higher,Married,Salesperson,House / apartment,96,1,15,1
42
- 1,0,1,0,1,16500.0,65,Pensioner,Higher education,Widow,Retired,House / apartment,420,0,40,1
43
- 0,1,0,1,2,12100.000000000002,26,Salaried,Secondary,Single,Office Worker,With parents,24,1,2,0
44
- 1,0,1,2,4,9900.0,35,Salaried,Lower secondary,Married,Factory Worker,House / apartment,60,1,10,1
45
- 0,0,1,0,1,27500.000000000004,55,Self-employed,Higher education,Divorced,Consultant,Rented apartment,180,1,30,1
46
- 1,1,0,3,5,13750.000000000002,40,Salaried,Incomplete higher,Married,Cooking Staff,House / apartment,120,1,15,0
47
- 0,1,1,1,2,19250.0,28,Self-employed,Secondary,Single,Designer,Rented apartment,36,1,5,1
48
- 1,0,0,0,1,24750.000000000004,30,Salaried,Higher education,Married,Engineer,With parents,48,1,8,1
49
- 0,1,1,2,3,14850.000000000002,48,Salaried,Secondary,Divorced,Salesperson,House / apartment,240,1,20,0
50
- 1,0,1,2,4,22000.0,45,Self-employed,Secondary,Married,Artist,House / apartment,120,1,15,1
51
- 0,1,0,0,1,30250.000000000004,50,Salaried,Higher education,Widow,Scientist,Rented apartment,300,0,25,1
52
- 1,0,1,1,2,14300.000000000002,27,Salaried,Incomplete higher,Single,Accountant,With parents,36,1,3,0
53
- 0,0,0,3,6,12100.000000000002,38,Salaried,Lower secondary,Divorced,Bus Driver,House / apartment,72,1,10,1
54
- 1,1,0,0,1,17600.0,55,Pensioner,Higher education,Married,Retired,House / apartment,420,0,30,0
55
- 0,1,1,2,3,15950.000000000002,33,Self-employed,Secondary,Married,Business Owner,Rented apartment,60,1,7,1
56
- 1,0,1,1,2,37400.0,29,Salaried,Higher education,Single,Software Developer,With parents,48,1,6,1
57
- 0,0,0,0,1,19800.0,42,Self-employed,Incomplete higher,Divorced,Entrepreneur,House / apartment,96,1,12,0
 
1
  Own_car,Own_property,Mobile_phone,Num_children,Household_size,Total_income,Age,Income_type,Education_type,Family_status,Occupation_type,Housing_type,Account_age,Employed,Years_employed,Target
2
+ 1,1,1,2,4,27500.000000000004,35,Salaried,Higher education,Married,Engineer,House / apartment,36,1,1,1
3
  0,0,1,0,1,11000.0,50,Pensioner,Secondary,Widow,Retired,Rented apartment,12,0,0,0
4
+ 1,1,0,1,3,41250.0,40,Self-employed,Higher education,Married,Business Owner,House / apartment,48,1,3,1
5
+ 0,1,0,1,2,16500.0,28,Salaried,Secondary,Single,Teacher,With parents,24,1,1,1
6
+ 1,0,1,0,1,24750.000000000004,32,Self-employed,Higher education,Divorced,Entrepreneur,Rented apartment,60,1,2,1
7
+ 0,1,1,3,5,19250.0,45,Salaried,Incomplete higher,Married,Office Worker,House / apartment,30,1,3,1
8
  1,0,0,0,1,13750.000000000002,55,Pensioner,Lower secondary,Widow,Retired,House / apartment,120,0,0,0
9
+ 0,1,1,2,3,33000.0,37,Salaried,Higher education,Married,Manager,House / apartment,36,1,4,1
10
+ 1,1,1,0,1,44000.0,29,Self-employed,Higher education,Single,Entrepreneur,House / apartment,48,1,2,1
11
+ 0,1,1,4,6,22000.0,38,Salaried,Secondary,Married,Salesperson,With parents,60,1,4,1
12
+ 1,0,0,1,2,30250.000000000004,26,Self-employed,Higher education,Married,Designer,Rented apartment,12,1,1,1
13
  0,0,0,0,1,16500.0,60,Pensioner,Lower secondary,Widow,Retired,House / apartment,180,0,0,0
14
+ 1,1,1,2,4,38500.0,45,Salaried,Higher education,Married,Manager,House / apartment,72,1,5,1
15
+ 0,1,1,3,5,35750.0,50,Salaried,Incomplete higher,Divorced,Teacher,House / apartment,96,1,5,1
16
+ 1,0,1,0,1,46750.00000000001,33,Self-employed,Higher education,Single,Consultant,Rented apartment,36,1,3,1
17
  0,1,0,1,3,17600.0,55,Pensioner,Secondary,Widow,Retired,House / apartment,150,0,0,0
18
+ 1,1,1,2,4,49500.00000000001,41,Salaried,Higher education,Married,Doctor,House / apartment,60,1,4,1
19
  0,0,1,0,1,12650.000000000002,24,Student,Incomplete higher,Single,Student,With parents,6,0,0,0
20
+ 1,0,0,1,3,26400.000000000004,35,Self-employed,Secondary,Divorced,Cooking Staff,Rented apartment,24,1,1,1
21
+ 0,1,1,3,5,30250.000000000004,50,Salaried,Higher education,Widow,Engineer,House / apartment,120,1,5,1
22
+ 1,0,1,0,2,38500.0,30,Salaried,Higher education,Married,Lawyer,Rented apartment,36,1,2,1
23
+ 0,1,0,2,4,17600.0,40,Self-employed,Secondary,Married,Carpenter,House / apartment,48,1,4,1
24
  1,0,1,1,2,14300.000000000002,29,Student,Higher education,Single,Student,With parents,12,0,0,0
25
  0,1,1,0,1,24750.000000000004,55,Pensioner,Lower secondary,Divorced,Retired,House / apartment,180,0,0,0
26
+ 1,1,1,1,3,44000.0,46,Salaried,Higher education,Married,Architect,House / apartment,72,1,4,1
27
+ 0,0,0,2,5,20350.0,33,Self-employed,Secondary,Single,Plumber,With parents,24,1,2,1
28
+ 1,1,1,0,1,35750.0,28,Self-employed,Higher education,Single,Designer,Rented apartment,36,1,1,1
29
  0,0,0,1,2,15950.000000000002,53,Pensioner,Incomplete higher,Widow,Retired,House / apartment,144,0,0,0
30
+ 1,1,0,3,6,31900.000000000004,39,Salaried,Secondary,Divorced,Teacher,House / apartment,60,1,3,1
31
+ 0,1,1,0,1,22000.0,31,Salaried,Higher education,Single,Nurse,Rented apartment,48,1,2,1
32
+ 1,0,1,2,4,28050.000000000004,42,Self-employed,Higher education,Married,Business Owner,House / apartment,60,1,4,1
33
+ 0,1,0,1,3,18700.0,47,Salaried,Secondary,Married,Factory Worker,With parents,72,1,3,0
34
+ 0,0,1,0,1,46750.00000000001,25,Self-employed,Higher education,Single,Entrepreneur,Rented apartment,12,1,0,1
35
+ 1,1,0,3,5,16500.0,55,Salaried,Secondary,Married,Teacher,House / apartment,240,1,5,1
36
+ 0,0,1,1,2,24750.000000000004,40,Salaried,Higher education,Divorced,Nurse,With parents,36,1,3,1
37
  1,0,1,0,1,11000.0,50,Pensioner,Lower secondary,Widow,Retired,Rented apartment,60,0,0,0
38
+ 0,1,0,2,4,19250.0,30,Salaried,Higher education,Married,Office Worker,House / apartment,24,1,1,1
39
+ 1,0,1,1,3,33000.0,33,Self-employed,Secondary,Single,Entrepreneur,Rented apartment,48,1,2,0
40
+ 0,0,0,0,1,41250.0,28,Salaried,Higher education,Single,Manager,With parents,12,1,1,1
41
+ 1,1,1,3,6,22000.0,45,Salaried,Incomplete higher,Married,Salesperson,House / apartment,96,1,4,1
42
+ 1,0,1,0,1,16500.0,65,Pensioner,Higher education,Widow,Retired,House / apartment,420,0,5,1
43
+ 0,1,0,1,2,12100.000000000002,26,Salaried,Secondary,Single,Office Worker,With parents,24,1,0,0
44
+ 1,0,1,2,4,9900.0,35,Salaried,Lower secondary,Married,Factory Worker,House / apartment,60,1,3,1
45
+ 0,0,1,0,1,27500.000000000004,55,Self-employed,Higher education,Divorced,Consultant,Rented apartment,180,1,5,1
46
+ 1,1,0,3,5,13750.000000000002,40,Salaried,Incomplete higher,Married,Cooking Staff,House / apartment,120,1,4,0
47
+ 0,1,1,1,2,19250.0,28,Self-employed,Secondary,Single,Designer,Rented apartment,36,1,1,1
48
+ 1,0,0,0,1,24750.000000000004,30,Salaried,Higher education,Married,Engineer,With parents,48,1,2,1
49
+ 0,1,1,2,3,14850.000000000002,48,Salaried,Secondary,Divorced,Salesperson,House / apartment,240,1,5,0
50
+ 1,0,1,2,4,22000.0,45,Self-employed,Secondary,Married,Artist,House / apartment,120,1,4,1
51
+ 0,1,0,0,1,30250.000000000004,50,Salaried,Higher education,Widow,Scientist,Rented apartment,300,0,5,1
52
+ 1,0,1,1,2,14300.000000000002,27,Salaried,Incomplete higher,Single,Accountant,With parents,36,1,1,0
53
+ 0,0,0,3,6,12100.000000000002,38,Salaried,Lower secondary,Divorced,Bus Driver,House / apartment,72,1,3,1
54
+ 1,1,0,0,1,17600.0,55,Pensioner,Higher education,Married,Retired,House / apartment,420,0,5,0
55
+ 0,1,1,2,3,15950.000000000002,33,Self-employed,Secondary,Married,Business Owner,Rented apartment,60,1,2,1
56
+ 1,0,1,1,2,37400.0,29,Salaried,Higher education,Single,Software Developer,With parents,48,1,2,1
57
+ 0,0,0,0,1,19800.0,42,Self-employed,Incomplete higher,Divorced,Entrepreneur,House / apartment,96,1,4,0
data/gpt_data.csv DELETED
@@ -1,57 +0,0 @@
1
- ID,Car,Property,Work phone,Phone,Email,Number of children,Household size,Income,Age,Income type,Education,Family,Occupation,Housing,Account age (months),Employed,Years of employment,Credit card Approval
2
- 0,Yes,Yes,No,Yes,Yes,2,4,50000,35,Salaried,Higher education,Married,Engineer,House / apartment,36,Yes,5,Yes
3
- 1,No,No,Yes,Yes,No,0,1,20000,50,Pensioner,Secondary,Widow,Retired,Rented apartment,12,No,0,No
4
- 2,Yes,Yes,No,No,Yes,1,3,75000,40,Self-employed,Higher education,Civil marriage,Business Owner,House / apartment,48,Yes,10,Yes
5
- 3,No,Yes,Yes,No,No,1,2,30000,28,Salaried,Secondary,Single,Teacher,With parents,24,Yes,3,Yes
6
- 4,Yes,No,No,Yes,Yes,0,1,45000,32,Self-employed,Higher education,Divorced,Freelancer,Rented apartment,60,Yes,7,Yes
7
- 5,No,Yes,Yes,Yes,No,3,5,35000,45,Salaried,Incomplete higher,Married,Clerk,House / apartment,30,Yes,10,Yes
8
- 6,Yes,No,No,No,Yes,0,1,25000,55,Pensioner,Lower secondary,Widow,Retired,House / apartment,120,No,0,No
9
- 7,No,Yes,Yes,Yes,Yes,2,3,60000,37,Salaried,Higher education,Civil marriage,Manager,House / apartment,36,Yes,12,Yes
10
- 8,Yes,Yes,Yes,Yes,Yes,0,1,80000,29,Self-employed,Higher education,Single,Entrepreneur,House / apartment,48,Yes,6,Yes
11
- 9,No,Yes,No,Yes,No,4,6,40000,38,Salaried,Secondary,Married,Salesperson,With parents,60,Yes,15,Yes
12
- 10,Yes,No,Yes,No,Yes,1,2,55000,26,Self-employed,Higher education,Civil marriage,Designer,Rented apartment,12,Yes,3,Yes
13
- 11,No,No,No,No,No,0,1,30000,60,Pensioner,Lower secondary,Widow,Retired,House / apartment,180,No,0,No
14
- 12,Yes,Yes,Yes,Yes,Yes,2,4,70000,45,Salaried,Higher education,Married,Manager,House / apartment,72,Yes,20,Yes
15
- 13,No,Yes,Yes,Yes,Yes,3,5,65000,50,Salaried,Incomplete higher,Divorced,Teacher,House / apartment,96,Yes,25,Yes
16
- 14,Yes,No,Yes,Yes,No,0,1,85000,33,Self-employed,Higher education,Single,Consultant,Rented apartment,36,Yes,10,Yes
17
- 15,No,Yes,No,No,Yes,1,3,32000,55,Pensioner,Secondary,Widow,Retired,House / apartment,150,No,0,No
18
- 16,Yes,Yes,Yes,Yes,Yes,2,4,90000,41,Salaried,Higher education,Married,Doctor,House / apartment,60,Yes,15,Yes
19
- 17,No,No,No,Yes,Yes,0,1,23000,24,Student,Incomplete higher,Single,Student,With parents,6,No,0,No
20
- 18,Yes,No,Yes,No,Yes,1,3,48000,35,Self-employed,Secondary,Divorced,Chef,Rented apartment,24,Yes,5,Yes
21
- 19,No,Yes,No,Yes,No,3,5,55000,50,Salaried,Higher education,Widow,Engineer,House / apartment,120,Yes,20,Yes
22
- 20,Yes,No,Yes,Yes,Yes,0,2,70000,30,Salaried,Higher education,Civil marriage,Lawyer,Rented apartment,36,Yes,8,Yes
23
- 21,No,Yes,Yes,No,Yes,2,4,32000,40,Self-employed,Secondary,Married,Carpenter,House / apartment,48,Yes,12,Yes
24
- 22,Yes,No,No,Yes,No,1,2,26000,29,Student,Higher education,Single,Student,With parents,12,No,0,No
25
- 23,No,Yes,Yes,Yes,Yes,0,1,45000,55,Pensioner,Lower secondary,Divorced,Retired,House / apartment,180,No,0,No
26
- 24,Yes,Yes,No,Yes,Yes,1,3,80000,46,Salaried,Higher education,Married,Architect,House / apartment,72,Yes,18,Yes
27
- 25,No,No,Yes,No,No,2,5,37000,33,Self-employed,Secondary,Single,Plumber,With parents,24,Yes,6,Yes
28
- 26,Yes,Yes,Yes,Yes,No,0,1,65000,28,Self-employed,Higher education,Single,Graphic Designer,Rented apartment,36,Yes,4,Yes
29
- 27,No,No,No,No,Yes,1,2,29000,53,Pensioner,Incomplete higher,Widow,Retired,House / apartment,144,No,0,No
30
- 28,Yes,Yes,Yes,No,Yes,3,6,58000,39,Salaried,Secondary,Divorced,Teacher,House / apartment,60,Yes,10,Yes
31
- 29,No,Yes,No,Yes,No,0,1,40000,31,Salaried,Higher education,Single,Nurse,Rented apartment,48,Yes,7,Yes
32
- 30,Yes,No,Yes,Yes,Yes,2,4,51000,42,Self-employed,Higher education,Civil marriage,Business Owner,House / apartment,60,Yes,14,Yes
33
- 31,No,Yes,No,No,No,1,3,34000,47,Salaried,Secondary,Married,Factory Worker,With parents,72,Yes,9,No
34
- 32,No,No,Yes,Yes,Yes,0,1,85000,25,Self-employed,Higher education,Single,Entrepreneur,Rented apartment,12,Yes,2,Yes
35
- 33,Yes,Yes,No,No,No,3,5,30000,55,Salaried,Secondary,Married,Teacher,House / apartment,240,Yes,30,Yes
36
- 34,No,No,Yes,Yes,Yes,1,2,45000,40,Salaried,Higher education,Divorced,Nurse,With parents,36,Yes,10,Yes
37
- 35,Yes,No,No,Yes,No,0,1,20000,50,Pensioner,Lower secondary,Widow,Retired,Rented apartment,60,No,0,No
38
- 36,No,Yes,Yes,No,Yes,2,4,35000,30,Salaried,Higher education,Civil marriage,Clerk,House / apartment,24,Yes,5,Yes
39
- 37,Yes,No,Yes,Yes,No,1,3,60000,33,Self-employed,Secondary,Single,Freelancer,Rented apartment,48,Yes,7,No
40
- 38,No,No,No,No,Yes,0,1,75000,28,Salaried,Higher education,Single,Manager,With parents,12,Yes,3,Yes
41
- 39,Yes,Yes,Yes,Yes,No,3,6,40000,45,Salaried,Incomplete higher,Married,Salesperson,House / apartment,96,Yes,15,Yes
42
- 40,Yes,No,Yes,Yes,Yes,0,1,30000,65,Pensioner,Higher education,Widow,Retired,House / apartment,420,No,40,Yes
43
- 41,No,Yes,No,No,No,1,2,22000,26,Salaried,Secondary,Single,Junior Clerk,With parents,24,Yes,2,No
44
- 42,Yes,No,Yes,Yes,No,2,4,18000,35,Salaried,Lower secondary,Married,Factory Worker,House / apartment,60,Yes,10,Yes
45
- 43,No,No,No,Yes,Yes,0,1,50000,55,Self-employed,Higher education,Divorced,Consultant,Rented apartment,180,Yes,30,Yes
46
- 44,Yes,Yes,Yes,No,No,3,5,25000,40,Salaried,Incomplete higher,Married,Cook,House / apartment,120,Yes,15,No
47
- 45,No,Yes,No,Yes,Yes,1,2,35000,28,Self-employed,Secondary,Single,Graphic Designer,Rented apartment,36,Yes,5,Yes
48
- 46,Yes,No,Yes,No,Yes,0,1,45000,30,Salaried,Higher education,Civil marriage,Engineer,With parents,48,Yes,8,Yes
49
- 47,No,Yes,Yes,Yes,No,2,3,27000,48,Salaried,Secondary,Divorced,Salesperson,House / apartment,240,Yes,20,No
50
- 48,Yes,No,Yes,Yes,No,2,4,40000,45,Self-employed,Secondary,Married,Artist,House / apartment,120,Yes,15,Yes
51
- 49,No,Yes,No,No,Yes,0,1,55000,50,Salaried,Higher education,Widow,Scientist,Rented apartment,300,No,25,Yes
52
- 50,Yes,No,Yes,Yes,Yes,1,2,26000,27,Salaried,Incomplete higher,Single,Junior Accountant,With parents,36,Yes,3,No
53
- 51,No,No,No,No,No,3,6,22000,38,Salaried,Lower secondary,Divorced,Bus Driver,House / apartment,72,Yes,10,Yes
54
- 52,Yes,Yes,Yes,No,Yes,0,1,32000,55,Pensioner,Higher education,Married,Retired,House / apartment,420,No,30,No
55
- 53,No,Yes,No,Yes,No,2,3,29000,33,Self-employed,Secondary,Civil marriage,Shop Owner,Rented apartment,60,Yes,7,Yes
56
- 54,Yes,No,Yes,Yes,Yes,1,2,68000,29,Salaried,Higher education,Single,Software Developer,With parents,48,Yes,6,Yes
57
- 55,No,No,No,No,Yes,0,1,36000,42,Self-employed,Incomplete higher,Divorced,Freelancer,House / apartment,96,Yes,12,No
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deployment_files/explain_model/versions.json DELETED
@@ -1 +0,0 @@
1
- {"concrete-python": "2.5.0rc1", "concrete-ml": "1.3.0", "python": "3.10.11"}
 
 
deployment_files/{approval_model β†’ model}/client.zip RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:e2ceb4a6e07cd13471c8c8c963d9e4de52d5af624e81775ebeb2421e29b9ba8c
3
- size 28667
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:09a8c2ae9e2e2e18a7e48123222312e3de32ef3b751bf88e8e090739f92c12f3
3
+ size 28549
deployment_files/{approval_model/server.zip β†’ model/pre_processor_bank.pkl} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:9e724012427c90fdc8df14360942909e5fa0accc8b27584880baab2a91533e78
3
- size 1729
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c76179d6cb576a61f6d57e29f45545d8c4dd2d86a3818730f5c6bdd35faebe4a
3
+ size 1098
deployment_files/{explain_model/client.zip β†’ model/pre_processor_third_party.pkl} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:506276661b4612d664d59f0d90aac1b5c09f942a850ec189aa16204d54433b27
3
- size 27714
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3a8d320eea9a0b2d28b9ee46b5b02df1dc85176746385d64e4c742edc99339f4
3
+ size 647
deployment_files/model/pre_processor_user.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a4300414ee222355b2ac9e0f5e4dc21b50f0a4de8e17e0ed4121dc766231d010
3
+ size 3340
deployment_files/{explain_model β†’ model}/server.zip RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:596ae66c7effd9733a8780088984d4fc08479d67c11586ee5787111329cb353f
3
- size 2035
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:87df3659c986799920676e358c316fc99f8c65951b71ccb9288c94af90c8a57b
3
+ size 1775
deployment_files/{approval_model β†’ model}/versions.json RENAMED
File without changes
deployment_files/pre_processor_third_party.pkl CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:8bc9d12a6ac40e3b7f88f3f15e688879b819bc4a995ad0e91fc08af85f55513b
3
- size 1128
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a4ac1351641df7d247a18c15405b22926584c88aa9640857cb1943a2bb7ee32e
3
+ size 1098
development.py CHANGED
@@ -6,46 +6,34 @@ import pandas
6
  import pickle
7
 
8
  from settings import (
9
- APPROVAL_DEPLOYMENT_PATH,
10
- EXPLAIN_DEPLOYMENT_PATH,
11
  DATA_PATH,
12
- APPROVAL_INPUT_SLICES,
13
- EXPLAIN_INPUT_SLICES,
14
  PRE_PROCESSOR_USER_PATH,
15
  PRE_PROCESSOR_BANK_PATH,
16
  PRE_PROCESSOR_THIRD_PARTY_PATH,
17
  USER_COLUMNS,
18
  BANK_COLUMNS,
19
- APPROVAL_THIRD_PARTY_COLUMNS,
20
- EXPLAIN_THIRD_PARTY_COLUMNS,
21
  )
22
  from utils.client_server_interface import MultiInputsFHEModelDev
23
  from utils.model import MultiInputDecisionTreeClassifier, MultiInputDecisionTreeRegressor
24
  from utils.pre_processing import get_pre_processors
25
 
26
 
27
- def get_multi_inputs(data, is_approval):
28
  """Get inputs for all three parties from the input data, using fixed slices.
29
 
30
  Args:
31
  data (numpy.ndarray): The input data to consider.
32
- is_approval (bool): If the data should be used for the 'approval' model (else, otherwise for
33
- the 'explain' model).
34
 
35
  Returns:
36
  (Tuple[numpy.ndarray]): The inputs for all three parties.
37
  """
38
- if is_approval:
39
- return (
40
- data[:, APPROVAL_INPUT_SLICES["user"]],
41
- data[:, APPROVAL_INPUT_SLICES["bank"]],
42
- data[:, APPROVAL_INPUT_SLICES["third_party"]]
43
- )
44
-
45
  return (
46
- data[:, EXPLAIN_INPUT_SLICES["user"]],
47
- data[:, EXPLAIN_INPUT_SLICES["bank"]],
48
- data[:, EXPLAIN_INPUT_SLICES["third_party"]]
49
  )
50
 
51
 
@@ -61,7 +49,7 @@ data_y = data_x.pop("Target").copy().to_frame()
61
  # Get data from all parties
62
  data_user = data_x[USER_COLUMNS].copy()
63
  data_bank = data_x[BANK_COLUMNS].copy()
64
- data_third_party = data_x[APPROVAL_THIRD_PARTY_COLUMNS].copy()
65
 
66
  # Feature engineer the data
67
  pre_processor_user, pre_processor_bank, pre_processor_third_party = get_pre_processors()
@@ -75,23 +63,23 @@ preprocessed_data_x = numpy.concatenate((preprocessed_data_user, preprocessed_da
75
 
76
  print("\nTrain and compile the model")
77
 
78
- model_approval = MultiInputDecisionTreeClassifier()
79
 
80
- model_approval, sklearn_model_approval = model_approval.fit_benchmark(preprocessed_data_x, data_y)
81
 
82
- multi_inputs_train = get_multi_inputs(preprocessed_data_x, is_approval=True)
83
 
84
- model_approval.compile(*multi_inputs_train, inputs_encryption_status=["encrypted", "encrypted", "encrypted"])
85
 
86
  print("\nSave deployment files")
87
 
88
  # Delete the deployment folder and its content if it already exists
89
- if APPROVAL_DEPLOYMENT_PATH.is_dir():
90
- shutil.rmtree(APPROVAL_DEPLOYMENT_PATH)
91
 
92
  # Save files needed for deployment (and enable cross-platform deployment)
93
- fhe_model_dev_approval = MultiInputsFHEModelDev(APPROVAL_DEPLOYMENT_PATH, model_approval)
94
- fhe_model_dev_approval.save(via_mlir=True)
95
 
96
  # Save pre-processors
97
  with (
@@ -103,44 +91,4 @@ with (
103
  pickle.dump(pre_processor_bank, file_bank)
104
  pickle.dump(pre_processor_third_party, file_third_party)
105
 
106
-
107
- print("\nLoad, train, compile and save files for the 'explain' model")
108
-
109
- # Define input and target data
110
- data_x = data.copy()
111
- data_y = data_x.pop("Years_employed").copy().to_frame()
112
- target_values = data_x.pop("Target").copy()
113
-
114
- # Get all data points whose target value is True (credit card has been approved)
115
- approved_mask = target_values == 1
116
- data_x_approved = data_x[approved_mask]
117
- data_y_approved = data_y[approved_mask]
118
-
119
- # Get data from all parties
120
- data_user = data_x_approved[USER_COLUMNS].copy()
121
- data_bank = data_x_approved[BANK_COLUMNS].copy()
122
- data_third_party = data_x_approved[EXPLAIN_THIRD_PARTY_COLUMNS].copy()
123
-
124
- preprocessed_data_user = pre_processor_user.transform(data_user)
125
- preprocessed_data_bank = pre_processor_bank.transform(data_bank)
126
- preprocessed_data_third_party = data_third_party.to_numpy()
127
-
128
- preprocessed_data_x = numpy.concatenate((preprocessed_data_user, preprocessed_data_bank, preprocessed_data_third_party), axis=1)
129
-
130
- model_explain = MultiInputDecisionTreeRegressor()
131
-
132
- model_explain, sklearn_model_explain = model_explain.fit_benchmark(preprocessed_data_x, data_y_approved)
133
-
134
- multi_inputs_train = get_multi_inputs(preprocessed_data_x, is_approval=False)
135
-
136
- model_explain.compile(*multi_inputs_train, inputs_encryption_status=["encrypted", "encrypted", "encrypted"])
137
-
138
- # Delete the deployment folder and its content if it already exists
139
- if EXPLAIN_DEPLOYMENT_PATH.is_dir():
140
- shutil.rmtree(EXPLAIN_DEPLOYMENT_PATH)
141
-
142
- # Save files needed for deployment (and enable cross-platform deployment)
143
- fhe_model_dev_explain = MultiInputsFHEModelDev(EXPLAIN_DEPLOYMENT_PATH, model_explain)
144
- fhe_model_dev_explain.save(via_mlir=True)
145
-
146
  print("\nDone !")
 
6
  import pickle
7
 
8
  from settings import (
9
+ DEPLOYMENT_PATH,
 
10
  DATA_PATH,
11
+ INPUT_SLICES,
 
12
  PRE_PROCESSOR_USER_PATH,
13
  PRE_PROCESSOR_BANK_PATH,
14
  PRE_PROCESSOR_THIRD_PARTY_PATH,
15
  USER_COLUMNS,
16
  BANK_COLUMNS,
17
+ THIRD_PARTY_COLUMNS,
 
18
  )
19
  from utils.client_server_interface import MultiInputsFHEModelDev
20
  from utils.model import MultiInputDecisionTreeClassifier, MultiInputDecisionTreeRegressor
21
  from utils.pre_processing import get_pre_processors
22
 
23
 
24
+ def get_multi_inputs(data):
25
  """Get inputs for all three parties from the input data, using fixed slices.
26
 
27
  Args:
28
  data (numpy.ndarray): The input data to consider.
 
 
29
 
30
  Returns:
31
  (Tuple[numpy.ndarray]): The inputs for all three parties.
32
  """
 
 
 
 
 
 
 
33
  return (
34
+ data[:, INPUT_SLICES["user"]],
35
+ data[:, INPUT_SLICES["bank"]],
36
+ data[:, INPUT_SLICES["third_party"]]
37
  )
38
 
39
 
 
49
  # Get data from all parties
50
  data_user = data_x[USER_COLUMNS].copy()
51
  data_bank = data_x[BANK_COLUMNS].copy()
52
+ data_third_party = data_x[THIRD_PARTY_COLUMNS].copy()
53
 
54
  # Feature engineer the data
55
  pre_processor_user, pre_processor_bank, pre_processor_third_party = get_pre_processors()
 
63
 
64
  print("\nTrain and compile the model")
65
 
66
+ model = MultiInputDecisionTreeClassifier()
67
 
68
+ model, sklearn_model = model.fit_benchmark(preprocessed_data_x, data_y)
69
 
70
+ multi_inputs_train = get_multi_inputs(preprocessed_data_x)
71
 
72
+ model.compile(*multi_inputs_train, inputs_encryption_status=["encrypted", "encrypted", "encrypted"])
73
 
74
  print("\nSave deployment files")
75
 
76
  # Delete the deployment folder and its content if it already exists
77
+ if DEPLOYMENT_PATH.is_dir():
78
+ shutil.rmtree(DEPLOYMENT_PATH)
79
 
80
  # Save files needed for deployment (and enable cross-platform deployment)
81
+ fhe_model_dev = MultiInputsFHEModelDev(DEPLOYMENT_PATH, model)
82
+ fhe_model_dev.save(via_mlir=True)
83
 
84
  # Save pre-processors
85
  with (
 
91
  pickle.dump(pre_processor_bank, file_bank)
92
  pickle.dump(pre_processor_third_party, file_third_party)
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  print("\nDone !")
server.py CHANGED
@@ -5,11 +5,11 @@ from typing import List, Optional
5
  from fastapi import FastAPI, File, Form, UploadFile
6
  from fastapi.responses import JSONResponse, Response
7
 
8
- from settings import APPROVAL_DEPLOYMENT_PATH, SERVER_FILES, CLIENT_TYPES
9
  from utils.client_server_interface import MultiInputsFHEModelServer
10
 
11
  # Load the server
12
- FHE_SERVER = MultiInputsFHEModelServer(APPROVAL_DEPLOYMENT_PATH)
13
 
14
 
15
  def _get_server_file_path(name, client_id, client_type=None):
 
5
  from fastapi import FastAPI, File, Form, UploadFile
6
  from fastapi.responses import JSONResponse, Response
7
 
8
+ from settings import DEPLOYMENT_PATH, SERVER_FILES, CLIENT_TYPES
9
  from utils.client_server_interface import MultiInputsFHEModelServer
10
 
11
  # Load the server
12
+ FHE_SERVER = MultiInputsFHEModelServer(DEPLOYMENT_PATH)
13
 
14
 
15
  def _get_server_file_path(name, client_id, client_type=None):
settings.py CHANGED
@@ -13,8 +13,7 @@ CLIENT_FILES = REPO_DIR / "client_files"
13
  SERVER_FILES = REPO_DIR / "server_files"
14
 
15
  # ALl deployment directories
16
- APPROVAL_DEPLOYMENT_PATH = DEPLOYMENT_PATH / "approval_model"
17
- EXPLAIN_DEPLOYMENT_PATH = DEPLOYMENT_PATH / "explain_model"
18
 
19
  # Path targeting pre-processor saved files
20
  PRE_PROCESSOR_USER_PATH = DEPLOYMENT_PATH / 'pre_processor_user.pkl'
@@ -33,8 +32,7 @@ SERVER_URL = "http://localhost:8000/"
33
  DATA_PATH = "data/data.csv"
34
 
35
  # Development settings
36
- APPROVAL_PROCESSED_INPUT_SHAPE = (1, 39)
37
- EXPLAIN_PROCESSED_INPUT_SHAPE = (1, 38)
38
 
39
  CLIENT_TYPES = ["user", "bank", "third_party"]
40
  INPUT_INDEXES = {
@@ -42,16 +40,11 @@ INPUT_INDEXES = {
42
  "bank": 1,
43
  "third_party": 2,
44
  }
45
- APPROVAL_INPUT_SLICES = {
46
  "user": slice(0, 36), # First position: start from 0
47
  "bank": slice(36, 37), # Second position: start from n_feature_user
48
  "third_party": slice(37, 39), # Third position: start from n_feature_user + n_feature_bank
49
  }
50
- EXPLAIN_INPUT_SLICES = {
51
- "user": slice(0, 36), # First position: start from 0
52
- "bank": slice(36, 37), # Second position: start from n_feature_user
53
- "third_party": slice(37, 38), # Third position: start from n_feature_user + n_feature_bank
54
- }
55
 
56
  # Fix column order for pre-processing steps
57
  USER_COLUMNS = [
@@ -60,8 +53,7 @@ USER_COLUMNS = [
60
  'Occupation_type',
61
  ]
62
  BANK_COLUMNS = ["Account_age"]
63
- APPROVAL_THIRD_PARTY_COLUMNS = ["Years_employed", "Employed"]
64
- EXPLAIN_THIRD_PARTY_COLUMNS = ["Employed"]
65
 
66
  _data = pandas.read_csv(DATA_PATH, encoding="utf-8")
67
 
@@ -77,13 +69,23 @@ ACCOUNT_MIN_MAX = get_min_max(_data, "Account_age")
77
  CHILDREN_MIN_MAX = get_min_max(_data, "Num_children")
78
  INCOME_MIN_MAX = get_min_max(_data, "Total_income")
79
  AGE_MIN_MAX = get_min_max(_data, "Age")
80
- EMPLOYED_MIN_MAX = get_min_max(_data, "Years_employed")
81
  FAMILY_MIN_MAX = get_min_max(_data, "Household_size")
82
 
 
 
 
 
83
  # App data choices
84
  INCOME_TYPES = list(_data["Income_type"].unique())
85
  OCCUPATION_TYPES = list(_data["Occupation_type"].unique())
86
  HOUSING_TYPES = list(_data["Housing_type"].unique())
87
  EDUCATION_TYPES = list(_data["Education_type"].unique())
88
  FAMILY_STATUS = list(_data["Family_status"].unique())
 
 
 
 
89
 
 
 
 
 
13
  SERVER_FILES = REPO_DIR / "server_files"
14
 
15
  # ALl deployment directories
16
+ DEPLOYMENT_PATH = DEPLOYMENT_PATH / "model"
 
17
 
18
  # Path targeting pre-processor saved files
19
  PRE_PROCESSOR_USER_PATH = DEPLOYMENT_PATH / 'pre_processor_user.pkl'
 
32
  DATA_PATH = "data/data.csv"
33
 
34
  # Development settings
35
+ PROCESSED_INPUT_SHAPE = (1, 39)
 
36
 
37
  CLIENT_TYPES = ["user", "bank", "third_party"]
38
  INPUT_INDEXES = {
 
40
  "bank": 1,
41
  "third_party": 2,
42
  }
43
+ INPUT_SLICES = {
44
  "user": slice(0, 36), # First position: start from 0
45
  "bank": slice(36, 37), # Second position: start from n_feature_user
46
  "third_party": slice(37, 39), # Third position: start from n_feature_user + n_feature_bank
47
  }
 
 
 
 
 
48
 
49
  # Fix column order for pre-processing steps
50
  USER_COLUMNS = [
 
53
  'Occupation_type',
54
  ]
55
  BANK_COLUMNS = ["Account_age"]
56
+ THIRD_PARTY_COLUMNS = ["Years_employed", "Employed"]
 
57
 
58
  _data = pandas.read_csv(DATA_PATH, encoding="utf-8")
59
 
 
69
  CHILDREN_MIN_MAX = get_min_max(_data, "Num_children")
70
  INCOME_MIN_MAX = get_min_max(_data, "Total_income")
71
  AGE_MIN_MAX = get_min_max(_data, "Age")
 
72
  FAMILY_MIN_MAX = get_min_max(_data, "Household_size")
73
 
74
+ # Default values
75
+ INCOME_VALUE = 12000
76
+ AGE_VALUE = 30
77
+
78
  # App data choices
79
  INCOME_TYPES = list(_data["Income_type"].unique())
80
  OCCUPATION_TYPES = list(_data["Occupation_type"].unique())
81
  HOUSING_TYPES = list(_data["Housing_type"].unique())
82
  EDUCATION_TYPES = list(_data["Education_type"].unique())
83
  FAMILY_STATUS = list(_data["Family_status"].unique())
84
+ YEARS_EMPLOYED_BINS = ['0-2', '2-5', '5-8', '8-11', '11-18', '18+']
85
+
86
+ # Years_employed bin order
87
+ YEARS_EMPLOYED_BIN_NAME_TO_INDEX = {bin_name: i for i, bin_name in enumerate(YEARS_EMPLOYED_BINS)}
88
 
89
+ assert len(YEARS_EMPLOYED_BINS) == len(list(_data["Years_employed"].unique())), (
90
+ "Years_employed bins are not matching the expected list"
91
+ )
utils/pre_processing.py CHANGED
@@ -56,9 +56,7 @@ def get_pre_processors():
56
  )
57
 
58
  pre_processor_third_party = ColumnTransformer(
59
- transformers=[
60
- ('standard_scaler', StandardScaler(), ['Years_employed']),
61
- ],
62
  remainder='passthrough',
63
  verbose_feature_names_out=False,
64
  )
 
56
  )
57
 
58
  pre_processor_third_party = ColumnTransformer(
59
+ transformers=[],
 
 
60
  remainder='passthrough',
61
  verbose_feature_names_out=False,
62
  )