|
import shutil |
|
import sys |
|
from pathlib import Path |
|
|
|
from concrete.ml.deployment import FHEModelDev |
|
from concrete.ml.deployment import FHEModelClient |
|
|
|
|
|
def compile_and_make_it_deployable(model_dev, X_train): |
|
|
|
path_to_model = Path("compiled_model") |
|
|
|
n_bits = 4 |
|
|
|
model_dev = compile_torch_model(model_dev, X_train, rounding_threshold_bits=6, p_error=0.1) |
|
|
|
|
|
accs = test_with_concrete( |
|
model_dev, |
|
test_dataloader, |
|
use_sim=True, |
|
) |
|
|
|
print(f"Simulated FHE execution for {n_bits} bit model_devwork accuracy: {accs:.2f}") |
|
|
|
|
|
shutil.rmtree(path_to_model, ignore_errors=True) |
|
fhemodel_dev = FHEModelDev(path_to_model, model_dev) |
|
fhemodel_dev.save(via_mlir=True) |
|
|
|
|
|
|
|
|
|
import time |
|
|
|
import numpy as np |
|
import torch |
|
import torch.utils |
|
from sklearn.datasets import load_digits |
|
from sklearn.model_selection import train_test_split |
|
from torch import nn |
|
from torch.utils.data import DataLoader, TensorDataset |
|
from tqdm import tqdm |
|
|
|
from concrete.ml.torch.compile import compile_torch_model |
|
|
|
X, y = load_digits(return_X_y=True) |
|
|
|
|
|
|
|
X = np.expand_dims(X.reshape((-1, 8, 8)), 1) |
|
|
|
X_train, X_test, Y_train, Y_test = train_test_split( |
|
X, y, test_size=0.25, shuffle=True, random_state=42 |
|
) |
|
|
|
|
|
class TinyCNN(nn.Module): |
|
"""A very small CNN to classify the sklearn digits data-set.""" |
|
|
|
def __init__(self, n_classes) -> None: |
|
"""Construct the CNN with a configurable number of classes.""" |
|
super().__init__() |
|
|
|
|
|
self.conv1 = nn.Conv2d(1, 8, 3, stride=1, padding=0) |
|
self.conv2 = nn.Conv2d(8, 16, 3, stride=2, padding=0) |
|
self.conv3 = nn.Conv2d(16, 32, 2, stride=1, padding=0) |
|
self.fc1 = nn.Linear(32, n_classes) |
|
|
|
def forward(self, x): |
|
"""Run inference on the tiny CNN, apply the decision layer on the reshaped conv output.""" |
|
x = self.conv1(x) |
|
x = torch.relu(x) |
|
x = self.conv2(x) |
|
x = torch.relu(x) |
|
x = self.conv3(x) |
|
x = torch.relu(x) |
|
x = x.flatten(1) |
|
x = self.fc1(x) |
|
return x |
|
|
|
|
|
torch.manual_seed(42) |
|
|
|
|
|
def train_one_epoch(model_dev, optimizer, train_loader): |
|
|
|
loss = nn.CrossEntropyLoss() |
|
|
|
model_dev.train() |
|
avg_loss = 0 |
|
for data, target in train_loader: |
|
optimizer.zero_grad() |
|
output = model_dev(data) |
|
loss_model_dev = loss(output, target.long()) |
|
loss_model_dev.backward() |
|
optimizer.step() |
|
avg_loss += loss_model_dev.item() |
|
|
|
return avg_loss / len(train_loader) |
|
|
|
|
|
def test_torch(model_dev, test_loader): |
|
"""Test the model_devwork: measure accuracy on the test set.""" |
|
|
|
|
|
model_dev.eval() |
|
|
|
all_y_pred = np.zeros((len(test_loader)), dtype=np.int64) |
|
all_targets = np.zeros((len(test_loader)), dtype=np.int64) |
|
|
|
|
|
idx = 0 |
|
for data, target in test_loader: |
|
|
|
endidx = idx + target.shape[0] |
|
all_targets[idx:endidx] = target.numpy() |
|
|
|
|
|
output = model_dev(data).argmax(1).detach().numpy() |
|
all_y_pred[idx:endidx] = output |
|
|
|
idx += target.shape[0] |
|
|
|
|
|
n_correct = np.sum(all_targets == all_y_pred) |
|
print( |
|
f"Test accuracy for fp32 weights and activations: " |
|
f"{n_correct / len(test_loader) * 100:.2f}%" |
|
) |
|
|
|
|
|
def test_with_concrete(quantized_module, test_loader, use_sim): |
|
"""Test a neural model_devwork that is quantized and compiled with Concrete ML.""" |
|
|
|
|
|
all_y_pred = np.zeros((len(test_loader)), dtype=np.int64) |
|
all_targets = np.zeros((len(test_loader)), dtype=np.int64) |
|
|
|
|
|
idx = 0 |
|
for data, target in tqdm(test_loader): |
|
data = data.numpy() |
|
target = target.numpy() |
|
|
|
fhe_mode = "simulate" if use_sim else "execute" |
|
|
|
|
|
y_pred = quantized_module.forward(data, fhe=fhe_mode) |
|
|
|
endidx = idx + target.shape[0] |
|
|
|
|
|
all_targets[idx:endidx] = target |
|
|
|
|
|
y_pred = np.argmax(y_pred, axis=1) |
|
all_y_pred[idx:endidx] = y_pred |
|
|
|
|
|
idx += target.shape[0] |
|
|
|
|
|
n_correct = np.sum(all_targets == all_y_pred) |
|
|
|
return n_correct / len(test_loader) |
|
|
|
|
|
|
|
N_EPOCHS = 50 |
|
|
|
|
|
train_dataset = TensorDataset(torch.Tensor(X_train), torch.Tensor(Y_train)) |
|
train_dataloader = DataLoader(train_dataset, batch_size=64) |
|
|
|
|
|
test_dataset = TensorDataset(torch.Tensor(X_test), torch.Tensor(Y_test)) |
|
test_dataloader = DataLoader(test_dataset) |
|
|
|
|
|
model_dev = TinyCNN(10) |
|
losses_bits = [] |
|
optimizer = torch.optim.Adam(model_dev.parameters()) |
|
for _ in tqdm(range(N_EPOCHS), desc="Training"): |
|
losses_bits.append(train_one_epoch(model_dev, optimizer, train_dataloader)) |
|
|
|
|
|
|
|
compile_and_make_it_deployable(model_dev, X_train) |
|
print("Your model is ready to be deployable.") |
|
|