Apprendimento federato per la generazione di testo

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza l'origine su GitHub Scarica quaderno

Questo tutorial si basa sui concetti del Federati di apprendimento per Immagine Classificazione tutorial, e dimostra molti altri approcci utili per l'apprendimento federata.

In particolare, carichiamo un modello Keras precedentemente addestrato e lo perfezioniamo utilizzando l'addestramento federato su un set di dati decentralizzato (simulato). Questo è praticamente importante per diversi motivi. La possibilità di utilizzare modelli serializzati semplifica la combinazione dell'apprendimento federato con altri approcci ML. Inoltre, questo permette l'uso di un numero crescente di modelli pre-addestrato --- per esempio, modelli di linguaggio di formazione da zero è raramente necessaria, come numerosi modelli pre-addestrati sono ora ampiamente disponibili (vedi, ad esempio, TF Hub ). Ha più senso invece partire da un modello pre-addestrato e perfezionarlo utilizzando Federated Learning, adattandosi alle particolari caratteristiche dei dati decentralizzati per una particolare applicazione.

Per questo tutorial, iniziamo con un RNN che genera caratteri ASCII e lo perfezioniamo tramite l'apprendimento federato. Mostriamo anche come i pesi finali possono essere riportati al modello Keras originale, consentendo una facile valutazione e generazione di testo utilizzando strumenti standard.

!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!'

Carica un modello pre-addestrato

Carichiamo un modello che è stato pre-formato in seguito alla tensorflow esercitazione Crea testo utilizzando un RNN con l'esecuzione ansioso . Tuttavia, invece di formazione su The Complete opere di Shakespeare , abbiamo pre-allenati il modello sul testo dal Charles Dickens' A Tale of Two Cities e A Christmas Carol .

Oltre ad espandere il vocabolario, non abbiamo modificato il tutorial originale, quindi questo modello iniziale non è allo stato dell'arte, ma produce previsioni ragionevoli ed è sufficiente per i nostri scopi di tutorial. Il modello finale è stato salvato con tf.keras.models.save_model(include_optimizer=False) .

Useremo l'apprendimento federato per mettere a punto questo modello per Shakespeare in questo tutorial, utilizzando una versione federata dei dati forniti da TFF.

Genera le tabelle di ricerca del vocabolario

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

Carica il modello pre-addestrato e genera del testo

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

Caricare e preelaborare i dati Shakespeare federati

Il tff.simulation.datasets pacchetto fornisce una varietà di set di dati che vengono suddivisi in "clienti", dove ogni corrisponde client a un set di dati su un particolare dispositivo che potrebbe partecipare alla formazione federata.

Questi set di dati forniscono distribuzioni di dati non IID realistiche che replicano nella simulazione le sfide dell'addestramento su dati decentralizzati reali. Alcuni dei pre-elaborazione di questi dati è stato fatto utilizzando gli strumenti del progetto Leaf ( github ).

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

I set di dati forniti da shakespeare.load_data() consistono in una sequenza di stringa Tensors , uno per ogni linea parlata da un particolare carattere in un gioco Shakespeare. Le chiavi del client sono costituiti dal nome del gioco unito con il nome del personaggio, così per esempio MUCH_ADO_ABOUT_NOTHING_OTHELLO corrisponde alle linee per il personaggio di Otello nella commedia Molto rumore per nulla. Si noti che in uno scenario di apprendimento federato reale i client non vengono mai identificati o tracciati dagli ID, ma per la simulazione è utile lavorare con set di dati con chiave.

Qui, ad esempio, possiamo guardare alcuni dati di 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)

Ora usiamo tf.data.Dataset trasformazioni per preparare questi dati per la formazione del char RNN caricata sopra.

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

Si noti che nella formazione delle sequenze originali e nella formazione di lotti sopra, usiamo drop_remainder=True per semplicità. Ciò significa che tutti i caratteri (client) che non hanno almeno (SEQ_LENGTH + 1) * BATCH_SIZE caratteri di testo avranno set di dati vuote. Un approccio tipico per risolvere questo problema sarebbe quello di riempire i batch con un token speciale, quindi mascherare la perdita per non tenere conto dei token di riempimento.

Questo complicherebbe l'esempio un po ', quindi per questo tutorial usiamo solo lotti pieno, come nel tutorial di serie . Tuttavia, nell'impostazione federata questo problema è più significativo, perché molti utenti potrebbero avere set di dati di piccole dimensioni.

