לשמור את התאריך! קלט / פלט של Google חוזר 18-20 במאי הירשם עכשיו
דף זה תורגם על ידי Cloud Translation API.
Switch to English

סקירה כללית

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

לפני שאנחנו מתחילים

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

!pip install --quiet --upgrade tensorflow-federated-nightly
!pip install --quiet --upgrade nest-asyncio

import nest_asyncio
nest_asyncio.apply()
import collections
import attr
import functools
import numpy as np
import tensorflow as tf
import tensorflow_federated as tff

np.random.seed(0)

בהדרכות סיווג התמונה והפקת טקסטים למדנו כיצד להגדיר צינורות מודלים ונתונים עבור למידה מאוחדת (FL), tff.learning הכשרה מאוחדת באמצעות שכבת ה- API של tff.learning של TFF.

זה רק קצה הקרחון כשמדובר במחקר של פלורידה. במדריך זה נדון כיצד ליישם אלגוריתמי למידה מאוחדים מבלי לדחות את ממשק ה- API של tff.learning . אנו שואפים להשיג את הדברים הבאים:

מטרות:

  • להבין את המבנה הכללי של אלגוריתמי למידה מאוחדים.
  • חקור את הליבה המאוחדת של TFF.
  • השתמש ב Core Federated כדי ליישם את ה- Federated Averaging ישירות.

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

הכנת נתוני הקלט

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

emnist_train, emnist_test = tff.simulation.datasets.emnist.load_data()

על מנת להזין את מערך הנתונים במודל שלנו, אנו משטחים את הנתונים וממירים כל דוגמה לטופלה של הטופס (flattened_image_vector, label) .

NUM_CLIENTS = 10
BATCH_SIZE = 20

def preprocess(dataset):

  def batch_format_fn(element):
    """Flatten a batch of EMNIST data and return a (features, label) tuple."""
    return (tf.reshape(element['pixels'], [-1, 784]), 
            tf.reshape(element['label'], [-1, 1]))

  return dataset.batch(BATCH_SIZE).map(batch_format_fn)

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

client_ids = np.random.choice(emnist_train.client_ids, size=NUM_CLIENTS, replace=False)

federated_train_data = [preprocess(emnist_train.create_tf_dataset_for_client(x))
  for x in client_ids
]

הכנת המודל

אנו משתמשים באותו מודל כמו במדריך לסיווג תמונות . למודל זה (מיושם באמצעות tf.keras ) יש שכבה נסתרת אחת, ואחריה שכבת softmax.

def create_keras_model():
  return tf.keras.models.Sequential([
      tf.keras.layers.Input(shape=(784,)),
      tf.keras.layers.Dense(10, kernel_initializer='zeros'),
      tf.keras.layers.Softmax(),
  ])

על מנת להשתמש במודל זה ב- TFF, אנו עוטפים את מודל Keras כ- tff.learning.Model . זה מאפשר לנו לבצע את המעבר קדימה של המודל בתוך TFF ולחלץ תפוקות מודל . לפרטים נוספים, עיין גם במדריך לסיווג תמונות .

def model_fn():
  keras_model = create_keras_model()
  return tff.learning.from_keras_model(
      keras_model,
      input_spec=federated_train_data[0].element_spec,
      loss=tf.keras.losses.SparseCategoricalCrossentropy(),
      metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])

אמנם השתמשנו ב- tf.keras כדי ליצור tff.learning.Model , אך TFF תומך במודלים כלליים הרבה יותר. למודלים אלה יש את המאפיינים הרלוונטיים הבאים שלוכדים את משקולות המודל:

  • trainable_variables : An iterable של טנזורים המתאים שכבות שאפשר לאלף.
  • non_trainable_variables : ניתן לחזור על הטנזורים המתאימים לשכבות שאינן ניתנות לאילוף.

למטרותינו, נשתמש רק trainable_variables . (שכן למודל שלנו יש רק כאלה!).

בניית אלגוריתם למידה פדרציה משלך

בעוד tff.learning ה- API של tff.learning מאפשר ליצור גרסאות רבות של Federated Averaging, ישנם אלגוריתמים מאוחדים אחרים שאינם משתלבים בצורה מסודרת במסגרת זו. לדוגמא, ייתכן שתרצה להוסיף אלגוריתמים רגולציה, גזירה או מסובכים יותר כגון אימון GAN מאוחד . ייתכן שתתעניין במקום זאת בניתוחים מאוחדים .

