דף זה תורגם על ידי Cloud Translation API.
Switch to English

למידה מאוחדת לסיווג תמונות

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

במדריך זה אנו משתמשים בדוגמת ההדרכה הקלאסית של MNIST כדי להציג את שכבת ה- API של Federated Learning (FL) של TFF, tff.learning - קבוצה של ממשקים ברמה גבוהה יותר שניתן להשתמש בהם לביצוע סוגים נפוצים של משימות למידה מאוחדות, כגון הכשרה מאוחדת, כנגד מודלים המסופקים על ידי המשתמשים המיושמים ב- TensorFlow.

מדריך זה ו- API של Federated Learning מיועדים בעיקר למשתמשים שרוצים לחבר את דגמי TensorFlow שלהם ל- TFF, ומתייחסים לרוב כאל קופסה שחורה. להבנה מעמיקה יותר של TFF וכיצד ליישם אלגוריתמי למידה מאוחדים משלך, עיין בהדרכות בממשק ה- API של FC Core - אלגוריתמים מאוחדים בהתאמה אישית חלק 1 וחלק 2 .

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

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

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


!pip install --quiet --upgrade tensorflow_federated_nightly
!pip install --quiet --upgrade nest_asyncio

import nest_asyncio
nest_asyncio.apply()

%load_ext tensorboard
import collections

import numpy as np
import tensorflow as tf
import tensorflow_federated as tff

np.random.seed(0)

tff.federated_computation(lambda: 'Hello, World!')()
b'Hello, World!'

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

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

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

כך נוכל לטעון אותו.

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

load_data() הנתונים המוחזרים על ידי load_data() הם מקרים של tff.simulation.ClientData , ממשק המאפשר לך למנות את קבוצת המשתמשים, לבנות tf.data.Dataset המייצג את הנתונים של משתמש מסוים tf.data.Dataset את מבנה של אלמנטים בודדים. כך תוכל להשתמש בממשק זה כדי לחקור את תוכן מערך הנתונים. זכור שבעוד ממשק זה מאפשר לך לחזור על מזהי לקוחות, זו רק תכונה של נתוני ההדמיה. כפי שתראו בקרוב, זהות הלקוח אינה משמשת את מסגרת הלמידה המאוחדת - מטרתם היחידה היא לאפשר לכם לבחור קבוצות משנה של הנתונים לצורך הדמיות.

len(emnist_train.client_ids)
3383
emnist_train.element_type_structure
OrderedDict([('pixels', TensorSpec(shape=(28, 28), dtype=tf.float32, name=None)), ('label', TensorSpec(shape=(), dtype=tf.int32, name=None))])
example_dataset = emnist_train.create_tf_dataset_for_client(
    emnist_train.client_ids[0])

example_element = next(iter(example_dataset))

example_element['label'].numpy()
1
from matplotlib import pyplot as plt
plt.imshow(example_element['pixels'].numpy(), cmap='gray', aspect='equal')
plt.grid(False)
_ = plt.show()

png

חקר הטרוגניות בנתונים מאוחדים

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

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

## Example MNIST digits for one client
figure = plt.figure(figsize=(20, 4))
j = 0

for example in example_dataset.take(40):
  plt.subplot(4, 10, j+1)
  plt.imshow(example['pixels'].numpy(), cmap='gray', aspect='equal')
  plt.axis('off')
  j += 1

png

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

