mirror of
https://github.com/luxfhe/examples.git
synced 2026-10-30 18:13:15 +00:00
ML examples: - credit_scoring: Credit risk prediction with HMEQ data - disease_prediction: Healthcare ML on encrypted records - cifar: Encrypted image classification - federated_learning: Distributed FHE training - hybrid_model: Mixed FHE/plaintext inference Deployment: - breast_cancer: Cancer detection API - sentiment_analysis: Encrypted NLP - server: FHE inference server Blockchain: - hardhat-template: Solidity + FHE - react-template: Full dApp with FHE
272 KiB
272 KiB
In [1]:
import shutil
from pathlib import Path
from time import time
from typing import Callable, Dict
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from preprocessing import prepare_data
from sklearn.model_selection import GridSearchCV
from tqdm.notebook import tqdm
from concrete.ml.sklearn import LogisticRegression as TorusLogisticRegression
from concrete.ml.sklearn import XGBClassifier as TorusXGBClassifierIn [2]:
# Load the train and testing sets
df_train, df_test = prepare_data()
TARGET_COLUMN = ["prognosis_encoded", "prognosis"]
y_train = df_train[TARGET_COLUMN[0]].values.flatten()
y_test = df_test[TARGET_COLUMN[0]].values.flatten()
X_train = df_train.drop(TARGET_COLUMN, axis=1)
X_test = df_test.drop(TARGET_COLUMN, axis=1)
df_train.head()Out [2]:
| itching | skin_rash | nodal_skin_eruptions | continuous_sneezing | shivering | chills | joint_pain | stomach_pain | acidity | ulcers_on_tongue | ... | scurving | skin_peeling | silver_like_dusting | small_dents_in_nails | inflammatory_nails | blister | red_sore_around_nose | yellow_crust_ooze | prognosis | prognosis_encoded | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1.0 | 1.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | Fungal Infection | 14 |
| 1 | 0.0 | 1.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | Fungal Infection | 14 |
| 2 | 1.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | Fungal Infection | 14 |
| 3 | 1.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | Fungal Infection | 14 |
| 4 | 1.0 | 1.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | Fungal Infection | 14 |
5 rows × 130 columns
In [3]:
print(
f"The dataset contains: {X_train.shape[1]} distinct symptoms and "
f"{len(np.unique(y_train))} unique diseases."
)The dataset contains: 128 distinct symptoms and 41 unique diseases.
In [4]:
# The Training data-set is well-balanced
value_counts = df_train[TARGET_COLUMN[1]].value_counts().sort_values(ascending=False)
plt.figure(figsize=(15, 5))
ax = value_counts.plot.bar(color="orange")
ax.set_xlabel("Diseases")
ax.set_ylim(0, 150)
ax.set_ylabel("Value Count")
ax.set_title("Disease Frequency")
plt.xticks(rotation=80)
plt.show()In [5]:
def grid_search(grid_param: Dict, clf: Callable) -> GridSearchCV:
"""
Perform a grid search to find the best hyper-parameters for a given classifier.
Args:
grid_param (Dict): The hyper-parameters to be tuned
clf (Callable): The given classifier
Returns:
GridSearchCV: The fitted GridSearchCV object.
"""
grid_search = GridSearchCV(
clf,
grid_param,
cv=4,
verbose=1,
n_jobs=-1,
).fit(X_train, y_train)
# The best model
print(f"Best parameters: {grid_search.best_params_}")
print(f"Best score: {grid_search.best_score_:.2%}")
return grid_searchIn [6]:
def fhe_evaluation(
grid_search: GridSearchCV, clf_class: Callable, grid_param: Dict, X_test: np.ndarray
) -> pd.DataFrame:
"""
Mesuare the time inference in FHE for each possible combinaison.
Args:
grid_search (GridSearchCV): Grid search object used to find the best hyperparameters
clf_class (Callable): The classifier model to be evaluated
grid_param (Dict): The hyper-parameters associated to the givin classifier
X_test (np.ndarray): One sample used to measure the time inference in FHE
Returns:
pd.DataFrame: All combinaison of hyper-parameters and their mean test scores, standard test
scores, and time inference in FHE
"""
# Save the grid search results as a DataFrame
grid_results = pd.DataFrame(grid_search.cv_results_)
grid_results.columns = grid_results.columns.str.replace("param_", "")
# Pick up relevent columns
grid_results = grid_results[list(grid_param.keys()) + ["mean_test_score", "std_test_score"]]
# Sort the results by mean_test_score in descending order
grid_results = grid_results.sort_values(by="mean_test_score", ascending=False)
fhe_inference_time = []
for _, param in tqdm(grid_results[list(grid_param.keys())].iterrows()):
# Instanciation
clf = clf_class(**dict(param))
# Training
clf.fit(X_train, y_train)
fhe_circuit = clf.compile(X_train)
# Key generation
fhe_circuit.client.keygen(force=False)
# Inference
start_time = time()
_ = clf.predict(X_test, fhe="execute")
fhe_inference_time.append(time() - start_time)
grid_results["fhe_inference_time"] = fhe_inference_time
return grid_resultsIn [7]:
grid_xgb_param = {
"n_bits": [2, 3, 4, 5, 6, 7],
"max_depth": [1, 2, 3],
"n_estimators": [2, 3],
}
gr_xgb = grid_search(grid_xgb_param, TorusXGBClassifier())Fitting 4 folds for each of 36 candidates, totalling 144 fits
Best parameters: {'max_depth': 2, 'n_bits': 7, 'n_estimators': 2}
Best score: 99.63%
In [8]:
grid_logit_param = {
"C": [0.001, 0.5, 0.9, 1.0],
"n_bits": [7, 10, 13, 14],
"solver": ["newton-cg", "sag", "saga", "lbfgs"],
}
gs_logit = grid_search(grid_logit_param, TorusLogisticRegression())Fitting 4 folds for each of 64 candidates, totalling 256 fits
Best parameters: {'C': 0.5, 'n_bits': 7, 'solver': 'newton-cg'}
Best score: 100.00%
In [9]:
# Take a random input as a test case to measure the execution time of all the Torus ML models
random_row = df_test.sample().values[0]
random_x, random_y = random_row[:-2][np.newaxis, :], random_row[-2]
print(f"Random disease: {random_y}")Random disease: Drug Reaction
In [10]:
xgb_results = fhe_evaluation(gr_xgb, TorusXGBClassifier, grid_xgb_param, random_x)0it [00:00, ?it/s]
In [11]:
logit_results = fhe_evaluation(gs_logit, TorusLogisticRegression, grid_logit_param, random_x)0it [00:00, ?it/s]
In [12]:
# XGB
index = 2
xgb_results.style.background_gradient(
subset=["n_bits", "mean_test_score", "fhe_inference_time"]
).apply(
lambda index: ["background-color: green"] * len(index), axis=1, subset=pd.IndexSlice[[index], :]
)Out [12]:
| n_bits | max_depth | n_estimators | mean_test_score | std_test_score | fhe_inference_time | |
|---|---|---|---|---|---|---|
| 35 | 7 | 3 | 3 | 0.996341 | 0.003659 | 3.591810 |
| 34 | 7 | 3 | 2 | 0.996341 | 0.003659 | 2.233943 |
| 33 | 6 | 3 | 3 | 0.996341 | 0.003659 | 3.013290 |
| 32 | 6 | 3 | 2 | 0.996341 | 0.003659 | 2.016890 |
| 22 | 7 | 2 | 2 | 0.996341 | 0.003659 | 2.075623 |
| 31 | 5 | 3 | 3 | 0.995122 | 0.004878 | 2.502872 |
| 30 | 5 | 3 | 2 | 0.995122 | 0.004878 | 1.947387 |
| 23 | 7 | 2 | 3 | 0.993902 | 0.006098 | 2.749011 |
| 21 | 6 | 2 | 3 | 0.993902 | 0.006098 | 2.424285 |
| 20 | 6 | 2 | 2 | 0.993902 | 0.006098 | 1.739055 |
| 19 | 5 | 2 | 3 | 0.992683 | 0.007317 | 2.004906 |
| 18 | 5 | 2 | 2 | 0.992683 | 0.007317 | 1.421936 |
| 11 | 7 | 1 | 3 | 0.984146 | 0.015854 | 1.421224 |
| 9 | 6 | 1 | 3 | 0.981707 | 0.018293 | 1.392182 |
| 10 | 7 | 1 | 2 | 0.980488 | 0.019512 | 1.102994 |
| 7 | 5 | 1 | 3 | 0.979268 | 0.020732 | 1.266870 |
| 8 | 6 | 1 | 2 | 0.979268 | 0.020732 | 1.021727 |
| 6 | 5 | 1 | 2 | 0.978049 | 0.021951 | 0.915732 |
| 29 | 4 | 3 | 3 | 0.964634 | 0.035366 | 1.971429 |
| 17 | 4 | 2 | 3 | 0.962195 | 0.037805 | 1.667527 |
| 28 | 4 | 3 | 2 | 0.962195 | 0.037805 | 1.429256 |
| 4 | 4 | 1 | 2 | 0.959756 | 0.040244 | 0.776403 |
| 16 | 4 | 2 | 2 | 0.959756 | 0.040244 | 1.172198 |
| 5 | 4 | 1 | 3 | 0.959756 | 0.040244 | 0.993163 |
| 15 | 3 | 2 | 3 | 0.951220 | 0.048780 | 0.868959 |
| 14 | 3 | 2 | 2 | 0.951220 | 0.048780 | 0.649739 |
| 26 | 3 | 3 | 2 | 0.951220 | 0.048780 | 0.733025 |
| 27 | 3 | 3 | 3 | 0.951220 | 0.048780 | 1.021891 |
| 25 | 2 | 3 | 3 | 0.950000 | 0.050000 | 0.865748 |
| 24 | 2 | 3 | 2 | 0.950000 | 0.050000 | 0.662444 |
| 12 | 2 | 2 | 2 | 0.928049 | 0.047561 | 0.580057 |
| 13 | 2 | 2 | 3 | 0.928049 | 0.047561 | 0.752185 |
| 3 | 3 | 1 | 3 | 0.913415 | 0.037805 | 0.527153 |
| 2 | 3 | 1 | 2 | 0.913415 | 0.037805 | 0.436342 |
| 1 | 2 | 1 | 3 | 0.793902 | 0.035366 | 0.423496 |
| 0 | 2 | 1 | 2 | 0.793902 | 0.035366 | 0.347739 |
In [13]:
# Logit
index = 42
logit_results.style.background_gradient(
subset=["n_bits", "mean_test_score", "fhe_inference_time"]
).apply(
lambda index: ["background-color: green"] * len(index), axis=1, subset=pd.IndexSlice[[index], :]
)Out [13]:
| C | n_bits | solver | mean_test_score | std_test_score | fhe_inference_time | |
|---|---|---|---|---|---|---|
| 32 | 0.900000 | 7 | newton-cg | 1.000000 | 0.000000 | 0.157327 |
| 48 | 1.000000 | 7 | newton-cg | 1.000000 | 0.000000 | 0.152806 |
| 35 | 0.900000 | 7 | lbfgs | 1.000000 | 0.000000 | 0.155307 |
| 36 | 0.900000 | 10 | newton-cg | 1.000000 | 0.000000 | 0.161859 |
| 37 | 0.900000 | 10 | sag | 1.000000 | 0.000000 | 0.152682 |
| 38 | 0.900000 | 10 | saga | 1.000000 | 0.000000 | 0.159098 |
| 39 | 0.900000 | 10 | lbfgs | 1.000000 | 0.000000 | 0.155115 |
| 40 | 0.900000 | 13 | newton-cg | 1.000000 | 0.000000 | 0.158407 |
| 41 | 0.900000 | 13 | sag | 1.000000 | 0.000000 | 0.179401 |
| 42 | 0.900000 | 13 | saga | 1.000000 | 0.000000 | 0.162146 |
| 43 | 0.900000 | 13 | lbfgs | 1.000000 | 0.000000 | 0.158415 |
| 44 | 0.900000 | 14 | newton-cg | 1.000000 | 0.000000 | 0.160598 |
| 45 | 0.900000 | 14 | sag | 1.000000 | 0.000000 | 0.160951 |
| 46 | 0.900000 | 14 | saga | 1.000000 | 0.000000 | 0.157854 |
| 47 | 0.900000 | 14 | lbfgs | 1.000000 | 0.000000 | 0.167108 |
| 49 | 1.000000 | 7 | sag | 1.000000 | 0.000000 | 0.166880 |
| 33 | 0.900000 | 7 | sag | 1.000000 | 0.000000 | 0.157420 |
| 50 | 1.000000 | 7 | saga | 1.000000 | 0.000000 | 0.168702 |
| 51 | 1.000000 | 7 | lbfgs | 1.000000 | 0.000000 | 0.152596 |
| 52 | 1.000000 | 10 | newton-cg | 1.000000 | 0.000000 | 0.165956 |
| 53 | 1.000000 | 10 | sag | 1.000000 | 0.000000 | 0.154174 |
| 54 | 1.000000 | 10 | saga | 1.000000 | 0.000000 | 0.155692 |
| 55 | 1.000000 | 10 | lbfgs | 1.000000 | 0.000000 | 0.160719 |
| 56 | 1.000000 | 13 | newton-cg | 1.000000 | 0.000000 | 0.194422 |
| 57 | 1.000000 | 13 | sag | 1.000000 | 0.000000 | 0.155808 |
| 58 | 1.000000 | 13 | saga | 1.000000 | 0.000000 | 0.160784 |
| 59 | 1.000000 | 13 | lbfgs | 1.000000 | 0.000000 | 0.157312 |
| 60 | 1.000000 | 14 | newton-cg | 1.000000 | 0.000000 | 0.164955 |
| 61 | 1.000000 | 14 | sag | 1.000000 | 0.000000 | 0.162410 |
| 62 | 1.000000 | 14 | saga | 1.000000 | 0.000000 | 0.163681 |
| 34 | 0.900000 | 7 | saga | 1.000000 | 0.000000 | 0.157975 |
| 63 | 1.000000 | 14 | lbfgs | 1.000000 | 0.000000 | 0.157038 |
| 16 | 0.500000 | 7 | newton-cg | 1.000000 | 0.000000 | 0.151466 |
| 23 | 0.500000 | 10 | lbfgs | 1.000000 | 0.000000 | 0.160798 |
| 31 | 0.500000 | 14 | lbfgs | 1.000000 | 0.000000 | 0.165182 |
| 17 | 0.500000 | 7 | sag | 1.000000 | 0.000000 | 0.153831 |
| 18 | 0.500000 | 7 | saga | 1.000000 | 0.000000 | 0.155847 |
| 19 | 0.500000 | 7 | lbfgs | 1.000000 | 0.000000 | 0.150635 |
| 20 | 0.500000 | 10 | newton-cg | 1.000000 | 0.000000 | 0.155852 |
| 22 | 0.500000 | 10 | saga | 1.000000 | 0.000000 | 0.154778 |
| 21 | 0.500000 | 10 | sag | 1.000000 | 0.000000 | 0.162897 |
| 24 | 0.500000 | 13 | newton-cg | 1.000000 | 0.000000 | 0.165403 |
| 25 | 0.500000 | 13 | sag | 1.000000 | 0.000000 | 0.160696 |
| 26 | 0.500000 | 13 | saga | 1.000000 | 0.000000 | 0.160969 |
| 27 | 0.500000 | 13 | lbfgs | 1.000000 | 0.000000 | 0.154805 |
| 28 | 0.500000 | 14 | newton-cg | 1.000000 | 0.000000 | 0.162446 |
| 29 | 0.500000 | 14 | sag | 1.000000 | 0.000000 | 0.154577 |
| 30 | 0.500000 | 14 | saga | 1.000000 | 0.000000 | 0.158933 |
| 8 | 0.001000 | 13 | newton-cg | 0.998780 | 0.001220 | 0.158000 |
| 2 | 0.001000 | 7 | saga | 0.998780 | 0.001220 | 0.155923 |
| 3 | 0.001000 | 7 | lbfgs | 0.998780 | 0.001220 | 0.153924 |
| 4 | 0.001000 | 10 | newton-cg | 0.998780 | 0.001220 | 0.155507 |
| 5 | 0.001000 | 10 | sag | 0.998780 | 0.001220 | 0.159290 |
| 6 | 0.001000 | 10 | saga | 0.998780 | 0.001220 | 0.152925 |
| 7 | 0.001000 | 10 | lbfgs | 0.998780 | 0.001220 | 0.154334 |
| 1 | 0.001000 | 7 | sag | 0.998780 | 0.001220 | 0.182924 |
| 9 | 0.001000 | 13 | sag | 0.998780 | 0.001220 | 0.152003 |
| 10 | 0.001000 | 13 | saga | 0.998780 | 0.001220 | 0.170150 |
| 11 | 0.001000 | 13 | lbfgs | 0.998780 | 0.001220 | 0.162193 |
| 12 | 0.001000 | 14 | newton-cg | 0.998780 | 0.001220 | 0.163208 |
| 13 | 0.001000 | 14 | sag | 0.998780 | 0.001220 | 0.162538 |
| 14 | 0.001000 | 14 | saga | 0.998780 | 0.001220 | 0.160604 |
| 15 | 0.001000 | 14 | lbfgs | 0.998780 | 0.001220 | 0.159032 |
| 0 | 0.001000 | 7 | newton-cg | 0.998780 | 0.001220 | 0.153284 |
In [14]:
# The most optimal model in the FHE realm is:
optimal_param = {"C": 0.9, "n_bits": 13, "solver": "sag"}
clf = TorusLogisticRegression(**optimal_param)
clf.fit(X_train, y_train)
fhe_circuit = clf.compile(X_train)
fhe_circuit.client.keygen(force=False)In [15]:
# Simulated FHE mode
start_time = time()
y_pred = clf.predict(random_x, fhe="simulate")
print(
f"Inference timing: {time() - start_time:.3f} s"
f"Prediction: {y_pred[0]} -> "
f"{df_test[df_test['prognosis_encoded'] == y_pred[0]]['prognosis'].values[0]}"
)Inference timing: 0.207 sPrediction: 13 -> Drug Reaction
In [16]:
# FHE mode
start_time = time()
y_pred = clf.predict(random_x, fhe="execute")
print(
f"Inference timing: {time() - start_time:.3f} s"
f"Prediction: {y_pred[0]} -> "
f"{df_test[df_test['prognosis_encoded'] == y_pred[0]]['prognosis'].values[0]}"
)Inference timing: 0.162 sPrediction: 13 -> Drug Reaction
In [17]:
from concrete.ml.deployment import FHEModelDev
path_to_model = Path("./health_prediction/deployment").resolve()
if path_to_model.exists():
shutil.rmtree(path_to_model)
dev = FHEModelDev(path_to_model, clf)
dev.save(via_mlir=True)