[Технический блог] Введение и внедрение вертикального федеративного обучения

искусственный интеллект

Введение в дело

Банк А достиг сотрудничества на уровне предприятия с интернет-компанией Б. Интернет-компания A и банк B имеют большое количество пересекающихся пользователей, а A имеет характерную информацию, такую ​​как поведение клиентов в Интернете. B имеет характерную информацию, такую ​​как состояние депозита и кредита клиента, а также информацию о маркировке клиента - ситуацию с погашением кредита клиентом (Y). B надеется объединить свою информацию об уникальных функциях с информацией об уникальных функциях A, чтобы обучить более мощную модель для выявления кредитных рисков клиентов, но из-за административных процедур между различными отраслями, конфиденциальности и безопасности пользовательских данных и других факторов предприятия A и B не могут напрямую обмениваться данных, и федеративное обучение появляется по мере необходимости.

Обзор федеративного обучения

Определение федеративного обучения

Федеративное обучение направлено на создание модели федеративного обучения на основе распределенных наборов данных. Во время обучения модели стороны могут обмениваться информацией, связанной с моделью (или в зашифрованном виде), но не необработанными данными. Этот обмен не раскрывает какие-либо защищенные частные части данных на каждом сайте. Обученную федеративную модель обучения можно разместить у каждого участника федеративной системы обучения или совместно использовать несколько сторон. Есть N участниковСовместная работа над соответствующими обучающими наборами данныхдля обучения модели машинного обучения. Традиционный подход заключается в объединении всех данныхСобираются и хранятся в одном месте, например, хранятся на облачном сервере данных, чтобы можно было получить модель машинного обучения путем обучения с централизованным набором данных на сервере.. В процессе обучения традиционными методами любой участник будет предоставлять свои данные серверу или даже другим участникам. Федеративное обучение — это способ совместного обучения модели без сбора всех данных сторон.процесс машинного обучения. Предполагатьицентрализованная модельи федеративная модельпоказатели эффективности. При построении моделей машинного обучения на распределенных источниках данных с использованием безопасного федеративного обучения мы допускаем, чтобы производительность федеративных моделей обучения была немного ниже, чем у централизованных моделей, при сохранении конфиденциальности пользователей.вЭто допустимая потеря производительности.

Классификация федеративного обучения

В соответствии с различным распределением данных, используемых в федеративном обучении среди участников, мы можем разделить федеративное обучение на три категории: горизонтальное федеративное обучение (HFL), вертикальное федеративное обучение (VFL) и федеративное трансферное обучение (Federated Transfer Learning, FTL). Вот различные распределения данных, на которые нацелены эти три типа федеративного обучения:

  • Горизонтальное федеративное обучение: данные разных участников имеют большое перекрытие признаков (по горизонтали), но выборки данных (по вертикали), то есть выборки, которым принадлежат признаки, имеют низкую степень перекрытия. Например, участниками федеративного обучения являются два банка, которые обслуживают разные региональные рынки, группы клиентов, которые они обслуживают, довольно разные, но характеристики клиентов могут иметь высокую степень совпадения из-за схожих бизнес-моделей.
  • Вертикальное федеративное обучение: выборки данных разных участников имеют большое перекрытие, но перекрытие признаков выборки невелико. Например, две компании (банк и компания электронной коммерции) предоставляют своим клиентам разные услуги и имеют данные о разных аспектах своих клиентов, но группы клиентов, которые они обслуживают, во многом пересекаются.

  • Федеративное трансферное обучение: данные разных участников не очень пересекаются по характеристикам и размерам выборки.

Вертикальный федеративный алгоритм обучения

Алгоритм вертикального федеративного обучения способствует установлению сотрудничества между предприятиями, используя свои собственные уникальные данные для совместного построения более мощной модели. В этой статье основное внимание будет уделено вертикальному федеративному алгоритму обучения, основанному на аддитивном гомоморфном шифровании.

Сценарий приложения