עבור אלגוריתמים מתקדמים יותר אלה, נצטרך לכתוב אלגוריתם מותאם אישית משלנו באמצעות TFF. במקרים רבים, אלגוריתמים מאוחדים מכילים 4 מרכיבים עיקריים:

  1. שלב שידור משרת ללקוח.
  2. שלב עדכון לקוח מקומי.
  3. שלב העלאת לקוח לשרת.
  4. שלב עדכון שרת.

ב- TFF, אנו בדרך כלל מייצגים אלגוריתמים מאוחדים כ- tff.templates.IterativeProcess (שאנו מכנים רק IterativeProcess לאורך כל הדרך). זהו מחלקה המכילה initialize ופונקציות next . כאן, initialize משמש לאתחול השרת, next יבצע סבב תקשורת אחד של האלגוריתם המאוחד. בוא נכתוב שלד כיצד אמור להראות תהליך האיטראציה שלנו עבור FedAvg.

ראשית, יש לנו פונקציה לאתחול שפשוט יוצרת tff.learning.Model ומחזירה את המשקולות שניתן tff.learning.Model .

def initialize_fn():
  model = model_fn()
  return model.trainable_variables

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

אנחנו רוצים גם לשרטט את next_fn .

def next_fn(server_weights, federated_dataset):
  # Broadcast the server weights to the clients.
  server_weights_at_client = broadcast(server_weights)

  # Each client computes their updated weights.
  client_weights = client_update(federated_dataset, server_weights_at_client)

  # The server averages these updates.
  mean_client_weights = mean(client_weights)

  # The server updates its model.
  server_weights = server_update(mean_client_weights)

  return server_weights

אנו נתמקד ביישום ארבעת המרכיבים הללו בנפרד. ראשית אנו מתמקדים בחלקים הניתנים ליישום ב- TensorFlow טהור, כלומר בשלבי עדכון הלקוח והשרת.

בלוקים של TensorFlow

עדכון לקוח

אנו נשתמש ב- tff.learning.Model שלנו כדי לבצע הכשרת לקוחות באותה צורה בה היית מאמן מודל TensorFlow. בפרט, נשתמש ב-tf.GradientTape כדי לחשב את השיפוע על קבוצות נתונים, ולאחר מכן client_optimizer את השיפוע באמצעות client_optimizer . אנו מתמקדים רק במשקולות שאפשר לאמן.

@tf.function
def client_update(model, dataset, server_weights, client_optimizer):
  """Performs training (using the server model weights) on the client's dataset."""
  # Initialize the client model with the current server weights.
  client_weights = model.trainable_variables
  # Assign the server weights to the client model.
  tf.nest.map_structure(lambda x, y: x.assign(y),
                        client_weights, server_weights)

  # Use the client_optimizer to update the local model.
  for batch in dataset:
    with tf.GradientTape() as tape:
      # Compute a forward pass on the batch of data
      outputs = model.forward_pass(batch)

    # Compute the corresponding gradient
    grads = tape.gradient(outputs.loss, client_weights)
    grads_and_vars = zip(grads, client_weights)

    # Apply the gradient using a client optimizer.
    client_optimizer.apply_gradients(grads_and_vars)

  return client_weights

עדכון שרת

עדכון השרת עבור FedAvg פשוט יותר מעדכון הלקוח. אנו מיישמים ממוצע פדרלי "וניל", בו אנו פשוט מחליפים את משקולות מודל השרת בממוצע של משקולות מודל הלקוח. שוב, אנו מתמקדים רק במשקולות שאפשר לאמן.

@tf.function
def server_update(model, mean_client_weights):
  """Updates the server model weights as the average of the client model weights."""
  model_weights = model.trainable_variables
  # Assign the mean client weights to the server model.
  tf.nest.map_structure(lambda x, y: x.assign(y),
                        model_weights, mean_client_weights)
  return model_weights

ניתן לפשט את קטע הטקסט פשוט על ידי החזרת mean_client_weights . עם זאת, יישומים מתקדמים יותר של Federated Averaging משתמשים mean_client_weights טכניקות מתוחכמות יותר, כמו מומנטום או הסתגלות.

