Questa pagina è stata tradotta dall'API Cloud Translation.
Switch to English

Apprendimento federato per la generazione di testo

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza sorgente su GitHub

Questa esercitazione si basa sui concetti dell'esercitazione sull'apprendimento federato per la classificazione delle immagini e mostra diversi altri approcci utili per l'apprendimento federato.

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 di apprendimento federato con altri approcci ML. Inoltre, questo consente l'uso di una gamma crescente di modelli pre-addestrati --- per esempio, l'addestramento di modelli linguistici da zero è raramente necessario, poiché numerosi modelli pre-addestrati sono ora ampiamente disponibili (vedere, ad esempio, TF Hub ). Invece, ha più senso partire da un modello pre-addestrato e perfezionarlo utilizzando Federated Learning, adattandosi alle caratteristiche particolari 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 originale di Keras, 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-addestrato seguendo il tutorial TensorFlow Generazione di testo utilizzando un RNN con un'esecuzione desiderosa . Tuttavia, invece di allenarci su The Complete Works of Shakespeare , abbiamo pre-addestrato il modello sul testo di A Tale of Two Cities e A Christmas Carol di Charles Dickens.

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

Carica ed elabora i dati federati di Shakespeare

Il pacchetto tff.simulation.datasets fornisce una varietà di set di dati suddivisi in "client", in cui ogni client corrisponde a un set di dati su un particolare dispositivo che potrebbe partecipare all'apprendimento federato.

Questi set di dati forniscono distribuzioni di dati non IID realistiche che replicano nella simulazione le sfide dell'addestramento su dati decentralizzati reali. Parte della pre-elaborazione di questi dati è stata eseguita 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 stringhe Tensors , una per ogni riga pronunciata da un particolare personaggio in un'opera di Shakespeare. Le chiavi client consistono nel nome della MUCH_ADO_ABOUT_NOTHING_OTHELLO unito al nome del personaggio, quindi per esempio MUCH_ADO_ABOUT_NOTHING_OTHELLO corrisponde alle linee per il personaggio Otello nella MUCH_ADO_ABOUT_NOTHING_OTHELLO 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 tf.data.Dataset per preparare questi dati per l'addestramento del char RNN caricato 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))

Nota che nella formazione delle sequenze originali e nella formazione dei batch 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 vuoti. Un approccio tipico per risolvere questo problema sarebbe riempire i batch con un token speciale, quindi mascherare la perdita per non tenere in considerazione i token di riempimento.

Ciò complicherebbe un po 'l'esempio, quindi per questo tutorial utilizziamo solo batch completi, come nel tutorial standard . Tuttavia, nell'impostazione federata questo problema è più significativo, perché molti utenti potrebbero avere piccoli set di dati.

Ora possiamo preelaborare 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 i dati preelaborati

Abbiamo caricato un modello keras non compilato, ma per eseguire keras_model.evaluate , dobbiamo compilarlo con una perdita e metriche. Compileremo anche un ottimizzatore, che verrà utilizzato come ottimizzatore sul dispositivo in Federated Learning.

Il tutorial originale non aveva una precisione a livello di caratteri (la frazione di previsioni in cui la probabilità più alta è stata posta sul carattere successivo corretto). Questa è una metrica utile, quindi la aggiungiamo. Tuttavia, dobbiamo definire una nuova classe di metrica per questo perché le nostre previsioni hanno il rango 3 (un vettore di logit per ciascuna delle previsioni BATCH_SIZE * SEQ_LENGTH ) e SparseCategoricalAccuracy aspetta solo previsioni di rango 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)

Ora possiamo compilare un modello e valutarlo 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

Ottimizza 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 stiamo eseguendo in modalità eager, (TF 2.0), attualmente TFF serializza i calcoli di TensorFlow costruendo le operazioni necessarie all'interno del contesto di with tf.Graph.as_default() " with tf.Graph.as_default() ". 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 per costruire un processo iterativo di Federated Averaging, che useremo per migliorare il modello (per i dettagli sull'algoritmo di Federated Averaging, vedere il documento Communication-Efficient Learning of Deep Networks from Decentralized Data ).

Usiamo un modello Keras compilato per eseguire la valutazione standard (non federata) dopo ogni round di formazione federata. Ciò è 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 benchmark 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 sia ancora 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 da fed_avg.initialize() è basato sugli inizializzatori casuali per il modello Keras, non sui pesi che sono stati caricati, poiché clone_model() non clona i pesi. Per iniziare 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 fatto abbastanza addestramento per fare una grande differenza, ma se ti alleni più a lungo su più dati di 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 taccuino:

  • Scrivi un ciclo di formazione più realistico in cui campionare i clienti su cui allenarsi in modo casuale.
  • Utilizzare " .repeat(NUM_EPOCHS) " sui set di dati del client per provare più epoche di addestramento locale (ad esempio, come in McMahan et. Al. ). Vedi anche Federated Learning for Image Classification che fa questo.
  • Modificare il comando compile() per sperimentare l'utilizzo di diversi algoritmi di ottimizzazione sul client.
  • Prova l'argomento server_optimizer a build_federated_averaging_process per provare diversi algoritmi per applicare gli aggiornamenti del modello sul server.
  • Prova l'argomento client_weight_fn a build_federated_averaging_process per provare diverse ponderazioni dei client. L'impostazione predefinita valuta gli aggiornamenti del client in base al numero di esempi sul client, ma è possibile eseguire ad esempio client_weight_fn=lambda _: tf.constant(1.0) .