# Number of examples per layer for a sample of clients
f = plt.figure(figsize=(12, 7))
f.suptitle('Label Counts for a Sample of Clients')
for i in range(6):
  client_dataset = emnist_train.create_tf_dataset_for_client(
      emnist_train.client_ids[i])
  plot_data = collections.defaultdict(list)
  for example in client_dataset:
    # Append counts individually per label to make plots
    # more colorful instead of one color per plot.
    label = example['label'].numpy()
    plot_data[label].append(label)
  plt.subplot(2, 3, i+1)
  plt.title('Client {}'.format(i))
  for j in range(10):
    plt.hist(
        plot_data[j],
        density=False,
        bins=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

png

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

# Each client has different mean images, meaning each client will be nudging
# the model in their own directions locally.

for i in range(5):
  client_dataset = emnist_train.create_tf_dataset_for_client(
      emnist_train.client_ids[i])
  plot_data = collections.defaultdict(list)
  for example in client_dataset:
    plot_data[example['label'].numpy()].append(example['pixels'].numpy())
  f = plt.figure(i, figsize=(12, 5))
  f.suptitle("Client #{}'s Mean Image Per Label".format(i))
  for j in range(10):
    mean_img = np.mean(plot_data[j], 0)
    plt.subplot(2, 5, j+1)
    plt.imshow(mean_img.reshape((28, 28)))
    plt.axis('off')

png

png

png

png

png

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

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

מכיוון שהנתונים הם כבר tf.data.Dataset , ניתן לבצע עיבוד מקדים באמצעות טרנספורמציות של tf.data.Dataset . כאן, אנו 28x28 את התמונות 28x28 למערכי אלמנט 784 28x28 את הדוגמאות האישיות, מארגנים אותן בקבוצות ומשנים את שם התכונות pixels label ל- x ו- y לשימוש עם Keras. אנו גם משליכים repeat על מערך הנתונים להפעלת מספר תקופות.

NUM_CLIENTS = 10
NUM_EPOCHS = 5
BATCH_SIZE = 20
SHUFFLE_BUFFER = 100
PREFETCH_BUFFER= 10

def preprocess(dataset):

  def batch_format_fn(element):
    """Flatten a batch `pixels` and return the features as an `OrderedDict`."""
    return collections.OrderedDict(
        x=tf.reshape(element['pixels'], [-1, 784]),
        y=tf.reshape(element['label'], [-1, 1]))

  return dataset.repeat(NUM_EPOCHS).shuffle(SHUFFLE_BUFFER).batch(
      BATCH_SIZE).map(batch_format_fn).prefetch(PREFETCH_BUFFER)

בואו נאמת שזה עבד.

preprocessed_example_dataset = preprocess(example_dataset)

sample_batch = tf.nest.map_structure(lambda x: x.numpy(),
                                     next(iter(preprocessed_example_dataset)))

sample_batch
OrderedDict([('x', array([[1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       ...,
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.]], dtype=float32)), ('y', array([[2],
       [1],
       [2],
       [3],
       [6],
       [0],
       [1],
       [4],
       [1],
       [0],
       [6],
       [9],
       [9],
       [3],
       [6],
       [1],
       [4],
       [8],
       [0],
       [2]], dtype=int32))])

יש לנו כמעט את כל אבני הבניין כדי לבנות מערכי נתונים מאוחדים.

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

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

def make_federated_data(client_data, client_ids):
  return [
      preprocess(client_data.create_tf_dataset_for_client(x))
      for x in client_ids
  ]

עכשיו, איך נבחר לקוחות?

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

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

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

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

sample_clients = emnist_train.client_ids[0:NUM_CLIENTS]

federated_train_data = make_federated_data(emnist_train, sample_clients)

print('Number of client datasets: {l}'.format(l=len(federated_train_data)))
print('First dataset: {d}'.format(d=federated_train_data[0]))
Number of client datasets: 10
First dataset: <DatasetV1Adapter shapes: OrderedDict([(x, (None, 784)), (y, (None, 1))]), types: OrderedDict([(x, tf.float32), (y, tf.int32)])>

יצירת מודל עם Keras

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

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, הוא צריך להיות עטוף במופע של ממשק tff.learning.Model , אשר חושף שיטות להחתמת המעבר קדימה של המודל, מאפייני מטא נתונים וכו ', בדומה ל- Keras, אך מציג גם אלמנטים, כגון דרכים לשלוט בתהליך חישוב מדדים מאוחדים. בואו לא נדאג לעת עתה; אם יש לך מודל של Keras כמו זה שהגדרנו לעיל, תוכל לעטוף אותו TFF על ידי tff.learning.from_keras_model , העברת המודל tff.learning.from_keras_model נתונים לדוגמה כארגומנטים, כמוצג להלן.

def model_fn():
  # We _must_ create a new model here, and _not_ capture it from an external
  # scope. TFF will call this within different graph contexts.
  keras_model = create_keras_model()
  return tff.learning.from_keras_model(
      keras_model,
      input_spec=preprocessed_example_dataset.element_spec,
      loss=tf.keras.losses.SparseCategoricalCrossentropy(),
      metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])

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

כעת, כשיש לנו מודל עטוף כ- tff.learning.Model לשימוש ב- TFF, אנו יכולים לתת ל- TFF לבנות אלגוריתם ממוצע של Federated Averaging על ידי הפעלת פונקציית העזר tff.learning.build_federated_averaging_process , כדלקמן.

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

הערה קריטית אחת באלגוריתם ה- Federated Averaging להלן, ישנם שני אופטימיזציה: אופטימיזציה של _Client ומייעל _שרת. מיטוב _client משמש רק לחישוב עדכוני מודלים מקומיים על כל לקוח. מיטוב _ שרת מחיל את העדכון הממוצע על המודל הגלובלי בשרת. בפרט, פירוש הדבר שבחירת האופטימיזציה וקצב הלמידה בהן עשויים להיות צריכים להיות שונים מאלה שהשתמשת בהם כדי להכשיר את המודל במערך iid סטנדרטי. אנו ממליצים להתחיל עם SGD רגיל, אולי בשיעור למידה נמוך מהרגיל. קצב הלמידה בו אנו משתמשים לא הותאם בקפידה, אל תהסס להתנסות.

