Open PereteanuGeorge opened 3 years ago
For simplicity of reproduction I will post the whole code:
import torch
from torchvision import datasets
import torchvision.transforms as transforms
torch.manual_seed(73)
train_data = datasets.MNIST('data', train=True, download=True, transform=transforms.ToTensor())
test_data = datasets.MNIST('data', train=False, download=True, transform=transforms.ToTensor())
batch_size = 64
train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_data, batch_size=1, shuffle=True)
cuda_dev = '0' # GPU device 0 (can be changed if multiple GPUs are available)
use_cuda = torch.cuda.is_available()
device = torch.device("cuda:" + cuda_dev if use_cuda else "cpu")
print('Device: ' + str(device))
if use_cuda:
print('GPU: ' + str(torch.cuda.get_device_name(int(cuda_dev))))
class ConvNet(torch.nn.Module):
def __init__(self, hidden=64, output=10):
super(ConvNet, self).__init__()
torch.nn.Sequential()
self.conv1 = torch.nn.Conv2d(1, 4, kernel_size=7, padding=0, stride=3)
self.fc1 = torch.nn.Linear(256, hidden)
self.fc2 = torch.nn.Linear(hidden, output)
def forward(self, x):
x = self.conv1(x)
x = x * x
x = x.view(-1, 256)
x = self.fc1(x)
x = x * x
x = self.fc2(x)
return x
def train(model, train_loader, criterion, optimizer, n_epochs=10):
# model in training mode
model.train()
for epoch in range(1, n_epochs + 1):
train_loss = 0.0
for data, target in train_loader:
optimizer.zero_grad()
data = data.to(device)
target = target.to(device)
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
train_loss += loss.item()
# calculate average losses
train_loss = train_loss / len(train_loader)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
# model in evaluation mode
model.eval()
return model
model = ConvNet().to(device)
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
model = train(model, train_loader, criterion, optimizer, 10)
def load_input():
idx = randint(1, len(test_loader))
i = 0
for data, target in test_loader:
if i == idx:
data_to_return = data
target_to_return = target
i += 1
return data_to_return, target_to_return
image, target = load_input()
print(f'target tensor is {target}')
prog = EvaProgram('prog', vec_size=32 * 32)
with prog:
image = Input('image')
result = model(image)
probs = torch.softmax(torch.tensor(result), 0)
label_max = torch.argmax(probs)
print(f'label_max type {type(label_max)}')
print(f'label_max value {label_max}')
Output('label_max', label_max.numpy())
prog.set_output_ranges(30)
prog.set_input_scales(30)
if __name__ == "__main__":
input = {'preds': image}
compiler = CKKSCompiler()
compiled, params, signature = compiler.compile(prog)
public_ctx, secret_ctx = generate_keys(params)
enc_inputs = public_ctx.encrypt(input, signature)
enc_outputs = public_ctx.execute(compiled, enc_inputs)
outputs = secret_ctx.decrypt(enc_outputs, signature)
print(f'expected {target}')
print(f'got {outputs}')
Hi George!
Unfortunately performing ML tasks with EVA is not as straightforward as passing Expr instances from an EvaProgram into an existing ML library. The fundamental reason for this is that homomorphic encryption does not offer all the operations that for example a Tensor in PyTorch does. For example, random access is not directly supported. EVA's Expr instances represent vectors of vec_size
approximate fixed-point values with the following operations:
+
, -
and *
for pointwise addition, subtraction and multiplication. Unary negation is also supported. The exponent **
operation expands to a tree of multiplications.<<
and >>
with a constant for vector rotation to left or right.You can emulate additional operations with these, such as random access to constant indices with <<
and multiplication with a mask of 1's and 0's, but doing so comes at a high cost. Generally to get good performance you have to redesign the basic ML operations, such as convolution, specifically for homomorphic encryption. We did some of this work in our previous project CHET (you can find a video here), but unfortunately we don't have an open source version of that.
A secondary reason existing ML libraries would not work is that when building an EvaProgram, nothing is immediately executed, but instead EVA traces the user's code and stores a DAG of operations to be executed. Only when you compile and execute the program do the actual operations happen. To make this work, the ML framework's functions for executing the model would have to be hooked up to EVA.
Generally the approach to adapting an existing ML framework to use EVA is to treat it like a new form of AI accelerator (or decelerator rather) and implement a new backend. For example for ONNX Runtime you would implement a new Execution Provider. This might still not be a very clean operation, as homomorphic encryption might not fulfill all the assumptions frameworks make of their backends. For example:
I do think private AI using homomorphic encryption is a very exciting prospect and many scenarios (especially around inferencing) can already provide valuable privacy benefits at a reasonable enough cost. However, there is still significant work to be done on the tooling side to make these kinds of applications easy to develop.
Hello,
I am trying to use EVA for a simple and encrypted MNIST model classifier.
The code for my ConvNet is the following
However, having this simple piece of code
Throws me this error: TypeError: conv2d(): argument 'input' (position 1) must be Tensor, not Expr
If however I replace the code for EVA with:
It gives me TypeError: No conversion to Term available for 0.
I get what both errors mean but I couldn't find any way of how to solve them. I was wondering if EVA supports ML tasks and if there any concrete examples other the one with image_processing, Thanks a lot!