אלגוריתמים מותאמים אישית, חלק 1: היכרות עם הליבה הפדרלית

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

הדרכה זו היא החלק הראשון של סדרה בת שני חלקים המדגים כיצד ליישם סוגים מותאמים אישית של אלגוריתמים Federated ב TensorFlow Federated (TFF) באמצעות Core Federated (FC) - סט של ממשקים ברמה נמוכה המשמשים כבסיס שעליו אנו מיישמים את הלמידה Federated (FL) שכבה.

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

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

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

שימושים מיועדים

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

אתה יכול להיות מודע tf.contrib.distribute , וכן שאלה טבעית לשאול בנקודה זו עשויה להיות: ובאילו דרכים במסגרת זו שונה? שתי המסגרות מנסות להפוך חישובי TensorFlow למבוזרים, אחרי הכל.

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

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

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

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

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

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

import nest_asyncio
nest_asyncio.apply()
import collections

import numpy as np
import tensorflow as tf
import tensorflow_federated as tff
@tff.federated_computation
def hello_world():
  return 'Hello, World!'

hello_world()
b'Hello, World!'

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

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

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

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

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

federated_float_on_clients = tff.type_at_clients(tf.float32)

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

str(federated_float_on_clients.member)
'float32'
str(federated_float_on_clients.placement)
'CLIENTS'

סוג Federated עם המרכיבים חבר T והשמה G יכול להיות מיוצג בצורה מרוכזת כמו {T}@G , כמוצג להלן.

str(federated_float_on_clients)
'{float32}@CLIENTS'

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

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

סוגים מאוחדים ב-TFF מגיעים בשני טעמים: אלה שבהם המרכיבים החברים של ערך מאוחד עשויים להיות שונים (כפי שנראה לעיל), ואלה שבהם ידוע שכולם שווים. זו נשלטת על ידי שלישי, אופציונלי all_equal פרמטר tff.FederatedType בנאי (שברירת המחדל שלו False ).

federated_float_on_clients.all_equal
False

סוג Federated עם מיקום G שבו כל T -typed המרכיבים חבר ידועים להיות שווה יכול להיות מיוצג בצורה מרוכזת כמו T@G (להבדיל {T}@G , כלומר, עם סוגריים מסולסלים ירד לשקף העובדה שהקבוצה מרובת המרכיבים החברים מורכבת מפריט בודד).

str(tff.type_at_clients(tf.float32, all_equal=True))
'float32@CLIENTS'

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

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

לדוגמה, נניח שיש לנו זוג float32 פרמטרים ו a b עבור מודל רגרסיה ליניארית חד ממדי פשוט. אנו יכולים לבנות את הסוג (הלא מאוחד) של מודלים כאלה לשימוש ב-TFF באופן הבא. פלטות הזווית <> במחרוזת הסוג המודפסת הן סימון TFF קומפקטי עבור tuples בשם או ללא שם.

simple_regression_model_type = (
    tff.StructType([('a', tf.float32), ('b', tf.float32)]))

str(simple_regression_model_type)
'<a=float32,b=float32>'

שים לב כי אנו רק לציין dtype ים לעיל. גם סוגים לא סקלרים נתמכים. בקוד למעלה, tf.float32 הוא סימון קיצור עבור כללי יותר tff.TensorType(dtype=tf.float32, shape=[]) .

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

str(tff.type_at_clients(
    simple_regression_model_type, all_equal=True))
'<a=float32,b=float32>@CLIENTS'

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

עכשיו, חוזר float32@CLIENTS - בעוד נראה משוכפל במכשירים רבים, הוא למעשה אחד float32 , שכן כל חבר זהים. באופן כללי, אתה יכול לחשוב על שום סוג Federated כל-שווה, כלומר, אחד בצורת T@G , כמו isomorphic לסוג הלא Federated T , שכן בשני המקרים, יש בעצם רק אחת (אם כי משוכפל בפוטנציה) פריט מסוג T .

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

מיקומים

סקירת עיצוב

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

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

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

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

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

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

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

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

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

מיקומים מתוכננים להיות אזרח ממדרגה ראשונה TFF כמו גם, והוא יכול להופיע כפרמטרים ותוצאות של placement סוג (כדי להיות מיוצג על ידי tff.PlacementType ב- API). בעתיד, אנו מתכננים לספק מגוון מפעילים לשינוי או שילוב של מיקומים, אך זה מחוץ לתחום של מדריך זה. לעת עתה, זה מספיק כדי לחשוב על placement בתור אטום פרימיטיבית מובנית סוג ב TFF, בדומה לאופן בו int ו bool הם מובנים אטום סוגים בפייתון, עם tff.CLIENTS להיות מילולי קבוע מסוג זה, לא בשונה 1 להיות מילולי מתמיד מסוג int .

ציון מיקומים

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

TFF אינו קובע מה גם את tff.CLIENTS או tff.SERVER בעצם מייצג.

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

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

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

