<jupyter_start><jupyter_text>***This is a simple implement of basic 1st attack introduced in ML-leak article on CIFAR10***  ***General setting code***  ***Install all independency here***<jupyter_code>!pip3 install torch==1.10.2+cpu torchvision==0.11.3+cpu torchaudio==0.10.2+cpu -f https://download.pytorch.org/whl/cpu/torch_stable.html
!pip3 install ipywidgets
!jupyter nbextension enable --py widgetsnbextension<jupyter_output>Looking in links: https://download.pytorch.org/whl/cpu/torch_stable.html
Collecting torch==1.10.2+cpu
  Downloading https://download.pytorch.org/whl/cpu/torch-1.10.2%2Bcpu-cp37-cp37m-linux_x86_64.whl (199.3 MB)
[K     |████████████████████████████████| 199.3 MB 2.1 MB/s 
[?25hCollecting torchvision==0.11.3+cpu
  Downloading https://download.pytorch.org/whl/cpu/torchvision-0.11.3%2Bcpu-cp37-cp37m-linux_x86_64.whl (16.1 MB)
[K     |████████████████████████████████| 16.1 MB 85.6 MB/s 
[?25hCollecting torchaudio==0.10.2+cpu
  Downloading https://download.pytorch.org/whl/cpu/torchaudio-0.10.2%2Bcpu-cp37-cp37m-linux_x86_64.whl (2.7 MB)
[K     |████████████████████████████████| 2.7 MB 8.9 MB/s 
[?25hRequirement already satisfied: typing-extensions in /usr/local/lib/python3.7/dist-packages (from torch==1.10.2+cpu) (3.10.0.2)
Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from torchvision==0.11.3+cpu) (1.21.5)
Requirement already satisfied: pillow!=8.3.0,>[...]<jupyter_text>***Import all dependency here***<jupyter_code># to monitor the progress
from tqdm import tqdm
import time
# basic dependency
import numpy as np
import random
# pytorch related
import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.data import random_split
from torch import optim
import torch.nn.functional as F
import torchvision.transforms as T
# for visulization
import matplotlib.pyplot as plt
from random import shuffle
# sklearn
from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score
from sklearn.metrics import precision_recall_fscore_support, accuracy_score
# other classifier
import lightgbm as lgb
# for mount from Colab to my drive
from google.colab import drive
drive.mount('/content/drive')
import os
os.chdir('/content/drive/My Drive')<jupyter_output>Mounted at /content/drive<jupyter_text>***Target and shadow model code***  ***Define a Neural Network class for shadow and target model***<jupyter_code># reuse the NN in badnet
class basicNet(nn.Module):

    def __init__(self,inputchannels,outputclasses):
        super().__init__()
        self.conv1 = nn.Conv2d(inputchannels, 16, 5)
        self.conv2 = nn.Conv2d(16, 32, 5)
        self.pool = nn.AvgPool2d(2)
        if inputchannels == 3:
          inputfeatures = 800
        else:
          inputfeatures = 512
        self.fc1 = nn.Linear(inputfeatures, 512)
        self.fc2 = nn.Linear(512, outputclasses)

    def forward(self, x):
        # conv block1
        x = self.conv1(x)
        x = F.relu(x)
        x = self.pool(x)
        # conv block2
        x = self.conv2(x)
        x = F.relu(x)
        x = self.pool(x)
        # reshape(flat) the feature to be the input of full connect layer
        x = x.view(-1, self.num_features(x))
        # fc block1
        x = self.fc1(x)
        x = F.relu(x)
        # fc block2
        x = self.fc2(x)
        x = F.softmax(x,dim=-1)
        return x

    def num_features(self, x):
        # size of different dimensions
        size_D = x.size()[1:]
        total = 1
        for i in size_D:
            total = total*i
        return total<jupyter_output><empty_output><jupyter_text>***Functions for training and evaluating(target/shadow model)***<jupyter_code>def train(model, dataloader, criterion, opt):
    running_loss = 0
    # switch to model:train
    # no difference here since no dropout and BN
    model.train()
    count = 0
    for i, data in tqdm(enumerate(dataloader)):
        opt.zero_grad()
        imgs, labels = data
        imgs = imgs.to(device)
        labels = labels.to(device)
        predict = model(imgs)
        #print(predict.dtype)
        #print(labels.dtype)
        loss = criterion(predict, labels)
        loss.backward()
        opt.step()
        count = i
        running_loss += loss
    return running_loss / count


def evaluation(model, dataloader, batch_size=64):
    # switch to model:eval
    # no difference here since no dropout and BN    
    model.eval()
    # y_tensorlist is a list consists of some tensors
    y_true_tensorlist = []
    y_predict_tensorlist = []
    for step, (batch_x, batch_y) in enumerate(dataloader):
        batch_x,batch_y = batch_x.to(device),batch_y.to(device)
        output = model(batch_x)
        #print(output.shape)
        batch_y_predict = torch.argmax(output, dim=1)
        
        y_predict_tensorlist.append(batch_y_predict)
        
        y_true_tensorlist.append(batch_y)
        
    # combine the tensors in the list into one
    y_true = torch.cat(y_true_tensorlist,0)
    y_predict = torch.cat(y_predict_tensorlist,0)

    # compute accuracy
    length = len(y_true)
    right_length = torch.sum(y_true == y_predict)
    #print(right_length/length)
    
    return right_length/length<jupyter_output><empty_output><jupyter_text>***Attack model's dataset code***  ***Functions used to create attack model's dataset***<jupyter_code>def attackdataset_x_1_batch(shadow,batch_x,device):
    shadow.eval()
    # Aim to get features(x)
    # output of model shadow is probabilities
    batch_y = shadow(batch_x.to(device))
    sorted_prob,_ = torch.sort(batch_y,descending=True)
    #print(sorted_prob.shape)

    # choose 3 highest probabilities as feature vector
    # This is a batch's output, not single image.
    # feature: x, x is already a tensor
    feature_vectors = torch.narrow(sorted_prob,dim=1,start=0,length=3)

    return feature_vectors

def get_attackdataset(shadow,train_loader,out_loader,device):
    shadow.eval()
    # 1. get a tensor list of x
    # 2. combine all tensors in tensor list x into a tensor X
    # 3. generate label tensor Y, the length is the same as X
    attack_set=[]

    attack_X_train_list = []
    for i, (batch_x, batch_y) in enumerate(train_loader):
        batch_x = batch_x.to(device)
        batch_y = batch_y.to(device)
        feature_vectors = attackdataset_x_1_batch(shadow,batch_x,device=device)
        attack_X_train_list.append(feature_vectors)
    attack_X_train = torch.cat(attack_X_train_list,0)
    for i in attack_X_train:
      attack_set.append((i.detach(),torch.ones(1)))

    # train:label = 1
    #attack_Y_train = torch.ones(attack_X_train.shape[0])
    #print('attack Y train shape: ',attack_Y_train.shape)

    attack_X_out_list = []
    for i, (batch_x, batch_y) in enumerate(out_loader):
        feature_vectors = attackdataset_x_1_batch(shadow,batch_x,device=device)
        attack_X_out_list.append(feature_vectors)
    attack_X_out = torch.cat(attack_X_out_list,0)
    for i in attack_X_out:
      attack_set.append((i.detach(),torch.zeros(1)))
    # out: label = 0
    #attack_Y_out = torch.zeros(attack_X_out.shape[0])
    #print('attack Y out shape: ',attack_Y_out.shape)

    # combine train and out to get the whole dataset's X and Y
    #attack_X = torch.cat([attack_X_train,attack_X_out],0)
    #attack_Y = torch.cat([attack_Y_train,attack_Y_out],0)
    #print('attack X total shape: ',attack_X.shape)
    #return attack_X.detach(),attack_Y
    shuffle(attack_set)
    print(attack_set[0])
    return attack_set<jupyter_output><empty_output><jupyter_text>***Define attack model's dataset class***<jupyter_code>class attack_dataset(Dataset):
    # N data points, features include N top 3 probabilities, labels include N labels(1/0)
    # elements in features and labels are already tensor.
    def __init__(self, attack_set, transform = None, device=torch.device("cpu")):
        self.attack_set = attack_set
        self.device = device
        self.transform = transform

    def __getitem__(self, item):
        # extract x,y , x,y are all tensors
        x = self.attack_set[item][0]
        y = self.attack_set[item][1]
        # send x,y to device
        x = x.to(self.device)
        y = y.to(self.device)
        
        return x, y

    def __len__(self):
        return len(self.attack_set)<jupyter_output><empty_output><jupyter_text>***Main part***  ***CIFAR10***  *Download, split and load data*<jupyter_code># prepare original data
transforms = T.Compose([T.ToTensor(),T.Normalize((0.4914, 0.4822, 0.4465), (0.247, 0.243, 0.261))])
train_data_CIFAR10 = datasets.CIFAR10(root="./data_1/", train=True,transform=transforms, download=True)
test_data_CIFAR10 = datasets.CIFAR10(root="./data_1/",train=False,transform=transforms, download=True)
# set the length of each split
length_train_data_CIFAR10 = len(train_data_CIFAR10)
lengthlist = [int(0.25*length_train_data_CIFAR10),int(0.25*length_train_data_CIFAR10),int(0.25*length_train_data_CIFAR10),int(0.25*length_train_data_CIFAR10)]
print(length_train_data_CIFAR10)
print(lengthlist)
# CIFAR10
# split the dataset into 4 part: target train/out, shadow train/out
train_target_dataset_CIFAR10,out_target_dataset_CIFAR10,train_shadow_dataset_CIFAR10,out_shadow_dataset_CIFAR10 = random_split(
    dataset=train_data_CIFAR10,
    lengths=lengthlist,
    generator=torch.Generator().manual_seed(19260817)
)
# This is the main dataloader with the total dataset
target_train_loader = DataLoader(dataset=train_target_dataset_CIFAR10, batch_size=64, shuffle = True)
target_out_loader = DataLoader(dataset=out_target_dataset_CIFAR10, batch_size=64, shuffle = True)

shadow_train_loader = DataLoader(dataset=train_shadow_dataset_CIFAR10, batch_size=64, shuffle = True)
shadow_out_loader = DataLoader(dataset=out_shadow_dataset_CIFAR10, batch_size=64, shuffle = True)

testloader = DataLoader(dataset=test_data_CIFAR10, batch_size=64, shuffle = True)<jupyter_output><empty_output><jupyter_text>*Pretrain target and shadow models*<jupyter_code># use train_target to train target model
# no gpu, thus device is only cpu
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Train target
# load model
# if use MNIST, inputchannels=1, if use CIFAR10, inputchannels=3, both output classes = 10
# related info could be get by using input_channels=train_data_loader.dataset.channels, output_num=train_data_loader.dataset.class_num
target = basicNet(inputchannels=3, outputclasses=10).to(device)
# settings
criterion = nn.CrossEntropyLoss()
sgd = optim.SGD(target.parameters(), lr=0.01, momentum=0.9)
epoch = 40

# train
print("start training: ")
for i in range(epoch):
    loss_train = train(target, target_train_loader, criterion, sgd)
# Acc
acc_train = evaluation(target, target_train_loader, batch_size=64)
acc_test = evaluation(target, testloader, batch_size=64)
print("epoch%d   loss: %.5f  training accuracy: %.5f  testing accuracy: %.5f"\
      % (epoch, loss_train, acc_train, acc_test))
torch.save(target.state_dict(), "./models/target_CIFAR10.pth")
# use train_shadow to train shadow model
# no gpu, thus device is only cpu
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Train shadow
# load model
# if use MNIST, inputchannels=1, if use CIFAR10, inputchannels=3, both output classes = 10
# related info could be get by using input_channels=train_data_loader.dataset.channels, output_num=train_data_loader.dataset.class_num
shadow = basicNet(inputchannels=3, outputclasses=10).to(device)
# settings
criterion = nn.CrossEntropyLoss()
sgd = optim.SGD(shadow.parameters(), lr=0.01, momentum=0.9)
epoch = 40

# train
print("start training: ")
for i in range(epoch):
    loss_train = train(shadow, shadow_train_loader, criterion, sgd)
# Acc
acc_train = evaluation(shadow, shadow_train_loader, batch_size=64)
acc_test = evaluation(shadow, testloader, batch_size=64)
print("epoch%d   loss: %.5f  training accuracy: %.5f  testing accuracy: %.5f"\
      % (epoch, loss_train, acc_train, acc_test))
torch.save(shadow.state_dict(), "./models/shadow_CIFAR10.pth")<jupyter_output>start training:<jupyter_text>*Load pretrained target and shadow models (to save time)*<jupyter_code># no gpu, thus device is only cpu
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# load pretrained model
target_pre = basicNet(inputchannels=1, outputclasses=10).to(device)
# if gpu, remove map_location=torch.device('cpu')
target_pre.load_state_dict(torch.load("./models/target_CIFAR10.pth",map_location=torch.device('cpu')))
target_pre.eval()
shadow_pre = basicNet(inputchannels=1, outputclasses=10).to(device)
# if gpu, remove map_location=torch.device('cpu')
shadow_pre.load_state_dict(torch.load("./models/shadow_CIFAR10.pth",map_location=torch.device('cpu')))
shadow_pre.eval()
# test if these models are loaded
acc_test1 = evaluation(target_pre, testloader, batch_size=64)
print(acc_test1)
acc_test2 = evaluation(shadow_pre, testloader, batch_size=4)
print(acc_test2)
shadow = shadow_pre
target = target_pre<jupyter_output><empty_output><jupyter_text>*Create dataset for attack model*<jupyter_code># 1. create train dataset for attack model
# use shadow model and dataset: out_shadow, train_shadow
print('Creating training dataset for attack model:')
train_attack_set = get_attackdataset(shadow,shadow_train_loader,shadow_out_loader,device=device)
train_attack = attack_dataset(attack_set=train_attack_set,device=device)
print('- - - - - - - - - - - - - - - - - - ')
print('Creating test dataset for attack model:')
# 2. create test dataset for attack model
# use target model and dataset: out_target, train_target
test_attack_set = get_attackdataset(target,target_train_loader,target_out_loader,device=device)
test_attack = attack_dataset(attack_set=test_attack_set,device=device)<jupyter_output>Creating training dataset for attack model:
(tensor([1.0000e+00, 3.6089e-10, 1.0456e-12]), tensor([0.]))
- - - - - - - - - - - - - - - - - - 
Creating test dataset for attack model:
(tensor([7.0362e-01, 2.9497e-01, 5.9392e-04]), tensor([1.]))<jupyter_text>*Attack model 1*  *Prepare dataset for attack model 1: LGBMClassifier*<jupyter_code>train_feature = []
train_label = []
for i in train_attack_set:
  train_feature.append(i[0].cpu().numpy())
  train_label.append(int(i[1]))

test_feature = []
test_label = []
for i in test_attack_set:
  test_feature.append(i[0].cpu().numpy())
  test_label.append(int(i[1]))<jupyter_output><empty_output><jupyter_text>*Attack model 1: LGBMClassifier and output*<jupyter_code>attack_model = lgb.LGBMClassifier(objective='binary', reg_lambda=10, n_estimators=10000)
attack_model.fit(train_feature, train_label)
predict = attack_model.predict(test_feature)
precision_general, recall_general, _, _ = precision_recall_fscore_support(y_pred=predict,y_true=test_label,average="binary")
print('End fitting and predicting')
print('- - - - - - - - - - - - - - - ')

print('result')
print('- - - - - - - - - - - - - - - ')
print('precision: {:.5f}'.format(precision_general))
print('recall: {:.5f}'.format(recall_general))<jupyter_output>End fitting and predicting
- - - - - - - - - - - - - - - 
result
- - - - - - - - - - - - - - - 
precision: 0.52849
recall: 0.55576<jupyter_text>*Attack model 2* Result is not good. Need more tuning or trick. Guess one possible way is to do some transforms on feature vectors such as log.  *Define a MLP class for attack model 2*<jupyter_code>class MLP(nn.Module):
        # 3 features, thus input size=3
        # Yes or no, and use sigmoid thus output size=1
        # If use softmax, output size = 2
        # Loss: cross entropy has contained softmax 
    def __init__(self, num_of_features=3):
        super().__init__()
        self.fc1 = nn.Linear(num_of_features, 64)
        self.fc2 = nn.Linear(64, 2)
        #self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        # Hidden layer
        x = self.fc1(x)
        x = self.fc2(x)
        x = F.softmax(x,dim=-1)
        return x<jupyter_output><empty_output><jupyter_text>*Functions for training and evaluating (attack model 2)*<jupyter_code>def attack_train(model, dataloader, criterion, opt):
    running_loss = 0
    # switch to model:train
    # no difference here since no dropout and BN
    model.train()
    count = 0
    for i, data in tqdm(enumerate(dataloader)):
        opt.zero_grad()
        imgs, labels = data
        imgs = imgs.to(device)
        labels = labels.to(device)
        predict = model(imgs)
        #print(predict.dtype)
        #print(labels.dtype)
        labels = torch.flatten(labels.long())
        #print(predict.shape,labels.shape)
        #print(predict)
        #print(labels)
        loss = criterion(predict, labels)
        loss.backward()
        opt.step()
        count = i
        running_loss += loss
    return running_loss / count

def attack_evaluation(model, dataloader, batch_size=64):
    # switch to model:eval
    # no difference here since no dropout and BN    
    model.eval()
    # y_tensorlist is a list consists of some tensors
    y_true_tensorlist = []
    y_predict_tensorlist = []
    for step, (batch_x, batch_y) in enumerate(dataloader):
        batch_x,batch_y = batch_x.to(device),batch_y.to(device)
        batch_y = torch.flatten(batch_y)
        output = model(batch_x)
        #output = F.softmax(output,dim=-1)
        #print(output)
        # compare cols
        batch_y_predict = torch.argmax(output, dim=1)
        #print(output)        
        #zero = torch.zeros_like(output)
        #one = torch.ones_like(output)
        #batch_y_predict = torch.where(output > 0.5, one, zero)
        #print(batch_y_predict)
        #print(batch_y_predict)
        y_predict_tensorlist.append(batch_y_predict)        
        y_true_tensorlist.append(batch_y)
        
    # combine the tensors in the list into one
    y_true = torch.cat(y_true_tensorlist,0)
    y_predict = torch.cat(y_predict_tensorlist,0)

    # compute accuracy
    length = len(y_true)
    right_length = torch.sum(y_true == y_predict)
    #print(right_length/length)
    
    return right_length/length,y_true,y_predict<jupyter_output><empty_output><jupyter_text>*Train attack model 2*<jupyter_code># no gpu, thus device is only cpu
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Train attack
attack = MLP(num_of_features=3).to(device)
# settings
#criterion = nn.BCELoss()
criterion = nn.NLLLoss()
# crossentropy contains softmax, use other loss
#criterion = nn.CrossEntropyLoss()
opt = optim.Adam(attack.parameters(), lr=0.00001)
epoch = 40
train_attack_dataloader = DataLoader(dataset=train_attack, batch_size=64, shuffle = True)
test_attack_dataloader = DataLoader(dataset=test_attack, batch_size=64, shuffle = True)
# train
print("start training: ")
for i in range(epoch):
    loss_train = attack_train(attack, train_attack_dataloader, criterion, opt)

    print("epoch%d   loss: %.5f "\
      % (i+1, loss_train))
torch.save(attack.state_dict(), "./models/attack_MNIST.pth")
_,y_true,y_predict = attack_evaluation(attack, test_attack_dataloader, batch_size=64)
precision_score(y_true.cpu().numpy(), y_predict.cpu().numpy(), average='binary')
recall_score(y_true.cpu().numpy(), y_predict.cpu().numpy(), average='binary')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12215438741721854, 'fraction_numerical': 0.025403559602649006, 'mean_word_length': 4.065251572327044, 'pattern_counts': {'":': 0, '<': 56, '<?xml version=': 0, '>': 58, 'https://': 5, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '141953', 'license': 'MIT', 'n_tokens_mistral': 7003, 'n_tokens_neox': 6609, 'n_words': 1441, 'path': '.ipynb_checkpoints/ML_leak_1st_attack_CIFAR10-checkpoint.ipynb', 'repo_name': 'Junjie-Chu/CISPA_codetest', 'size': '19328'}