Module tiresias.benchmark.helpers

Expand source code
import torch
import numpy as np
from tqdm import tqdm
from sklearn.base import ClassifierMixin, RegressorMixin

import tiresias.core.mechanisms as mechanisms
from tiresias.core.gradients import get_gradients, put_gradients, merge_gradients

def _ldp(x, epsilon, delta, continuous=True):
    if continuous:
        low, high = np.min(x), np.max(x)
        return mechanisms.bounded_continuous(x, low=low, high=high, epsilon=epsilon)
    else:
        return mechanisms.finite_categorical(x, set(x), epsilon=epsilon)

def make_ldp(X, y, epsilon, delta, classification=True):
    num_rows, num_cols = X.shape
    assert X.shape[0] == y.shape[0]

    p = 0.9 # use 90% of budget for X, 10% for Y
    X, y = X.copy(), y.copy()
    for col_idx in range(0, num_cols):
        X[:,col_idx] = _ldp(X[:,col_idx], p * epsilon / X.shape[1], p * delta / X.shape[1])
    y = _ldp(y, (1.0 - p) * epsilon, (1.0 - p) * delta, continuous=not classification)
    return X, y

class FederatedLearningWrapper(object):

    def __init__(self, model, loss, epsilon, delta, epochs, lr, batch_size):
        self.model = model
        self.epsilon = epsilon
        self.delta = delta
        self.epochs = epochs
        self.loss = loss
        self.lr = lr
        self.batch_size = batch_size

    def fit(self, X, Y):
        optimizer = torch.optim.Adam(self.model.parameters(), lr=self.lr)
        epsilon = self.epsilon / self.epochs
        delta = self.delta / self.epochs
        for epoch in tqdm(range(self.epochs)):
            gradients = []
            for i in range(len(X)):
                self.model.zero_grad()
                x = torch.FloatTensor(X[i]).unsqueeze(0)
                y = torch.tensor(Y[i]).unsqueeze(0)
                loss = self.loss(self.model(x), y)
                loss.backward()
                gradients.append(get_gradients(self.model, epsilon, delta))
                if len(gradients) > self.batch_size:
                    put_gradients(self.model, merge_gradients(gradients))
                    torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)
                    optimizer.step()
                    gradients = []

    def predict(self, X):
        Y = []
        for i in range(len(X)):
            x = torch.FloatTensor(X[i]).unsqueeze(0)
            y = self.model(x)[0]
            Y.append(y.detach().numpy())
        return np.stack(Y)

class FederatedLearningClassifier(ClassifierMixin):

    def __init__(self, epsilon, delta, epochs, lr):
        self.epsilon = epsilon
        self.delta = delta
        self.epochs = epochs
        self.lr = lr

    def fit(self, X, y):
        self._class_to_i = {k: i for i, k in enumerate(set(y))}
        self._i_to_class = {v: k for k, v in self._class_to_i.items()}

        self._model = FederatedLearningWrapper(
            model=torch.nn.Sequential(
                torch.nn.Linear(X.shape[1], 16),
                torch.nn.ReLU(inplace=True),
                torch.nn.Linear(16, len(self._class_to_i)),
            ),
            loss=torch.nn.functional.cross_entropy, 
            epsilon=self.epsilon, 
            delta=self.delta,
            epochs=self.epochs, 
            lr=self.lr,
            batch_size=128,
        )
        self._model.fit(X, np.array([self._class_to_i[k] for k in y]))

    def predict(self, X):
        y_pred = self._model.predict(X)
        y_pred = np.argmax(y_pred, axis=1)
        return [self._i_to_class[i] for i in y_pred]

class FederatedLearningRegressor(RegressorMixin):

    def __init__(self, epsilon, delta, epochs, lr):
        self.epsilon = epsilon
        self.delta = delta
        self.epochs = epochs
        self.lr = lr

    def fit(self, X, y):
        self._model = FederatedLearningWrapper(
            model=torch.nn.Sequential(
                torch.nn.Linear(X.shape[1], 16),
                torch.nn.ReLU(inplace=True),
                torch.nn.Linear(16, 1),
            ),
            loss=torch.nn.functional.mse_loss, 
            epsilon=self.epsilon, 
            delta=self.delta,
            epochs=self.epochs, 
            lr=self.lr,
            batch_size=128,
        )
        self._model.fit(X, y)

    def predict(self, X):
        y_pred = self._model.predict(X)
        return y_pred