הכרזה על חישובים מאוחדים

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

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

@tff.federated_computation(tff.type_at_clients(tf.float32))
def get_average_temperature(sensor_readings):
  return tff.federated_mean(sensor_readings)

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

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

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

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

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

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

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

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

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

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

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

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

get_average_temperature([68.5, 70.3, 69.8])
69.53334

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

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

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

אתה יכול לאמת זאת על ידי הוספת הצהרת הדפסה, באופן הבא:

@tff.federated_computation(tff.type_at_clients(tf.float32))
def get_average_temperature(sensor_readings):

  print ('Getting traced, the argument is "{}".'.format(
      type(sensor_readings).__name__))

  return tff.federated_mean(sensor_readings)
Getting traced, the argument is "ValueImpl".

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

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

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

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

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

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

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

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

כפי שצוין לעיל, חישובים מאוחדים ומרכיביהם מובנים בצורה הטובה ביותר כמודלים של מערכות מבוזרות, וניתן לחשוב על חיבור חישובים מאוחדים כחיבור מערכות מבוזרות מורכבות יותר ממערכות פשוטות יותר. אתה יכול לחשוב על tff.federated_mean למפעיל כסוג של מובנית חישוב Federated תבנית עם חתימה סוג ({T}@CLIENTS -> T@SERVER) (אכן, בדיוק כמו חישובים שאתה כותב, מפעיל זה גם יש תסביך מבנה - מתחת למכסה המנוע אנו מפרקים אותו למפעילים פשוטים יותר).

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

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

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

לוגיקה של TensorFlow

הכרזה על חישובי TensorFlow

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

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

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

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

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

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

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

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

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

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

עכשיו אתה יכול גם להשתמש add_half כאבן בניין ב חישובים אחרים. לדוגמה, הנה איך אתה יכול להשתמש tff.federated_map למפעיל ליישם add_half pointwise לכל המרכיבים חבר לצוף Federated בהתקנים הלקוח.

@tff.federated_computation(tff.type_at_clients(tf.float32))
def add_half_on_clients(x):
  return tff.federated_map(add_half, x)
str(add_half_on_clients.type_signature)
'({float32}@CLIENTS -> {float32}@CLIENTS)'

ביצוע חישובי TensorFlow

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

add_half_on_clients([1.0, 3.0, 2.0])
[<tf.Tensor: shape=(), dtype=float32, numpy=1.5>,
 <tf.Tensor: shape=(), dtype=float32, numpy=3.5>,
 <tf.Tensor: shape=(), dtype=float32, numpy=2.5>]

שוב, ראוי לציין כי הפנייה בחישוב add_half_on_clients בצורה זו המדמה תהליך מבוזר. הנתונים נצרכים על לקוחות ומוחזרים על לקוחות. ואכן, חישוב זה גורם לכל לקוח לבצע פעולה מקומית. אין tff.SERVER הזכירו במפורש במערכת זו (גם אם בפועל, מנצח עיבוד כזה עשוי להיות כרוך אחד). תחשוב על חישוב מוגדר בדרך זו כמקבילה תיאורטית את Map שלב ב MapReduce .

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

ההבדל היחיד בין שיטות Python מעוטרות tff.federated_computation ואלה מעוטרים tff.tf_computation הוא שהאחרונה הן בהמשכים כמו גרפי TensorFlow (בעוד לשעבר אינו רשאים להכיל קוד TensorFlow מוטבע ישירות אליהם).

מתחת למכסה המנוע, כל שיטה מעוטר tff.tf_computation משבית ביצוע להוט באופן זמני על מנת לאפשר את המבנה של חישוב כדי ליפול בפח. בעוד שביצוע להוט מושבת באופן מקומי, אתה מוזמן להשתמש בבניית TensorFlow, AutoGraph, TensorFlow 2.0 וכו', כל עוד אתה כותב את ההיגיון של החישוב שלך בצורה כזו שניתן יהיה להסיד אותו בצורה נכונה.

לדוגמה, הקוד הבא ייכשל:

try:

  # Eager mode
  constant_10 = tf.constant(10.)

  @tff.tf_computation(tf.float32)
  def add_ten(x):
    return x + constant_10

except Exception as err:
  print (err)
Attempting to capture an EagerTensor without building a function.

האמור לעיל נכשל בגלל constant_10 שכבר נבנה מחוץ לתרשים tff.tf_computation בונה פנימי בגוף add_ten בתהליך בהמשכים.

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

def get_constant_10():
  return tf.constant(10.)

@tff.tf_computation(tf.float32)
def add_ten(x):
  return x + get_constant_10()

add_ten(5.0)
15.0

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

עבודה עם tf.data.Dataset ים

כפי שצוין קודם לכן, תכונה ייחודית של tff.tf_computation הים היא שהם מאפשרים לך לעבוד עם tf.data.Dataset ים המוגדר בצורה מופשטת כפרמטרים רשמיים על ידי הקוד שלך. פרמטרים להיות מיוצגים TensorFlow כמו ערכות נתונים צריכות להיות הכריזו באמצעות tff.SequenceType הבנאי.