אתגר : יישם גרסה של server_update המעדכנת את משקלי השרת כך שתהיה נקודת האמצע של משקולות דגם ומשקולות_ממוצע_. (הערה: גישה מסוג "נקודת אמצע" מסוג זה מקבילה לעבודה האחרונה בנושא אופטימיזציית Lookahead !).

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

זה ידרוש את הליבה המאוחדת של TFF.

מבוא לליבה המאוחדת

ה- Federated Core (FC) הוא קבוצה של ממשקים ברמה נמוכה יותר המשמשים את הבסיס tff.learning ה- API של tff.learning . עם זאת, ממשקים אלה אינם מוגבלים ללמידה. למעשה, הם יכולים לשמש לניתוח ולחישובים רבים אחרים על פני נתונים מבוזרים.

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

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

נתונים מאוחדים

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

לדוגמא, נניח שיש לנו מכשירי לקוח שלכל אחד מהם יש מצוף המייצג את הטמפרטורה של חיישן. נוכל לייצג אותו כצף מאוחד

federated_float_on_clients = tff.FederatedType(tf.float32, tff.CLIENTS)

סוגים מאוחדים מוגדרים לפי סוג T של המרכיבים החברים בו (למשל tf.float32 ) וקבוצת G של מכשירים. אנו נתמקד במקרים בהם G הוא tff.CLIENTS או tff.SERVER . סוג מאוחד כזה מיוצג כ- {T}@G , כפי שמוצג להלן.

str(federated_float_on_clients)
'{float32}@CLIENTS'

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

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

חישובים מאוחדים

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

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

@tff.federated_computation(tff.FederatedType(tf.float32, tff.CLIENTS))
def get_average_temperature(client_temperatures):
  return tff.federated_mean(client_temperatures)

אתה יכול לשאול, tf.function זה שונה tf.function tf.function ב- TensorFlow? התשובה העיקרית היא שהקוד שנוצר על ידי tff.federated_computation אינו קוד TensorFlow ולא Python; זהו מפרט של מערכת מבוזרת בשפת דבק פנימית שאינה תלויה בפלטפורמה.

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

str(get_average_temperature.type_signature)
'({float32}@CLIENTS -> float32@SERVER)'

tff.federated_computation זה מקבל טיעונים מהסוג המאוחד {float32}@CLIENTS ומחזיר ערכים מהסוג המאוחד {float32}@SERVER . חישובים מאוחדים עשויים לעבור גם משרת ללקוח, מלקוח ללקוח או משרת לשרת. חישובים מאוחדים יכולים גם להיות מורכבים כמו פונקציות רגילות, כל עוד חתימות הסוג שלהם מתאימות.

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

get_average_temperature([68.5, 70.3, 69.8])
69.53334

חישובים לא להוטים ו- TensorFlow

יש להכיר שתי הגבלות עיקריות. ראשית, כאשר המתורגמן של פייתון נתקל tff.federated_computation , הפונקציה tff.federated_computation פעם אחת tff.federated_computation לשימוש עתידי. בשל האופי המבוזר של למידה מאוחדת, שימוש עתידי זה עשוי להתרחש במקומות אחרים, כגון סביבת ביצוע מרחוק. לכן, חישובי TFF הם באופן בסיסי לא להוטים . התנהגות זו היא דומה למדי לזה של tf.function מעצב ב TensorFlow.

שנית, חישוב מאוחד יכול לכלול רק אופרטורים מאוחדים (כגון tff.federated_mean ), הם אינם יכולים להכיל פעולות TensorFlow. קוד TensorFlow חייב להיות מוגבל לבלוקים מעוטרים ב- tff.tf_computation . ניתן לקשט ישירות את קוד TensorFlow הרגיל ביותר, כמו הפונקציה הבאה שלוקחת מספר ומוסיפה לו 0.5 .

@tff.tf_computation(tf.float32)
def add_half(x):
  return tf.add(x, 0.5)

לאלה יש גם חתימות סוג, אך ללא מיקומים . למשל, אנחנו יכולים להתקשר

str(add_half.type_signature)
'(float32 -> float32)'

כאן אנו רואים הבדל חשוב בין tff.federated_computation ו- tff.tf_computation . לשניים יש מיקומים מפורשים, ואילו לשניים אין.

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

@tff.federated_computation(tff.FederatedType(tf.float32, tff.CLIENTS))
def add_half_on_clients(x):
  return tff.federated_map(add_half, x)

