mirror of
https://github.com/luxfi/torus-ml.git
synced 2026-10-30 15:15:23 +00:00
The distribution called itself torus-ml while shipping a package directory named concrete, so the name a user installs and the name they import disagreed — and most of the tree already said torus.ml, which could not resolve against src/concrete. Moved src/concrete to src/torus and pointed the include at it. The dependency was Zama's concrete-python from their index. It is torus-fhe from ours, which is what luxfhe/torus already ships: that frontend declares torus-fhe and ships a torus/fhe module, so every 'from torus.fhe' in this tree was already naming our compiler and only the dependency lagged. Two distributions share one namespace, which is the shape the upstream this forked from used and the reason the split works: torus-fhe gives torus.fhe, torus-ml gives torus.ml. Swept every other spelling — identifiers, enum members, workflow variables, filenames, notebooks. LICENSE and NOTICE keep the upstream attribution a fork owes; the figures carry the old name in JPEG metadata a text sweep cannot reach.
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%