mirror of
https://github.com/luxfhe/ml-sdk.git
synced 2026-10-30 18:13:04 +00:00
Files edited: 150 Brand-hit files before: 163 Brand-hit files after: 28 Substitutions: - Zama Concrete ML -> TorusML - Zama Concrete / Concrete-> Torus - Zama fhEVM / fhEVM -> TorusEVM - Zama TFHE-rs / tfhe-rs -> Lux-FHE (github.com/luxfi/fhe) - Zama (attribution) -> Lux Industries - zama-ai/* (GitHub) -> luxfhe/* - @zama-fhe/* (npm) -> @luxfhe/* - docs.zama.ai -> docs.lux.network - Fhenix / CoFHE -> dropped or rewritten to Lux FHE / TorusEVM coprocessor Text-only changes. No runtime code semantics altered.
26 KiB
26 KiB
In [1]:
import time
import numpy as np
import pandas as pd
from sklearn.datasets import fetch_openml
from sklearn.model_selection import GridSearchCV, ShuffleSplit, train_test_split
from xgboost import XGBClassifier
from concrete.ml.sklearn import XGBClassifier as ConcreteXGBClassifierNo CUDA runtime is found, using CUDA_HOME='/usr/local/cuda'
In [2]:
# pylint: disable-next=no-member
all_data = fetch_openml(data_id=40945, as_frame=True, cache=True).frame
all_data = all_data.convert_dtypes()
all_data["survived"] = all_data["survived"].astype(int)
print(all_data.columns)Index(['pclass', 'survived', 'name', 'sex', 'age', 'sibsp', 'parch', 'ticket',
'fare', 'cabin', 'embarked', 'boat', 'body', 'home.dest'],
dtype='object')
In [3]:
train_data, test_data = train_test_split(all_data, test_size=len(all_data.index) - 891)
datasets = [train_data, test_data]In [4]:
train_data.head()Out [4]:
| pclass | survived | name | sex | age | sibsp | parch | ticket | fare | cabin | embarked | boat | body | home.dest | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 995 | 3 | 0.0 | Markoff, Mr. Marin | male | 35.0 | 0 | 0 | 349213 | 7.8958 | <NA> | C | <NA> | <NA> | <NA> |
| 1147 | 3 | 0.0 | Riihivouri, Miss. Susanna Juhantytar 'Sanni' | female | 22.0 | 0 | 0 | 3101295 | 39.6875 | <NA> | S | <NA> | <NA> | <NA> |
| 485 | 2 | 0.0 | Levy, Mr. Rene Jacques | male | 36.0 | 0 | 0 | SC/Paris 2163 | 12.875 | D | C | <NA> | <NA> | Montreal, PQ |
| 1265 | 3 | 0.0 | Van Impe, Miss. Catharina | female | 10.0 | 0 | 2 | 345773 | 24.15 | <NA> | S | <NA> | <NA> | <NA> |
| 632 | 3 | 0.0 | Andersson, Mrs. Anders Johan (Alfrida Konstant... | female | 39.0 | 1 | 5 | 347082 | 31.275 | <NA> | S | <NA> | <NA> | Sweden Winnipeg, MN |
In [6]:
print("-" * 3, "Train data", "-" * 3)
print(train_data.isnull().sum(), "\n")
print("-" * 3, "Test data", "-" * 3)
print(test_data.isnull().sum())--- Train data --- pclass 0 survived 0 name 0 sex 0 age 173 sibsp 0 parch 0 ticket 0 fare 0 cabin 691 embarked 1 boat 556 body 806 home.dest 378 dtype: int64 --- Test data --- pclass 0 survived 0 name 0 sex 0 age 90 sibsp 0 parch 0 ticket 0 fare 1 cabin 323 embarked 1 boat 267 body 382 home.dest 186 dtype: int64
In [7]:
for incomp_var in train_data.columns:
missing_val = pd.concat(datasets)[incomp_var].isnull().sum()
if missing_val > 0 and incomp_var != "survived":
total_val = pd.concat(datasets).shape[0]
print(f"Percentage of missing values in {incomp_var}: {missing_val/total_val*100:.1f}%")Percentage of missing values in age: 20.1% Percentage of missing values in fare: 0.1% Percentage of missing values in cabin: 77.5% Percentage of missing values in embarked: 0.2% Percentage of missing values in boat: 62.9% Percentage of missing values in body: 90.8% Percentage of missing values in home.dest: 43.1%
In [8]:
drop_column = ["cabin", "boat", "body", "home.dest", "ticket"]
for dataset in datasets:
# Remove irrelevant variables
dataset.drop(drop_column, axis=1, inplace=True)In [9]:
for dataset in datasets:
# Complete missing Age values with median
dataset.age.fillna(dataset.age.median(), inplace=True)
# Complete missing Embarked values with the most common one
dataset.embarked.fillna(dataset.embarked.mode()[0], inplace=True)
# Complete missing Fare values with median
dataset.fare.fillna(dataset.fare.median(), inplace=True)In [10]:
def get_bin_labels(bin_name, number_of_bins):
labels = []
for i in range(number_of_bins):
labels.append(bin_name + f"_{i}")
return labels
for dataset in datasets:
# Emphasize on relatives
dataset["FamilySize"] = dataset.sibsp + dataset.parch + 1
dataset["IsAlone"] = 1
dataset.IsAlone[dataset.FamilySize > 1] = 0
# Consider an individual's social status
dataset["Title"] = dataset.name.str.extract(r" ([A-Za-z]+)\.", expand=False)
# Group fares and ages in "bins" or "buckets"
dataset["FareBin"] = pd.qcut(dataset.fare, 4, labels=get_bin_labels("FareBin", 4))
dataset["AgeBin"] = pd.cut(dataset.age.astype(int), 5, labels=get_bin_labels("AgeBin", 5))
# Remove now-irrelevant variables
drop_column = ["name", "sibsp", "parch", "fare", "age"]
dataset.drop(drop_column, axis=1, inplace=True)In [11]:
data = pd.concat(datasets)
titles = data.Title.value_counts()
print(titles)Title Mr 757 Miss 260 Mrs 197 Master 61 Dr 8 Rev 8 Col 4 Ms 2 Major 2 Mlle 2 Lady 1 Mme 1 Countess 1 Dona 1 Sir 1 Jonkheer 1 Capt 1 Don 1 Name: count, dtype: Int64
In [12]:
uncommon_titles = titles[titles < 10].index
for dataset in datasets:
dataset.Title.replace(uncommon_titles, "Rare", inplace=True)In [13]:
categorical_features = train_data.select_dtypes(exclude=np.number).columns
x_train = pd.get_dummies(train_data, prefix=categorical_features)
x_test = pd.get_dummies(test_data, prefix=categorical_features)In [14]:
target = "survived"
x_train = x_train.drop(columns=[target])
y_train = train_data[target]
x_test = x_test.drop(columns=[target])
y_test = test_data[target]In [15]:
# Instantiate the Cross-Validation generator
cv = ShuffleSplit(n_splits=5, test_size=0.3, random_state=0)
# Set the parameters to tune.
# Those ranges are voluntarily small in order to keep the FHE execution time per inference
# relatively low. In fact, we found out that, in this particular Titanic example, models with
# larger numbers of estimators or maximum depth don't score a much better accuracy.
param_grid = {
"max_depth": list(range(1, 5)),
"n_estimators": list(range(1, 5)),
"learning_rate": [0.01, 0.1, 1],
}
# Instantiate and fit the model through grid-search cross-validation
time_begin = time.time()
model = GridSearchCV(XGBClassifier(), param_grid, cv=cv, scoring="roc_auc")
model.fit(x_train, y_train)
cv_xgb_duration = time.time() - time_begin
print(f"Best hyper-parameters found in {cv_xgb_duration:.2f}s :", model.best_params_)Best hyper-parameters found in 7.98s : {'learning_rate': 1, 'max_depth': 2, 'n_estimators': 4}
In [16]:
# The TorusML model needs an additional parameter used for quantization
param_grid["n_bits"] = [2]
# Instantiate and fit the model through grid-search cross-validation
time_begin = time.time()
concrete_model = GridSearchCV(ConcreteXGBClassifier(), param_grid, cv=cv, scoring="roc_auc")
concrete_model.fit(x_train, y_train)
cv_concrete_duration = time.time() - time_begin
print(f"Best hyper-parameters found in {cv_concrete_duration:.2f}s :", concrete_model.best_params_)Best hyper-parameters found in 40.99s : {'learning_rate': 0.1, 'max_depth': 4, 'n_bits': 2, 'n_estimators': 4}
In [17]:
# Compute the predictions in clear using XGBoost
clear_predictions = model.predict(x_test)
# Compute the predictions in clear using TorusML
clear_quantized_predictions = concrete_model.predict(x_test)
# Compile the TorusML model on a subset
fhe_circuit = concrete_model.best_estimator_.compile(x_train.head(100))
# Generate the keys
# This step is not absolutely necessary, as keygen() is called, when necessary,
# within the predict method.
# However, it is useful to run it beforehand in order to be able to
# measure the prediction executing time separately from the key generation one
time_begin = time.time()
fhe_circuit.keygen()
key_generation_duration = time.time() - time_begin
# Compute the predictions in FHE using TorusML
time_begin = time.time()
fhe_predictions = concrete_model.best_estimator_.predict(x_test, fhe="execute")
prediction_duration = time.time() - time_begin
print(f"Key generation time: {key_generation_duration:.2f}s")
print(f"Total execution time for {len(clear_predictions)} inferences: {prediction_duration:.2f}s")
print(f"Execution time per inference in FHE: {prediction_duration / len(clear_predictions):.2f}s")Key generation time: 0.94s Total execution time for 418 inferences: 65.49s Execution time per inference in FHE: 0.16s
In [18]:
number_of_equal_preds = np.sum(fhe_predictions == clear_quantized_predictions)
pred_similarity = number_of_equal_preds / len(clear_predictions) * 100
print(
"Prediction similarity between both TorusML models (quantized clear and FHE): "
f"{pred_similarity:.2f}%",
)
accuracy_fhe = np.mean(fhe_predictions == y_test) * 100
print(
"Accuracy of prediction in FHE on the test set " f"{accuracy_fhe:.2f}%",
)Prediction similarity between both TorusML models (quantized clear and FHE): 100.00% Accuracy of prediction in FHE on the test set 78.23%