פונקציה זו כמעט זהה ל- add_half , אלא שהיא מקבלת רק ערכים עם מיקום ב- tff.CLIENTS ומחזירה ערכים עם אותה מיקום. אנו יכולים לראות זאת בחתימת הסוג שלו:

str(add_half_on_clients.type_signature)
'({float32}@CLIENTS -> {float32}@CLIENTS)'

לסיכום:

  • TFF פועלת על פי ערכים מאוחדים.
  • לכל ערך מאוחד יש סוג מאוחד , עם סוג (למשל tf.float32 ) ומיקום (למשל tff.CLIENTS ).
  • ניתן לשנות ערכים מאוחדים באמצעות חישובים מאוחדים , שעליהם להיות מעוטרים ב- tff.federated_computation ובחתימת סוג מאוחד.
  • קוד TensorFlow חייב להיות כלול בבלוקים עם מעצבי tff.tf_computation .
  • לאחר מכן ניתן לשלב בלוקים אלה בחישובים מאוחדים.

בונה אלגוריתם למידה פדרציה משלך, מחדש

כעת, לאחר שקיבלנו הצצה לליבה המאוחדת, אנו יכולים לבנות אלגוריתם למידה מאוחד. זכור שלמעלה, הגדרנו initialize_fn ו- next_fn עבור האלגוריתם שלנו. next_fn יעשה שימוש ב- client_update server_update שהגדרנו באמצעות קוד TensorFlow טהור.

עם זאת, על מנת להפוך את האלגוריתם שלנו חישוב מאוחד נצטרך הוא next_fn ו initialize_fn לכל להיות tff.federated_computation .

חסימות מאוגדות של TensorFlow

יצירת חישוב האתחול

פונקציית האתחול תהיה די פשוטה: ניצור מודל באמצעות model_fn . עם זאת, זכור שעלינו להפריד את קוד TensorFlow שלנו באמצעות tff.tf_computation .

@tff.tf_computation
def server_init():
  model = model_fn()
  return model.trainable_variables

לאחר מכן נוכל להעביר זאת ישירות לחישוב מאוחד באמצעות tff.federated_value .

@tff.federated_computation
def initialize_fn():
  return tff.federated_value(server_init(), tff.SERVER)

יצירת next_fn

כעת אנו משתמשים בקוד עדכון הלקוח והשרת שלנו כדי לכתוב את האלגוריתם בפועל. ראשית נהפוך את client_update שלנו ל- tff.tf_computation שמקבל מערכי נתונים של לקוח ומשקלי שרת, tff.tf_computation טנסור משקולות לקוח מעודכן.

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

dummy_model = model_fn()
tf_dataset_type = tff.SequenceType(dummy_model.input_spec)

בואו נסתכל על חתימת סוג הנתונים. זכרו שצילמנו 28 על 28 תמונות (עם תוויות שלמות) ושיטחנו אותן.

str(tf_dataset_type)
'<float32[?,784],int32[?,1]>*'

אנו יכולים גם לחלץ את סוג משקולות הדגם באמצעות פונקציית server_init שלנו לעיל.

model_weights_type = server_init.type_signature.result

בבחינת חתימת הסוג נוכל לראות את הארכיטקטורה של המודל שלנו!

str(model_weights_type)
'<float32[784,10],float32[10]>'

כעת אנו יכולים ליצור את tff.tf_computation שלנו לעדכון הלקוח.

@tff.tf_computation(tf_dataset_type, model_weights_type)
def client_update_fn(tf_dataset, server_weights):
  model = model_fn()
  client_optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
  return client_update(model, tf_dataset, server_weights, client_optimizer)

ניתן tff.tf_computation גרסת tff.tf_computation של עדכון השרת באופן דומה, באמצעות סוגים שכבר חילצנו.

@tff.tf_computation(model_weights_type)
def server_update_fn(mean_client_weights):
  model = model_fn()
  return server_update(model, mean_client_weights)

אחרון חביב, עלינו ליצור את tff.federated_computation שמביא את כל זה יחד. פונקציה זו תקבל שני ערכים מאוחדים , האחד המתאים למשקלי השרת (עם מיקום tff.SERVER ), והשני המתאים למערכי הנתונים של הלקוח (עם מיקום tff.CLIENTS ).

