mirror of
https://github.com/luxfhe/workshop.git
synced 2026-10-30 17:06:44 +00:00
825 KiB
825 KiB
In [1]:
import warnings
warnings.simplefilter(action="ignore", category=FutureWarning)
import time
from functools import partial
import matplotlib.pyplot as plt
import numpy as np
import torch
from sklearn.metrics import r2_score
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPRegressor
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from torus.ml.sklearn import (
DecisionTreeRegressor,
LinearRegression,
LinearSVR,
NeuralNetRegressor,
RandomForestRegressor,
XGBRegressor,
)
%matplotlib inline
rng = np.random.RandomState(42)In [2]:
def make_regression_data(
n_samples=200,
n_features=1,
bias=0.0,
noise_scale=1.0,
loc=0.0,
scale=1.0,
polynomial_exp=1,
target_scale=1.0,
feature_scale=1.0,
):
"""
Generates a dataset for regression models.
"""
X = rng.randn(n_samples, n_features)
# To avoid to have to big numbers on polynomial datasets
if polynomial_exp > 1:
feature_scale = 1
X = feature_scale * np.sort(X, 0)
scale = scale * polynomial_exp
noise = noise_scale * rng.normal(loc=loc, scale=scale, size=n_samples)
y = X.ravel() ** polynomial_exp + bias + noise
y *= target_scale
return X, yIn [3]:
# pylint: disable=too-many-locals,too-many-statements
def make_regressor_comparison(title, regressors, **kwargs):
print(title)
# Create subplots where each column represents a polynomial degree
subplot_col = kwargs.get("polynomial_exp", 1)
fig, axs = plt.subplots(len(regressors), subplot_col, figsize=(15, 8), sharex=False)
# Create data-sets for each polynomial degree
for i in range(subplot_col):
kwargs_copy = kwargs.copy()
kwargs_copy["polynomial_exp"] = i + 1
X, y = make_regression_data(**kwargs_copy)
# Split the data into training and test sets
# Use 15 percent (30 points for a data-set of 200 points) for prediction
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
sort_test_index = np.argsort(X_test.ravel())
X_test = X_test[sort_test_index, :]
y_test = y_test[sort_test_index]
# Feature preprocessing
# Linear models require polynomial features to be applied before training
# to fit a non-linear model and other models perform better with this transoformation
pipe = Pipeline(
[
("poly", PolynomialFeatures(i + 1)),
("scaler", StandardScaler()),
]
)
X_poly_train = pipe.fit_transform(X_train)
X_poly_test = pipe.transform(X_test)
# Iterate over the given regressors
for j, (regressor, model_name) in enumerate(regressors):
print(f"Evaluation of {model_name}")
if np.ndim(axs) > 1:
axs[0, i].set_title(f"Polynomial degree {i + 1}")
ax = axs[j, i]
else:
try:
axs[i].set_title(f"Polynomial degree {i + 1}")
ax = axs[i]
except IndexError:
ax = axs
ax.set_title(f"Polynomial degree {i + 1}")
# Plot the training points
ax.scatter(
X_train,
y_train,
edgecolors="k",
label="Train data",
)
# Plot the testing points
ax.scatter(
X_test,
y_test,
marker="D",
alpha=0.6,
edgecolors="k",
label="Test data",
)
# Instantiate the model
model = regressor()
# Train the model and retrieve both the Torus ML model and its equivalent one from
# scikit-learn
# If the model is a NeuralNetClassifier, instantiate a scikit-learn MLPClassifier
# separately in order to be able to be able to compare the results with a float model
# that doesn't use QAT
if model.__class__ == NeuralNetRegressor:
sklearn_model = MLPRegressor(
alpha=1,
activation="identity",
max_iter=1000,
hidden_layer_sizes=(25,),
learning_rate_init=0.005,
)
sklearn_model.fit(X_poly_train, y_train)
# When we apply PolynomialFeatures the input dim is equal to degree of polynome + 1
model.module__input_dim = i + 2
torus_model = model.fit(X_poly_train, y_train.reshape(-1, 1))
else:
torus_model, sklearn_model = model.fit_benchmark(X_poly_train, y_train)
# Compute the predictions in clear using the scikit-learn model
sklearn_y_pred = sklearn_model.predict(X_poly_test)
# Compile the Contrete-ML model
circuit = torus_model.compile(X_poly_train)
print(
"Generating a key for a " f"{circuit.graph.maximum_integer_bit_width()}-bit circuit"
)
time_begin = time.time()
circuit.client.keygen(force=False)
time_end = time.time()
print(f"Key generation time: {time_end - time_begin:.2f} seconds")
# Compute the predictions in FHE using the Torus ML model
time_begin = time.time()
torus_y_pred = torus_model.predict(X_poly_test[:1], fhe="execute")
time_end = time.time()
print(f"Execution time: {(time_end - time_begin):.2f} " "seconds per sample in FHE")
# Compute predictions for all test examples with the simulate mode
torus_y_pred = torus_model.predict(X_poly_test, fhe="simulate")
# Measure the R2 score
sklearn_score = r2_score(sklearn_y_pred, y_test)
torus_score = r2_score(torus_y_pred, y_test)
is_a_tree_based_model = torus_model.__class__ in [
DecisionTreeRegressor,
RandomForestRegressor,
XGBRegressor,
]
# If the model is not a tree-based model, retrieve the maximum integer bitwidth
# reached within its circuit.
bitwidth = None
if not is_a_tree_based_model:
bitwidth = circuit.graph.maximum_integer_bit_width()
# Plot the predictions
ax.plot(X_test, torus_y_pred, c="blue", linewidth=2.5, label="Torus ML")
# Plot the predictions
ax.plot(X_test, sklearn_y_pred, c="red", linewidth=2.5, label="scikit-learn")
ax.text(
0.5,
0.80,
f"Torus ML R2: {torus_score:.2f}\n scikit-learn R2: {sklearn_score:.2f}\n",
transform=ax.transAxes,
fontsize=12,
va="top",
ha="right",
)
if bitwidth:
ax.text(
0.75,
0.1,
f"bitwidth={bitwidth}",
transform=ax.transAxes,
fontsize=12,
va="bottom",
ha="left",
)
handles, labels = ax.get_legend_handles_labels()
fig.legend(handles, labels, loc="upper left")
scaler = 0.5
if len(regressors) == 3:
scaler = 0.3
fig.text(
-0.05, 0.75 - j * scaler, f"{model_name}", ha="center", va="bottom", fontsize=14
)
plt.tight_layout(pad=1.2)
plt.show()In [4]:
params_neural_net = {
"module__n_w_bits": 6,
"module__n_a_bits": 8,
"module__n_accum_bits": 16,
"module__n_hidden_neurons_multiplier": 10,
"module__n_layers": 2, # 1 hidden layer
"module__activation_function": torch.nn.Identity,
"max_epochs": 400,
"verbose": 0,
"lr": 0.1,
}
neural_network_regressor = [
(
partial(NeuralNetRegressor, batch_size=32, **params_neural_net),
"Neural Net",
),
]
make_regressor_comparison(
"NN Regressors",
neural_network_regressor,
n_samples=250,
polynomial_exp=3,
bias=20,
scale=0.25,
target_scale=1,
feature_scale=10,
)NN Regressors Evaluation of Neural Net
Generating a key for a 13-bit circuit Key generation time: 1.87 seconds
Execution time: 15.87 seconds per sample in FHE Evaluation of Neural Net
Generating a key for a 14-bit circuit Key generation time: 1.83 seconds
Execution time: 14.79 seconds per sample in FHE Evaluation of Neural Net
Generating a key for a 14-bit circuit Key generation time: 1.84 seconds
Execution time: 20.05 seconds per sample in FHE
In [5]:
np.random.seed(42)
linear_regressor = [
(partial(LinearSVR, n_bits={"op_inputs": 5, "op_weights": 2}, C=0.5), "Linear SVR"),
(partial(LinearRegression, n_bits={"op_inputs": 5, "op_weights": 2}), "Linear Regression"),
]
make_regressor_comparison(
"linear",
linear_regressor,
polynomial_exp=3,
bias=20,
scale=0.25,
target_scale=1,
feature_scale=10,
)linear Evaluation of Linear SVR
Generating a key for a 8-bit circuit Key generation time: 0.00 seconds Execution time: 0.00 seconds per sample in FHE Evaluation of Linear Regression
Generating a key for a 8-bit circuit Key generation time: 0.00 seconds Execution time: 0.00 seconds per sample in FHE Evaluation of Linear SVR
Generating a key for a 8-bit circuit Key generation time: 0.00 seconds Execution time: 0.00 seconds per sample in FHE Evaluation of Linear Regression
Generating a key for a 8-bit circuit Key generation time: 0.00 seconds Execution time: 0.00 seconds per sample in FHE Evaluation of Linear SVR
Generating a key for a 8-bit circuit Key generation time: 0.00 seconds Execution time: 0.00 seconds per sample in FHE Evaluation of Linear Regression
Generating a key for a 8-bit circuit Key generation time: 0.00 seconds Execution time: 0.00 seconds per sample in FHE
In [6]:
tree_regressors = [
(partial(DecisionTreeRegressor, n_bits=5, max_depth=5), "Decision Tree"),
(partial(RandomForestRegressor, n_bits=5), "RandomForestRegressor"),
(
partial(XGBRegressor, n_bits=6, n_estimators=50, max_depth=3, gamma=1, learning_rate=0.3),
"XGB",
),
]
make_regressor_comparison(
"Tree-Based Regressors",
tree_regressors,
n_samples=300,
polynomial_exp=3,
bias=20,
scale=0.25,
target_scale=1,
feature_scale=10,
)Tree-Based Regressors Evaluation of Decision Tree
Generating a key for a 7-bit circuit Key generation time: 0.86 seconds
Execution time: 1.02 seconds per sample in FHE
Evaluation of RandomForestRegressor
Generating a key for a 7-bit circuit Key generation time: 0.75 seconds
Execution time: 0.85 seconds per sample in FHE
Evaluation of XGB
Generating a key for a 8-bit circuit Key generation time: 0.72 seconds
Execution time: 0.83 seconds per sample in FHE
Evaluation of Decision Tree
Generating a key for a 7-bit circuit Key generation time: 0.84 seconds
Execution time: 0.62 seconds per sample in FHE
Evaluation of RandomForestRegressor
Generating a key for a 7-bit circuit Key generation time: 0.73 seconds
Execution time: 1.01 seconds per sample in FHE
Evaluation of XGB
Generating a key for a 8-bit circuit Key generation time: 0.74 seconds
Execution time: 0.97 seconds per sample in FHE
Evaluation of Decision Tree
Generating a key for a 7-bit circuit Key generation time: 0.72 seconds
Execution time: 0.51 seconds per sample in FHE
Evaluation of RandomForestRegressor
Generating a key for a 7-bit circuit Key generation time: 0.71 seconds
Execution time: 0.69 seconds per sample in FHE
Evaluation of XGB
Generating a key for a 8-bit circuit Key generation time: 0.72 seconds
Execution time: 0.76 seconds per sample in FHE