למידה מאוחדת ליצירת טקסטים

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-GitHub הורד מחברת

הדרכה זו בונה על המושגים של למידת Federated עבור סיווג תמונת הדרכה, ומציגה מספר גישות שימושיות נוספות ללמידת Federated.

בפרט, אנו טוענים מודל Keras שעבר הכשרה, ומשכללים אותו באמצעות אימון מאוחד על מערך נתונים מבוזר (מדומה). זה חשוב מעשית מכמה סיבות. היכולת להשתמש במודלים סדרתיים מקלה על שילוב למידה מאוחדת עם גישות ML אחרות. יתר על כן, זה מאפשר שימוש של מגוון הולך וגדל של דגמים מאומנים מראש --- למשל, מודלי שפת הדרכה מאפסים הם לעתים נדירות יש צורך, כמו מודלים טרום מאומנים רבים זמינים כעת נרחב (ראה, למשל, רכזת TF ). במקום זאת, הגיוני יותר להתחיל ממודל שהוכשר מראש, ולחדד אותו באמצעות Federated Learning, תוך התאמה למאפיינים הספציפיים של הנתונים המבוזרים עבור יישום מסוים.

עבור הדרכה זו, אנו מתחילים עם RNN שיוצר תווי ASCII, ומשכללים אותו באמצעות למידה מאוחדת. אנו גם מראים כיצד ניתן להחזיר את המשקולות הסופיות למודל Keras המקורי, מה שמאפשר הערכה קלה ויצירת טקסט באמצעות כלים סטנדרטיים.

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

טען דגם מאומן מראש

אנו לטעון מודל היה מראש מאומן בעקבות הדרכת TensorFlow דור טקסט באמצעות RNN עם ביצוע להוט . עם זאת, במקום הכשרה על מכלול יצירתו של שייקספיר , אנו מראש מאומן המודל על טקסט מתוך צ'ארלס דיקנס מעשייה אחת של שתי ערים ו A Christmas Carol .

מלבד הרחבת אוצר המילים, לא שינינו את ההדרכה המקורית, כך שהמודל הראשוני הזה אינו מתקדם, אבל הוא מייצר תחזיות סבירות ומספיק למטרות ההדרכה שלנו. המודל הסופי ניצל עם tf.keras.models.save_model(include_optimizer=False) .

אנו נשתמש בלמידה מאוחדת כדי לכוונן את המודל הזה עבור שייקספיר במדריך זה, באמצעות גרסה מאוחדת של הנתונים שסופקו על ידי TFF.

צור את טבלאות בדיקת הקול

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

טען את המודל שהוכשר מראש והפק קצת טקסט

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

טען ועבד מראש את נתוני שייקספיר המאוחדים

tff.simulation.datasets החבילה מספקת מגוון של מערכי נתונים אשר התפצלה "לקוחות", שבו כל אחד מהם מקביל הלקוח במערך במכשיר מסוים שעשוי להשתתף בלמידה Federated.

מערכי נתונים אלה מספקים הפצות נתונים מציאותיים שאינם IID המשכפלים בסימולציה את האתגרים של אימון על נתונים מבוזרים אמיתיים. חלק העיבוד-מראש של נתונים אלה נעשה שימוש בכלים מן הפרויקט ליף ( GitHub ).

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

מערכי נתונים שסופקו על ידי shakespeare.load_data() מורכב מרצף של מחרוזת Tensors , אחד לכל קו המדוברת על ידי דמות מסוימת במחזה של שייקספיר. מפתחות הלקוח מורכבים בשם המחזה הצטרף עם השם של הדמות, כך למשל MUCH_ADO_ABOUT_NOTHING_OTHELLO תואמת לקווים עבור הדמות אותלו במחזה מהומה על לא מאומה. שים לב שבתרחיש אמיתי של למידה מאוחדת לקוחות לעולם אינם מזוהים או עוקבים אחר מזהים, אך לצורך סימולציה כדאי לעבוד עם מערכי נתונים מקודדים.

כאן, למשל, אנו יכולים להסתכל על כמה נתונים מהמלך ליר:

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

כעת אנו משתמשים tf.data.Dataset טרנספורמציות להכין נתונים'תרגול העוזרת 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))

הערה כי ההיווצרות של הרצפים המקוריים וב ההיווצרות של קבוצות לעיל, אנו משתמשים drop_remainder=True לפשטות. אמצעי זה כי תווים כלשהם (לקוחות) כי אין לפחות (SEQ_LENGTH + 1) * BATCH_SIZE תווים של טקסט יצטרך מערכי נתונים ריקים. גישה טיפוסית לטפל בזה תהיה לרפד את האצוות עם אסימון מיוחד, ולאחר מכן להסוות את ההפסד כדי לא לקחת בחשבון את אסימוני הריפוד.

זה יסבך את הדוגמה במקצת, ולכן עבור הדרכה זו אנו משתמשים רק בקבוצות מלאות, כמו ההדרכה הסטנדרטית . עם זאת, בהגדרה המאוחדת בעיה זו משמעותית יותר, מכיוון שלמשתמשים רבים עשויים להיות מערכי נתונים קטנים.

