Esta página foi traduzida pela API Cloud Translation.
Switch to English

Aprendizagem federada para geração de texto

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub

Este tutorial se baseia nos conceitos do tutorial Federated Learning for Image Classification e demonstra várias outras abordagens úteis para o aprendizado federado.

Em particular, carregamos um modelo Keras previamente treinado e o refinamos usando treinamento federado em um conjunto de dados descentralizado (simulado). Isso é praticamente importante por vários motivos. A capacidade de usar modelos serializados facilita a combinação do aprendizado federado com outras abordagens de ML. Além disso, isso permite o uso de uma gama crescente de modelos pré-treinados - por exemplo, treinar modelos de linguagem do zero raramente é necessário, pois vários modelos pré-treinados estão agora amplamente disponíveis (consulte, por exemplo, TF Hub ). Em vez disso, faz mais sentido começar a partir de um modelo pré-treinado e refiná-lo usando o Aprendizado Federado, adaptando-se às características específicas dos dados descentralizados para um aplicativo específico.

Para este tutorial, começamos com um RNN que gera caracteres ASCII e o refinamos por meio do aprendizado federado. Também mostramos como os pesos finais podem ser realimentados para o modelo Keras original, permitindo uma avaliação fácil e geração de texto usando ferramentas padrão.

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

Carregue um modelo pré-treinado

Carregamos um modelo que foi pré-treinado seguindo o tutorial do TensorFlow Geração de texto usando um RNN com execução rápida . No entanto, em vez de treinar em As Obras Completas de Shakespeare , pré-treinamos o modelo no texto de Um Conto de Duas Cidades e Uma Conto de Natal de Charles Dickens.

Além de expandir o vocabulário, não modificamos o tutorial original, portanto, este modelo inicial não é o estado da arte, mas produz previsões razoáveis ​​e é suficiente para nossos propósitos de tutorial. O modelo final foi salvo com tf.keras.models.save_model(include_optimizer=False) .

Usaremos o aprendizado federado para ajustar este modelo para Shakespeare neste tutorial, usando uma versão federada dos dados fornecidos pela TFF.

Gerar as tabelas de pesquisa de vocabulário

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

Carregue o modelo pré-treinado e gere algum texto

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 

Carregar e pré-processar os dados federados de Shakespeare

O pacote tff.simulation.datasets fornece uma variedade de conjuntos de dados que são divididos em "clientes", onde cada cliente corresponde a um conjunto de dados em um dispositivo específico que pode participar do aprendizado federado.

Esses conjuntos de dados fornecem distribuições de dados não-IID realistas que reproduzem na simulação os desafios do treinamento em dados reais descentralizados. Parte do pré-processamento desses dados foi feito usando ferramentas do projeto Leaf ( github ).

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

Os conjuntos de dados fornecidos por shakespeare.load_data() consistem em uma sequência de Tensors de string, um para cada linha falada por um determinado personagem em uma peça de Shakespeare. As chaves do cliente consistem no nome da peça junto com o nome do personagem, então por exemplo MUCH_ADO_ABOUT_NOTHING_OTHELLO corresponde às falas do personagem Othello na peça Much Ado About Nothing . Observe que, em um cenário de aprendizado federado real, os clientes nunca são identificados ou rastreados por ids, mas para simulação é útil trabalhar com conjuntos de dados codificados.

Aqui, por exemplo, podemos olhar alguns dados de 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)

Agora usamos transformaçõestf.data.Dataset para preparar esses dados para treinar o char RNN carregado acima.

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

Observe que na formação das sequências originais e na formação dos lotes acima, usamos drop_remainder=True para simplificar. Isso significa que quaisquer caracteres (clientes) que não tenham pelo menos (SEQ_LENGTH + 1) * BATCH_SIZE caracteres de texto terão conjuntos de dados vazios. Uma abordagem típica para resolver isso seria preencher os lotes com um token especial e, em seguida, mascarar a perda para não levar os tokens de preenchimento em consideração.

Isso complicaria um pouco o exemplo, portanto, para este tutorial, usamos apenas lotes completos, como no tutorial padrão . No entanto, na configuração federada, esse problema é mais significativo, porque muitos usuários podem ter pequenos conjuntos de dados.