Уточняя случай в начале, предприятие B имеет признаки X3 и Y (метки), которые можно смоделировать независимо, а предприятие A имеет признаки X1 и X2, но не имеет Y и не может быть смоделировано независимо.Теперь компании A и B сотрудничают, чтобы установить совместная модель.Очевидно, что эффект будет больше, чем от одностороннего моделирования данных предприятия B.image.pngНо как две стороны могут сотрудничать для совместного обучения модели? В качестве примера логистической регрессии функция потерь и формула градиента классической логистической регрессии выглядят следующим образом:

Можно видеть, что расчет градиента неотделим от данных объекта (x) и данных метки (y). Поэтому одно из самых прямых направлений взаимодействия данных заключается в том, что одна сторона отправляет свои уникальные данные другой стороне напрямую в незашифрованном виде, а другая сторона вычисляет градиент и затем возвращает. Однако такой способ взаимодействия приведет к утечке информации, и одна сторона получит всю информацию, что явно не соответствует спецификации. Поскольку передача открытого текста невозможна, одним из решений является отправка требуемых данных в виде зашифрованного текста, но это вызовет другую проблему.Одна сторона не может расшифровать зашифрованные данные другой стороны после их получения.Как их рассчитать? В настоящее время необходимо ввести алгоритм гомоморфного шифрования.image.png

Введение в алгоритмы гомоморфного шифрования

Из-за нехватки места здесь будет представлена ​​только роль гомоморфного алгоритма шифрования, но его конкретные детали не будут представлены. Гомоморфное шифрование — это специальный метод шифрования, который позволяет обрабатывать зашифрованный текст для получения зашифрованного результата, то есть непосредственно обрабатывается зашифрованный текст, за которым следует обработка открытого текста и последующее шифрование результата обработки.Тот же результат. С точки зрения абстрактной алгебры гомоморфизм сохраняется. Предположим, что есть два числа x, y, OP(x, y) представляет операцию между x и y (сложение, вычитание, умножение, деление, возведение в степень...). E (x) представляет операцию шифрования для x, а D (x) представляет операцию дешифрования для x. Когда определенный алгоритм шифрования удовлетворяет гомоморфизму для операции OP, выражение выглядит следующим образом:илиВ соответствии с диапазоном и количеством операций, которые может поддерживать алгоритм, алгоритмы гомоморфного шифрования можно разделить на частичное гомоморфное шифрование (PHE), слегка гомоморфное шифрование (SHE) и полностью гомоморфное шифрование (FHE).Диапазон и количество операций, которые он поддерживает. расширяются по очереди. Алгоритм вертикального федеративного обучения после этой статьи будет реализован на основе алгоритма Пайе, который представляет собой частично гомоморфный алгоритм шифрования, поддерживающий сложение и умножение с константами. Ниже я продемонстрирую роль алгоритма Пайе на основе библиотеки Python phe.

#phe库需要安装
from phe import paillier
#生成公钥与私钥
public_key, private_key = paillier.generate_paillier_keypair()
#需要加密的数据
secret_number_list = [3.141592653, 300, -4.6e-12]
#公钥加密
encrypted_number_list = [public_key.encrypt(x) for x in secret_number_list]
#私钥解密
[private_key.decrypt(x) for x in encrypted_number_list]

image.pngПоддерживает сложение и вычитание, а также умножение и деление с константами

a, b, c = encrypted_number_list
a_plus_5 = a + 5                    #= a + 5
print("a + 5 =",private_key.decrypt(a_plus_5))
a_plus_b = a + b                    #= a + b
print("a + b =",private_key.decrypt(a_plus_b))
a_times_3_5 = a * 3.5               #= a * 3.5
print("a * 3.5 =",private_key.decrypt(a_times_3_5))
a_minus_1 = a - 1                 #= a + (-1)
print("a - 1=",private_key.decrypt(a_minus_1))
a_div_minus_3_1 = a / -3.1          #= a * (-1/3.1)
print("a / -3.1 =",private_key.decrypt(a_div_minus_3_1))
a_minus_b = a - b                   #= a + (b*-1)
print("a - b =",private_key.decrypt(a_minus_b))

image.pngЕсли логика внутри некоторых функций представляет собой сложение или умножение с константами, она также поддерживается.

