通过差分隐私维护医疗数据的隐私
想象一下,您在一家医院担任DL研究人员,而您的工作是寻找帮助医生抗击疾病的方法。由于冠状病毒(无形的敌人)大流行,越来越多的患者和大量CT影像等待被诊断。您决定创建一个图像分类器,这不仅可以减轻临床医生的负担,并帮助他们做出更明智的决定,还可以加快诊断速度并可能挽救生命。但是,这是一个挑战。您的CT图像未标记。 引起您注意的是,其他5家医院都对CT扫描进行了注释(对于COVID 19,“阳性”或“阴性”),这正是您所需要的。尽管这些医院愿意提供帮助,但它们在共享患者信息方面存在隐私方面的顾虑,因此无法提供数据。
如何在不直接访问它们的情况下利用这些数据集?您如何保证这些医院的患者数据将受到保护?甚至有可能吗?我们尝试在此博客文章中回答这些问题。
有可能的!在此博客文章中,我们将使用COVID-19数据集进行动手演示-换句话说,我们将使用不可见的数据检测不可见的敌人。
目标
建立深度学习模型时,请保留训练数据(来自合作医院的数据)的隐私。
方法
解决我们问题的方法在于一种称为差分隐私的隐私保护方法。我们将特别关注PATE分析。如果您不熟悉这些术语,请不要担心,因为我们将首先介绍这些概念。
什么是差分隐私?
差分隐私是辛西娅·德沃克(Cynthia Dwork)等人[ 1 ] 引入的隐私概念,可确保统计分析不会损害隐私。它确保了个人数据对整体模型输出的影响是有限的。换句话说,无论数据集中有没有特定个体的数据,算法的输出几乎相同。
差分隐私[ 来源 ]
如上图所示,John的信息存在于第一个数据集中,而第二个不存在,但模型输出是相同的。直觉是,想要获取John数据的对手无法确定John是否存在于数据集中-更不用说他数据的内容了。因此,保证了约翰的隐私。
差分隐私通常通过在模型或统计查询的输入级别(本地差分隐私)或输出级别(全局差分隐私)添加统计噪声来起作用。噪声的添加确保了隐藏个人用户的贡献,但同时我们在不牺牲隐私的情况下获得了对总体人群的见识。添加的噪声量取决于称为隐私预算的参数,该参数通常用epsilon(ε)表示。epsilon的值越小(即添加的噪声越多),它提供的隐私就越高,反之亦然。如下图所示,我们可以看到,随着更多噪音被添加到人脸图像中,它变得越匿名,但其用途就越少。因此,选择正确的ε值非常重要。
添加噪声的效果[ 来源 ]
我们将要使用的差分性隐私方法称为PATE(教师合奏的私人聚合),它是由Papernot等人提出的。(3)。PATE框架类似于任何其他受监督的机器学习方法,但此处的结果模型保证了隐私。以下段落对PATE框架的工作原理提供了一些见解:
首先,从训练不相交的数据集获得几个模型,它们没有共同的训练示例。这些模型称为教师模型。然后将输入发送到所有这些教师模型中,并且它们各自产生输出,通常是班级标签。输入图像的最后一个类是所有教师模型的输出的总和。
如果所有/大多数模型都在特定类别上达成一致,则很容易得出输入类别。这意味着不会泄漏有关任何单个训练示例的私人信息,因为如果从一个数据集中删除了任何训练示例,则模型仍将在同一输出上达成一致。在这种情况下,隐私预算较低,并且该算法满足差分隐私。
另一方面,如果模型不同意(因此隐私预算较高),则可能导致输入图像属于哪个类别的混淆,并最终导致隐私泄漏。为了解决此问题,我们应用了最大报告噪声(RNM)算法,该算法将随机噪声添加到每个模型的输出中。此方法提供了有意义且强大的隐私保证。目前,该算法是完全差分私有的。
PATE分析的优点在于它不仅在这里结束。要增加隐私性要付出更多的努力。
我们不能使用聚合教师模型进行推理的主要原因有两个:
- 每当我们做出预测时,隐私预算就会增加。因此,我们最终将达到不可避免的隐私泄露点。
- 通过多次查询或模型检查,对手可以访问教师的训练数据,这将是灾难性的。
由于这些原因,创建学生模型成为必要。学生模型是一个未标记的公共数据集,现在可以用教师模型标记。给学生加上标签后,可以丢弃教师模型,并且仅对学生模型进行推断。现在,唯一可用的模型是学生模型,该模型已经从汇总的教师那里学到了概括。当使用学生模型进行推理时,隐私预算不会随每个查询而增加,并且在最坏的情况下,对手只能获得由教师提供的具有不同隐私权的嘈杂标签。
如果您是像我这样的视觉学习者,则不妨查看一下有关PATE框架的有趣漫画。
为COVID-19创建差分私有分类器
让我们提醒自己这个目标。目的是为您的医院训练分类器,以识别患者是否患有COVID-19。您拥有CT扫描图像的未标记数据集,其中包含可能或可能没有病毒的患者。现在,您需要来自其他5家医院的数据来标记您的数据集,并且由于隐私原因,您无法直接访问该数据。在了解了不同的隐私和PATE框架之后,您决定尝试一下。这些是您将要执行的步骤:
- 您将要求5家医院中的每家在他们自己的数据集上训练模型。生成的模型是教师模型。
- 然后,您将使用5个教师模型中的每一个为每个CT图像生成5个标签。
- 为了确保教师训练数据集的私密性,您可以在生成的标签上应用报告最大噪声(RNM)算法。对于每个CT扫描,您会在生成的5个标签中获得最频繁的标签,然后添加噪声以使其具有差分性。
- 现在,您可以使用嘈杂的标签来训练将在您的医院中部署的模型(学生模型)。
是时候卷起袖子,开始有趣的部分了。编码!
步骤1:安装PySyft
我们正在阅读的完整笔记本可以在此github存储库中找到。首先,我们必须设置环境并导入将要使用的库。本教程假定您正在使用Google Colab,因为它包含我们需要的大多数依赖关系,而我们唯一需要安装的库是PySyft。
PySyft是由OpenMined创建的开源框架,可在深度学习中实现安全的私有计算。Pysyft将使我们能够执行PATE分析。运行以下代码以安装PySyft并导入库。
## install syft package to use Private Aggregation of Teacher Ensembles (PATE)
!pip install syft
# import our libraries
import numpy as np
import pandas as pd
import torch
from torchvision import datasets, transforms,models
from torch.utils.data import Dataset, Subset, DataLoader
from torch import nn, optim
import torch.nn.functional as F
from PIL import Image
import time, os, random# libary from pysyft needed to perform pate analysis
from syft.frameworks.torch.dp import pate# we'll train on GPU if it is available
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
我们的数据集将被上传到Google Drive,因此我们必须获得授权访问。获得访问权限后,我们使用导航到我们的项目目录cd
## authorize access to google drive
from google.colab import drive
drive.mount('/content/drive')# navigate to project directory
%cd '/content/drive/My Drive/Colab Notebooks/OpenMined/'
当前,用于COVID-19诊断目的的开源数据集不多。可以理解,由于患者隐私和法律原因,访问此类数据集具有挑战性。这说明了为什么隐私保护方法非常重要。由于缺乏COVID-19数据,我们将使用一个数据集,您可以在这里下载。
可以在Images_Processed文件夹中找到该数据集。它分为COVID和非COVID图像文件夹。图像的标签可在Data_Split文件夹中找到。
现在,我们必须组织该项目。在您的Google驱动器上创建一个数据目录,并将这2个文件夹上传到该目录中。现在,将Images_Processed文件夹重命名为images,将Data_Split文件夹重命名为labels。最后,您的数据目录应如下所示:
步骤2:创建教师和学生数据集
为了以差分化的私有方式训练模型,我们需要两个主要组成部分:私有数据集(教师)和公共未标记数据集(学生)。在labels目录中,您会注意到标签已分为训练集,验证集和测试集。因此,我们必须自己创建教师和学生数据集。
我们将训练集用作教师的训练数据集,测试数据集将用作学生的训练数据集,而验证集将用于测试学生模型和正常模型(已进行过无差分训练的模型)的性能隐私)。下表中对此进行了总结:
是时候加载训练,验证和测试数据集了。我们首先创建一个自定义数据集加载器,创建数据转换,最后加载数据集。
# Custom dataset
#from https://github.com/UCSD-AI4H/COVID-CT/blob/master/baseline methods/DenseNet169/DenseNet_predict.py
class CovidCTDataset(Dataset):def __init__(self, root_dir, txt_COVID, txt_NonCOVID, transform=None):"""Args:txt_path (string): Path to the txt file with annotations.root_dir (string): Directory with all the images.transform (callable, optional): Optional transform to be appliedon a sample.File structure:- root_dir- CT_COVID- img1.png- img2.png- ......- CT_NonCOVID- img1.png- img2.png- ......"""self.root_dir = root_dirself.txt_path = [txt_COVID,txt_NonCOVID]self.classes = ['CT_COVID', 'CT_NonCOVID']self.num_cls = len(self.classes)self.img_list = []for c in range(self.num_cls):cls_list = [[os.path.join(self.root_dir,self.classes[c],item), c] for item in read_txt(self.txt_path[c])]self.img_list += cls_listself.transform = transformdef __len__(self):return len(self.img_list)def __getitem__(self, idx):if torch.is_tensor(idx):idx = idx.tolist()img_path = self.img_list[idx][0]image = Image.open(img_path).convert('RGB')if self.transform:image = self.transform(image)label = int(self.img_list[idx][1])return image, labeldef read_txt(txt_path):with open(txt_path) as f:lines = f.readlines()txt_data = [line.strip() for line in lines]return txt_data
batchsize=16
path = './data/images'# Transforms used for datasets
data_transforms = transforms.Compose([transforms.Resize(224),transforms.RandomResizedCrop((224),scale=(0.5,1.0)),transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])# divided among teachers
trainset = CovidCTDataset(root_dir=f'{path}',txt_COVID='./data/labels/COVID/trainCT_COVID.txt',txt_NonCOVID='./data/labels/NonCOVID/trainCT_NonCOVID.txt',transform= data_transforms)# used as student valid set
validset = CovidCTDataset(root_dir=f'{path}',txt_COVID='./data/labels/COVID/valCT_COVID.txt',txt_NonCOVID='./data/labels/NonCOVID/valCT_NonCOVID.txt',transform= data_transforms)# used as student train set
testset = CovidCTDataset(root_dir=f'{path}',txt_COVID='./data/labels/COVID/testCT_COVID.txt',txt_NonCOVID='./data/labels/NonCOVID/testCT_NonCOVID.txt',transform= data_transforms)print("Number of Classes: ",len(trainset.classes))
len(trainset), len(testset), len(validset)
我们已经成功加载了数据,现在让我们可视化数据和标签。
data_loader = DataLoader(trainset, batch_size=batchsize, shuffle=True)import matplotlib.pyplot as plt## Method to display Image for Tensor
def imshow(image, ax=None, title=None, normalize=True):"""Imshow for Tensor."""if ax is None:fig, ax = plt.subplots()#print(type(image))image = image.numpy().transpose((1, 2, 0))if normalize:mean = np.array([0.485, 0.456, 0.406])std = np.array([0.229, 0.224, 0.225])image = std * image + meanimage = np.clip(image, 0, 1)ax.imshow(image)ax.spines['top'].set_visible(False)ax.spines['right'].set_visible(False)ax.spines['left'].set_visible(False)ax.spines['bottom'].set_visible(False)ax.tick_params(axis='both', length=0)ax.set_xticklabels('')ax.set_yticklabels('')return ax# Displaying Images and other info about the train set
images, labels = next(iter(data_loader))
print(" Image Size",images.size())
print(" Image Size",images[ii].size())fig, axes = plt.subplots(figsize=(16,5), ncols=5)
for ii in range(5):ax = axes[ii]ax.set_title(labels[ii])imshow(images[ii], ax=ax, normalize=True)
我们的输出是:
现在,我们可以继续在不同医院之间划分训练集。我们有5所医院,因此我们有5名教师。这里要注意的一件事是数据集必须是不相交的。也就是说,没有两个数据集应该有重叠的训练示例。如前所述,差分隐私指出,如果从数据集中删除某人的数据,则该数据集的输出将保持不变,因为该人没有对数据集做出贡献。想象一下,即使有一个重复的个人数据,即使我们删除了一个重复数据,该个人数据仍然对输出有贡献,因此在这种情况下,差分隐私将无法工作,因为我们无法保留该个人的隐私。
因此,在将训练数据划分为子集时,我们必须非常谨慎。
接下来,我们将在5位教师/医院之间划分训练集,并为这5位教师中的每位教师创建训练加载器和验证加载器。
# TEACHERS
#divide train set among teachers and create dataloaders for valid and trainsets
num_teachers = 5
valid_per = 0.2 #20% for validation
batch_size = 32def teacher_dataloaders(transet=trainset, num_teachers=num_teachers, batch_size=batch_size, valid_per = 0.2):trainloaders = []validloaders = []teacher_data_len = len(trainset) // num_teachers# create a list of shuffled indicesmy_list = random.sample(range(1,len(trainset)), len(trainset)-1)random.shuffle(my_list)for i in range(num_teachers):# get particular subset of dataindice = my_list[i*teacher_data_len: (i+1)*teacher_data_len]data_subset = Subset(trainset, indice)# split into train and validation setvalid_size = int(len(data_subset) * valid_per)train_size = len(data_subset) - valid_sizetrain_subset, valid_subset = torch.utils.data.random_split(data_subset, [train_size,valid_size])#create data loaderstrainloader = DataLoader(train_subset, batch_size=batch_size, shuffle=True, num_workers=1)validloader = DataLoader(valid_subset, batch_size=batch_size, shuffle=False, num_workers=1)#add dataloaders to listtrainloaders.append(trainloader)validloaders.append(validloader)return trainloaders, validloaders# creating dataloaders
trainloaders, validloaders = teacher_dataloaders()
len(trainloaders), len(validloaders)
请注意,我们现在为教师配备了5个训练数据集加载器和5个验证数据集加载器。现在,我们为学生(我们的医院)创建训练和验证数据加载器。
# # STUDENT
# split into train and validation set
valid_size = int(len(testset) * 0.2)
train_size = len(testset) - valid_size
student_train_subset, student_valid_subset = torch.utils.data.random_split(testset, [train_size,valid_size])#create data loaders
student_train_loader = DataLoader(student_train_subset, batch_size=batch_size, shuffle=False, num_workers=1)
student_valid_loader = DataLoader(student_valid_subset, batch_size=batch_size, shuffle=False, num_workers=1)len(student_train_loader), len(student_valid_loader)
步骤3:训练教师数据集
现在我们的学生和教师数据集已经准备就绪,每个医院都可以训练他们的数据以创建5个不同的模型。
我们首先定义一个简单的CNN模型,教师和学生都将使用该模型进行训练。
class SimpleCNN(torch.nn.Module):def __init__(self):super(SimpleCNN, self).__init__() # b, 3, 32, 32layer1 = torch.nn.Sequential()layer1.add_module('conv1', torch.nn.Conv2d(3, 32, 3, 1, padding=1))#b, 32, 32, 32layer1.add_module('relu1', torch.nn.ReLU(True))layer1.add_module('pool1', torch.nn.MaxPool2d(2, 2))self.layer1 = layer1layer4 = torch.nn.Sequential()layer4.add_module('fc1', torch.nn.Linear(401408, 2)) self.layer4 = layer4def forward(self, x):conv1 = self.layer1(x)fc_input = conv1.view(conv1.size(0), -1)fc_out = self.layer4(fc_input)return fc_out
然后,我们定义训练循环。我们不会保存教师模型,因为在生成学生标签之后,它们将对我们无用。同样,这将确保不存在模型的副本。
def train(n_epochs, trainloader, validloader, model, optimizer, criterion, use_cuda, save_path= None, is_not_teacher=False):"""returns trained model"""# # initialize tracker for minimum validation lossvalid_loss_min = np.Inffor epoch in range(1, n_epochs+1):# initialize variables to monitor training and validation losstrain_loss = 0.0valid_loss = 0.0train_correct = 0.0train_total = 0.0valid_correct =0.0valid_total = 0.0# train the model #model.train()for batch_idx, (data, target) in enumerate(trainloader):# move to GPUif use_cuda:data, target = data.cuda(), target.cuda()# initialize weights to zerooptimizer.zero_grad()output = model(data)loss = criterion(output, target)loss.backward()optimizer.step() train_loss = train_loss + ((1 / (batch_idx + 1)) * (loss.data - train_loss))# convert output probabilities to predicted classpred = output.data.max(1, keepdim=True)[1]# compare predictions to true labeltrain_correct += np.sum(np.squeeze(pred.eq(target.data.view_as(pred))).cpu().numpy())train_total += data.size(0)train_acc = 100. * train_correct / train_total# validate the modelmodel.eval()for batch_idx, (data, target) in enumerate(validloader):# move to GPUif use_cuda:data, target = data.cuda(), target.cuda()output = model(data)loss = criterion(output, target)valid_loss = valid_loss + ((1 / (batch_idx + 1)) * (loss.data - valid_loss))pred = output.data.max(1, keepdim=True)[1]# compare predictions to true labelvalid_correct += np.sum(np.squeeze(pred.eq(target.data.view_as(pred))).cpu().numpy())valid_total += data.size(0)valid_acc = 100. * valid_correct / valid_total# print training/validation statisticsprint('Epoch: {} \n\tTrain Loss: {:.6f} \tTrain Acc: {:.6f} \n\tValid Loss: {:.6f} \tValid Acc: {:.6f}'.format(epoch,train_loss,train_acc,valid_loss,valid_acc ))## save the student model if validation loss has decreasedif is_not_teacher:if valid_loss < valid_loss_min:torch.save(model.state_dict(), save_path)print('\tValidation loss decreased ({:.6f} --> {:.6f}). Saving model ...'.format(valid_loss_min,valid_loss))valid_loss_min = valid_lossreturn model
现在,我们定义我们的超参数。我们将使用CrossEntropyLoss和Adam优化器。我们还将训练每位教师50个epoch。
# instantiate model and move it to GPU if available
model = SimpleCNN()
model.to(device)#define hyperparameters
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters() , lr=0.001)
epochs = 50
最后,我们准备训练我们的教师模型。
# Training teachers
teacher_models = []
i = 1
for trainloader, validloader in zip(trainloaders, validloaders):print(" Training Teacher {}".format(i))teacher_model = train(epochs, trainloader, validloader, model, optimizer, criterion, True)teacher_models.append(teacher_model)i+=1print("="*40)
步骤4:取得私人学生标签
教师已经完成训练,我们有5个教师模型。我们可以使用模型为我们的医院生成标签。5个模型中的每个模型都会为我们的数据集中的每个图像生成一个标签。因此,我们希望在医院数据集中为每个图像生成5个标签。
# get private labels
def student_train_labels(teacher_models, dataloader):student_labels = []# get label from each teacherfor model in teacher_models:student_label = []for images,_ in dataloader:with torch.no_grad():images = images.cuda()outputs = model(images)preds = torch.argmax(torch.exp(outputs), dim=1)student_label.append(preds.tolist())# add all teacher predictions to student_labels student_label = sum(student_label, [])student_labels.append(student_label)return student_labelspredicted_labels = student_train_labels(teacher_models, student_train_loader)
predicted_labels = np.array([np.array(p) for p in predicted_labels]).transpose(1, 0)# We see here that we have 5 labels for each image in our dataset
print(predicted_labels.shape)
# See labels of 3rd Image Scan
print(predicted_labels[3])
我们预测标签的形状为(163,5),这意味着我们的医院数据中有163个训练示例,并且每个教师为每个标签生成了5个标签。第三张CT图像的预测标签为[0 1 1 0 0]。这表示3个教师模型同意COVID-19的图像为负,而2个教师模型不同意。在这种情况下,我们将进行多数表决,并说此特定图像对COVID-19不利。
步骤5:添加拉普拉斯噪声
我们的预测标签的形状为(163,5),这意味着我们的医院数据中有163个训练示例,每个教师为每个图像生成了5个标签。第三张CT图像的预测标签为[0 1 1 0 0]。这意味着3个教师模型同意COVID-19的图像为负,而2个模型则不同意。在这种情况下,我们选择票数最高的标签,并得出结论,该特定图像对于COVID-19为负。
但是,有时标签不明显。例如,考虑具有6个教师模型的情况,其中3个教师模型说结果是肯定的,而另外3个教师模型说是否定的。在这种情况下,我们将无法达成共识,并且可能会误将图像分类。通过将经过仔细计算的随机拉普拉斯噪声添加到每个教师提供的所有标签中,可以解决此问题。然后,从所有教师那里获得票数最多的标签。结果将是每次CT扫描的一个标签,该标签是所有教师数据的通用分类,并且具有不同的私有性。
这被称为全局差分隐私,因为我们仅在模型训练后才添加噪声。我们选择添加拉普拉斯噪声,是因为它保证不会泄漏超过epsilon的信息,并且已被广泛使用。
我们定义了一种add_noise()
方法,该方法将预测的标签和epsilon(ε)的值作为输入。我们可以控制使用epsilon添加的噪声量。
# Get private labels with the most votes count and add noise them
def add_noise(predicted_labels, epsilon=0.1):noisy_labels = []for preds in predicted_labels:# get labels with max voteslabel_counts = np.bincount(preds, minlength=2)# add laplacian noise to labelepsilon = epsilonbeta = 1/epsilonfor i in range(len(label_counts)):label_counts[i] += np.random.laplace(0, beta, 1)# after adding noise we get labels with max countsnew_label = np.argmax(label_counts)noisy_labels.append(new_label)#return noisy_labelsreturn np.array(noisy_labels)labels_with_noise = add_noise(predicted_labels, epsilon=0.1) print(labels_with_noise)print(labels_with_noise.shape)
我们的最终标签是
[1 1 0 0 0 0 0 0 0 1 0 1 0 0 1 1 0 1 1 0 0 1 1 0 1 1 0 0 1 1 0 0 0 1 0 1 10 0 0 1 1 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 1 0 0 0 0 1 0 0 0 1 1 0 0 0 1 01 0 0 0 0 0 0 0 1 0 0 1 1 0 0 0 0 0 1 1 1 0 0 0 0 0 0 1 0 1 0 1 1 0 1 0 00 0 0 0 1 1 1 0 0 0 0 1 0 0 0 0 0 0 1 1 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 1 10 0 0 0 0 0 0 1 0 1 1 0 0 1 1](163,)
我们可以将这些标签保存到文件中,然后丢弃教师模型。
#write to csv file
import csv
def write_csv(data):with open('labels.csv', 'a') as outfile:writer = csv.writer(outfile)writer.writerow(data)write_csv(labels_with_noise)
步骤6:执行PATE分析
请记住,我们获得的标签来自私人信息,因此,这些新标签中可能包含一些信息泄漏。泄漏的信息量在很大程度上取决于添加的噪声量,该噪声量由epsilon确定。因此,选择正确的epsilon值非常重要,PATE可用于执行epsilon分析。它可以帮助我们回答以下问题:“如果要发布这些标签,多少信息会通过这些标签泄漏?” 。
在PySyft中,我们可以使用perform_analysis
从所有教师获取的预测标签列表以及刚刚计算出的新的噪音标签作为输入并返回2个值的方法。的数据相关的ε-和数据独立小量。perform_analysis方法的主要目标是向我们展示教师之间的一致程度。
与数据无关的epsilon显示最坏情况下可能泄漏的最大信息量,而与数据无关的epsilon向我们展示教师之间的一致程度。依赖于数据的小数据表明,教师模型具有很高的一致性,并且该模型没有记住私人信息(过拟合),而是根据私人数据进行了概括。因此,依赖于数据的低ε表明隐私泄漏率低。perform_analysis()方法的另一件很酷的事情是,当我们使用出乎意料的小/大epsilon时,它会向我们发出警告。
在尝试了不同的epsilon值和noise_eps
变量后,我们决定使用epsilon值0.1,我们得到与数据相关的epsilon 15.536462732485106和与数据无关的epsilon 1536462732485116。您可以通过运行单元格来查看此内容:
# Performing PATE analysis
data_dep_eps, data_ind_eps = pate.perform_analysis(teacher_preds=predicted_labels.T, indices=labels_with_noise_exp, noise_eps=0.1, delta=1e-5)
print('Data dependent epsilon:', data_dep_eps)
print('Data independent epsilon:', data_ind_eps)
步骤7:训练学生数据集
现在我们有了来自教师的嘈杂标签,我们可以继续训练我们的医院数据(即学生)。在训练模型之前,我们必须用教师的新标签替换旧的学生数据加载器,该加载器包含我们下载的数据集中的原始标签。请记住,在现实生活中,我们没有原始标签。
# We have to create a new training dataloader for the student with the newly created
# labels with noise. We have to replace the old labels with the new labels
def new_student_data_loader(dataloader, noisy_labels, batch_size=32):image_list = []for image,_ in dataloader:image_list.append(image)data = np.vstack(image_list)new_dataset = list(zip(data, noisy_labels))new_dataloader = DataLoader(new_dataset, batch_size, shuffle=False)return new_dataloaderlabeled_student_trainloader = new_student_data_loader(student_train_loader, labels_with_noise)
len(labeled_student_trainloader),len(student_valid_loader)
接下来,我们训练学生模型。我们使用新标记的火车装载机进行训练,并使用有效装载机的数据集评估模型的性能。为了简单起见,我们使用与训练教师相同的CNN模型和超参数。但是,在这种情况下,我们将保存学生模型,因为这是将要部署的模型。
student_model = train(epochs, labeled_student_trainloader, student_valid_loader, model, optimizer, criterion, True, save_path='./models/student.pth.tar', is_not_teacher=True)
训练普通深度学习模型
为了进行比较,我们使用原始标签训练了正常模型。此模型不会以任何方式实现隐私。
# Normal DL Training
normal_model = train(epochs, student_train_loader, student_valid_loader, model, optimizer, criterion, True, save_path='./models/normal.pth.tar', is_not_teacher=True)
普通DL模型与隐私保护模型的比较
现在,我们在测试数据集上比较了隐私保护学生模型和常规深度学习模型的性能。这两个模型以前从未在测试集中看到过数据,并且都使用相同的模型和超参数进行了训练。
# Create a dataloader for the test Dataset
batch_size=16
print(len(validset))
dataloader = DataLoader(validset, batch_size=batchsize, shuffle=False)
# We set a seed for the dataset to prevent it from producing different values every time it is run
seed = 3
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = Falsedef test(dataloader, model, criterion, use_cuda):# monitor test loss and accuracytest_loss = 0.correct = 0.total = 0.model.eval()for batch_idx, (data, target) in enumerate(dataloader):# move to GPUif use_cuda:data, target = data.cuda(), target.cuda()# forward pass: compute predicted outputs by passing inputs to the modeloutput = model(data)# calculate the lossloss = criterion(output, target)# update average test losstest_loss = test_loss + ((1 / (batch_idx + 1)) * (loss.data - test_loss))# convert output probabilities to predicted classpred = output.data.max(1, keepdim=True)[1]# compare predictions to true labelcorrect += np.sum(np.squeeze(pred.eq(target.data.view_as(pred))).cpu().numpy())total += data.size(0)print('\tTest Loss: {:.6f}'.format(test_loss))print('\tTest Accuracy: %2d%% (%2d/%2d)' % (100. * correct / total, correct, total))# call test function
print("Student Model")
test(dataloader, student_model, criterion, True)print("\n=======================\nNormal Model")
test(dataloader, normal_model, criterion, True)
这是我们的最终结果:
我们将模型从65%的准确性提高到61%的准确性,但是在不牺牲隐私的情况下挽救了许多生命。您不认为这值得牺牲吗?我做。
参考文献
- Dwork,C.,McSherry,F.,Nissim,K。,和Smith,A。(2006年3月)。在专用数据分析中校准噪声灵敏度。在密码学理论会议上(第265-284页)。施普林格,柏林,海德堡。
- Nicolas Sartor(2019年5月)。从3个难度级别解释差分隐私
- Nicolas Papernot等人(2017)从私人训练数据进行深度学习的半监督知识转移。
- 数据集来源
- Nicolas Papernot和Ian Goodfellow(2018年4月),隐私和机器学习:两个意想不到的盟友?
- 关于Udacity的安全和私人AI课程
本文翻译自Openmined官方博客,链接地址https://blog.openmined.org/maintaining-privacy-in-medical-data-with-differential-privacy/
- 这篇文章的作者:IVOLINE NGONG