iterative_process = tff.learning.build_federated_averaging_process(
    model_fn,
    client_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=0.02),
    server_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=1.0))

מה קרה הרגע? TFF בנתה זוג חישובים מאוחדים tff.templates.IterativeProcess אותם ל- tff.templates.IterativeProcess בו חישובים אלה זמינים כצמד מאפיינים initialize ועבור next .

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

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

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

str(iterative_process.initialize.type_signature)
'( -> <model=<trainable=<float32[784,10],float32[10]>,non_trainable=<>>,optimizer_state=<int64>,delta_aggregate_state=<>,model_broadcast_state=<>>@SERVER)'

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

בואו נפעיל את חישוב initialize לבניית מצב השרת.

state = iterative_process.initialize()

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

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

SERVER_STATE, FEDERATED_DATA -> SERVER_STATE, TRAINING_METRICS

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

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

state, metrics = iterative_process.next(state, federated_train_data)
print('round  1, metrics={}'.format(metrics))
round  1, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.12037037312984467,loss=3.0108425617218018>>

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

NUM_ROUNDS = 11
for round_num in range(2, NUM_ROUNDS):
  state, metrics = iterative_process.next(state, federated_train_data)
  print('round {:2d}, metrics={}'.format(round_num, metrics))
round  2, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.14814814925193787,loss=2.8865506649017334>>
round  3, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.148765429854393,loss=2.9079062938690186>>
round  4, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.17633745074272156,loss=2.724686622619629>>
round  5, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.20226337015628815,loss=2.6334855556488037>>
round  6, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.22427983582019806,loss=2.5482592582702637>>
round  7, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.24094650149345398,loss=2.4472343921661377>>
round  8, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.259876549243927,loss=2.3809611797332764>>
round  9, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.29814815521240234,loss=2.156442403793335>>
round 10, metrics=<broadcast=<>,aggregation=<>,train=<sparse_categorical_accuracy=0.31687241792678833,loss=2.122845411300659>>

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

הצגת מדדי מודל ב- TensorBoard

לאחר מכן, בואו לדמיין את המדדים מחישובים מאוחדים אלה באמצעות Tensorboard.

נתחיל ביצירת הספרייה וכותב הסיכום המתאים שאליו כתוב את המדדים.


logdir = "/tmp/logs/scalars/training/"
summary_writer = tf.summary.create_file_writer(logdir)
state = iterative_process.initialize()

התווה את המדדים הסקלריים הרלוונטיים עם אותו כותב סיכומים.


with summary_writer.as_default():
  for round_num in range(1, NUM_ROUNDS):
    state, metrics = iterative_process.next(state, federated_train_data)
    for name, value in metrics.train._asdict().items():
      tf.summary.scalar(name, value, step=round_num)

הפעל את TensorBoard באמצעות ספריית יומן השורשים שצוינה לעיל. הנתונים עלולים להימשך כמה שניות.


%tensorboard --logdir /tmp/logs/scalars/ --port=0

