Part 10: এনক্রিপ্টেড গ্রেডিয়েন্ট এগ্রিগেশন এর সাথে ফেডারেটেড লার্নিং (Federated Learning with Encrypted Gradient Aggregation)

গত কয়েকটি বিভাগে(sections), আমরা কয়েকটি সহজ প্রোগ্রাম তৈরি করে এনক্রিপ্ট করা গণনা সম্পর্কে শিখছি। এই বিভাগে, আমরা ফিরে যাব Federated Learning Demo of Part 4, যেখানে আমাদের একজন "বিশ্বস্ত এগ্রিগেটর/trusted aggregator" ছিলেন যিনি একাধিক কর্মী থেকে মডেল আপডেটগুলি গড় করার জন্য দায়বদ্ধ ছিলেন।

আমরা এখন এই বিশ্বস্ত সংগ্রহকারীকে সরানোর জন্য এনক্রিপ্ট করা গণনার জন্য আমাদের নতুন সরঞ্জামগুলি ব্যবহার করব কারণ এটি ঠিক আদর্শ পন্থা নয়। এটি ধরে নেয় যে আমরা এই সংবেদনশীল তথ্যে অ্যাক্সেস পাওয়ার জন্য যথেষ্ট বিশ্বাসযোগ্য কাউকে খুঁজে পেতে পারি। যা সবসময় সঠিক নয়।

সুতরাং, এই নোটবুকে, আমরা দেখাব যে কীভাবে কেউ সুরক্ষাগ্রহীতা সম্পাদনের জন্য এসএমপিসি(SMPC) ব্যবহার করতে পারে, যেন আমাদের কোনও "বিশ্বস্ত এগ্রিগ্রেটর" প্রয়োজন হয় না।

Authors:

অনুবাদক:

Section 1: সাধারণ ফেডারেটেড লার্নিং (Normal Federated Learning)

প্রথমত, এখানে এমন কিছু কোড রয়েছে যা বোস্টন হাউজিং ডেটাসেটে(Boston Housing Dataset) ক্লাসিক ফেডারেটেড লার্নিং প্রয়োগ করে। কোডের এই বিভাগটি কয়েকটি বিভাগে বিভক্ত।

প্রতিষ্ঠাপন (Setting Up)


In [0]:
import pickle

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import TensorDataset, DataLoader

class Parser:
    """Parameters for training"""
    def __init__(self):
        self.epochs = 10
        self.lr = 0.001
        self.test_batch_size = 8
        self.batch_size = 8
        self.log_interval = 10
        self.seed = 1
    
args = Parser()

torch.manual_seed(args.seed)
kwargs = {}

ডেটাসেট লোড হচ্ছে (Loading the Dataset)


In [0]:
with open('../data/BostonHousing/boston_housing.pickle','rb') as f:
    ((X, y), (X_test, y_test)) = pickle.load(f)

X = torch.from_numpy(X).float()
y = torch.from_numpy(y).float()
X_test = torch.from_numpy(X_test).float()
y_test = torch.from_numpy(y_test).float()
# preprocessing
mean = X.mean(0, keepdim=True)
dev = X.std(0, keepdim=True)
mean[:, 3] = 0. # the feature at column 3 is binary,
dev[:, 3] = 1.  # so we don't standardize it
X = (X - mean) / dev
X_test = (X_test - mean) / dev
train = TensorDataset(X, y)
test = TensorDataset(X_test, y_test)
train_loader = DataLoader(train, batch_size=args.batch_size, shuffle=True, **kwargs)
test_loader = DataLoader(test, batch_size=args.test_batch_size, shuffle=True, **kwargs)

নিউরাল নেটওয়ার্ক স্ট্রাকচার (Neural Network Structure)


In [0]:
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(13, 32)
        self.fc2 = nn.Linear(32, 24)
        self.fc3 = nn.Linear(24, 1)

    def forward(self, x):
        x = x.view(-1, 13)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