Ora si può trattare il nostro raw_example_dataset , e controllare i tipi:

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

Compilare il modello e testare sui dati preelaborati

Abbiamo caricato un modello keras non compilato, ma al fine di eseguire keras_model.evaluate , abbiamo bisogno di compilarlo con una perdita e metriche. Compileremo anche in un ottimizzatore, che verrà utilizzato come ottimizzatore su dispositivo in Federated Learning.

Il tutorial originale non aveva una precisione a livello di carattere (la frazione di previsioni in cui la probabilità più alta è stata inserita nel carattere successivo corretto). Questa è una metrica utile, quindi la aggiungiamo. Tuttavia, abbiamo bisogno di definire una nuova classe metrica per questo, perché le nostre previsioni hanno rango 3 (un vettore di logit per ciascuno dei BATCH_SIZE * SEQ_LENGTH previsioni), e SparseCategoricalAccuracy si aspetta solo di rango 2 previsioni.

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)

Ora siamo in grado di compilare un modello, e valutare sul nostro 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

Perfeziona il modello con Federated Learning

TFF serializza tutti i calcoli TensorFlow in modo che possano essere potenzialmente eseguiti in un ambiente non Python (anche se al momento è disponibile solo un runtime di simulazione implementato in Python). Anche se ci sono in esecuzione in modalità ansioso, (TF 2.0), attualmente TFF serializza tensorflow calcoli costruendo i ops necessarie all'interno del contesto di un " with tf.Graph.as_default() " dichiarazione. Pertanto, dobbiamo fornire una funzione che TFF possa utilizzare per introdurre il nostro modello in un grafico che controlla. Lo facciamo come segue:

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

Ora siamo pronti a costruire un processo iterativo federati della media, che useremo per migliorare il modello (per i dettagli su l'algoritmo di calcolo della media federati, vedere il documento di comunicazione-efficace apprendimento di profondo reti da decentralizzata dei dati ).

Usiamo un modello Keras compilato per eseguire una valutazione standard (non federata) dopo ogni ciclo di formazione federata. Questo è utile per scopi di ricerca quando si esegue l'apprendimento federato simulato ed è disponibile un set di dati di test standard.

In un ambiente di produzione realistico, questa stessa tecnica potrebbe essere utilizzata per prendere modelli addestrati con l'apprendimento federato e valutarli su un set di dati di riferimento centralizzato per scopi di test o di garanzia della qualità.

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

Ecco il ciclo più semplice possibile, in cui eseguiamo la media federata per un round su un singolo client su un singolo batch:

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

Ora scriviamo un ciclo di formazione e valutazione leggermente più interessante.

Affinché questa simulazione funzioni ancora in modo relativamente veloce, ci alleniamo sugli stessi tre client ogni round, considerando solo due minibatch per ciascuno.

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)

Lo stato iniziale del modello prodotto dalla fed_avg.initialize() si basa sulle inizializzatori casuali per il modello Keras, non i pesi che sono stati caricati, poiché clone_model() non clone fa i pesi. Per avviare l'addestramento da un modello pre-addestrato, impostiamo i pesi del modello nello stato del server direttamente dal modello caricato.

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

Con le modifiche predefinite, non abbiamo svolto un addestramento sufficiente per fare una grande differenza, ma se ti alleni più a lungo su più dati Shakespeare, dovresti vedere una differenza nello stile del testo generato con il modello aggiornato:

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

Estensioni suggerite

Questo tutorial è solo il primo passo! Ecco alcune idee su come potresti provare a estendere questo blocco note:

  • Scrivi un ciclo di allenamento più realistico in cui campiona i clienti su cui allenarsi in modo casuale.
  • Usa " .repeat(NUM_EPOCHS) " sui set di dati dei clienti di provare molteplici epoche di formazione locale (ad esempio, come in McMahan et. Al. ). Vedi anche Federated Learning per la classificazione Immagine che fa questo.
  • Cambiare la compile() comando per sperimentare utilizzando diversi algoritmi di ottimizzazione sul client.
  • Prova il server_optimizer argomento per build_federated_averaging_process per provare diversi algoritmi per l'applicazione degli aggiornamenti del modello sul server.
  • Prova il client_weight_fn argomento a build_federated_averaging_process per provare diverse ponderazioni dei clienti. Gli aggiornamenti pesi di default del client per il numero di esempi sul client, ma si può fare ad esempio client_weight_fn=lambda _: tf.constant(1.0) .