Odpowiedz już dziś na lokalne wydarzenie TensorFlow Everywhere!
Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Federated Learning for Text Generation

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

Ten samouczek opiera się na koncepcjach z samouczka Federated Learning for Image Classification i przedstawia kilka innych przydatnych podejść do uczenia federacyjnego.

W szczególności ładujemy wcześniej wytrenowany model Keras i udoskonalamy go za pomocą szkolenia federacyjnego w (symulowanym) zdecentralizowanym zbiorze danych. Jest to praktycznie ważne z kilku powodów. Możliwość korzystania z modeli serializowanych ułatwia mieszanie uczenia federacyjnego z innymi podejściami ML. Co więcej, pozwala to na użycie coraz większej liczby wstępnie wyszkolonych modeli - na przykład rzadko konieczne jest szkolenie modeli językowych od zera, ponieważ wiele wstępnie wytrenowanych modeli jest obecnie szeroko dostępnych (patrz np. TF Hub ). Zamiast tego bardziej sensowne jest rozpoczęcie od wstępnie wytrenowanego modelu i udoskonalenie go za pomocą funkcji Federated Learning, dostosowując się do szczególnych cech zdecentralizowanych danych dla określonej aplikacji.

W tym samouczku zaczynamy od RNN, który generuje znaki ASCII i udoskonalamy go poprzez uczenie federacyjne. Pokazujemy również, jak ostateczne wagi mogą zostać zwrócone do oryginalnego modelu Keras, co pozwala na łatwą ocenę i generowanie tekstu przy użyciu 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 zgodnie z instrukcją TensorFlow Generowanie tekstu przy użyciu RNN z przyspieszonym wykonaniem . 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 generuje rozsądne prognozy i jest wystarczający do celów naszego samouczka. Ostateczny model został zapisany za pomocą tf.keras.models.save_model(include_optimizer=False) .

W tym samouczku wykorzystamy uczenie federacyjne, aby dostroić ten model pod kątem Szekspira, używając federacyjnej wersji danych dostarczonych przez TFF.

Wygeneruj tabele wyszukiwania słownika

# 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 przetwarzanie wstępne danych Federated Shakespeare

Pakiet tff.simulation.datasets zawiera różne zestawy danych, które są podzielone na „klientów”, gdzie każdy klient odpowiada tff.simulation.datasets danych na określonym urządzeniu, które może uczestniczyć w uczeniu federacyjnym.

Te zestawy danych zapewniają realistyczne rozkłady danych innych niż IID, które odzwierciedlają w symulacji wyzwania szkolenia na rzeczywistych zdecentralizowanych danych. Część wstępnego przetwarzania tych danych została wykonana przy użyciu narzędzi z projektu Leaf ( github ).

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

Zbiory danych dostarczone przez shakespeare.load_data() składają się z sekwencji Tensors ciągów, po jednym dla każdego wiersza wypowiedzianego przez określoną postać w sztuce Szekspira. Klucze klienta składają się z nazwy gry połączonej z nazwą postaci, więc na przykład MUCH_ADO_ABOUT_NOTHING_OTHELLO odpowiada MUCH_ADO_ABOUT_NOTHING_OTHELLO postaci Othello w sztuce Wiele hałasu o nic . Należy zauważyć, że w prawdziwym scenariuszu uczenia federacyjnego klienci nigdy nie są identyfikowani ani śledzeni na podstawie identyfikatorów, ale w przypadku symulacji warto pracować 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)

Teraz używamy transformacjitf.data.Dataset aby przygotować te dane do uczenia załadowanego powyżej znaku RNN.

# 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))

Zauważ, że podczas tworzenia oryginalnych sekwencji i tworzenia partii powyżej używamy drop_remainder=True dla uproszczenia. Oznacza to, że wszystkie znaki (klienci), które nie mają co najmniej (SEQ_LENGTH + 1) * BATCH_SIZE znaków w tekście, będą miały puste (SEQ_LENGTH + 1) * BATCH_SIZE danych. Typowym podejściem do rozwiązania tego problemu byłoby wypełnienie paczek specjalnym tokenem, a następnie zamaskowanie straty, aby nie brać pod uwagę żetonów wypełnienia.

