Sfederowane nauczanie do generowania tekstu

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Ten poradnik opiera się na koncepcji w Federacji Learning dla wizerunku klasyfikowania samouczku i demonstruje kilka innych użytecznych podejść do stowarzyszonym nauki.

W szczególności ładujemy wcześniej wyszkolony model Keras i udoskonalamy go za pomocą szkolenia sfederowanego na (symulowanym) zdecentralizowanym zestawie danych. Jest to praktycznie ważne z kilku powodów. Możliwość korzystania z modeli serializowanych ułatwia mieszanie nauki federacyjnej z innymi podejściami do uczenia maszynowego. Ponadto, pozwala to na korzystanie z coraz większej gamy wstępnie przeszkolony modeli --- na przykład modele językowe szkolenia od podstaw jest rzadko konieczne, ponieważ wiele wstępnie przeszkolony modele są obecnie szeroko dostępne (patrz, np TF Hub ). Zamiast tego bardziej sensowne jest rozpoczęcie od wstępnie wytrenowanego modelu i udoskonalenie go za pomocą Federated Learning, dostosowując się do szczególnych cech zdecentralizowanych danych dla konkretnej aplikacji.

W tym samouczku zaczynamy od sieci RNN, która generuje znaki ASCII i udoskonala je za pomocą uczenia sfederowanego. Pokazujemy również, jak ostateczne wagi można przywrócić do oryginalnego modelu Keras, co pozwala na łatwą ocenę i generowanie tekstu za pomocą standardowych narzędzi.

!pip install --quiet --upgrade tensorflow-federated-nightly
!pip install --quiet --upgrade nest-asyncio

import nest_asyncio
nest_asyncio.apply()
import collections
import functools
import os
import time

import numpy as np
import tensorflow as tf
import tensorflow_federated as tff

np.random.seed(0)

# Test the TFF is working:
tff.federated_computation(lambda: 'Hello, World!')()
b'Hello, World!'

Załaduj wstępnie wytrenowany model

Ładujemy model, który został wstępnie przeszkolony z samouczka TensorFlow generacji tekstu przy użyciu RNN z upragnieniem wykonania . Jednak zamiast treningu na wszystkie dzieła Szekspira , my wstępnie przeszkolony model sprawie tekstu od Charlesa Dickensa Opowieść o dwóch miastach i A Christmas Carol .

Poza rozszerzeniem słownictwa, nie zmodyfikowaliśmy oryginalnego samouczka, więc ten początkowy model nie jest najnowocześniejszy, ale daje rozsądne przewidywania i jest wystarczający do celów naszego samouczka. Ostateczny model został zapisany z tf.keras.models.save_model(include_optimizer=False) .

W tym samouczku użyjemy uczenia sfederowanego, aby dostroić ten model dla Szekspira, korzystając ze sfederowanej wersji danych dostarczonych przez TFF.

Wygeneruj tabele wyszukiwania słownictwa

# A fixed vocabularly of ASCII chars that occur in the works of Shakespeare and Dickens:
vocab = list('dhlptx@DHLPTX $(,048cgkoswCGKOSW[_#\'/37;?bfjnrvzBFJNRVZ"&*.26:\naeimquyAEIMQUY]!%)-159\r')

# Creating a mapping from unique characters to indices
char2idx = {u:i for i, u in enumerate(vocab)}
idx2char = np.array(vocab)

Załaduj wstępnie wytrenowany model i wygeneruj tekst

def load_model(batch_size):
  urls = {
      1: 'https://storage.googleapis.com/tff-models-public/dickens_rnn.batch1.kerasmodel',
      8: 'https://storage.googleapis.com/tff-models-public/dickens_rnn.batch8.kerasmodel'}
  assert batch_size in urls, 'batch_size must be in ' + str(urls.keys())
  url = urls[batch_size]
  local_file = tf.keras.utils.get_file(os.path.basename(url), origin=url)  
  return tf.keras.models.load_model(local_file, compile=False)