if __name__ == "__main__":
    from sklearn.datasets import load_boston
    from tiresias.core import machine_learning as ml

    X, y = load_boston(return_X_y=True)

    for epsilon in [100.0]:

        clf = ml.LinearRegression(epsilon=epsilon)
        clf.fit(X, y)
        print("ML (%s): %s" % (epsilon, clf.score(X, y)))

        clf = FederatedLearningRegressor(
            epsilon=epsilon,
            delta=1.0 / len(X),
            epochs=32,
            lr=0.01,
        )
        clf.fit(X, y)
        print("FL (%s): %s" % (epsilon, clf.score(X, y)))

Functions

def make_ldp(X, y, epsilon, delta, classification=True)
Expand source code
def make_ldp(X, y, epsilon, delta, classification=True):
    num_rows, num_cols = X.shape
    assert X.shape[0] == y.shape[0]

    p = 0.9 # use 90% of budget for X, 10% for Y
    X, y = X.copy(), y.copy()
    for col_idx in range(0, num_cols):
        X[:,col_idx] = _ldp(X[:,col_idx], p * epsilon / X.shape[1], p * delta / X.shape[1])
    y = _ldp(y, (1.0 - p) * epsilon, (1.0 - p) * delta, continuous=not classification)
    return X, y

Classes

class FederatedLearningClassifier (epsilon, delta, epochs, lr)

Mixin class for all classifiers in scikit-learn.

Expand source code
class FederatedLearningClassifier(ClassifierMixin):

    def __init__(self, epsilon, delta, epochs, lr):
        self.epsilon = epsilon
        self.delta = delta
        self.epochs = epochs
        self.lr = lr

    def fit(self, X, y):
        self._class_to_i = {k: i for i, k in enumerate(set(y))}
        self._i_to_class = {v: k for k, v in self._class_to_i.items()}

        self._model = FederatedLearningWrapper(
            model=torch.nn.Sequential(
                torch.nn.Linear(X.shape[1], 16),
                torch.nn.ReLU(inplace=True),
                torch.nn.Linear(16, len(self._class_to_i)),
            ),
            loss=torch.nn.functional.cross_entropy, 
            epsilon=self.epsilon, 
            delta=self.delta,
            epochs=self.epochs, 
            lr=self.lr,
            batch_size=128,
        )
        self._model.fit(X, np.array([self._class_to_i[k] for k in y]))

    def predict(self, X):
        y_pred = self._model.predict(X)
        y_pred = np.argmax(y_pred, axis=1)
        return [self._i_to_class[i] for i in y_pred]

Ancestors

  • sklearn.base.ClassifierMixin

Methods

def fit(self, X, y)
Expand source code
def fit(self, X, y):
    self._class_to_i = {k: i for i, k in enumerate(set(y))}
    self._i_to_class = {v: k for k, v in self._class_to_i.items()}

    self._model = FederatedLearningWrapper(
        model=torch.nn.Sequential(
            torch.nn.Linear(X.shape[1], 16),
            torch.nn.ReLU(inplace=True),
            torch.nn.Linear(16, len(self._class_to_i)),
        ),
        loss=torch.nn.functional.cross_entropy, 
        epsilon=self.epsilon, 
        delta=self.delta,
        epochs=self.epochs, 
        lr=self.lr,
        batch_size=128,
    )
    self._model.fit(X, np.array([self._class_to_i[k] for k in y]))
def predict(self, X)
Expand source code
def predict(self, X):
    y_pred = self._model.predict(X)
    y_pred = np.argmax(y_pred, axis=1)
    return [self._i_to_class[i] for i in y_pred]
class FederatedLearningRegressor (epsilon, delta, epochs, lr)

Mixin class for all regression estimators in scikit-learn.