To trochę skomplikowałoby przykład, więc w tym samouczku używamy tylko pełnych partii, tak jak w standardowym samouczku . Jednak w ustawieniach federacyjnych ten problem jest bardziej istotny, ponieważ wielu użytkowników może mieć małe zbiory danych.

Teraz możemy wstępnie przetworzyć 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

Załadowaliśmy nieskompilowany model keras, ale aby uruchomić keras_model.evaluate , musimy skompilować go z uwzględnieniem strat i metryk. Skompilujemy również w optymalizatorze, 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órych najwyższe prawdopodobieństwo zostało umieszczone na prawidłowym następnym znaku). To przydatna miara, więc ją dodajemy. Jednak w tym celu musimy zdefiniować nową klasę metryki, ponieważ nasze przewidywania mają rangę 3 (wektor logitów dla każdego z BATCH_SIZE * SEQ_LENGTH ), a SparseCategoricalAccuracy oczekuje tylko prognoz o randze 2.

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 naszym przykładowym 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 (mimo że w tej chwili dostępne jest tylko środowisko uruchomieniowe symulacji zaimplementowane w języku Python). Mimo że pracujemy w trybie przyspieszonym (TF 2.0), obecnie TFF serializuje obliczenia TensorFlow, konstruując niezbędne operacje w kontekście instrukcji „ with tf.Graph.as_default() ”. Dlatego musimy zapewnić funkcję, której TFF może użyć do wprowadzenia naszego modelu do wykresu, 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 iteracyjnego procesu uśredniania federacyjnego, którego użyjemy do ulepszenia modelu (szczegółowe informacje na temat algorytmu uśredniania federacyjnego można znaleźć w artykule Communication-Efficient Learning of Deep Networks from Decentralized Data ).

Używamy skompilowanego modelu Keras do przeprowadzania standardowej (niefederowanej) oceny po każdej rundzie szkolenia federacyjnego. Jest to przydatne do celów badawczych podczas przeprowadzania symulowanego uczenia się federacyjnego i istnieje standardowy testowy zestaw danych.

W realistycznym środowisku produkcyjnym ta sama technika może być użyta do wzięcia modeli szkolonych w ramach uczenia federacyjnego i oceny ich na scentralizowanym zestawie danych wzorcowych do celów testowania lub zapewnienia 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 uśrednianie federacyjne dla jednej rundy na jednym kliencie w pojedynczej 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

Teraz napiszmy nieco ciekawszą pętlę szkoleniową 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 dwa minibaty w każdej rundzie.

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 utworzonego przez fed_avg.initialize() jest oparty na losowych inicjalizatorach modelu Keras, a nie na załadowanych wagach, ponieważ clone_model() nie clone_model() wag. Aby rozpocząć szkolenie od wstępnie wytrenowanego modelu, ustawiamy wagi modeli 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ącego szkolenia, aby zrobić dużą różnicę, ale jeśli trenujesz dłużej na większej liczbie danych Szekspira, powinieneś zauważyć różnicę w stylu tekstu wygenerowanego 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 tylko pierwszy krok! Oto kilka pomysłów, jak możesz spróbować rozszerzyć ten notatnik:

  • Napisz bardziej realistyczną pętlę szkoleniową, w której losowo będziesz próbować klientów.
  • Użyj „ .repeat(NUM_EPOCHS) ” na zestawach danych klienta, aby wypróbować wiele epok lokalnego uczenia (np. Jak w McMahan i in. ). Zobacz także Federated Learning for Image Classification, które to robi.
  • Zmień polecenie compile() aby eksperymentować z użyciem różnych algorytmów optymalizacji na kliencie.
  • Spróbuj server_optimizer argument build_federated_averaging_process wypróbować różne algorytmy do stosowania nowości modelowych na serwerze.
  • Wypróbuj argument client_weight_fn do build_federated_averaging_process aby wypróbować różne wagi klientów. Domyślnie waży aktualizację klienta przez liczbę przykładów na kliencie, ale możesz zrobić np. client_weight_fn=lambda _: tf.constant(1.0) .