«Дифференциальная конфиденциальность - это ограничение алгоритмов, используемых для публикации совокупной информации о статистической базе данных, которое ограничивает раскрытие частной информации о записях, информация о которых находится в базе данных». - Википедия

Здесь мы собираемся применить дифференциальную конфиденциальность и анализ PATE к набору данных MNIST. Мы не обсуждаем здесь какую-либо теорию, скорее мы создадим практический код для этого набора данных. И сравните Data Independent Epsilon и Data Dependent Epsilon с его помощью.

Во-первых, позвольте мне вкратце объяснить, как это работает. Нам нужно обучить наш набор данных частным лейблам. Но лейблы частные разглядывать не будем. Скорее, мы приобретем наши лейблы где-нибудь еще. Вот почему нам нужны хорошо обученные модели, из которых наш набор данных будет получать свои метки. Мы можем назвать их «Наборы данных для учителей» и «Модели для учителей». Итак, наш набор данных в "Student Dataset". Мы обучим наш набор данных Student на каждой модели Teacher. И получить их ярлыки. Мы можем анонимизировать метки, используя случайные числа Лапласа от Numpy. И тогда мы получим наши лучшие прогнозы от этих лейблов и будем использовать их в качестве наших окончательных лейблов. Наконец, мы можем обучить наш набор данных Student этим заключительным меткам и протестировать их с помощью набора данных Student Test.

Теперь у нас нет набора данных для учителей или учеников в MNIST. Итак, мы разделим набор обучающих данных на подмножества и будем использовать их как наборы данных для учителей. И мы будем использовать тестовый набор данных как Student. Мы разделим набор данных Student на обучающую и тестовую части. Итак, нам нужно выполнить следующие шаги:

  1. Определите наборы данных учителя
  2. Определите набор данных Student и разделите его на наборы данных для обучения и тестирования
  3. Определите модель нейронной сети
  4. Обучите наборы данных Teacher и найдите модели Teacher
  5. Используя модели каждого обученного учителя, найдите прогнозы для набора данных обучения учащихся
  6. Затем получите частную метку для набора данных обучения учащихся
  7. PATE анализирует прогнозы и метки
  8. Замените старые метки из набора данных обучения учащихся частными метками
  9. Обучите набор данных для обучения студентов с помощью новых меток и проверьте точность с помощью набора данных для обучения студентов

Мы можем начать с импорта всех необходимых библиотек и функций.

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets
import torchvision.transforms as transforms
from torch.utils.data import Subset

Скачивание набора данных и преобразование их в FloatTensor.

transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5, ), (0.5,))])
train_data = datasets.MNIST(root=’data’, train=True, download=True, transform=transform)
test_data = datasets.MNIST(root=’data’, train=False, download=True, transform=transform)

Мы собираемся разделить набор данных для обучения на части «num_teachers» и называть их наборами данных для учителей. Затем загрузите их в разные загрузчики данных.

batch_size = 32
num_teachers = 100
def teacher_loader_fn(train_data, num_teachers):
  teacher_loaders = []
  data_size = len(train_data) // num_teachers
  for i in range(num_teachers):
    indices = list(range(i*data_size, (i+1)*data_size))
    subset = Subset(train_data, indices)
    loader = torch.utils.data.DataLoader(subset, batch_size=batch_size)
    teacher_loaders.append(loader)
  return teacher_loaders
teacher_loaders = teacher_loader_fn(train_data, num_teachers)

Теперь, как я сказал ранее, мы будем использовать тестовый набор данных как Student. Нам нужно разделить тестовый набор данных на две части; (i) данные об обучении студентов, (ii) данные тестирования студентов. Большую часть данных мы возьмем для обучения. Длина набора данных для обучения студентов будет «num_student_train_set», а остальные будут использоваться в качестве набора данных для тестирования студентов. А затем загрузите их в загрузчики данных.

num_student_train_set = 9000
student_train_data = Subset(test_data, list(range(num_student_train_set)))
student_test_data = Subset(test_data, list(range(num_student_train_set, len(test_data))))
student_train_loader = torch.utils.data.DataLoader(student_train_data, batch_size=batch_size)
student_test_loader = torch.utils.data.DataLoader(student_test_data, batch_size=batch_size)

Мы собираемся определить модель, которую мы будем использовать для наборов данных учителей и учеников. Здесь я определяю простую модель. Но мы можем улучшить его позже для лучшего приближения.

class Network(nn.Module):
  def __init__(self):
    super(Network, self).__init__()
    self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
    self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
    self.conv2_drop = nn.Dropout2d()
    self.fc1 = nn.Linear(320, 50)
    self.fc2 = nn.Linear(50, 10)
def forward(self, x):
    x = F.relu(F.max_pool2d(self.conv1(x), 2))
    x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
    x = x.view(-1, 320)
    x = F.relu(self.fc1(x))
    x = F.dropout(x, training=self.training)
    x = self.fc2(x)
    x = F.log_softmax(x, dim=1)
    return x

