Google I / O kehrt vom 18. bis 20. Mai zurück! Reservieren Sie Platz und erstellen Sie Ihren Zeitplan Registrieren Sie sich jetzt
Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Föderiertes Lernen zur Texterzeugung

Ansicht auf TensorFlow.org In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

Dieses Lernprogramm baut auf den Konzepten des Lernprogramms " Verbündetes Lernen für die Bildklassifizierung" auf und zeigt einige andere nützliche Ansätze für das Verbundlernen.

Insbesondere laden wir ein zuvor trainiertes Keras-Modell und verfeinern es mithilfe eines Verbundtrainings für einen (simulierten) dezentralen Datensatz. Dies ist aus mehreren Gründen praktisch wichtig. Die Möglichkeit, serialisierte Modelle zu verwenden, macht es einfach, Verbundlernen mit anderen ML-Ansätzen zu kombinieren. Darüber hinaus ermöglicht dies die Verwendung einer zunehmenden Anzahl von vorab trainierten Modellen - beispielsweise ist das Training von Sprachmodellen von Grund auf selten erforderlich, da zahlreiche vorab trainierte Modelle mittlerweile weit verbreitet sind (siehe z. B. TF Hub ). Stattdessen ist es sinnvoller, von einem vorab trainierten Modell auszugehen und es mithilfe von Federated Learning zu verfeinern, um es an die besonderen Merkmale der dezentralen Daten für eine bestimmte Anwendung anzupassen.

In diesem Tutorial beginnen wir mit einer RNN, die ASCII-Zeichen generiert, und verfeinern sie durch Verbundlernen. Wir zeigen auch, wie die endgültigen Gewichte auf das ursprüngliche Keras-Modell zurückgeführt werden können, was eine einfache Auswertung und Texterstellung mit Standardwerkzeugen ermöglicht.

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

Laden Sie ein vorab trainiertes Modell

Wir laden ein Modell, das nach dem TensorFlow-Tutorial vorab trainiert wurde. Texterstellung mit einem RNN mit eifriger Ausführung . Anstatt jedoch auf The Complete Works of Shakespeare zu trainieren, haben wir das Modell auf den Text aus Charles Dickens ' A Tale of Two Cities und A Christmas Carol vorbereitet.

Abgesehen von der Erweiterung des Wortschatzes haben wir das ursprüngliche Tutorial nicht geändert, sodass dieses ursprüngliche Modell nicht auf dem neuesten Stand der Technik ist, aber vernünftige Vorhersagen liefert und für unsere Tutorialzwecke ausreicht. Das endgültige Modell wurde mit tf.keras.models.save_model(include_optimizer=False) gespeichert.

In diesem Lernprogramm werden wir das Verbundlernen verwenden, um dieses Modell für Shakespeare zu optimieren. Dabei wird eine Verbundversion der von TFF bereitgestellten Daten verwendet.

Generieren Sie die Vokabeltabellen

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

Laden Sie das vorab trainierte Modell und generieren Sie Text

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

Laden und verarbeiten Sie die Federated Shakespeare-Daten

Das Paket tff.simulation.datasets bietet eine Vielzahl von Datasets, die in "Clients" aufgeteilt sind, wobei jeder Client einem Dataset auf einem bestimmten Gerät entspricht, das möglicherweise am Verbundlernen teilnimmt.

Diese Datensätze bieten realistische Nicht-IID-Datenverteilungen, die in der Simulation die Herausforderungen des Trainings an realen dezentralen Daten replizieren. Ein Teil der Vorverarbeitung dieser Daten wurde mit Tools aus dem Leaf-Projekt ( github ) durchgeführt.

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

Die von shakespeare.load_data() bereitgestellten Datensätze bestehen aus einer Folge von String- Tensors , einer für jede Zeile, die von einem bestimmten Charakter in einem Shakespeare-Spiel gesprochen wird. Die Client-Schlüssel bestehen aus dem Namen des Spiels und dem Namen des Charakters. So entspricht beispielsweise MUCH_ADO_ABOUT_NOTHING_OTHELLO den Zeilen für den Charakter Othello im Stück Viel Lärm um nichts . Beachten Sie, dass in einem realen Verbundlernszenario Clients niemals durch IDs identifiziert oder verfolgt werden. Für die Simulation ist es jedoch hilfreich, mit verschlüsselten Datensätzen zu arbeiten.

Hier können wir uns zum Beispiel einige Daten von King Lear ansehen:

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

Wir verwenden jetzttf.data.Dataset Transformationen, um diese Daten für das Training des oben geladenen char RNN vorzubereiten.

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

Beachten Sie, dass wir bei der Bildung der ursprünglichen Sequenzen und bei der Bildung der obigen drop_remainder=True Einfachheit halber drop_remainder=True . Dies bedeutet, dass alle Zeichen (Clients), die nicht mindestens (SEQ_LENGTH + 1) * BATCH_SIZE Textzeichen haben, leere Datensätze haben. Ein typischer Ansatz, um dies zu beheben, besteht darin, die Stapel mit einem speziellen Token aufzufüllen und dann den Verlust zu maskieren, um die Auffüll-Token nicht zu berücksichtigen.

Dies würde das Beispiel etwas komplizieren. Daher verwenden wir für dieses Lernprogramm nur vollständige Stapel, wie im Standard-Lernprogramm . In der Verbundeinstellung ist dieses Problem jedoch bedeutender, da viele Benutzer möglicherweise über kleine Datenmengen verfügen.