def generate_text(model, start_string):
  # From https://www.tensorflow.org/tutorials/sequences/text_generation
  num_generate = 200
  input_eval = [char2idx[s] for s in start_string]
  input_eval = tf.expand_dims(input_eval, 0)
  text_generated = []
  temperature = 1.0

  model.reset_states()
  for i in range(num_generate):
    predictions = model(input_eval)
    predictions = tf.squeeze(predictions, 0)
    predictions = predictions / temperature
    predicted_id = tf.random.categorical(
        predictions, num_samples=1)[-1, 0].numpy()
    input_eval = tf.expand_dims([predicted_id], 0)
    text_generated.append(idx2char[predicted_id])

  return (start_string + ''.join(text_generated))
# Text generation requires a batch_size=1 model.
keras_model_batch1 = load_model(batch_size=1)
print(generate_text(keras_model_batch1, 'What of TensorFlow Federated, you ask? '))
Downloading data from https://storage.googleapis.com/tff-models-public/dickens_rnn.batch1.kerasmodel
16195584/16193984 [==============================] - 0s 0us/step
16203776/16193984 [==============================] - 0s 0us/step
What of TensorFlow Federated, you ask? Sall
yesterday. Received the Bailey."

"Mr. Lorry, grimmering himself, or low varked thends the winter, and the eyes of Monsieur
Defarge. "Let his mind, hon in his
life and message; four declare

Ładowanie i wstępne przetwarzanie sfederowanych danych Szekspira

tff.simulation.datasets pakiet zawiera wiele zestawów danych, które są podzielone na „klientów”, gdzie każdy odpowiada klienta do zbioru danych na temat konkretnego urządzenia, które mogą uczestniczyć w stowarzyszonym nauki.

Te zestawy danych zapewniają realistyczne dystrybucje danych innych niż IID, które odtwarzają w symulacji wyzwania związane z uczeniem się na rzeczywistych zdecentralizowanych danych. Niektóre wstępnego przetwarzania tych danych została wykonana przy użyciu narzędzi z projektu liści ( github ).

train_data, test_data = tff.simulation.datasets.shakespeare.load_data()

Zestawy danych dostarczonych przez shakespeare.load_data() składają się z sekwencji smyczkowych Tensors , po jednym dla każdej linii mówionego przez daną postać w sztuce Szekspira. Klucze klienta składa się z nazwy gry połączone z nazwą postaci, więc na przykład MUCH_ADO_ABOUT_NOTHING_OTHELLO odpowiada linii do charakteru Othello w sztuce wiele hałasu o nic. Należy zauważyć, że w rzeczywistym scenariuszu uczenia sfederowanego klienci nigdy nie są identyfikowani ani śledzeni przez identyfikatory, ale w przypadku symulacji przydatna jest praca z zestawami danych z kluczami.

Tutaj na przykład możemy spojrzeć na niektóre dane z King Lear:

# Here the play is "The Tragedy of King Lear" and the character is "King".
raw_example_dataset = train_data.create_tf_dataset_for_client(
    'THE_TRAGEDY_OF_KING_LEAR_KING')
# To allow for future extensions, each entry x
# is an OrderedDict with a single key 'snippets' which contains the text.
for x in raw_example_dataset.take(2):
  print(x['snippets'])
tf.Tensor(b'', shape=(), dtype=string)
tf.Tensor(b'What?', shape=(), dtype=string)

Mamy teraz używać tf.data.Dataset transformacje przygotować te dane za szkolenie char RNN załadowany powyżej.

# Input pre-processing parameters
SEQ_LENGTH = 100
BATCH_SIZE = 8
BUFFER_SIZE = 100  # For dataset shuffling
# Construct a lookup table to map string chars to indexes,
# using the vocab loaded above:
table = tf.lookup.StaticHashTable(
    tf.lookup.KeyValueTensorInitializer(
        keys=vocab, values=tf.constant(list(range(len(vocab))),
                                       dtype=tf.int64)),
    default_value=0)


def to_ids(x):
  s = tf.reshape(x['snippets'], shape=[1])
  chars = tf.strings.bytes_split(s).values
  ids = table.lookup(chars)
  return ids


def split_input_target(chunk):
  input_text = tf.map_fn(lambda x: x[:-1], chunk)
  target_text = tf.map_fn(lambda x: x[1:], chunk)
  return (input_text, target_text)