# Run this this cell to clean your directory of old output for future graphs from this directory.
rm -R /tmp/logs/scalars/*

על מנת להציג מדדי הערכה באותה צורה, תוכל ליצור תיקיית eval נפרדת, כמו "יומני / סקלר / eval", כדי לכתוב ל- TensorBoard.

התאמה אישית של יישום המודל

Keras הוא ממשק ה- API ברמה גבוהה המומלצת עבור TensorFlow , ואנחנו ממליצים להשתמש בדגמי Keras (דרך tff.learning.from_keras_model ) ב- TFF במידת האפשר.

עם זאת, tff.learning מספק ממשק מודל ברמה נמוכה יותר, tff.learning.Model , החושף את הפונקציונליות המינימלית הדרושה לשימוש במודל ללימוד מאוחד. יישום ישיר של ממשק זה (אולי עדיין באמצעות אבני בניין כמו tf.keras.layers ) מאפשר התאמה אישית מקסימאלית מבלי לשנות את הפנימיות של אלגוריתמי הלמידה המאוחדים.

אז בואו נעשה הכל מחדש מאפס.

הגדרת משתני מודל, מעבר קדימה ומדדים

הצעד הראשון הוא לזהות את המשתנים של TensorFlow איתם אנו הולכים לעבוד. על מנת להפוך את הקוד הבא לקריא יותר, בואו נגדיר מבנה נתונים שייצג את הסט כולו. זה יכלול משתנה כגון weights ו bias שאנחנו נכשיר, כמו גם משתנים יקיימו סטטיסטיקה ומונה המצטבר שונים נעדכן במהלך אימון, כגון loss_sum , accuracy_sum , ו num_examples .

MnistVariables = collections.namedtuple(
    'MnistVariables', 'weights bias num_examples loss_sum accuracy_sum')

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

def create_mnist_variables():
  return MnistVariables(
      weights=tf.Variable(
          lambda: tf.zeros(dtype=tf.float32, shape=(784, 10)),
          name='weights',
          trainable=True),
      bias=tf.Variable(
          lambda: tf.zeros(dtype=tf.float32, shape=(10)),
          name='bias',
          trainable=True),
      num_examples=tf.Variable(0.0, name='num_examples', trainable=False),
      loss_sum=tf.Variable(0.0, name='loss_sum', trainable=False),
      accuracy_sum=tf.Variable(0.0, name='accuracy_sum', trainable=False))

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

def mnist_forward_pass(variables, batch):
  y = tf.nn.softmax(tf.matmul(batch['x'], variables.weights) + variables.bias)
  predictions = tf.cast(tf.argmax(y, 1), tf.int32)

  flat_labels = tf.reshape(batch['y'], [-1])
  loss = -tf.reduce_mean(
      tf.reduce_sum(tf.one_hot(flat_labels, 10) * tf.math.log(y), axis=[1]))
  accuracy = tf.reduce_mean(
      tf.cast(tf.equal(predictions, flat_labels), tf.float32))

  num_examples = tf.cast(tf.size(batch['y']), tf.float32)

  variables.num_examples.assign_add(num_examples)
  variables.loss_sum.assign_add(loss * num_examples)
  variables.accuracy_sum.assign_add(accuracy * num_examples)

  return loss, predictions

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

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

def get_local_mnist_metrics(variables):
  return collections.OrderedDict(
      num_examples=variables.num_examples,
      loss=variables.loss_sum / variables.num_examples,
      accuracy=variables.accuracy_sum / variables.num_examples)

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

@tff.federated_computation
def aggregate_mnist_metrics_across_clients(metrics):
  return collections.OrderedDict(
      num_examples=tff.federated_sum(metrics.num_examples),
      loss=tff.federated_mean(metrics.loss, metrics.num_examples),
      accuracy=tff.federated_mean(metrics.accuracy, metrics.num_examples))
  

ארגומנט metrics הקלט תואם את OrderedDict שהוחזר על ידי get_local_mnist_metrics לעיל, אך באופן קריטי הערכים אינם עוד tf.Tensors - הם "מסומנים" כ tff.Value s, כדי להבהיר שלא ניתן עוד לתפעל אותם באמצעות TensorFlow, אלא רק באמצעות המפעילים tff.federated_mean של TFF כמו tff.federated_mean ו- tff.federated_sum . המילון המוחזר של אגרגטים גלובליים מגדיר את מערך המדדים שיהיה זמין בשרת.

בניית מופע של tff.learning.Model

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

class MnistModel(tff.learning.Model):

  def __init__(self):
    self._variables = create_mnist_variables()

  @property
  def trainable_variables(self):
    return [self._variables.weights, self._variables.bias]

  @property
  def non_trainable_variables(self):
    return []

  @property
  def local_variables(self):
    return [
        self._variables.num_examples, self._variables.loss_sum,
        self._variables.accuracy_sum
    ]

  @property
  def input_spec(self):
    return collections.OrderedDict(
        x=tf.TensorSpec([None, 784], tf.float32),
        y=tf.TensorSpec([None, 1], tf.int32))

  @tf.function
  def forward_pass(self, batch, training=True):
    del training
    loss, predictions = mnist_forward_pass(self._variables, batch)
    num_exmaples = tf.shape(batch['x'])[0]
    return tff.learning.BatchOutput(
        loss=loss, predictions=predictions, num_examples=num_exmaples)

  @tf.function
  def report_local_outputs(self):
    return get_local_mnist_metrics(self._variables)

  @property
  def federated_output_computation(self):
    return aggregate_mnist_metrics_across_clients

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

להלן מספר נקודות שכדאי להדגיש:

  • הכל קובע שהמודל שלך ישתמש חייב להילקח כמשתנים של TensorFlow, מכיוון ש- TFF אינו משתמש בפייתון בזמן הריצה (זכור שיש לכתוב את הקוד שלך כך שניתן יהיה לפרוס אותו למכשירים ניידים; עיין במדריך האלגוריתמים המותאמים אישית לעומק יותר. פרשנות לסיבות).
  • המודל שלך צריך לתאר איזו צורת נתונים הוא מקבל ( input_spec ), כמו באופן כללי, TFF היא סביבה מוקלדת מאוד ורוצה לקבוע חתימות סוג לכל הרכיבים. הכרזה על פורמט הקלט של המודל שלך היא חלק חיוני ממנו.
  • למרות שלא נדרש טכנית, אנו ממליצים לעטוף את כל ההיגיון של TensorFlow (מעבר קדימה, חישובים tf.function וכו ') כ- tf.function , שכן הדבר מסייע להבטיח שניתן לסדר את ה- TensorFlow ומסיר את הצורך בתלות בקרה מפורשת.

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

מדמה אימונים מאוחדים עם המודל החדש

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

iterative_process = tff.learning.build_federated_averaging_process(
    MnistModel,
    client_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=0.02))
state = iterative_process.initialize()
state, metrics = iterative_process.next(state, federated_train_data)
print('round  1, metrics={}'.format(metrics))
round  1, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.9713594913482666,accuracy=0.13518518209457397>>

for round_num in range(2, 11):
  state, metrics = iterative_process.next(state, federated_train_data)
  print('round {:2d}, metrics={}'.format(round_num, metrics))
round  2, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.975412607192993,accuracy=0.14032921195030212>>
round  3, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.9395227432250977,accuracy=0.1594650149345398>>
round  4, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.710164785385132,accuracy=0.17139917612075806>>
round  5, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.5891618728637695,accuracy=0.20267489552497864>>
round  6, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.5148487091064453,accuracy=0.21666666865348816>>
round  7, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.2816808223724365,accuracy=0.2580246925354004>>
round  8, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.3656885623931885,accuracy=0.25884774327278137>>
round  9, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=2.23549222946167,accuracy=0.28477364778518677>>
round 10, metrics=<broadcast=<>,aggregation=<>,train=<num_examples=4860.0,loss=1.974222183227539,accuracy=0.35329216718673706>>

כדי לראות מדדים אלה בתוך TensorBoard, עיין בשלבים המפורטים לעיל ב "הצגת מדדי מודל ב- TensorBoard".

הַעֲרָכָה

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

כדי לבצע הערכה על נתונים מאוחדים, אתה יכול לבנות חישוב מאוחד אחר המיועד למטרה זו בדיוק, באמצעות הפונקציה tff.learning.build_federated_evaluation , ולהעביר את בונה המודל שלך כארגומנט. שים לב שבניגוד ל- Federated Averaging, שבו השתמשנו ב- MnistTrainableModel , זה מספיק כדי לעבור את MnistModel . הערכה אינה מבצעת ירידת שיפוע ואין צורך לבנות אופטימיזציה.

לצורך ניסויים ומחקר, כאשר קיים מערך בדיקה מרכזי, למידה מאוחדת ליצירת טקסטים מדגימה אפשרות הערכה נוספת: לקיחת המשקולות המאומנות מלמידה מאוחדת, החלתם על מודל קרס רגיל ואז פשוט להתקשר אל tf.keras.models.Model.evaluate() במערך נתונים מרכזי.

evaluation = tff.learning.build_federated_evaluation(MnistModel)

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

str(evaluation.type_signature)
'(<<trainable=<float32[784,10],float32[10]>,non_trainable=<>>@SERVER,{<x=float32[?,784],y=int32[?,1]>*}@CLIENTS> -> <num_examples=float32@SERVER,loss=float32@SERVER,accuracy=float32@SERVER>)'

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

SERVER_MODEL, FEDERATED_DATA -> TRAINING_METRICS

בואו נעורר הערכה לגבי המצב האחרון אליו הגענו במהלך האימון. על מנת לחלץ את המודל המאומן האחרון ממצב השרת, אתה פשוט ניגש לחבר ה- .model באופן הבא.

train_metrics = evaluation(state.model, federated_train_data)

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

str(train_metrics)
'<num_examples=4860.0,loss=1.7142657041549683,accuracy=0.38683128356933594>'

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

federated_test_data = make_federated_data(emnist_test, sample_clients)

len(federated_test_data), federated_test_data[0]
(10,
 <DatasetV1Adapter shapes: OrderedDict([(x, (None, 784)), (y, (None, 1))]), types: OrderedDict([(x, tf.float32), (y, tf.int32)])>)
test_metrics = evaluation(state.model, federated_test_data)
str(test_metrics)
'<num_examples=580.0,loss=1.861915111541748,accuracy=0.3362068831920624>'

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