שימו לב ששני הסוגים הללו הוגדרו לעיל! אנחנו פשוט צריכים לתת להם את המיקום הנכון באמצעות tff.FederatedType .

federated_server_type = tff.FederatedType(model_weights_type, tff.SERVER)
federated_dataset_type = tff.FederatedType(tf_dataset_type, tff.CLIENTS)

זוכר את 4 האלמנטים של אלגוריתם FL?

  1. שלב שידור משרת ללקוח.
  2. שלב עדכון לקוח מקומי.
  3. שלב העלאת לקוח לשרת.
  4. שלב עדכון שרת.

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

@tff.federated_computation(federated_server_type, federated_dataset_type)
def next_fn(server_weights, federated_dataset):
  # Broadcast the server weights to the clients.
  server_weights_at_client = tff.federated_broadcast(server_weights)

  # Each client computes their updated weights.
  client_weights = tff.federated_map(
      client_update_fn, (federated_dataset, server_weights_at_client))

  # The server averages these updates.
  mean_client_weights = tff.federated_mean(client_weights)

  # The server updates its model.
  server_weights = tff.federated_map(server_update_fn, mean_client_weights)

  return server_weights

כעת יש לנו tff.federated_computation הן לאתחול האלגוריתם והן להפעלת שלב אחד של האלגוריתם. לסיום האלגוריתם שלנו, אנו מעבירים אותם ל- tff.templates.IterativeProcess .

federated_algorithm = tff.templates.IterativeProcess(
    initialize_fn=initialize_fn,
    next_fn=next_fn
)

בואו נסתכל על חתימת הסוג של initialize והפונקציות next של תהליך האיטראציה שלנו.

str(federated_algorithm.initialize.type_signature)
'( -> <float32[784,10],float32[10]>@SERVER)'

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

str(federated_algorithm.next.type_signature)
'(<<float32[784,10],float32[10]>@SERVER,{<float32[?,784],int32[?,1]>*}@CLIENTS> -> <float32[784,10],float32[10]>@SERVER)'

כאן אנו רואים כי federated_algorithm.next מקבל מודל שרת ונתוני לקוח ומחזיר מודל שרת מעודכן.

הערכת האלגוריתם

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

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

שים לב שאנחנו take את 1000 האלמנטים הראשונים רק מסיבות של יעילות חישובית, אך בדרך כלל נשתמש במערך הבדיקה כולו.

central_emnist_test = emnist_test.create_tf_dataset_from_all_clients().take(1000)
central_emnist_test = preprocess(central_emnist_test)

לאחר מכן אנו כותבים פונקציה המקבלת מצב שרת ומשתמשים ב- Keras כדי להעריך את מערך הבדיקה. אם אתה מכיר את tf.Keras , כל זה ייראה מוכר, אם כי שימו לב לשימוש set_weights !

def evaluate(server_state):
  keras_model = create_keras_model()
  keras_model.compile(
      loss=tf.keras.losses.SparseCategoricalCrossentropy(),
      metrics=[tf.keras.metrics.SparseCategoricalAccuracy()]  
  )
  keras_model.set_weights(server_state)
  keras_model.evaluate(central_emnist_test)

עכשיו, בואו נתחל את האלגוריתם שלנו ונעריך את ערכת הבדיקה.

server_state = federated_algorithm.initialize()
evaluate(server_state)
50/50 [==============================] - 0s 2ms/step - loss: 2.3026 - sparse_categorical_accuracy: 0.0910

בואו נתאמן לכמה סיבובים ונראה אם ​​משהו משתנה.

for round in range(15):
  server_state = federated_algorithm.next(server_state, federated_train_data)
evaluate(server_state)
50/50 [==============================] - 0s 1ms/step - loss: 2.1706 - sparse_categorical_accuracy: 0.2440

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

שינוי האלגוריתם שלנו

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

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

אתגר: הוסף גזיר מעבר צבע לפונקציה client_update .

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

אתגר קשה יותר: יישם ממוצעים מאוחדים עם ירידה בשיעור הלמידה על הלקוחות.

בשלב זה, ייתכן שתתחיל להבין כמה גמישות יש במה שאתה יכול ליישם במסגרת זו. לרעיונות (כולל התשובה לאתגר הקשה לעיל) תוכלו לראות את קוד המקור עבור tff.learning.build_federated_averaging_process , או לבדוק פרויקטים מחקריים שונים באמצעות TFF.