model = Net()
optimizer = optim.SGD(model.parameters(), lr=args.lr)

হুকিং পাইটর্চ (Hooking PyTorch)


In [0]:
import syft as sy

hook = sy.TorchHook(torch)
bob = sy.VirtualWorker(hook, id="bob")
alice = sy.VirtualWorker(hook, id="alice")
james = sy.VirtualWorker(hook, id="james")

compute_nodes = [bob, alice]

কর্মীদের ডেটা প্রেরণ করুন (Send data to the workers)
সাধারণত তাদের কাছে এটি ইতিমধ্যে থাকত, কেবলমাত্র ডেমো উদ্দেশ্যে আমরা ম্যানুয়ালি এটি প্রেরণ করছি


In [0]:
train_distributed_dataset = []

for batch_idx, (data,target) in enumerate(train_loader):
    data = data.send(compute_nodes[batch_idx % len(compute_nodes)])
    target = target.send(compute_nodes[batch_idx % len(compute_nodes)])
    train_distributed_dataset.append((data, target))

প্রশিক্ষণ ফাংশন (Training Function)


In [0]:
def train(epoch):
    model.train()
    for batch_idx, (data,target) in enumerate(train_distributed_dataset):
        worker = data.location
        model.send(worker)

        optimizer.zero_grad()
        # update the model
        pred = model(data)
        loss = F.mse_loss(pred.view(-1), target)
        loss.backward()
        optimizer.step()
        model.get()
            
        if batch_idx % args.log_interval == 0:
            loss = loss.get()
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * data.shape[0], len(train_loader),
                       100. * batch_idx / len(train_loader), loss.item()))

টেস্টিং ফাংশন (Testing Function)


In [0]:
def test():
    model.eval()
    test_loss = 0
    for data, target in test_loader:
        output = model(data)
        test_loss += F.mse_loss(output.view(-1), target, reduction='sum').item() # sum up batch loss
        pred = output.data.max(1, keepdim=True)[1] # get the index of the max log-probability
        
    test_loss /= len(test_loader.dataset)
    print('\nTest set: Average loss: {:.4f}\n'.format(test_loss))

মডেল প্রশিক্ষণ (Training the Model)


In [0]:
import time

In [0]:
t = time.time()

for epoch in range(1, args.epochs + 1):
    train(epoch)

    
total_time = time.time() - t
print('Total', round(total_time, 2), 's')

পারফরম্যান্স গণনা করা হচ্ছে (Calculating Performance)


In [0]:
test()

Section 2: এনক্রিপ্টেড এগ্রিগেশন যোগ করা

এখন আমরা এনক্রিপশন ব্যবহার করে গ্রেডিয়েন্টগুলি এগ্রিগেট করার জন্য এই উদাহরণটি সামান্য পরিবর্তন করতে চলেছি। মূল যে পরিবর্তনটি আসছে তা আসলে কোডের train() ফাংশনে ১ বা ২ লাইন , যা আমরা নির্দেশ করব। এই মুহুর্তের জন্য, আসুন আমরা আমাদের ডেটা পুনরায় প্রক্রিয়া করি এবং bob ও alice-এর জন্য একটি মডেল ইনিশিয়ালাইজ করি।


In [0]:
remote_dataset = (list(),list())

train_distributed_dataset = []

for batch_idx, (data,target) in enumerate(train_loader):
    data = data.send(compute_nodes[batch_idx % len(compute_nodes)])
    target = target.send(compute_nodes[batch_idx % len(compute_nodes)])
    remote_dataset[batch_idx % len(compute_nodes)].append((data, target))

def update(data, target, model, optimizer):
    model.send(data.location)
    optimizer.zero_grad()
    pred = model(data)
    loss = F.mse_loss(pred.view(-1), target)
    loss.backward()
    optimizer.step()
    return model

bobs_model = Net()
alices_model = Net()