import numpy as np
enc_mean = np.mean(encrypted_number_list)
enc_dot = np.dot(encrypted_number_list, [2, -400.1, 5318008])
print("enc_mean:", private_key.decrypt(enc_mean))
print("enc_dot:", private_key.decrypt(enc_dot))

image.png

Поток алгоритма

Формулы потерь и градиента логистической регрессии содержат экспоненциальные операции, поэтому, если алгоритм Пайе будет использоваться для шифрования, исходную формулу необходимо изменить, чтобы ее можно было представить только сложением и умножением. Распространенным способом преобразования возведения в степень в сложение и умножение является использование разложения Тейлора для аппроксимации.image.pngВерхняя половина итоговой преобразованной матрицы градиента — это градиент, необходимый участнику А для обновления своих параметров (включая обычные члены), а нижняя половина соответствует В. Наша цель состоит в том, чтобы надеяться, что участники A и B смогут выполнять как можно больше отдельных вычислений, а затем получать свои соответствующие результаты вычисления градиента посредством взаимодействия зашифрованной информации, поэтому нам необходимо в определенной степени разделить вычислительные задачи. дизайн может быть использован процесс. В каждом раунде обновления параметров каждому участнику необходимо последовательно выполнять следующие вычисления и взаимодействия:

  1. Каждый из участников A и B инициализирует свои собственные параметры, а участник C генерирует пару ключей и распределяет открытый ключ между A и B.
  2. Расчет участника А, шифруется открытым ключом и отправляется B. Расчет участника B, зашифрованный открытым ключом и отправленный А.
  3. В этот момент A и B можно рассчитать отдельноа также([[x]] представляет гомоморфно зашифрованную форму x).
  4. A и B должны отправить зашифрованный градиент в C для расшифровки, но чтобы C не мог напрямую получить информацию о градиенте, A и B могут добавить к градиенту случайное число.иа затем отправить его на C. После того, как C получает зашифрованный градиент, расшифровывает его и возвращает A и B.
  5. A и B нужно только вычесть случайное число, добавленное между ними, чтобы получить реальный градиент и обновить свои параметры.

image.png

Код

Ниже мы реализуем весь этот алгоритм на основе кода Python. Чтобы более четко показать поток алгоритма, реализация потока взаимодействия будет предельно упрощена.

импортировать необходимые модули

import math
import numpy as np
from phe import paillier
import pandas as pd
from sklearn import datasets
from sklearn.datasets import load_diabetes
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle

Определение сторон

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

class Client:
    def __init__(self, config):
        ## 模型参数
        self.config = config
        ## 中间计算结果
        self.data = {}
        ## 与其他节点的连接状况
        self.other_client = {}
    
    ## 与其他参与方建立连接
    def connect(self, client_name, target_client):
        self.other_client[client_name] = target_client
    
    ## 向特定参与方发送数据
    def send_data(self, data, target_client):
        target_client.data.update(data)

Участник А предоставляет данные о характеристиках только в процессе обучения.