def preprocess(dataset):
  return (
      # Map ASCII chars to int64 indexes using the vocab
      dataset.map(to_ids)
      # Split into individual chars
      .unbatch()
      # Form example sequences of SEQ_LENGTH +1
      .batch(SEQ_LENGTH + 1, drop_remainder=True)
      # Shuffle and form minibatches
      .shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)
      # And finally split into (input, target) tuples,
      # each of length SEQ_LENGTH.
      .map(split_input_target))

Należy zauważyć, że w tworzeniu oryginalnych sekwencji i w tworzeniu partii powyżej, używamy drop_remainder=True dla prostoty. Oznacza to, że wszystkie znaki (klientów), które nie mają co najmniej (SEQ_LENGTH + 1) * BATCH_SIZE znaków tekstu będzie mieć pustych zbiorów danych. Typowym podejściem do rozwiązania tego problemu byłoby wypełnienie partii specjalnym tokenem, a następnie zamaskowanie utraty, aby nie uwzględniać tokenów dopełniania.

To komplikuje przykład nieco, więc na tym kursie używamy tylko pełnych partii, tak jak w standardowym poradniku . Jednak w ustawieniu federacyjnym ten problem jest bardziej znaczący, ponieważ wielu użytkowników może mieć małe zestawy danych.

Teraz możemy Preprocesuj naszą raw_example_dataset i sprawdzić typy:

example_dataset = preprocess(raw_example_dataset)
print(example_dataset.element_spec)
(TensorSpec(shape=(8, 100), dtype=tf.int64, name=None), TensorSpec(shape=(8, 100), dtype=tf.int64, name=None))

Skompiluj model i przetestuj na wstępnie przetworzonych danych

My załadowaliśmy się nieskompilowanego modelu Keras, ale w celu uruchomienia keras_model.evaluate , musimy go skompilować ze stratą i metryki. Skompilujemy również optymalizator, który będzie używany jako optymalizator na urządzeniu w Federated Learning.

Oryginalny samouczek nie miał dokładności na poziomie znaków (ułamek przewidywań, w którym największe prawdopodobieństwo zostało umieszczone na prawidłowym następnym znaku). To przydatna metryka, więc ją dodajemy. Jednak musimy zdefiniować nową klasę metryczną do tego, ponieważ nasze przewidywania mają rangę 3 (a wektor logits dla każdego z BATCH_SIZE * SEQ_LENGTH przewidywania), a SparseCategoricalAccuracy oczekuje tylko Pozycja 2 prognoz.

class FlattenedCategoricalAccuracy(tf.keras.metrics.SparseCategoricalAccuracy):

  def __init__(self, name='accuracy', dtype=tf.float32):
    super().__init__(name, dtype=dtype)

  def update_state(self, y_true, y_pred, sample_weight=None):
    y_true = tf.reshape(y_true, [-1, 1])
    y_pred = tf.reshape(y_pred, [-1, len(vocab), 1])
    return super().update_state(y_true, y_pred, sample_weight)

Teraz możemy skompilować model, i ocenić go na naszej example_dataset .

BATCH_SIZE = 8  # The training and eval batch size for the rest of this tutorial.
keras_model = load_model(batch_size=BATCH_SIZE)
keras_model.compile(
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=[FlattenedCategoricalAccuracy()])

# Confirm that loss is much lower on Shakespeare than on random data
loss, accuracy = keras_model.evaluate(example_dataset.take(5), verbose=0)
print(
    'Evaluating on an example Shakespeare character: {a:3f}'.format(a=accuracy))

# As a sanity check, we can construct some completely random data, where we expect
# the accuracy to be essentially random:
random_guessed_accuracy = 1.0 / len(vocab)
print('Expected accuracy for random guessing: {a:.3f}'.format(
    a=random_guessed_accuracy))
random_indexes = np.random.randint(
    low=0, high=len(vocab), size=1 * BATCH_SIZE * (SEQ_LENGTH + 1))
data = collections.OrderedDict(
    snippets=tf.constant(
        ''.join(np.array(vocab)[random_indexes]), shape=[1, 1]))
random_dataset = preprocess(tf.data.Dataset.from_tensor_slices(data))
loss, accuracy = keras_model.evaluate(random_dataset, steps=10, verbose=0)
print('Evaluating on completely random data: {a:.3f}'.format(a=accuracy))
Downloading data from https://storage.googleapis.com/tff-models-public/dickens_rnn.batch8.kerasmodel
16195584/16193984 [==============================] - 0s 0us/step
16203776/16193984 [==============================] - 0s 0us/step
Evaluating on an example Shakespeare character: 0.402000
Expected accuracy for random guessing: 0.012
Evaluating on completely random data: 0.011

