mirror of
https://github.com/luxfhe/docs.git
synced 2026-10-30 18:13:39 +00:00
512 KiB
512 KiB
In [1]:
import time
import numpy as np
import sklearn
from sklearn.datasets import fetch_openml
from sklearn.linear_model import PoissonRegressor as SklearnPoissonRegressor
from sklearn.metrics import mean_poisson_deviance
from sklearn.model_selection import train_test_split
from concrete.ml.sklearn import PoissonRegressor as ConcretePoissonRegressorIn [2]:
%matplotlib inline
import matplotlib.pyplot as plt
from IPython.display import displayIn [3]:
df, _ = fetch_openml(
data_id=41214, as_frame=True, cache=True, data_home="~/.cache/sklearn", return_X_y=True
)
df = df.head(50000)In [4]:
df["Frequency"] = df["ClaimNb"] / df["Exposure"]In [5]:
plt.ioff()
fig, ax = plt.subplots(1, 2, figsize=(15, 7))
fig.patch.set_facecolor("white")
ax[0].set_title("Frequency of claims vs. Driver Age")
ax[0].set_xlabel("Driver Age")
ax[0].set_ylabel("Frequency of claims")
ax[0].scatter(df["DrivAge"], df["Frequency"], marker="o", color="#ffb700")
ax[1].set_title("Histogram of Frequency of claims")
ax[1].set_xlabel("Frequency of claims")
ax[1].set_ylabel("Count")
df["Frequency"].hist(bins=30, log=True, ax=ax[1], color="black")
display(fig)In [6]:
df_train, df_test = train_test_split(df, test_size=0.2, random_state=0)In [7]:
train_data = df_train["DrivAge"].values.reshape(-1, 1).astype(np.float64)
test_data = np.sort(df_test["DrivAge"].values).reshape(-1, 1).astype(np.float64)In [8]:
sklearn_pr = SklearnPoissonRegressor(max_iter=300)
sklearn_pr.fit(train_data, df_train["Frequency"], sample_weight=df_train["Exposure"]);In [9]:
sklearn_predictions = sklearn_pr.predict(test_data)In [10]:
plt.clf()
fig, ax = plt.subplots(1, figsize=(12, 8))
fig.patch.set_facecolor("white")
ax.plot(test_data, sklearn_predictions, color="black", label="Float clear trend line")
ax.scatter(df_test["DrivAge"], df_test["Frequency"], marker="o", color="#ffb700")
ax.set_xlabel("Driver Age")
ax.set_ylim(0, 10)
ax.set_title("Regression with sklearn")
ax.set_ylabel("Frequency of claims")
ax.legend(loc="upper right")
display(fig)In [11]:
concrete_pr = ConcretePoissonRegressor(n_bits=8)
concrete_pr.fit(train_data, df_train["Frequency"], sample_weight=df_train["Exposure"])Out [11]:
PoissonRegressor()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
PoissonRegressor()
In [12]:
concrete_predictions = concrete_pr.predict(test_data)In [13]:
y_true = df_test["Frequency"]
sample_weight = df_test["Exposure"]
sklearn_score = mean_poisson_deviance(y_true, sklearn_predictions, sample_weight=sample_weight)
concrete_score = mean_poisson_deviance(y_true, concrete_predictions, sample_weight=sample_weight)
print(f"mean Poisson deviance (scikit-learn): {sklearn_score:.4f}")
print(f"mean Poisson deviance (Concrete ML): {concrete_score:.4f}")mean Poisson deviance (scikit-learn): 1.6743 mean Poisson deviance (Concrete ML): 1.6743
In [14]:
plt.clf()
fig, ax = plt.subplots(1, figsize=(12, 8))
fig.patch.set_facecolor("white")
# Plot the scikit-learn in clear model's main trend line
ax.plot(
test_data,
sklearn_predictions,
color="black",
label=f"scikit-learn float, d={sklearn_score:.3f}",
)
# Plot the Concrete quantized in clear model's main trend line
ax.plot(
test_data,
concrete_predictions,
color="red",
label=f"Concrete ML quantized, d={concrete_score:.3f}",
)
# Plot the test data
ax.scatter(df_test["DrivAge"], df_test["Frequency"], marker="o", color="gray", label="Test data")
# Parametrize the main figure
ax.set_xlabel("Driver Age")
ax.set_ylim(0, 10)
ax.set_title("Poisson Regression, float in clear and quantized in clear trend lines")
ax.set_ylabel("Frequency of claims")
ax.legend(loc="upper left")
ax.grid()
# Set a zoomed-in figure
axins = ax.inset_axes([0.5, 0.5, 0.47, 0.47])
# Plot the scikit-learn in clear model's zoomed trend line
axins.plot(
test_data,
sklearn_predictions,
color="black",
)
# Plot the Concrete quantized in clear model's zoomed trend line
axins.plot(
test_data,
concrete_predictions,
color="red",
)
# Parametrize the zoomed figure
x1, x2, y1, y2 = 60, 65, 0.3, 0.7
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)
axins.grid()
ax.indicate_inset_zoom(axins, edgecolor="black")
display(fig)In [15]:
fhe_circuit = concrete_pr.compile(train_data)In [16]:
print(f"Generating a key for an {fhe_circuit.graph.maximum_integer_bit_width()}-bit circuit")Generating a key for an 11-bit circuit
In [17]:
time_begin = time.time()
fhe_circuit.client.keygen(force=False)
print(f"Key generation time: {time.time() - time_begin:.4f} seconds")Key generation time: 0.0027 seconds
In [18]:
time_begin = time.time()
concrete_predictions_fhe = concrete_pr.predict(test_data, fhe="execute")
print(f"Execution time: {(time.time() - time_begin) / len(test_data):.4f} seconds per sample")Execution time: 0.0027 seconds per sample
In [19]:
concrete_fhe_score = mean_poisson_deviance(
y_true, concrete_predictions_fhe, sample_weight=sample_weight
)
print(f"mean Poisson deviance (Concrete FHE): {concrete_fhe_score:.4f}")mean Poisson deviance (Concrete FHE): 1.6743
In [20]:
plt.clf()
fig, ax = plt.subplots(1, figsize=(12, 8))
fig.patch.set_facecolor("white")
# Plot the scikit-learn in clear model's main trend line
ax.plot(
test_data,
sklearn_predictions,
color="black",
label=f"scikit-learn float, d={sklearn_score:.3f}",
)
# Plot the Concrete quantized in clear model's main trend line
ax.plot(
test_data,
concrete_predictions,
color="red",
label=f"Concrete ML quantized, d={concrete_score:.3f}",
)
# Plot the Concrete FHE model's main trend line
ax.plot(
test_data,
concrete_predictions_fhe,
color="blue",
label=f"Concrete ML FHE, d={concrete_fhe_score:.3f}",
)
# Plot the test data
ax.scatter(df_test["DrivAge"], df_test["Frequency"], marker="o", color="gray", label="Test data")
# Parametrize the main figure
ax.set_xlabel("Driver Age")
ax.set_ylim(0, 10)
ax.set_title("Poisson Regression, float in clear, quantized in clear and FHE trend lines")
ax.set_ylabel("Frequency of claims")
ax.legend(loc="upper left")
ax.grid()
# Set a zoomed-in figure
axins = ax.inset_axes([0.5, 0.5, 0.47, 0.47])
# Plot the scikit-learn in clear model's zoomed trend line
axins.plot(
test_data,
sklearn_predictions,
color="black",
)
# Plot the Concrete FHE model's zoomed trend line
axins.plot(
test_data,
concrete_predictions,
color="red",
)
# Plot the Concrete FHE model's zoomed trend line
axins.plot(
test_data,
concrete_predictions_fhe,
color="blue",
)
# Parametrize the zoomed figure
x1, x2, y1, y2 = 60, 65, 0.3, 0.7
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)
axins.grid()
ax.indicate_inset_zoom(axins, edgecolor="black")
display(fig)In [21]:
import warnings
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline, make_pipeline
from sklearn.preprocessing import (
FunctionTransformer,
KBinsDiscretizer,
OneHotEncoder,
StandardScaler,
)
warnings.filterwarnings("ignore")In [22]:
sklearn_sparse_arg = (
{"sparse": False} if "1.1." in sklearn.__version__ else {"sparse_output": False}
)
log_scale_transformer = make_pipeline(FunctionTransformer(np.log, validate=False), StandardScaler())
linear_model_preprocessor = ColumnTransformer(
[
("passthrough_numeric", "passthrough", ["BonusMalus"]),
("binned_numeric", KBinsDiscretizer(n_bins=10), ["VehAge", "DrivAge"]),
("log_scaled_numeric", log_scale_transformer, ["Density"]),
(
"onehot_categorical",
OneHotEncoder(**sklearn_sparse_arg),
["VehBrand", "VehPower", "VehGas", "Region", "Area"],
),
],
remainder="drop",
)In [23]:
sklearn_pr = Pipeline(
[
("preprocessor", linear_model_preprocessor),
("regressor", SklearnPoissonRegressor()),
]
)
n_bits = 16
concrete_pr = Pipeline(
[
("preprocessor", linear_model_preprocessor),
("regressor", ConcretePoissonRegressor(n_bits=n_bits)),
]
)
sklearn_pr.fit(df_train, df_train["Frequency"], regressor__sample_weight=df_train["Exposure"])
concrete_pr.fit(df_train, df_train["Frequency"], regressor__sample_weight=df_train["Exposure"]);In [24]:
def score_estimator(estimator, df_test, fhe="disable"):
"""Score an estimator on the test set."""
if fhe == "execute":
time_begin = time.time()
y_pred = estimator.predict(df_test, fhe="execute")
print(
f"FHE execution time: {(time.time() - time_begin) / len(df_test):.4f} "
"seconds per sample\n"
)
else:
y_pred = estimator.predict(df_test)
y_pred = np.squeeze(y_pred)
y_true = df_test["Frequency"]
sample_weight = df_test["Exposure"]
# Ignore non-positive predictions, as they are invalid for the Tweedie deviance (except if
# power is equal to 0, making the model equivalent to a Linear Regression). We want to
# issue a warning if for some reason (e.g., low quantization, user error), the regressor
# predictions are negative.
# Find all strictly positive values
mask = y_pred > 0
# If any non-positive values are found, issue a warning
if (~mask).any():
n_masked, n_samples = (~mask).sum(), mask.shape[0]
print(
"WARNING: Estimator yields invalid, non-positive predictions "
f"for {n_masked} samples out of {n_samples}. These predictions "
"are ignored when computing the Poisson deviance."
)
return mean_poisson_deviance(y_true[mask], y_pred[mask], sample_weight=sample_weight[mask])In [25]:
sklearn_score = score_estimator(sklearn_pr, df_test)
concrete_score = score_estimator(concrete_pr, df_test)
print(f"scikit-learn (clear) deviance score: {sklearn_score:.4f}")
print(f"Concrete'ML (FHE) deviance score: {concrete_score:.4f}")
# Measure the error of the FHE quantized model with respect to the clear scikit-learn
# float model
score_difference = abs(concrete_score - sklearn_score) * 100 / sklearn_score
print(
"Relative difference between scikit-learn (clear) and Concrete-ml (FHE) scores:",
f"{score_difference:.2f}%\n",
)scikit-learn (clear) deviance score: 1.5905 Concrete'ML (FHE) deviance score: 1.5905 Relative difference between scikit-learn (clear) and Concrete-ml (FHE) scores: 0.00%
In [26]:
n_bits_values = list(range(2, 20))
concrete_deviance_scores = []
for n_bits in n_bits_values:
concrete_regressor = Pipeline(
[
("preprocessor", linear_model_preprocessor),
("regressor", ConcretePoissonRegressor(n_bits=n_bits)),
]
)
concrete_regressor.fit(
df_train, df_train["Frequency"], regressor__sample_weight=df_train["Exposure"]
)
concrete_deviance_scores.append(score_estimator(concrete_regressor, df_test))In [27]:
plt.clf()
fig, ax = plt.subplots(1, figsize=(12, 8))
fig.patch.set_facecolor("white")
ax.hlines(y=sklearn_score, xmax=2, xmin=19, color="r", label="scikit-learn")
ax.plot(n_bits_values, concrete_deviance_scores, label="Concrete ML")
ax.set_xlabel("Number of bits")
ax.set_ylabel("Poisson deviance")
ax.set_xticks(n_bits_values)
ax.set_xticklabels([str(k) for k in n_bits_values])
ax.grid()
ax.legend(loc="upper right")
display(fig)In [28]:
n_bits = 11
poisson_regressor_fhe = Pipeline(
[
("preprocessor", linear_model_preprocessor),
("regressor", ConcretePoissonRegressor(n_bits=n_bits)),
]
)
poisson_regressor_fhe.fit(
df_train, df_train["Frequency"], regressor__sample_weight=df_train["Exposure"]
);In [29]:
# Compile needs some preprocessed data in order to run.
df_test_processed = poisson_regressor_fhe["preprocessor"].transform(df_test)
# pylint: disable-next=no-member
fhe_circuit = poisson_regressor_fhe["regressor"].compile(df_test_processed)In [30]:
print(f"Generating a key for an {fhe_circuit.graph.maximum_integer_bit_width()}-bit circuit")Generating a key for an 26-bit circuit
In [31]:
time_begin = time.time()
fhe_circuit.client.keygen(force=False)
print(f"Key generation time: {time.time() - time_begin:.4f} seconds")Key generation time: 0.0007 seconds
In [32]:
# Reducing the test set from 10000 to 1000 for faster FHE execution
df_test = df_test[:1000]
concrete_score_fhe = score_estimator(poisson_regressor_fhe, df_test, fhe="execute")
print(f"scikit-learn (clear) deviance score: {score_estimator(sklearn_pr, df_test):.4f}")
print(f"Concrete ML (FHE) deviance score: {concrete_score_fhe:.4f}")
# Measure the error of the FHE quantized model with respect to the clear scikit-learn
# float model
score_difference = abs(concrete_score - sklearn_score) * 100 / sklearn_score
print(
"Relative difference between scikit-learn (clear) and Concrete-ml (FHE) scores:",
f"{score_difference:.2f}%\n",
)FHE execution time: 0.0392 seconds per sample scikit-learn (clear) deviance score: 1.5934 Concrete ML (FHE) deviance score: 1.5925 Relative difference between scikit-learn (clear) and Concrete-ml (FHE) scores: 0.00%