לדוגמה, מפרט סוג tff.SequenceType(tf.float32) מגדיר רצף מופשט של אלמנטים צפים ב TFF. רצפים יכולים להכיל טנסורים או מבנים מקוננים מורכבים (נראה דוגמאות לאלו בהמשך). הייצוג התמציתי של רצף של T -typed פריטים הוא T* .

float32_sequence = tff.SequenceType(tf.float32)

str(float32_sequence)
'float32*'

Suppose that in our temperature sensor example, each sensor holds not just one temperature reading, but multiple. Here's how you can define a TFF computation in TensorFlow that calculates the average of temperatures in a single local data set using the tf.data.Dataset.reduce operator.

@tff.tf_computation(tff.SequenceType(tf.float32))
def get_local_temperature_average(local_temperatures):
  sum_and_count = (
      local_temperatures.reduce((0.0, 0), lambda x, y: (x[0] + y, x[1] + 1)))
  return sum_and_count[0] / tf.cast(sum_and_count[1], tf.float32)
str(get_local_temperature_average.type_signature)
'(float32* -> float32)'

In the body of a method decorated with tff.tf_computation , formal parameters of a TFF sequence type are represented simply as objects that behave like tf.data.Dataset , ie, support the same properties and methods (they are currently not implemented as subclasses of that type - this may change as the support for data sets in TensorFlow evolves).

You can easily verify this as follows.

@tff.tf_computation(tff.SequenceType(tf.int32))
def foo(x):
  return x.reduce(np.int32(0), lambda x, y: x + y)

foo([1, 2, 3])
6

Keep in mind that unlike ordinary tf.data.Dataset s, these dataset-like objects are placeholders. They don't contain any elements, since they represent abstract sequence-typed parameters, to be bound to concrete data when used in a concrete context. Support for abstractly-defined placeholder data sets is still somewhat limited at this point, and in the early days of TFF, you may encounter certain restrictions, but we won't need to worry about them in this tutorial (please refer to the documentation pages for details).

When locally executing a computation that accepts a sequence in a simulation mode, such as in this tutorial, you can feed the sequence as Python list, as below (as well as in other ways, eg, as a tf.data.Dataset in eager mode, but for now, we'll keep it simple).

get_local_temperature_average([68.5, 70.3, 69.8])
69.53333

Like all other TFF types, sequences like those defined above can use the tff.StructType constructor to define nested structures. For example, here's how one could declare a computation that accepts a sequence of pairs A , B , and returns the sum of their products. We include the tracing statements in the body of the computation so that you can see how the TFF type signature translates into the dataset's output_types and output_shapes .

@tff.tf_computation(tff.SequenceType(collections.OrderedDict([('A', tf.int32), ('B', tf.int32)])))
def foo(ds):
  print('element_structure = {}'.format(ds.element_spec))
  return ds.reduce(np.int32(0), lambda total, x: total + x['A'] * x['B'])
element_structure = OrderedDict([('A', TensorSpec(shape=(), dtype=tf.int32, name=None)), ('B', TensorSpec(shape=(), dtype=tf.int32, name=None))])
str(foo.type_signature)
'(<A=int32,B=int32>* -> int32)'
foo([{'A': 2, 'B': 3}, {'A': 4, 'B': 5}])
26

The support for using tf.data.Datasets as formal parameters is still somewhat limited and evolving, although functional in simple scenarios such as those used in this tutorial.

Putting it all together

Now, let's try again to use our TensorFlow computation in a federated setting. Suppose we have a group of sensors that each have a local sequence of temperature readings. We can compute the global temperature average by averaging the sensors' local averages as follows.

@tff.federated_computation(
    tff.type_at_clients(tff.SequenceType(tf.float32)))
def get_global_temperature_average(sensor_readings):
  return tff.federated_mean(
      tff.federated_map(get_local_temperature_average, sensor_readings))

Note that this isn't a simple average across all local temperature readings from all clients, as that would require weighing contributions from different clients by the number of readings they locally maintain. We leave it as an exercise for the reader to update the above code; the tff.federated_mean operator accepts the weight as an optional second argument (expected to be a federated float).

Also note that the input to get_global_temperature_average now becomes a federated float sequence . Federated sequences is how we will typically represent on-device data in federated learning, with sequence elements typically representing data batches (you will see examples of this shortly).

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

Here's how we can locally execute the computation on a sample of data in Python. Notice that the way we supply the input is now as a list of list s. The outer list iterates over the devices in the group represented by tff.CLIENTS , and the inner ones iterate over elements in each device's local sequence.

get_global_temperature_average([[68.0, 70.0], [71.0], [68.0, 72.0, 70.0]])
70.0

This concludes the first part of the tutorial... we encourage you to continue on to the second part .