Dostosuj model za pomocą Federated Learning

TFF serializuje wszystkie obliczenia TensorFlow, dzięki czemu można je potencjalnie uruchomić w środowisku innym niż Python (chociaż w tej chwili dostępne jest tylko środowisko uruchomieniowe symulacji zaimplementowane w Pythonie). Nawet mimo tego, że są uruchomione w trybie chętny, (TF 2.0), obecnie TFF serializes TensorFlow obliczenia konstruując niezbędne ops wewnątrz kontekście „ with tf.Graph.as_default() ” oświadczenie. Dlatego musimy zapewnić funkcję, której TFF może użyć do wprowadzenia naszego modelu do grafu, który kontroluje. Robimy to w następujący sposób:

# Clone the keras_model inside `create_tff_model()`, which TFF will
# call to produce a new copy of the model inside the graph that it will 
# serialize. Note: we want to construct all the necessary objects we'll need 
# _inside_ this method.
def create_tff_model():
  # TFF uses an `input_spec` so it knows the types and shapes
  # that your model expects.
  input_spec = example_dataset.element_spec
  keras_model_clone = tf.keras.models.clone_model(keras_model)
  return tff.learning.from_keras_model(
      keras_model_clone,
      input_spec=input_spec,
      loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
      metrics=[FlattenedCategoricalAccuracy()])

Teraz jesteśmy gotowi do skonstruowania iteracyjny proces uśredniania Federalne, które będziemy używać w celu poprawy modelu (Szczegółowe informacje o algorytm uśredniania Federalne, zobacz papieru Communication-Efektywne uczenie głębokiej sieci od zdecentralizowanej Danych ).

Używamy skompilowanego modelu Keras do wykonywania standardowej (niefederacyjnej) oceny po każdej rundzie szkolenia federacyjnego. Jest to przydatne do celów badawczych, gdy przeprowadza się symulowane uczenie federacyjne i istnieje standardowy zestaw danych testowych.

W realistycznym środowisku produkcyjnym ta sama technika może zostać wykorzystana do wykorzystania modeli wytrenowanych za pomocą sfederowanego uczenia się i oceny ich na scentralizowanym zestawie danych porównawczych do celów testowania lub zapewniania jakości.

# This command builds all the TensorFlow graphs and serializes them: 
fed_avg = tff.learning.build_federated_averaging_process(
    model_fn=create_tff_model,
    client_optimizer_fn=lambda: tf.keras.optimizers.SGD(lr=0.5))

Oto najprostsza możliwa pętla, w której uruchamiamy sfederowane uśrednianie dla jednej rundy na jednym kliencie w jednej partii:

state = fed_avg.initialize()
state, metrics = fed_avg.next(state, [example_dataset.take(5)])
train_metrics = metrics['train']
print('loss={l:.3f}, accuracy={a:.3f}'.format(
    l=train_metrics['loss'], a=train_metrics['accuracy']))
loss=4.403, accuracy=0.132

Napiszmy teraz nieco ciekawszą pętlę treningową i ewaluacyjną.

Aby ta symulacja nadal przebiegała stosunkowo szybko, w każdej rundzie trenujemy na tych samych trzech klientach, biorąc pod uwagę tylko dwie minipartie na każdą.

def data(client, source=train_data):
  return preprocess(source.create_tf_dataset_for_client(client)).take(5)


clients = [
    'ALL_S_WELL_THAT_ENDS_WELL_CELIA', 'MUCH_ADO_ABOUT_NOTHING_OTHELLO',
]

train_datasets = [data(client) for client in clients]

# We concatenate the test datasets for evaluation with Keras by creating a 
# Dataset of Datasets, and then identity flat mapping across all the examples.
test_dataset = tf.data.Dataset.from_tensor_slices(
    [data(client, test_data) for client in clients]).flat_map(lambda x: x)