Expand source code
class FederatedLearningRegressor(RegressorMixin):

    def __init__(self, epsilon, delta, epochs, lr):
        self.epsilon = epsilon
        self.delta = delta
        self.epochs = epochs
        self.lr = lr

    def fit(self, X, y):
        self._model = FederatedLearningWrapper(
            model=torch.nn.Sequential(
                torch.nn.Linear(X.shape[1], 16),
                torch.nn.ReLU(inplace=True),
                torch.nn.Linear(16, 1),
            ),
            loss=torch.nn.functional.mse_loss, 
            epsilon=self.epsilon, 
            delta=self.delta,
            epochs=self.epochs, 
            lr=self.lr,
            batch_size=128,
        )
        self._model.fit(X, y)

    def predict(self, X):
        y_pred = self._model.predict(X)
        return y_pred

Ancestors

  • sklearn.base.RegressorMixin

Methods

def fit(self, X, y)
Expand source code
def fit(self, X, y):
    self._model = FederatedLearningWrapper(
        model=torch.nn.Sequential(
            torch.nn.Linear(X.shape[1], 16),
            torch.nn.ReLU(inplace=True),
            torch.nn.Linear(16, 1),
        ),
        loss=torch.nn.functional.mse_loss, 
        epsilon=self.epsilon, 
        delta=self.delta,
        epochs=self.epochs, 
        lr=self.lr,
        batch_size=128,
    )
    self._model.fit(X, y)
def predict(self, X)
Expand source code
def predict(self, X):
    y_pred = self._model.predict(X)
    return y_pred
class FederatedLearningWrapper (model, loss, epsilon, delta, epochs, lr, batch_size)
Expand source code
class FederatedLearningWrapper(object):

    def __init__(self, model, loss, epsilon, delta, epochs, lr, batch_size):
        self.model = model
        self.epsilon = epsilon
        self.delta = delta
        self.epochs = epochs
        self.loss = loss
        self.lr = lr
        self.batch_size = batch_size

    def fit(self, X, Y):
        optimizer = torch.optim.Adam(self.model.parameters(), lr=self.lr)
        epsilon = self.epsilon / self.epochs
        delta = self.delta / self.epochs
        for epoch in tqdm(range(self.epochs)):
            gradients = []
            for i in range(len(X)):
                self.model.zero_grad()
                x = torch.FloatTensor(X[i]).unsqueeze(0)
                y = torch.tensor(Y[i]).unsqueeze(0)
                loss = self.loss(self.model(x), y)
                loss.backward()
                gradients.append(get_gradients(self.model, epsilon, delta))
                if len(gradients) > self.batch_size:
                    put_gradients(self.model, merge_gradients(gradients))
                    torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)
                    optimizer.step()
                    gradients = []

    def predict(self, X):
        Y = []
        for i in range(len(X)):
            x = torch.FloatTensor(X[i]).unsqueeze(0)
            y = self.model(x)[0]
            Y.append(y.detach().numpy())
        return np.stack(Y)

Methods

def fit(self, X, Y)
Expand source code
def fit(self, X, Y):
    optimizer = torch.optim.Adam(self.model.parameters(), lr=self.lr)
    epsilon = self.epsilon / self.epochs
    delta = self.delta / self.epochs
    for epoch in tqdm(range(self.epochs)):
        gradients = []
        for i in range(len(X)):
            self.model.zero_grad()
            x = torch.FloatTensor(X[i]).unsqueeze(0)
            y = torch.tensor(Y[i]).unsqueeze(0)
            loss = self.loss(self.model(x), y)
            loss.backward()
            gradients.append(get_gradients(self.model, epsilon, delta))
            if len(gradients) > self.batch_size:
                put_gradients(self.model, merge_gradients(gradients))
                torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)
                optimizer.step()
                gradients = []
def predict(self, X)
Expand source code
def predict(self, X):
    Y = []
    for i in range(len(X)):
        x = torch.FloatTensor(X[i]).unsqueeze(0)
        y = self.model(x)[0]
        Y.append(y.detach().numpy())
    return np.stack(Y)