עכשיו אנחנו יכולים preprocess שלנו raw_example_dataset , ולבדוק את הסוגים:

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

הרכיב את המודל ובדוק את הנתונים המעובדים מראש

העמסנו מודל keras עבר קומפילציה, אבל כדי לרוץ keras_model.evaluate , אנחנו צריכים לאסוף אותו עם הפסד ומדדים. כמו כן, נעשה קומפילציה באופטימיזר, שישמש כמייעל במכשיר ב-Federated Learning.

למדריך המקורי לא היה דיוק ברמת ה-char (חלק מהתחזיות שבו ההסתברות הגבוהה ביותר הונחה על ה-char הבא הנכון). זהו מדד שימושי, אז אנחנו מוסיפים אותו. עם זאת, אנחנו צריכים להגדיר מחלקה ערך חדש זה כי יש את תחזיותינו דרגה 3 (א וקטור של logits עבור כל אחד BATCH_SIZE * SEQ_LENGTH תחזיות), ו SparseCategoricalAccuracy מצפה רק 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)

עכשיו אנחנו יכולים להרכיב מודל, ולהעריך אותו על שלנו 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

כוונן את המודל עם Federated Learning

TFF מסדרת את כל חישובי TensorFlow כך שיוכלו להיות מופעלים בסביבה שאינה של Python (למרות שכרגע, רק זמן ריצה של סימולציה מיושם ב-Python זמין). למרות שאנו פועלים במצב להוט, (TF 2.0), כיום TFF serializes TensorFlow חישובים על ידי בניית ops הכרחי בתוך ההקשר של " with tf.Graph.as_default() " הצהרה. לפיכך, אנחנו צריכים לספק פונקציה שבה TFF יכול להשתמש כדי להציג את המודל שלנו לתוך גרף שהוא שולט. אנו עושים זאת באופן הבא:

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

עכשיו אנחנו מוכנים לבנות תהליך איטרטיבי ממוצעי Federated, אשר נשתמש כדי לשפר את המודל (לפרטים על אלגוריתם ממוצעי Federated, רואה את נייר למידה יעילה-תקשורת של דיפ רשתות מתוך מבוזר נתונים ).

אנו משתמשים במודל Keras מלוקט כדי לבצע הערכה סטנדרטית (לא מאוחדת) לאחר כל סבב של הכשרה מאוחדת. זה שימושי למטרות מחקר בעת ביצוע סימולציה של למידה מאוחדת וישנו מערך נתונים סטנדרטי לבדיקה.

במסגרת ייצור מציאותית ניתן להשתמש באותה טכניקה כדי לקחת מודלים שהוכשרו עם למידה מאוחדת ולהעריך אותם במערך נתונים מרכזי בנצ'מרק למטרות בדיקה או אבטחת איכות.

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

הנה הלולאה הפשוטה ביותר האפשרית, שבה אנו מריצים ממוצע מאוחד לסיבוב אחד על לקוח בודד באצווה אחת:

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

עכשיו בואו נכתוב לולאת הדרכה והערכה קצת יותר מעניינת.

כדי שהסימולציה הזו עדיין תפעל מהר יחסית, אנחנו מתאמנים על אותם שלושה לקוחות בכל סיבוב, רק לוקחים בחשבון שני מיני-באצ'ים לכל אחד.

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)

המצב ההתחלתי של הדגם המיוצר על ידי fed_avg.initialize() מבוסס על initializers האקראי עבור מודל Keras, לא משקולת כי הועמסו, מאז clone_model() לא שיבוט את המשקולות. כדי להתחיל אימון מדגם מאומן מראש, אנו מגדירים את משקלי הדגם במצב השרת ישירות מהדגם הנטען.

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

עם שינויי ברירת המחדל, לא עשינו מספיק אימונים כדי לעשות הבדל גדול, אבל אם אתה מתאמן יותר על נתונים נוספים של שייקספיר, אתה אמור לראות הבדל בסגנון הטקסט שנוצר עם המודל המעודכן:

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

הרחבות מוצעות

הדרכה זו היא רק הצעד הראשון! הנה כמה רעיונות כיצד תוכל לנסות להרחיב את המחברת הזו:

  • כתבו לולאת אימון מציאותית יותר שבה תדגמו לקוחות להתאמן עליהם באופן אקראי.
  • השתמש " .repeat(NUM_EPOCHS) " על מערכי נתונים הלקוח לנסות תקופות רבות של אימונים מקומיים (למשל, כמו McMahan et. Al. ). ראו גם Federated למידה עבור סיווג תמונה אשר עושה זאת.
  • שנו את compile() הפקודה להתנסות באמצעות אלגוריתמים אופטימיזציה שונות על הלקוח.
  • נסה את server_optimizer טיעון כדי build_federated_averaging_process לנסות אלגוריתמים שונים ליישום עדכוני המודל בשרת.
  • נסו את client_weight_fn טיעון כדי כדי build_federated_averaging_process לנסות במשקלים שונים של לקוחות. העדכונים הלקוח משקולות מחדל במספר דוגמאות על הלקוח, אבל אתה יכול לעשות למשל client_weight_fn=lambda _: tf.constant(1.0) .