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.
35 KiB
35 KiB
In [1]:
import sys
import time
import numpy
from sklearn.datasets import fetch_california_housing
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error
from sklearn.model_selection import train_test_split
from sklearn.utils import resample
import torus.ml
from torus.ml.sklearn import DecisionTreeRegressor as TorusDecisionTreeRegressor
print(f"Using TorusML version {torus.ml.version.__version__}")
print(f"With Python version {sys.version}")Using TorusML version 1.6.0 With Python version 3.8.18 (default, Jul 16 2024, 19:04:03) [GCC 9.4.0]
In [2]:
features_all, target_all = fetch_california_housing(return_X_y=True)
features, target = resample(features_all, target_all, replace=True, n_samples=6000, random_state=42)
# Split data in train-test groups
x_train, x_test, y_train, y_test = train_test_split(
features,
target,
test_size=0.15,
random_state=42,
)In [3]:
%matplotlib inline
import matplotlib.pyplot as plt
plt.hist(target, bins=15, density=True)
plt.show()In [4]:
# Utility functions
def print_as_dollars(x):
"""Prints the value * 100'000$"""
return f"{x * 10**5:.2f}$"
def print_compare_to_baseline(x, baseline_error):
"""Prints percentage improvement over baseline"""
return f"{(x - baseline_error) / baseline_error * 100 :.2f}% of baseline"
mean_error = mean_absolute_error(y_test, numpy.repeat([numpy.median(y_test)], y_test.shape))
print(f"Mean Absolute Overall Error : {print_as_dollars(mean_error)}")Mean Absolute Overall Error : 89922.83$
In [5]:
canary = LinearRegression()
canary.fit(x_train[:, :1], y_train)
baseline_error = mean_absolute_error(canary.predict(x_test[:, :1]), y_test)
print(f"Baseline Mean Error : {print_as_dollars(baseline_error)}")Baseline Mean Error : 62719.65$
In [6]:
default_model = TorusDecisionTreeRegressor(criterion="absolute_error", n_bits=6, random_state=42)
begin = time.time()
default_model.fit(x_train, y_train)
print(f"Training on {x_train.shape[0]} samples in {(time.time() - begin):.4f} seconds")Training on 5100 samples in 21.5514 seconds
In [7]:
default_error = mean_absolute_error(default_model.predict(x_test), y_test)
print(
f"Default Model Mean Error: {print_as_dollars(default_error)},"
f"{print_compare_to_baseline(default_error, baseline_error)}"
)Default Model Mean Error: 43788.10$,-30.18% of baseline
In [8]:
# Find best hyper parameters with cross validation
from sklearn.model_selection import GridSearchCV
# List of hyper parameters to tune
param_grid = {
"criterion": ["absolute_error"],
"random_state": [42],
"max_depth": [10],
"n_bits": [6, 7],
"max_features": [2, 5],
"min_samples_leaf": [2, 5],
"min_samples_split": [2, 10],
}
grid_search = GridSearchCV(
TorusDecisionTreeRegressor(),
param_grid,
cv=3,
scoring="neg_mean_absolute_error",
error_score="raise",
n_jobs=1,
)
gs_results = grid_search.fit(x_train, y_train)
print("Best hyper parameters:", gs_results.best_params_)
print(f"Min lost: {print_as_dollars(-gs_results.best_score_)}")Best hyper parameters: {'criterion': 'absolute_error', 'max_depth': 10, 'max_features': 5, 'min_samples_leaf': 2, 'min_samples_split': 2, 'n_bits': 7, 'random_state': 42}
Min lost: 43828.16$
In [9]:
# We fix all parameters as the best ones, except for n_bits.
best = gs_results.best_params_
cv_errors = [
{"n_bits": params["n_bits"], "score": score}
for params, score in zip(
gs_results.cv_results_["params"], gs_results.cv_results_["mean_test_score"]
)
if (params["max_depth"] == best["max_depth"])
and (params["max_features"] == best["max_features"]) # noqa: W503
and (params["min_samples_leaf"] == best["min_samples_leaf"]) # noqa: W503
and (params["min_samples_split"] == best["min_samples_split"]) # noqa: W503
]
for el in cv_errors:
print(f"Error for n_bits={el['n_bits']} is {print_as_dollars(-el['score'])}")Error for n_bits=6 is 45585.76$ Error for n_bits=7 is 43828.16$
In [10]:
# Build the model with best hyper parameters
model = TorusDecisionTreeRegressor(
max_depth=gs_results.best_params_["max_depth"],
max_features=gs_results.best_params_["max_features"],
min_samples_leaf=gs_results.best_params_["min_samples_leaf"],
min_samples_split=gs_results.best_params_["min_samples_split"],
n_bits=6,
random_state=42,
)In [11]:
model, sklearn_model = model.fit_benchmark(x_train, y_train)In [12]:
# Compute average precision on test
y_pred_torus = model.predict(x_test)
y_pred_sklearn = sklearn_model.predict(x_test)
torus_average_precision = mean_absolute_error(y_test, y_pred_torus)
sklearn_average_precision = mean_absolute_error(y_test, y_pred_sklearn)
print(
f"Sklearn Mean Error: {print_as_dollars(sklearn_average_precision)},"
f"{print_compare_to_baseline(sklearn_average_precision, baseline_error)}"
)
print(
f"Torus Mean Error: {print_as_dollars(torus_average_precision)},"
f"{print_compare_to_baseline(torus_average_precision, baseline_error)}"
)Sklearn Mean Error: 45960.28$,-26.72% of baseline Torus Mean Error: 46691.16$,-25.56% of baseline
In [13]:
from torus.compiler import check_gpu_available
use_gpu_if_available = False
device = "cuda" if use_gpu_if_available and check_gpu_available() else "cpu"
x_train_subset = x_train[:500]
begin = time.time()
circuit = model.compile(x_train_subset, device=device)
print(f"Compiled with {len(x_train_subset)} samples in {(time.time() - begin):.4f} seconds")Compiled with 500 samples in 1.4179 seconds
In [14]:
print(f"Generating a key for an {circuit.graph.maximum_integer_bit_width()}-bit circuit")
time_begin = time.time()
circuit.client.keygen(force=False)
print(f"Key generation time: {time.time() - time_begin:.2f} seconds")Generating a key for an 8-bit circuit Key generation time: 0.84 seconds
In [15]:
FHE_SAMPLES = 3
x_test_small = x_test[:FHE_SAMPLES]
y_pred = y_test[:FHE_SAMPLES]In [16]:
# Predict in FHE for a few examples
time_begin = time.time()
y_pred_fhe = model.predict(x_test_small, fhe="execute")
print(f"Execution time: {(time.time() - time_begin) / FHE_SAMPLES:.2f} seconds per sample")Execution time: 1.54 seconds per sample
In [17]:
# Check prediction FHE vs sklearn
print("Cipher estimates:")
print(f"{', '.join(f'{print_as_dollars(x)}' for x in y_pred_fhe)}")
print("Plain estimates:")
print(f"{', '.join(f'{print_as_dollars(x)}' for x in y_pred)}")
print("Differences:")
print(f"{', '.join(f'{print_as_dollars(x)}' for x in (y_pred_fhe - y_pred))}")Cipher estimates: 293651.38$, 277778.33$, 126984.38$ Plain estimates: 290800.00$, 214600.00$, 102800.00$ Differences: 2851.38$, 63178.33$, 24184.38$
In [18]:
# Concatenate all the steps in one function of n_bits
def evaluate(n_bits):
model = TorusDecisionTreeRegressor(
max_depth=gs_results.best_params_["max_depth"],
max_features=gs_results.best_params_["max_features"],
min_samples_leaf=gs_results.best_params_["min_samples_leaf"],
min_samples_split=gs_results.best_params_["min_samples_split"],
n_bits=n_bits,
random_state=42,
)
model, sklearn_model = model.fit_benchmark(x_train, y_train)
y_pred_torus = model.predict(x_test)
y_pred_sklearn = sklearn_model.predict(x_test)
torus_average_precision = mean_absolute_error(y_test, y_pred_torus)
sklearn_average_precision = mean_absolute_error(y_test, y_pred_sklearn)
print(
f"Sklearn Mean Error: {print_as_dollars(sklearn_average_precision)},"
f"{print_compare_to_baseline(sklearn_average_precision, baseline_error)}"
)
print(
f"Torus Mean Error: {print_as_dollars(torus_average_precision)},"
f"{print_compare_to_baseline(torus_average_precision, baseline_error)}"
)
x_train_subset = x_train[:500]
begin = time.time()
circuit = model.compile(x_train_subset)
print(
f"Circuit compiled with {len(x_train_subset)} samples in {(time.time() - begin):.4f} "
"seconds"
)
print(f"Generating a key for an {circuit.graph.maximum_integer_bit_width()}-bit circuit")
time_begin = time.time()
circuit.client.keygen(force=False)
print(f"Key generation time: {time.time() - time_begin:.2f} seconds")
time_begin = time.time()
model.predict(x_test_small, fhe="execute")
print(f"Execution time: {(time.time() - time_begin) / FHE_SAMPLES:.2f} seconds per sample")
for n_bits in [6, 7]:
header = f"N_BITS = {n_bits}"
print(header)
print("-" * len(header))
evaluate(n_bits)
print()N_BITS = 6 ----------
Sklearn Mean Error: 45960.28$,-26.72% of baseline Torus Mean Error: 46691.16$,-25.56% of baseline
Circuit compiled with 500 samples in 1.4455 seconds Generating a key for an 8-bit circuit Key generation time: 0.82 seconds
Execution time: 1.64 seconds per sample N_BITS = 7 ----------
Sklearn Mean Error: 45960.28$,-26.72% of baseline Torus Mean Error: 43046.38$,-31.37% of baseline
Circuit compiled with 500 samples in 1.3987 seconds Generating a key for an 9-bit circuit Key generation time: 0.58 seconds
Execution time: 1.40 seconds per sample