mirror of
https://github.com/luxfhe/workshop.git
synced 2026-10-30 17:06:44 +00:00
73 KiB
73 KiB
In [ ]:
# Source : https://scikit-learn.org/stable/auto_examples/linear_model/plot_tweedie_regression_insurance_claims.html # noqa # pylint: disable=line-too-long
# Authors: Christian Lorentzen <[email protected]>
# Roman Yurchak <[email protected]>
# Olivier Grisel <[email protected]>
# Modified to integrate Torus ML functions by Lux
# License: BSD 3 clause
import sys
import time
from collections import defaultdict
from timeit import default_timer as timer
import numpy as np
import sklearn
from sklearn.compose import ColumnTransformer
from sklearn.datasets import fetch_openml
from sklearn.linear_model import GammaRegressor as SklearnGammaRegressor
from sklearn.linear_model import PoissonRegressor as SklearnPoissonRegressor
from sklearn.linear_model import TweedieRegressor as SklearnTweedieRegressor
from sklearn.metrics import mean_gamma_deviance, mean_poisson_deviance, mean_tweedie_deviance
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import (
FunctionTransformer,
KBinsDiscretizer,
OneHotEncoder,
StandardScaler,
)
from torus.ml.sklearn import GammaRegressor as TorusGammaRegressor
from torus.ml.sklearn import PoissonRegressor as TorusPoissonRegressor
from torus.ml.sklearn import TweedieRegressor as TorusTweedieRegressorIn [2]:
%matplotlib inline
import matplotlib.pyplot as plt
from IPython.display import displayIn [3]:
# Getting the original data-set containing the risk features
# Link: https://www.openml.org/d/41214
risks_data, _ = fetch_openml(
data_id=41214, as_frame=True, cache=True, data_home="~/.cache/sklearn", return_X_y=True
)
# Getting the data set containing claims amount
# Link: https://www.openml.org/d/41215
claims_data, _ = fetch_openml(
data_id=41215, as_frame=True, cache=True, data_home="~/.cache/sklearn", return_X_y=True
)In [4]:
# Set IDpol as index
risks_data["IDpol"] = risks_data["IDpol"].astype(int)
risks_data.set_index("IDpol", inplace=True)
# Grouping claims mounts together if they are associated with the same policy
claims_data = claims_data.groupby("IDpol").sum()
# Merging the two sets over policy IDs
data = risks_data.join(claims_data, how="left")
# Only keeping the first 100 000 for faster running time
data = data.head(100000)
# Filtering out unknown claim amounts
data["ClaimAmount"].fillna(0, inplace=True)
# Filtering out claims with zero amount, as the severity (gamma) model
# requires strictly positive target values
data.loc[(data["ClaimAmount"] == 0) & (data["ClaimNb"] >= 1), "ClaimNb"] = 0
# Removing unreasonable outliers
data["ClaimNb"] = data["ClaimNb"].clip(upper=4)
data["Exposure"] = data["Exposure"].clip(upper=1)
data["ClaimAmount"] = data["ClaimAmount"].clip(upper=200000)In [ ]:
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",
)
x = linear_model_preprocessor.fit_transform(data)In [6]:
# Creating target values for Poisson
data["Frequency"] = data["ClaimNb"] / data["Exposure"]
# Creating target values for Gamma
data["AvgClaimAmount"] = data["ClaimAmount"] / np.fmax(data["ClaimNb"], 1)
# Creating target values for Tweedie
# Insurances companies are interested in modeling the Pure Premium, that is the expected total
# claim amount per unit of exposure for each policyholder in their portfolio
data["PurePremium"] = data["ClaimAmount"] / data["Exposure"]In [7]:
plt.ioff()
fig, ax = plt.subplots(1, 3, figsize=(15, 7))
# Set the figure's main parameters
fig.patch.set_facecolor("white")
fig.suptitle("Different target values distribution")
fig.supylabel("Count")
# Frequency of claims distribution
ax[0].set_title("Poisson")
ax[0].set_xlabel("Frequency of claims")
data["Frequency"].hist(bins=30, log=True, ax=ax[0], color="black")
# Average amount of claims distribution
ax[1].set_title("Gamma")
ax[1].set_xlabel("Average amount of claims")
data["AvgClaimAmount"].hist(bins=30, log=True, ax=ax[1], color="blue")
# PurePrenium distribution
ax[2].set_title("Tweedie")
ax[2].set_xlabel("PurePrenium")
data["PurePremium"].hist(bins=30, log=True, ax=ax[2], color="red")
display(fig)In [8]:
train_data, test_data, x_train_data, x_test_data = train_test_split(
data,
x,
test_size=0.2,
random_state=0,
)
_, test_data, _, x_test_data = train_test_split(
test_data,
x_test_data,
test_size=50,
random_state=0,
)In [9]:
gamma_mask_train = train_data["ClaimAmount"] > 0
gamma_mask_test = test_data["ClaimAmount"] > 0
parameters_glms = {
"Poisson": {
"sklearn": SklearnPoissonRegressor,
"torus": TorusPoissonRegressor,
"init_parameters": {
"alpha": 1e-3,
"max_iter": 400,
},
"fit_parameters": {
"X": x_train_data,
"y": train_data["Frequency"],
"sample_weight": train_data["Exposure"],
},
"x_test": x_test_data,
"score_parameters": {
"y_true": test_data["Frequency"],
"sample_weight": test_data["Exposure"],
},
"deviance": mean_poisson_deviance,
},
"Gamma": {
"sklearn": SklearnGammaRegressor,
"torus": TorusGammaRegressor,
"init_parameters": {
"alpha": 10.0,
"max_iter": 300,
},
"fit_parameters": {
"X": x_train_data[gamma_mask_train],
"y": train_data[gamma_mask_train]["AvgClaimAmount"],
"sample_weight": train_data[gamma_mask_train]["ClaimNb"],
},
"x_test": x_test_data[gamma_mask_test],
"score_parameters": {
"y_true": test_data[gamma_mask_test]["AvgClaimAmount"],
"sample_weight": test_data[gamma_mask_test]["ClaimNb"],
},
"deviance": mean_gamma_deviance,
},
"Tweedie": {
"sklearn": SklearnTweedieRegressor,
"torus": TorusTweedieRegressor,
"init_parameters": {
"power": 1.9,
"alpha": 0.1,
"max_iter": 10000,
},
"fit_parameters": {
"X": x_train_data,
"y": train_data["PurePremium"],
"sample_weight": train_data["Exposure"],
},
"x_test": x_test_data,
"score_parameters": {
"y_true": test_data["PurePremium"],
"sample_weight": test_data["Exposure"],
"power": 1.9,
},
"deviance": mean_tweedie_deviance,
},
}In [10]:
def compare_regressors(n_bits, fhe="simulate"):
# pylint: disable=too-many-locals
scores = defaultdict(list)
predictions = defaultdict(list)
for glm, parameters_glm in parameters_glms.items():
# Retrieve the regressors
sklearn_class = parameters_glm["sklearn"]
torus_class = parameters_glm["torus"]
# Instantiate the models
init_parameters = parameters_glm["init_parameters"]
sklearn_glm = sklearn_class(**init_parameters)
torus_glm = torus_class(n_bits=n_bits, **init_parameters)
# Fit the models
fit_parameters = parameters_glm["fit_parameters"]
sklearn_glm.fit(**fit_parameters)
torus_glm.fit(**fit_parameters)
x_train_subset = fit_parameters["X"][:100]
# Compile the Torus ML model if it needs to be executed in FHE
if fhe in ["execute", "simulate"]:
circuit = torus_glm.compile(x_train_subset)
# Generate the key
print(
"Generating a key for an "
f"{circuit.graph.maximum_integer_bit_width()}-bit circuit"
)
sys.stdout.flush()
time_begin = time.time()
circuit.client.keygen(force=False)
print(f"Key generation time: {time.time() - time_begin:.4f} seconds")
# Compute the predictions using sklearn (floating points, in the clear)
x_test = parameters_glm["x_test"]
sklearn_predictions = sklearn_glm.predict(x_test)
# Compute the predictions using Torus ML (quantized, in the clear)
torus_q_predictions = torus_glm.predict(x_test)
# Compute the predictions using Torus ML (in FHE)
start = timer()
torus_predictions = torus_glm.predict(
x_test,
fhe=fhe,
)
end = timer()
run_time = end - start
# Compute the deviance scores
mean_deviance = parameters_glm["deviance"]
score_parameters = parameters_glm["score_parameters"]
sklearn_score = mean_deviance(y_pred=sklearn_predictions, **score_parameters)
torus_q_score = mean_deviance(y_pred=torus_q_predictions, **score_parameters)
torus_score = mean_deviance(y_pred=torus_predictions, **score_parameters)
# Print the deviance scores
fhe_message = "in FHE" if fhe == "execute" else "in clear"
print(f"Mean {glm} deviance (scikit-learn): {sklearn_score:.4f}")
print(f"Mean {glm} deviance (Torus ML, quantized): {torus_q_score:.4f}")
print(
f"Mean {glm} deviance (Torus ML {fhe_message}, "
f"with {run_time / len(x_test):.4f} seconds "
f"per inference): {torus_score:.4f}"
)
# Measure the error of the FHE quantized model with respect to the clear scikit-learn
# float model
score_difference = abs(torus_score - sklearn_score) * 100 / sklearn_score
print(
"Relative difference between scikit-learn (clear) and Torus-ml (FHE) scores:",
f"{score_difference:.2f}%\n",
)
# Store the results
scores["sklearn"].append(sklearn_score)
scores["torus"].append(torus_score)
predictions["sklearn"].append(sklearn_predictions)
predictions["torus"].append(torus_predictions)
return scores, predictionsIn [11]:
n_bits = 11
fhe = "execute"
scores, predictions = compare_regressors(n_bits, fhe=fhe)Generating a key for an 25-bit circuit
Key generation time: 0.0002 seconds
Mean Poisson deviance (scikit-learn): 1.1568 Mean Poisson deviance (Torus ML, quantized): 1.1589 Mean Poisson deviance (Torus ML in FHE, with 0.0209 seconds per inference): 1.1589 Relative difference between scikit-learn (clear) and Torus-ml (FHE) scores: 0.19%
Generating a key for an 24-bit circuit
Key generation time: 0.0002 seconds Mean Gamma deviance (scikit-learn): 0.3179 Mean Gamma deviance (Torus ML, quantized): 0.3180 Mean Gamma deviance (Torus ML in FHE, with 0.0170 seconds per inference): 0.3180 Relative difference between scikit-learn (clear) and Torus-ml (FHE) scores: 0.03%
Generating a key for an 25-bit circuit
Key generation time: 0.0002 seconds
Mean Tweedie deviance (scikit-learn): 37.6493 Mean Tweedie deviance (Torus ML, quantized): 37.7585 Mean Tweedie deviance (Torus ML in FHE, with 0.0176 seconds per inference): 37.7585 Relative difference between scikit-learn (clear) and Torus-ml (FHE) scores: 0.29%