
Предлагаем на обзорно ознакомится с отрывком «Федеративное обучение»
Идея федеративного обучения зародилась из того, что многие данные, содержащие полезную информацию для решения задач (например, для диагностики онкологических заболеваний с использованием МРТ), трудно получить в количествах, достаточных для обучения мощной модели глубокого обучения. Кроме полезной информации, необходимой для обучения модели, наборы данных содержат также другие сведения, не имеющие отношения к решаемой задаче, но их раскрытие кому-либо потенциально может нанести вред.
Федеративное обучение — это методика заключения модели в защищенную среду и ее обучение без перемещения данных куда-либо. Рассмотрим пример.
import numpy as np
from collections import Counter
import random
import sys
import codecsnp.random.seed(12345)
with codecs.open('spam.txt',"r",encoding='utf-8',errors='ignore') as f: ← Данные можно получить здесь http://www2.aueb.gr/users/ion/data/enron-spam/
raw = f.readlines()
vocab, spam, ham = (set(["<unk>"]), list(), list())
for row in raw:
spam.append(set(row[:-2].split(" ")))
for word in spam[-1]:
vocab.add(word)
with codecs.open('ham.txt',"r",encoding='utf-8',errors='ignore') as f:
raw = f.readlines()
for row in raw:
ham.append(set(row[:-2].split(" ")))
for word in ham[-1]:
vocab.add(word)
vocab, w2i = (list(vocab), {})
for i,w in enumerate(vocab):
w2i[w] = i
def to_indices(input, l=500):
indices = list()
for line in input:
if(len(line) < l):
line = list(line) + ["<unk>"] * (l - len(line))
idxs = list()
for word in line:
idxs.append(w2i[word])
indices.append(idxs)
return indices
Обучаем выявлять спам
Допустим, нам нужно обучить модель определять спам по электронным письмам людей
В данном случае мы говорим о классификации электронной почты. Нашу первую модель мы обучим на общедоступном наборе данных с названием Enron. Это огромный корпус электронных писем, опубликованных в ходе слушаний по делу компании Enron (теперь это стандартный аналитический корпус электронной почты). Интересный факт: я был знаком с людьми, которым по роду своей деятельности приходилось читать/комментировать этот набор данных, и они отмечают, что люди посылали друг другу в этих письмах самую разную информацию (часто очень личную). Но так как этот корпус был обнародован в ходе судебных разбирательств, в настоящее время его можно использовать без ограничений.
Код в предыдущем и в этом разделе реализует только подготовительные операции. Файлы с входными данными (ham.txt и spam.txt) доступны на веб-странице книги: www.manning.com/books/grokking-deep-learning и в репозитории GitHub: github.com/iamtrask/Grokking-Deep-Learning. Мы должны предварительно обработать его, чтобы подготовить его для передачи в класс Embedding из главы 13, где мы создали свой фреймворк глубокого обучения. Как и прежде, все слова в этом корпусе преобразуются в списки индексов. Кроме того, мы приводим все письма к одинаковой длине в 500 слов, либо обрезая их, либо дополняя лексемами . Благодаря этому мы получаем набор данных прямоугольной формы.
spam_idx = to_indices(spam)
ham_idx = to_indices(ham)
train_spam_idx = spam_idx[0:-1000]
train_ham_idx = ham_idx[0:-1000]
test_spam_idx = spam_idx[-1000:]
test_ham_idx = ham_idx[-1000:]
train_data = list()
train_target = list()
test_data = list()
test_target = list()
for i in range(max(len(train_spam_idx),len(train_ham_idx))):
train_data.append(train_spam_idx[i%len(train_spam_idx)])
train_target.append([1])
train_data.append(train_ham_idx[i%len(train_ham_idx)])
train_target.append([0])
for i in range(max(len(test_spam_idx),len(test_ham_idx))):
test_data.append(test_spam_idx[i%len(test_spam_idx)])
test_target.append([1])
test_data.append(test_ham_idx[i%len(test_ham_idx)])
test_target.append([0])
def train(model, input_data, target_data, batch_size=500, iterations=5):
n_batches = int(len(input_data) / batch_size)
for iter in range(iterations):
iter_loss = 0
for b_i in range(n_batches):
# дополняющая лексема не должна оказывать влияния на прогноз
model.weight.data[w2i['<unk>']] *= 0
input = Tensor(input_data[b_i*bs:(b_i+1)*bs], autograd=True)
target = Tensor(target_data[b_i*bs:(b_i+1)*bs], autograd=True)
pred = model.forward(input).sum(1).sigmoid()
loss = criterion.forward(pred,target)
loss.backward()
optim.step()
iter_loss += loss.data[0] / bs
sys.stdout.write("\r\tLoss:" + str(iter_loss / (b_i+1)))
print()
return model
def test(model, test_input, test_output):
model.weight.data[w2i['<unk>']] *= 0
input = Tensor(test_input, autograd=True)
target = Tensor(test_output, autograd=True)
pred = model.forward(input).sum(1).sigmoid()
return ((pred.data > 0.5) == target.data).mean()
Определив вспомогательные функции train() и test(), мы можем инициализировать нейронную сеть и обучить ее, написав всего несколько строк кода. Уже после трех итераций сеть оказывается в состоянии классифицировать контрольный набор данных с точностью 99.45 % (контрольный набор данных хорошо сбалансирован, поэтому этот результат можно считать превосходным):
model = Embedding(vocab_size=len(vocab), dim=1)
model.weight.data *= 0
criterion = MSELoss()
optim = SGD(parameters=model.get_parameters(), alpha=0.01)
for i in range(3):
model = train(model, train_data, train_target, iterations=1)
print("% Correct on Test Set: " + \
str(test(model, test_data, test_target)*100))
______________________________________________________________________________
Loss:0.037140416860871446
% Correct on Test Set: 98.65
Loss:0.011258669226059114
% Correct on Test Set: 99.15
Loss:0.008068268387986223
% Correct on Test Set: 99.45
Сделаем модель федеративной
Выше было выполнено самое обычное глубокое обучение. Теперь добавим конфиденциальности
В предыдущем разделе мы реализовали пример анализа электронной почты. Теперь поместим все электронные письма в одно место. Это старый добрый метод работы (который все еще широко используется во всем мире). Для начала сымитируем окружение федеративного обучения, в котором имеется несколько разных коллекций писем:
bob = (train_data[0:1000], train_target[0:1000])
alice = (train_data[1000:2000], train_target[1000:2000])
sue = (train_data[2000:], train_target[2000:])
Пока ничего сложного. Теперь мы можем выполнить ту же процедуру обучения, что и прежде, но уже на трех отдельных наборах данных. После каждой итерации мы будем усреднять значения в моделях Боба (Bob), Алисы (Alice) и Сью (Sue) и оценивать результаты. Обратите внимание, что некоторые методы федеративного обучения предусматривают объединение после каждого пакета (или коллекции пакетов); я же решил сохранить код максимально простым:
for i in range(3):
print("Starting Training Round...")
print("\tStep 1: send the model to Bob")
bob_model = train(copy.deepcopy(model), bob[0], bob[1], iterations=1)
print("\n\tStep 2: send the model to Alice")
alice_model = train(copy.deepcopy(model),
alice[0], alice[1], iterations=1)
print("\n\tStep 3: Send the model to Sue")
sue_model = train(copy.deepcopy(model), sue[0], sue[1], iterations=1)
print("\n\tAverage Everyone's New Models")
model.weight.data = (bob_model.weight.data + \
alice_model.weight.data + \
sue_model.weight.data)/3
print("\t% Correct on Test Set: " + \
str(test(model, test_data, test_target)*100))
print("\nRepeat!!\n")
Ниже дан фрагмент с результатами. Эта модель достигла практически того же уровня точности, что и предыдущая, и теоретически у нас отсутствовал доступ к обучающим данным — или нет? Как бы там ни было, но каждый человек изменяет модель в процессе обучения, верно? Неужели мы действительно не сможем ничего выудить из их наборов данных?
Starting Training Round...
Step 1: send the model to Bob
Loss:0.21908166249699718
......
Step 3: Send the model to Sue
Loss:0.015368461608470256
Average Everyone's New Models
% Correct on Test Set: 98.8
Взламываем федеративную модель
Рассмотрим простой пример, как извлечь информацию из обучающего набора данных
Федеративное обучение страдает двумя большими проблемами, особенно трудноразрешимыми, когда у каждого человека имеется лишь маленькая горстка обучающих примеров, — скорость и конфиденциальность. Как оказывается, если у кого-то имеется лишь несколько обучающих примеров (или модель, присланная вам, была обучена лишь на нескольких примерах: обучающем пакете), вы все еще можете довольно много узнать об исходных данных. Если представить, что у вас есть 10 000 человек (и у каждого имеется очень небольшой объем данных), большую часть времени вы потратите на пересылку модели туда и обратно и не так много — на обучение (особенно если модель очень большая).
Но не будем забегать вперед. Давайте посмотрим, что можно узнать после того, как пользователь выполнит обновление весов на одном пакете:
import copy
bobs_email = ["my", "computer", "password", "is", "pizza"]
bob_input = np.array([[w2i[x] for x in bobs_email]])
bob_target = np.array([[0]])
model = Embedding(vocab_size=len(vocab), dim=1)
model.weight.data *= 0
bobs_model = train(copy.deepcopy(model),
bob_input, bob_target, iterations=1, batch_size=1)
Боб создает и обучает модель на электронных письмах в своем почтовом ящике. Но так случилось, что он сохранил свой пароль, послав самому себе письмо с текстом: «My computer password is pizza». Наивный Боб! Посмотрев, какие весовые коэффициенты изменились, мы можем выяснить словарь (и понять смысл) письма Боба:
for i, v in enumerate(bobs_model.weight.data - model.weight.data):
if(v != 0):
print(vocab[i])
Таким несложным способом мы узнали сверхсекретный пароль Боба (и, возможно, его кулинарные предпочтения). И что же делать? Как доверять федеративному обучению, если так легко узнать, какие обучающие данные вызвали изменение весов?
is
pizza
computer
password
my
» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок
Для Хаброжителей скидка 30% на предзаказ книги по купону — Grokking Deep Learning