bobs_optimizer = optim.SGD(bobs_model.parameters(), lr=args.lr)
alices_optimizer = optim.SGD(alices_model.parameters(), lr=args.lr)

models = [bobs_model, alices_model]
params = [list(bobs_model.parameters()), list(alices_model.parameters())]
optimizers = [bobs_optimizer, alices_optimizer]

আমাদের প্রশিক্ষণ যুক্তি তৈরি করি (Building our Training Logic)

একমাত্র আসল পার্থক্য এই ট্রেন পদ্ধতির অভ্যন্তরে। আসুন ধাপে ধাপে তা দেখে নিই

Part A: শিখান ( Train)


In [0]:
# this is selecting which batch to train on
data_index = 0
# update remote models
# we could iterate this multiple times before proceeding, but we're only iterating once per worker here
for remote_index in range(len(compute_nodes)):
    data, target = remote_dataset[remote_index][data_index]
    models[remote_index] = update(data, target, models[remote_index], optimizers[remote_index])

Part B: এঙ্ক্রিপ্টেড এগ্রিগেশন (Encrypted Aggregation)


In [0]:
# create a list where we'll deposit our encrypted model average
new_params = list()

In [0]:
# iterate through each parameter
for param_i in range(len(params[0])):

    # for each worker
    spdz_params = list()
    for remote_index in range(len(compute_nodes)):
        
        # select the identical parameter from each worker and copy it
        copy_of_parameter = params[remote_index][param_i].copy()
        
        # since SMPC can only work with integers (not floats), we need
        # to use Integers to store decimal information. In other words,
        # we need to use "Fixed Precision" encoding.
        fixed_precision_param = copy_of_parameter.fix_precision()
        
        # now we encrypt it on the remote machine. Note that 
        # fixed_precision_param is ALREADY a pointer. Thus, when
        # we call share, it actually encrypts the data that the
        # data is pointing TO. This returns a POINTER to the 
        # MPC secret shared object, which we need to fetch.
        encrypted_param = fixed_precision_param.share(bob, alice, crypto_provider=james)
        
        # now we fetch the pointer to the MPC shared value
        param = encrypted_param.get()
        
        # save the parameter so we can average it with the same parameter
        # from the other workers
        spdz_params.append(param)

    # average params from multiple workers, fetch them to the local machine
    # decrypt and decode (from fixed precision) back into a floating point number
    new_param = (spdz_params[0] + spdz_params[1]).get().float_precision()/2
    
    # save the new averaged parameter
    new_params.append(new_param)

Part C: পরিষ্করণ (Cleanup)


In [0]:
with torch.no_grad():
    for model in params:
        for param in model:
            param *= 0

    for model in models:
        model.get()

    for remote_index in range(len(compute_nodes)):
        for param_index in range(len(params[remote_index])):
            params[remote_index][param_index].set_(new_params[param_index])

