Pull to refresh

Глубокое обучение. Федеративное обучение

Reading time 6 min
Views 9K
imageПривет, Хаброжители! Мы недавно сдали в типографию книгу Эндрю Траска (Andrew W. Trask), закладывающую фундамент для дальнейшего овладения технологией глубокого обучения. Она начинается с описания основ нейронных сетей и затем подробно рассматривает дополнительные уровни и архитектуры.

Предлагаем на обзорно ознакомится с отрывком «Федеративное обучение»

Идея федеративного обучения зародилась из того, что многие данные, содержащие полезную информацию для решения задач (например, для диагностики онкологических заболеваний с использованием МРТ), трудно получить в количествах, достаточных для обучения мощной модели глубокого обучения. Кроме полезной информации, необходимой для обучения модели, наборы данных содержат также другие сведения, не имеющие отношения к решаемой задаче, но их раскрытие кому-либо потенциально может нанести вред.

Федеративное обучение — это методика заключения модели в защищенную среду и ее обучение без перемещения данных куда-либо. Рассмотрим пример.

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
Tags:
Hubs:
+5
Comments 13
Comments Comments 13

Articles

Information

Website
piter.com
Registered
Founded
Employees
201–500 employees
Location
Россия