class ClientA(Client):
    def __init__(self, X, config):
        super().__init__(config)
        self.X = X
        self.weights = np.zeros(X.shape[1])
        
    def compute_z_a(self):
        z_a = np.dot(self.X, self.weights)
        return z_a
    
	## 加密梯度的计算,对应step4
    def compute_encrypted_dJ_a(self, encrypted_u):
        encrypted_dJ_a = self.X.T.dot(encrypted_u) + self.config['lambda'] * self.weights
        return encrypted_dJ_a
    
	##参数的更新
    def update_weight(self, dJ_a):
        self.weights = self.weights - self.config["lr"] * dJ_a / len(self.X)
        return

    ## A: step2
    def task_1(self, client_B_name):
        dt = self.data
        assert "public_key" in dt.keys(), "Error: 'public_key' from C in step 1 not successfully received."
        public_key = dt['public_key']
        z_a = self.compute_z_a()
        u_a = 0.25 * z_a
        z_a_square = z_a ** 2
        encrypted_u_a = np.asarray([public_key.encrypt(x) for x in u_a])
        encrypted_z_a_square = np.asarray([public_key.encrypt(x) for x in z_a_square])
        dt.update({"encrypted_u_a": encrypted_u_a})
        data_to_B = {"encrypted_u_a": encrypted_u_a, "encrypted_z_a_square": encrypted_z_a_square}
        self.send_data(data_to_B, self.other_client[client_B_name])
    
    ## A: step3、4
    def task_2(self, client_C_name):
        dt = self.data
        assert "encrypted_u_b" in dt.keys(), "Error: 'encrypted_u_b' from B in step 1 not successfully received."
        encrypted_u_b = dt['encrypted_u_b']
        encrypted_u = encrypted_u_b + dt['encrypted_u_a']
        encrypted_dJ_a = self.compute_encrypted_dJ_a(encrypted_u)
        mask = np.random.rand(len(encrypted_dJ_a))
        encrypted_masked_dJ_a = encrypted_dJ_a + mask
        dt.update({"mask": mask})
        data_to_C = {'encrypted_masked_dJ_a': encrypted_masked_dJ_a}
        self.send_data(data_to_C, self.other_client[client_C_name])
       
    ## A: step6
    def task_3(self):
        dt = self.data
        assert "masked_dJ_a" in dt.keys(), "Error: 'masked_dJ_a' from C in step 2 not successfully received."
        masked_dJ_a = dt['masked_dJ_a']
        dJ_a = masked_dJ_a - dt['mask']
        self.update_weight(dJ_a)
        print(f"A weight: {self.weights}")
        return

Участник B предоставляет как данные о характеристиках, так и данные о метках в процессе обучения.

class ClientB(Client):
    def __init__(self, X, y, config):
        super().__init__(config)
        self.X = X
        self.y = y
        self.weights = np.zeros(X.shape[1])
        self.data = {}
        
    def compute_u_b(self):
        z_b = np.dot(self.X, self.weights)
        u_b = 0.25 * z_b - self.y + 0.5
        return z_b, u_b

    def compute_encrypted_dJ_b(self, encrypted_u):
        encrypted_dJ_b = self.X.T.dot(encrypted_u) + self.config['lambda'] * self.weights
        return encrypted_dJ_b

    def update_weight(self, dJ_b):
        self.weights = self.weights - self.config["lr"] * dJ_b / len(self.X)
        
    ## B: step2
    def task_1(self, client_A_name):
        try:
            dt = self.data
            assert "public_key" in dt.keys(), "Error: 'public_key' from C in step 1 not successfully received."
            public_key = dt['public_key']
        except Exception as e:
            print("B step 1 exception: %s" % e)
        try:
            z_b, u_b = self.compute_u_b()
            encrypted_u_b = np.asarray([public_key.encrypt(x) for x in u_b])
            dt.update({"encrypted_u_b": encrypted_u_b})
            dt.update({"z_b": z_b})
        except Exception as e:
            print("Wrong 1 in B: %s" % e)

        data_to_A= {"encrypted_u_b": encrypted_u_b}
        self.send_data(data_to_A, self.other_client[client_A_name])
	
    ## B: step3、4
    def task_2(self,client_C_name):
        try:
            dt = self.data
            assert "encrypted_u_a" in dt.keys(), "Error: 'encrypt_u_a' from A in step 1 not successfully received."
            encrypted_u_a = dt['encrypted_u_a']
            encrypted_u = encrypted_u_a + dt['encrypted_u_b']
            encrypted_dJ_b = self.compute_encrypted_dJ_b(encrypted_u)
            mask = np.random.rand(len(encrypted_dJ_b))
            encrypted_masked_dJ_b = encrypted_dJ_b + mask
            dt.update({"mask": mask})
        except Exception as e:
            print("B step 2 exception: %s" % e)
        try:
            assert "encrypted_z_a_square" in dt.keys(), "Error: 'encrypted_z_a_square' from A in step 1 not successfully received."
            encrypted_z = 4*encrypted_u_a + dt['z_b']
            encrypted_loss = np.sum((0.5-self.y)*encrypted_z + 0.125*dt["encrypted_z_a_square"] + 0.125*dt["z_b"] * (encrypted_z+4*encrypted_u_a))
        except Exception as e:
            print("B step 2 exception: %s" % e)
        data_to_C = {"encrypted_masked_dJ_b": encrypted_masked_dJ_b, "encrypted_loss": encrypted_loss}
        self.send_data(data_to_C, self.other_client[client_C_name])
	
    ## B: step6
    def task_3(self):
        try:
            dt = self.data
            assert "masked_dJ_b" in dt.keys(), "Error: 'masked_dJ_b' from C in step 2 not successfully received."
            masked_dJ_b = dt['masked_dJ_b']
            dJ_b = masked_dJ_b - dt['mask']
            self.update_weight(dJ_b)
        except Exception as e:
            print("A step 3 exception: %s" % e)
        print(f"B weight: {self.weights}")
        return