Początkowy stan modelu wyprodukowanego przez fed_avg.initialize() opiera się na przypadkowych inicjalizatory modelu Keras, a nie wagi, które zostały załadowane, ponieważ clone_model() nie robi klon wagi. Aby rozpocząć trenowanie ze wstępnie wytrenowanego modelu, ustawiamy wagi modelu w stanie serwera bezpośrednio z załadowanego modelu.

NUM_ROUNDS = 5

# The state of the FL server, containing the model and optimization state.
state = fed_avg.initialize()

# Load our pre-trained Keras model weights into the global model state.
state = tff.learning.state_with_new_model_weights(
    state,
    trainable_weights=[v.numpy() for v in keras_model.trainable_weights],
    non_trainable_weights=[
        v.numpy() for v in keras_model.non_trainable_weights
    ])


def keras_evaluate(state, round_num):
  # Take our global model weights and push them back into a Keras model to
  # use its standard `.evaluate()` method.
  keras_model = load_model(batch_size=BATCH_SIZE)
  keras_model.compile(
      loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
      metrics=[FlattenedCategoricalAccuracy()])
  state.model.assign_weights_to(keras_model)
  loss, accuracy = keras_model.evaluate(example_dataset, steps=2, verbose=0)
  print('\tEval: loss={l:.3f}, accuracy={a:.3f}'.format(l=loss, a=accuracy))


for round_num in range(NUM_ROUNDS):
  print('Round {r}'.format(r=round_num))
  keras_evaluate(state, round_num)
  state, metrics = fed_avg.next(state, train_datasets)
  train_metrics = metrics['train']
  print('\tTrain: loss={l:.3f}, accuracy={a:.3f}'.format(
      l=train_metrics['loss'], a=train_metrics['accuracy']))

print('Final evaluation')
keras_evaluate(state, NUM_ROUNDS + 1)
Round 0
    Eval: loss=3.324, accuracy=0.401
    Train: loss=4.360, accuracy=0.155
Round 1
    Eval: loss=4.361, accuracy=0.049
    Train: loss=4.235, accuracy=0.164
Round 2
    Eval: loss=4.219, accuracy=0.177
    Train: loss=4.081, accuracy=0.221
Round 3
    Eval: loss=4.080, accuracy=0.174
    Train: loss=3.940, accuracy=0.226
Round 4
    Eval: loss=3.991, accuracy=0.176
    Train: loss=3.840, accuracy=0.226
Final evaluation
    Eval: loss=3.909, accuracy=0.171

Przy domyślnych zmianach nie przeprowadziliśmy wystarczająco dużo treningu, aby zrobić dużą różnicę, ale jeśli trenujesz dłużej na większej ilości danych Szekspira, powinieneś zauważyć różnicę w stylu tekstu generowanego za pomocą zaktualizowanego modelu:

# Set our newly trained weights back in the originally created model.
keras_model_batch1.set_weights([v.numpy() for v in keras_model.weights])
# Text generation requires batch_size=1
print(generate_text(keras_model_batch1, 'What of TensorFlow Federated, you ask? '))
What of TensorFlow Federated, you ask? Shalways, I will call your
compet with any city brought their faces uncompany," besumed him. "When he
sticked Madame Defarge pushed the lamps.

"Have I often but no unison. She had probably come,

Sugerowane rozszerzenia

Ten samouczek to dopiero pierwszy krok! Oto kilka pomysłów na rozszerzenie tego notatnika:

  • Napisz bardziej realistyczną pętlę treningową, w której losowo wybierasz klientów do treningu.
  • Use „ .repeat(NUM_EPOCHS) ” w zbiorach danych klienta, aby spróbować wielu epok szkolenia miejscowego (na przykład jak w patrz praca: McMahan et. Al. ). Zobacz także Federalne nauki dla klasyfikacji obrazu , która to robi.
  • Zmień compile() polecenia do eksperymentu z użyciem różnych algorytmów optymalizacyjnych na kliencie.
  • Spróbuj server_optimizer argument build_federated_averaging_process wypróbować różne algorytmy do stosowania nowości modelowych na serwerze.
  • Spróbuj client_weight_fn argument do build_federated_averaging_process próbować różnych współczynników korygujących klientów. Aktualizacje domyślne ciężary Klientowi przez szereg przykładów na kliencie, ale można to zrobić np client_weight_fn=lambda _: tf.constant(1.0) .