Мы собираемся провести обучение в два этапа. Во-первых, мы определяем метод «поезда».

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
epochs=20
def train(model, trainloader, criterion, optimizer, epochs, print_every):
  model.to(device)
  running_loss = 0
  for e in range(epochs):
    model.train()
    for images, labels in trainloader:
      images, labels = images.to(device), labels.to(device)
 
      optimizer.zero_grad()
 
      output = model(images)
      loss = criterion(output, labels)
      loss.backward()
      optimizer.step()
 
      running_loss += loss.item()

Затем мы обучаем загрузчиков данных учителей этому методу и получаем взамен соответствующие модели учителей.

def train_teachers(num_teachers, teacher_loaders):
  models = []
  for t in range(num_teachers):
    print(“Training teacher {}”.format(t+1))
    model = Network()
    criterion = nn.NLLLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.003)
    train(model, teacher_loaders[t], criterion, optimizer, epochs, print_every)
    models.append(model)
  return models
teacher_models = train_teachers(num_teachers, teacher_loaders)

Определение метода «прогнозирования» для оценки моделей учителей с помощью загрузчика обучения учеников.

def predict(model, dataloader):
  outputs = torch.zeros(0, dtype=torch.long).to(device)
  model.to(device)
  model.eval()
  for images, labels in dataloader:
    images, labels = images.to(device), labels.to(device)
    output = model(images)
    ps = torch.argmax(torch.exp(output), dim=1)
    outputs = torch.cat((outputs, ps))
 
  return outputs

Определение метода для поиска прогнозов с использованием метода «прогноз» и возврат прогнозов в виде массива numpy. Это прогнозы, которые мы получаем, запуская наш набор данных Student Train для каждой модели Teacher. Итак, форма этого «предшественника» будет (num_teachers, num_student_train_set).
[(100, 9000) в данном случае]

def prediction_fn(models, data_loader):
  preds = torch.torch.zeros((len(models), num_student_train_set), dtype=torch.long)
  for i, model in enumerate(models):
   results = predict(model, data_loader)
   preds[i] = results
  return preds.numpy()
preds = prediction_fn(teacher_models, student_train_loader)

Теперь получаем частные метки обучения для набора данных обучения студентов в соответствии с каждым прогнозом. Мы используем np.random.laplace для рандомизации данных. И, наконец, получите метки для нашего набора данных для обучения студентов. Его форма (num_student_train_set,).
[(в данном случае 9000,)]

epsilon = 0.2
def get_student_labels(preds, epsilon):
  labels = np.array([]).astype(int)
  for image_preds in np.transpose(preds):
    label_counts = np.bincount(image_preds, minlength=10)
    beta = 1 / epsilon
for i in range(len(label_counts)):
      label_counts[i] += np.random.laplace(0, beta, 1)
new_label = np.argmax(label_counts)
    labels = np.append(labels, new_label)
  return labels
student_labels = get_student_labels(preds, epsilon)

Анализ PATE:

from syft.frameworks.torch.differential_privacy import pate
data_dep_eps, data_ind_eps = pate.perform_analysis(teacher_preds=preds, indices=student_labels, noise_eps=epsilon, delta=1e-5)
print(“Data Independent Epsilon:”, data_ind_eps)
print(“Data Dependent Epsilon:”, data_dep_eps)

Результат:

Data Independent Epsilon: 1451.5129254649705 
Data Dependent Epsilon: 1.47499221208096

Теперь мы собираемся удалить метки из загрузчиков данных обучения студентов и соответственно добавить эти новые метки:

def student_loader(student_train_loader, labels):
  student_iterator = iter(student_train_loader)
  for i, (data, _) in enumerate(student_iterator):
    student_train_label = torch.from_numpy(labels[i*len(data):(i+1)*len(data)])
    yield data, student_train_label

Используя ту же модель, мы сейчас обучаем набор данных Student Train и тестируем набор данных Student Test и определяем точность:

student_model = Network()
criterion = nn.NLLLoss()
optimizer = optim.Adam(student_model.parameters(), lr=0.001)
student_model.to(device)
epochs=20
steps = 0
running_loss = 0
for e in range(epochs):
  student_model.train()
  train_loader = student_loader(student_train_loader, student_labels)
  for images, labels in train_loader:
    images, labels = images.to(device), labels.to(device)
    steps += 1
optimizer.zero_grad()
    output = student_model(images)
    loss = criterion(output, labels)
    loss.backward()
    optimizer.step()