Основная роль участника C во всем процессе обучения — раздать секретный ключ и окончательно расшифровать зашифрованные градиенты A и B.

class ClientC(Client):
    """
    Client C as trusted dealer.
    """
    def __init__(self, A_d_shape, B_d_shape, config):
        super().__init__(config)
        self.A_data_shape = A_d_shape
        self.B_data_shape = B_d_shape
        self.public_key = None
        self.private_key = None
        ## 保存训练中的损失值(泰展开近似)
        self.loss = []
	
    ## C: step1
    def task_1(self, client_A_name, client_B_name):
        try:
            public_key, private_key = paillier.generate_paillier_keypair()
            self.public_key = public_key
            self.private_key = private_key
        except Exception as e:
            print("C step 1 error 1: %s" % e)

        data_to_AB = {"public_key": public_key}
        self.send_data(data_to_AB, self.other_client[client_A_name])
        self.send_data(data_to_AB, self.other_client[client_B_name])
        return
	
    ## C: step5
    def task_2(self, client_A_name, client_B_name):
        try:
            dt = self.data
            assert "encrypted_masked_dJ_a" in dt.keys() and "encrypted_masked_dJ_b" in dt.keys(), "Error: 'masked_dJ_a' from A or 'masked_dJ_b' from B in step 2 not successfully received."
            encrypted_masked_dJ_a = dt['encrypted_masked_dJ_a']
            encrypted_masked_dJ_b = dt['encrypted_masked_dJ_b']
            masked_dJ_a = np.asarray([self.private_key.decrypt(x) for x in encrypted_masked_dJ_a])
            masked_dJ_b = np.asarray([self.private_key.decrypt(x) for x in encrypted_masked_dJ_b])
        except Exception as e:
            print("C step 2 exception: %s" % e)

        try:
            assert "encrypted_loss" in dt.keys(), "Error: 'encrypted_loss' from B in step 2 not successfully received."
            encrypted_loss = dt['encrypted_loss']
            loss = self.private_key.decrypt(encrypted_loss) / self.A_data_shape[0] + math.log(2)
            print("******loss: ", loss, "******")
            self.loss.append(loss)
        except Exception as e:
            print("C step 2 exception: %s" % e)

        data_to_A = {"masked_dJ_a": masked_dJ_a}
        data_to_B = {"masked_dJ_b": masked_dJ_b}
        self.send_data(data_to_A, self.other_client[client_A_name])
        self.send_data(data_to_B, self.other_client[client_B_name])
        return

Генерация смоделированных данных

Здесь набор смоделированных данных будет создан на основе данных о раке молочной железы, установленных в sklearn.Участник A получает некоторые данные о функциях, а участник B получает часть данных о функциях и данные метки.

def load_data():
    # 加载数据
    breast = load_breast_cancer()
    # 数据拆分
    X_train, X_test, y_train, y_test = train_test_split(breast.data, breast.target, random_state=1)
    # 数据标准化
    std = StandardScaler()
    X_train = std.fit_transform(X_train)
    X_test = std.transform(X_test)
    return X_train, y_train, X_test, y_test