Agora podemos pré-processar nosso raw_example_dataset e verificar os tipos:

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

Compile o modelo e teste os dados pré-processados

Carregamos um modelo keras não compilado, mas para executar keras_model.evaluate , precisamos compilá-lo com uma perda e métricas. Também compilaremos em um otimizador, que será usado como o otimizador no dispositivo no Federated Learning.

O tutorial original não tinha precisão de nível de caractere (a fração de previsões onde a maior probabilidade foi colocada no próximo caractere correto). Esta é uma métrica útil, por isso a adicionamos. No entanto, precisamos definir uma nova classe de métrica para isso porque nossas previsões têm classificação 3 (um vetor de logits para cada uma das previsões BATCH_SIZE * SEQ_LENGTH ) e SparseCategoricalAccuracy espera apenas previsões de classificação 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)

Agora podemos compilar um modelo e avaliá-lo em nosso 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

Ajuste o modelo com Federated Learning

O TFF serializa todos os cálculos do TensorFlow para que possam ser executados em um ambiente não Python (embora, no momento, apenas um tempo de execução de simulação implementado em Python esteja disponível). Embora estejamos executando no modo ansioso (TF 2.0), atualmente o TFF serializa os cálculos do TensorFlow construindo as operações necessárias dentro do contexto de uma with tf.Graph.as_default() " with tf.Graph.as_default() ". Portanto, precisamos fornecer uma função que a TFF possa usar para apresentar nosso modelo em um gráfico que ele controla. Fazemos isso da seguinte maneira:

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

Agora estamos prontos para construir um processo iterativo de Média Federada, que usaremos para melhorar o modelo (para obter detalhes sobre o algoritmo de Média Federada, consulte o artigo Aprendizagem Eficiente na Comunicação de Redes Profundas de Dados Descentralizados ).

Usamos um modelo Keras compilado para realizar a avaliação padrão (não federada) após cada rodada de treinamento federado. Isso é útil para fins de pesquisa ao fazer aprendizagem federada simulada e há um conjunto de dados de teste padrão.

Em um ambiente de produção realista, essa mesma técnica pode ser usada para pegar modelos treinados com aprendizado federado e avaliá-los em um conjunto de dados de referência centralizado para fins de teste ou garantia de qualidade.

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

Aqui está o loop mais simples possível, onde executamos a média federada para uma rodada em um único cliente em um único lote:

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

Agora vamos escrever um loop de treinamento e avaliação um pouco mais interessante.

Para que esta simulação ainda rode relativamente rápido, treinamos nos mesmos três clientes a cada rodada, considerando apenas dois minibatches para cada.

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)

O estado inicial do modelo produzido por fed_avg.initialize() é baseado nos inicializadores aleatórios para o modelo Keras, não nos pesos que foram carregados, uma vez que clone_model() não clone_model() os pesos. Para iniciar o treinamento a partir de um modelo pré-treinado, definimos os pesos do modelo no estado do servidor diretamente do modelo carregado.

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

Com as alterações padrão, não fizemos treinamento suficiente para fazer uma grande diferença, mas se você treinar por mais tempo com mais dados de Shakespeare, verá uma diferença no estilo do texto gerado com o modelo atualizado:

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

Extensões sugeridas

Este tutorial é apenas o primeiro passo! Aqui estão algumas idéias de como você pode tentar estender este bloco de notas:

  • Escreva um ciclo de treinamento mais realista em que você amostra clientes para treinar aleatoriamente.
  • Use " .repeat(NUM_EPOCHS) " nos conjuntos de dados do cliente para tentar várias épocas de treinamento local (por exemplo, como em McMahan et. Al. ). Consulte também Aprendizado federado para classificação de imagens que faz isso.
  • Altere o comando compile() para experimentar o uso de diferentes algoritmos de otimização no cliente.
  • Tente o argumento server_optimizer para build_federated_averaging_process para tentar diferentes algoritmos para aplicar as atualizações de modelo no servidor.
  • Tente o argumento client_weight_fn para to build_federated_averaging_process para tentar diferentes pesos dos clientes. O padrão pondera as atualizações do cliente pelo número de exemplos no cliente, mas você pode fazer, por exemplo, client_weight_fn=lambda _: tf.constant(1.0) .