running_loss += loss.item()
    if steps % 50 == 0:
      test_loss = 0
      accuracy = 0
      student_model.eval()
      with torch.no_grad():
        for images, labels in student_test_loader:
          images, labels = images.to(device), labels.to(device)
          log_ps = student_model(images)
          test_loss += criterion(log_ps, labels).item()
 
          ps = torch.exp(log_ps)
          top_p, top_class = ps.topk(1, dim=1)
          equals = top_class == labels.view(*top_class.shape)
          accuracy += torch.mean(equals.type(torch.FloatTensor))
      student_model.train()
      print(“Epoch: {}/{}.. “.format(e+1, epochs), “Training Loss: {:.3f}.. “.format(running_loss/len(student_train_loader)), “Test Loss: {:.3f}.. “.format(test_loss/len(student_test_loader)), “Test Accuracy: {:.3f}”.format(accuracy/len(student_test_loader)))
      running_loss = 0

Окончательный результат:

Epoch: 1/20...  Training Loss: 1.186...  Test Loss: 0.336...  Test Accuracy: 0.916 
Epoch: 2/20...  Training Loss: 0.484...  Test Loss: 0.227...  Test Accuracy: 0.935 
Epoch: 3/20...  Training Loss: 0.371...  Test Loss: 0.205...  Test Accuracy: 0.940
...   ...   ...   ...   ...   ...   ...   ...   ...   ...   ...
...   ...   ...   ...   ...   ...   ...   ...   ...   ...   ...
...   ...   ...   ...   ...   ...   ...   ...   ...   ...   ...
Epoch: 18/20...  Training Loss: 0.186...  Test Loss: 0.155...  Test Accuracy: 0.960 
Epoch: 19/20...  Training Loss: 0.185...  Test Loss: 0.161...  Test Accuracy: 0.964 
Epoch: 20/20...  Training Loss: 0.173...  Test Loss: 0.153...  Test Accuracy: 0.959

Теперь мы хотим знать, насколько сильно пострадает наша точность, если мы это сделаем. Для этого мы обучим набор данных MNIST и протестируем его, как обычно, с той же моделью, указанной выше, с тем же количеством эпох. Давай посмотрим что происходит.

trainloader = torch.utils.data.DataLoader(train_data, batch_size=64, shuffle=True)
testloader = torch.utils.data.DataLoader(test_data, batch_size=64, shuffle=True)
model = Network()
criterion = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr=0.003)
epochs = 20
steps = 0
train_losses, test_losses = [], []
for e in range(epochs):
    running_loss = 0
    for images, labels in trainloader:
        
        optimizer.zero_grad()
        
        log_ps = model(images)
        loss = criterion(log_ps, labels)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
        
    else:
        test_loss = 0
        accuracy = 0
        
        with torch.no_grad():
            model.eval()
            for images, labels in testloader:
                log_ps = model(images)
                test_loss += criterion(log_ps, labels)
                
                ps = torch.exp(log_ps)
                top_p, top_class = ps.topk(1, dim=1)
                equals = top_class == labels.view(*top_class.shape)
                accuracy += torch.mean(equals.type(torch.FloatTensor))
        
        model.train()
        
        train_losses.append(running_loss/len(trainloader))
        test_losses.append(test_loss/len(testloader))
print("Epoch: {}/{}.. ".format(e+1, epochs),
              "Training Loss: {:.3f}.. ".format(train_losses[-1]),
              "Test Loss: {:.3f}.. ".format(test_losses[-1]),
              "Test Accuracy: {:.3f}".format(accuracy/len(testloader)))

Результат:

Epoch: 1/20.. Training Loss: 0.454.. Test Loss: 0.083.. Test Accuracy: 0.975 
Epoch: 2/20.. Training Loss: 0.251.. Test Loss: 0.069.. Test Accuracy: 0.981 
Epoch: 3/20.. Training Loss: 0.211.. Test Loss: 0.064.. Test Accuracy: 0.980
...   ...   ...   ...   ...   ...   ...   ...   ...   ...   ...
...   ...   ...   ...   ...   ...   ...   ...   ...   ...   ...
...   ...   ...   ...   ...   ...   ...   ...   ...   ...   ...
Epoch: 18/20..  Training Loss: 0.136..  Test Loss: 0.031..  Test Accuracy: 0.990 
Epoch: 19/20..  Training Loss: 0.133..  Test Loss: 0.034..  Test Accuracy: 0.990 
Epoch: 20/20..  Training Loss: 0.140..  Test Loss: 0.041..  Test Accuracy: 0.987

Из этого результата мы видим, что точность немного отличается от предыдущего. Собственно, этот лучше предыдущего. Но мы можем поставить под угрозу эту небольшую точность в обмен на конфиденциальность наших данных. Мы также должны иметь в виду, что количество данных в предыдущей модели было всего 1000, а здесь у нас есть 60000 данных. Так что, если бы мы могли позволить себе такой большой объем данных, результат был бы намного лучше.

На этом пока все. Пожалуйста, добавьте ответ ниже для любых исправлений, советов или чего-либо еще. Я буду писать об основах конфиденциальности и федеративного обучения. Надеюсь увидеть тебя там.

[Огромное спасибо курсу Udacity Secure and Privacy AI Challenge за предоставленную мне возможность узнать это.]