## 将特征分配给A和B
def vertically_partition_data(X, X_test, A_idx, B_idx):
    """
    Vertically partition feature for party A and B
    :param X: train feature
    :param X_test: test feature
    :param A_idx: feature index of party A
    :param B_idx: feature index of party B
    :return: train data for A, B; test data for A, B
    """
    XA = X[:, A_idx]  
    XB = X[:, B_idx]  
    XB = np.c_[np.ones(X.shape[0]), XB]
    XA_test = X_test[:, A_idx]
    XB_test = X_test[:, B_idx]
    XB_test = np.c_[np.ones(XB_test.shape[0]), XB_test]
    return XA, XB, XA_test, XB_test

Осуществление тренировочного процесса

def vertical_logistic_regression(X, y, X_test, y_test, config):
    """
    Start the processes of the three clients: A, B and C.
    :param X: features of the training dataset
    :param y: labels of the training dataset
    :param X_test: features of the test dataset
    :param y_test: labels of the test dataset
    :param config: the config dict
    :return: True
    """
    
    ## 获取数据
    XA, XB, XA_test, XB_test = vertically_partition_data(X, X_test, config['A_idx'], config['B_idx'])
    print('XA:',XA.shape, '   XB:',XB.shape)
    
    ## 各参与方的初始化
    client_A = ClientA(XA, config)
    print("Client_A successfully initialized.")
    client_B = ClientB(XB, y, config)
    print("Client_B successfully initialized.")
    client_C =  ClientC(XA.shape, XB.shape, config)
    print("Client_C successfully initialized.")
    
    ## 各参与方之间连接的建立
    client_A.connect("B", client_B)
    client_A.connect("C", client_C)
    client_B.connect("A", client_A)
    client_B.connect("C", client_C)
    client_C.connect("A", client_A)
    client_C.connect("B", client_B)
    
    ## 训练
    for i in range(config['n_iter']):
        client_C.task_1("A", "B")
        client_A.task_1("B")
        client_B.task_1("A")
        client_A.task_2("C")
        client_B.task_2("C")
        client_C.task_2("A", "B")
        client_A.task_3()
        client_B.task_3()
    print("All process done.")
    return True



config = {
    'n_iter': 100,
    'lambda': 10,
    'lr': 0.05,
    'A_idx': [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
    'B_idx': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
}

X, y, X_test, y_test = load_data()
vertical_logistic_regression(X, y, X_test, y_test, config)

image.png

тренировочный эффект

Чтобы проверить эффект обучения алгоритма продольного федеративного обучения. Вы можете установить обычный централизованный алгоритм логистической регрессии обучения в качестве контрольной группы на основе набора данных о раке молочной железы, использовать те же данные тренировочного набора и ту же модель логистической регрессии для обучения и наблюдать кривую снижения его значения потерь и на тот же тестовый набор точность предсказания. Ниже приведено падение значения потерь для обучения в двух случаях:image.pngЧто представляет каждая кривая: Логистика: кривая изменения стоимости потерь обычной логистической регрессии с использованием функции нормальных потерь. Taylor_Logistic: кривая изменения значения потерь обычной логистической регрессии с использованием функции потерь, соответствующей разложению Тейлора. Taylor_Taylor: кривая изменения величины потерь продольной логистической регрессии с использованием функции потерь, аппроксимированной разложением Тейлора.

Ниже приведена разница между точностью и AUC результатов обучения обычной логистической регрессии и продольной логистической регрессии для разных наборов данных в sklearn, где строки представляют количество выборок, feat представляет количество функций, логистика представляет результаты обучения централизованного логистическая регрессия, вертикальная Представляет обучающий эффект алгоритма продольного федеративного обучения.image.pngИз сравнения результатов обучения видно, что по сравнению с обычной логистической регрессией алгоритм продольной логистической регрессии может достичь хорошего эффекта обучения на наборе экспериментальных данных, обеспечивая при этом конфиденциальность данных всех сторон.

использованная литература

[1] Yang Q , Liu Y , Chen T , et al. Federated Machine Learning: Concept and Applications[J]. ACM Transactions on Intelligent Systems and Technology, 2019, 10(2):1-19. [2] Hardy S , Henecka W , Ivey-Law H , et al. Private federated learning on vertically partitioned data via entity resolution and additively homomorphic encryption[J]. 2017. [3] zhuanlan.zhihu.com/p/94105330