Jetzt können wir unser raw_example_dataset vorverarbeiten und die Typen überprüfen:

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

Kompilieren Sie das Modell und testen Sie die vorverarbeiteten Daten

Wir haben ein nicht kompiliertes Keras-Modell geladen, aber um keras_model.evaluate ausführen zu keras_model.evaluate , müssen wir es mit einem Verlust und Metriken kompilieren. Wir werden auch ein Optimierungsprogramm kompilieren, das als On-Device-Optimierungsprogramm in Federated Learning verwendet wird.

Das ursprüngliche Tutorial hatte keine Genauigkeit auf Zeichenebene (der Bruchteil der Vorhersagen, bei denen die höchste Wahrscheinlichkeit auf das richtige nächste Zeichen gesetzt wurde). Dies ist eine nützliche Metrik, daher fügen wir sie hinzu. Wir müssen hierfür jedoch eine neue Metrikklasse definieren, da unsere Vorhersagen Rang 3 haben (ein Protokollvektor für jede der Vorhersagen von BATCH_SIZE * SEQ_LENGTH ) und SparseCategoricalAccuracy nur Vorhersagen von Rang 2 erwartet.

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)

Jetzt können wir ein Modell kompilieren und es in unserem 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

Optimieren Sie das Modell mit Federated Learning

TFF serialisiert alle TensorFlow-Berechnungen, sodass sie möglicherweise in einer Nicht-Python-Umgebung ausgeführt werden können (obwohl derzeit nur eine in Python implementierte Simulationslaufzeit verfügbar ist). Obwohl wir im eifrigen Modus (TF 2.0) arbeiten, serialisiert TFF derzeit TensorFlow-Berechnungen, indem die erforderlichen Operationen im Kontext einer Anweisung " with tf.Graph.as_default() " erstellt werden. Daher müssen wir eine Funktion bereitstellen, mit der TFF unser Modell in ein von ihm gesteuertes Diagramm einführen kann. Wir machen das wie folgt:

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

Jetzt sind wir bereit, einen iterativen Federated Averaging-Prozess zu erstellen, mit dem wir das Modell verbessern werden (Einzelheiten zum Federated Averaging-Algorithmus finden Sie im Artikel Kommunikationseffizientes Lernen tiefer Netzwerke aus dezentralen Daten ).

Wir verwenden ein kompiliertes Keras-Modell, um nach jeder Runde des Verbundtrainings eine Standardbewertung (ohne Verbund) durchzuführen. Dies ist für Forschungszwecke nützlich, wenn simuliertes Verbundlernen durchgeführt wird, und es gibt einen Standardtestdatensatz.

In einer realistischen Produktionsumgebung kann dieselbe Technik verwendet werden, um Modelle, die mit Verbundlernen trainiert wurden, zu Test- oder Qualitätssicherungszwecken in einem zentralen Benchmark-Datensatz zu bewerten.

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

Hier ist die einfachste mögliche Schleife, in der wir eine Verbundmittelung für eine Runde auf einem einzelnen Client in einem einzelnen Stapel ausführen:

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

Schreiben wir nun eine etwas interessantere Trainings- und Bewertungsschleife.

Damit diese Simulation immer noch relativ schnell abläuft, trainieren wir in jeder Runde auf denselben drei Clients, wobei jeweils nur zwei Minibatches berücksichtigt werden.

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)

Der Anfangszustand des von fed_avg.initialize() erstellten fed_avg.initialize() basiert auf den zufälligen Initialisierern für das Keras-Modell und nicht auf den geladenen Gewichten, da clone_model() die Gewichte nicht clone_model() . Um mit dem Training von einem vorab trainierten Modell aus zu beginnen, setzen wir die Modellgewichte im Serverstatus direkt vom geladenen Modell aus.

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

Mit den Standardänderungen haben wir nicht genug trainiert, um einen großen Unterschied zu machen. Wenn Sie jedoch länger mit mehr Shakespeare-Daten trainieren, sollten Sie einen Unterschied im Stil des mit dem aktualisierten Modell generierten Textes feststellen:

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

Vorgeschlagene Erweiterungen

Dieses Tutorial ist nur der erste Schritt! Hier sind einige Ideen, wie Sie versuchen können, dieses Notizbuch zu erweitern:

  • Schreiben Sie eine realistischere Trainingsschleife, in der Sie Kunden zum zufälligen Training auswählen.
  • Verwenden Sie " .repeat(NUM_EPOCHS) " für die Client-Datasets, um mehrere Epochen des lokalen Trainings zu .repeat(NUM_EPOCHS) (z. B. wie bei McMahan et al. ). Siehe auch Federated Learning für die Bildklassifizierung, die dies tut.
  • Ändern Sie den Befehl compile() , um mit verschiedenen Optimierungsalgorithmen auf dem Client zu experimentieren.
  • Versuchen Sie das Argument server_optimizer für build_federated_averaging_process , um verschiedene Algorithmen zum Anwenden der build_federated_averaging_process auf den Server auszuprobieren.
  • Versuchen Sie das Argument build_federated_averaging_process , client_weight_fn zu build_federated_averaging_process , um verschiedene Gewichtungen der Clients zu build_federated_averaging_process . Die Standardeinstellung gewichtet Clientaktualisierungen nach der Anzahl der Beispiele auf dem Client. Sie können jedoch z. B. client_weight_fn=lambda _: tf.constant(1.0) .