সব একসাথে করা যাক (Let's put it all Together!!)

এবং এখন যেহেতু আমরা প্রতিটি পদক্ষেপ জানি, আমরা এগুলি একসাথে একটি প্রশিক্ষণ লুপে(loop) রাখতে পারি!


In [0]:
def train(epoch):
    for data_index in range(len(remote_dataset[0])-1):
        # update remote models
        for remote_index in range(len(compute_nodes)):
            data, target = remote_dataset[remote_index][data_index]
            models[remote_index] = update(data, target, models[remote_index], optimizers[remote_index])

        # encrypted aggregation
        new_params = list()
        for param_i in range(len(params[0])):
            spdz_params = list()
            for remote_index in range(len(compute_nodes)):
                spdz_params.append(params[remote_index][param_i].copy().fix_precision().share(bob, alice, crypto_provider=james).get())

            new_param = (spdz_params[0] + spdz_params[1]).get().float_precision()/2
            new_params.append(new_param)

        # cleanup
        with torch.no_grad():
            for model in params:
                for param in model:
                    param *= 0

            for model in models:
                model.get()

            for remote_index in range(len(compute_nodes)):
                for param_index in range(len(params[remote_index])):
                    params[remote_index][param_index].set_(new_params[param_index])

In [0]:
def test():
    models[0].eval()
    test_loss = 0
    for data, target in test_loader:
        output = models[0](data)
        test_loss += F.mse_loss(output.view(-1), target, reduction='sum').item() # sum up batch loss
        pred = output.data.max(1, keepdim=True)[1] # get the index of the max log-probability
        
    test_loss /= len(test_loader.dataset)
    print('Test set: Average loss: {:.4f}\n'.format(test_loss))

In [0]:
t = time.time()

for epoch in range(args.epochs):
    print(f"Epoch {epoch + 1}")
    train(epoch)
    test()

    
total_time = time.time() - t
print('Total', round(total_time, 2), 's')

অভিনন্দন সম্প্রদায় যোগদানের সময়! (Congratulations!!! - Time to Join the Community!)

এই নোটবুক টিউটোরিয়ালটি সম্পন্ন করার জন্য অভিনন্দন! আপনি যদি এটি উপভোগ করেন এবং গোপনীয়তা সংরক্ষণ, AI এবং AI সরবরাহ চেইনের (ডেটা) বিকেন্দ্রীভূত মালিকানার দিকে আন্দোলনে যোগ দিতে চান, আপনি নিম্নলিখিত উপায়ে এটি করতে পারেন!

গিটহাবে পাইসিফ্ট কে স্টার দিন (Star PySyft on GitHub)

আমাদের সম্প্রদায়কে সাহায্য করার সবচেয়ে সহজ উপায় হল রিপোসিটোরি গুলোতে ষ্টার করা এটি আমরা যে অসাধারণ সরঞ্জামগুলি তৈরি করছি তার সচেতনতা বাড়াতে সহায়তা করে।

আমাদের স্ল্যাকে যোগ দিন (Join our Slack!)

সর্বশেষতম অগ্রগতিতে আপ টু ডেট রাখার সর্বোত্তম উপায় হ'ল আমাদের সম্প্রদায়ে যোগদান করা! আপনি ফর্মটি পূরণ করে এটি করতে পারেন http://slack.openmined.org

একটি কোড প্রকল্পে যোগদান করুন! (Join a Code Project!)

আমাদের সম্প্রদায়ে অবদান রাখার সর্বোত্তম উপায় হল কোড অবদানকারী হয়ে উঠুন! যে কোনও সময় আপনি পাইসাইফ্ট গিটহাবে ইস্যু পৃষ্ঠাতে যেতে পারেন এবং "প্রকল্পগুলি" এর জন্য ফিল্টার করতে পারেন। এটি আপনাকে শীর্ষ স্তরের সমস্ত টিকিট দেখিয়ে দেবে যে আপনি কোন প্রকল্পগুলিতে যোগদান করতে পারেন তার একটি ওভারভিউ দেয়! আপনি যদি কোনও প্রকল্পে যোগ দিতে না চান তবে আপনি কিছুটা কোডিং করতে চান তবে আপনি "ভাল প্রথম ইস্যু" চিহ্নিত গিটহাবে ইস্যুগুলি অনুসন্ধান করে আরও "ওয়ান অফ" মিনি-প্রকল্পগুলির সন্ধান করতে পারেন।

দান করুন (Donate)

আপনার যদি আমাদের কোডবেসে অবদান রাখার সময় না থাকে তবে তবুও সমর্থন দিতে চান, আপনি আমাদের ওপেন কালেক্টিভেরও Backer হয়ে উঠতে পারেন। সমস্ত অনুদান আমাদের ওয়েব হোস্টিং এবং অন্যান্য সম্প্রদায় ব্যয় যেমন হ্যাকাথনস এবং মেটআপগুলির (hackathons and meetups!) দিকে যায়!


In [0]: