התאמה אישית במכשיר (ODP) נועדה להגן על המידע של משתמשי הקצה מפני אפליקציות. אפליקציות משתמשות ב-ODP כדי להתאים אישית את המוצרים והשירותים שלהן למשתמשי קצה, אבל הן לא יוכלו לראות את ההתאמות האישיות המדויקות שבוצעו עבור המשתמש (אלא אם יש אינטראקציות ישירות מחוץ ל-ODP בין האפליקציה לבין משתמש הקצה). באפליקציות עם מודלים של למידת מכונה או ניתוחים סטטיסטיים, ODP מספקת קבוצה של שירותים ואלגוריתמים כדי לוודא שהם עוברים אנונימיזציה בצורה נכונה באמצעות מנגנוני הפרטיות הדיפרנציאלית המתאימים. פרטים נוספים זמינים בהסבר על התאמה אישית במכשיר.
ה-ODP מריץ קוד של מפתחים בIsolatedProcess שאין לו גישה ישירה לרשת, לדיסקים מקומיים או לשירותים אחרים שפועלים במכשיר, אבל יש לו גישה למקורות הנתונים הבאים שמאוחסנים באופן מקומי:
-
RemoteData– נתונים של זוגות מפתח/ערך שלא ניתן לשנות שהורדו משרתי קצה עורפיים מרוחקים שמופעלים על ידי מפתחים, אם רלוונטי. -
LocalData– נתונים משתנים של מפתח/ערך שנשמרים באופן מקומי על ידי המפתח, אם רלוונטי. -
UserData– נתוני משתמשים שסופקו על ידי הפלטפורמה.
יש תמיכה בפלטים הבאים:
- פלט קבוע: אפשר להשתמש בפלט הזה לעיבוד מקומי עתידי, ליצירת פלט שמוצג, לאימון מודלים באמצעות למידה משותפת או לניתוח סטטיסטי בין מכשירים באמצעות ניתוח משותף.
- הפלט שמוצג:
- מפתחים יכולים להחזיר קוד HTML שעובר עיבוד על ידי ODP בתוך
WebViewבתוךSurfaceView. התוכן שמוצג שם לא יהיה גלוי לאפליקציה שמפעילה את התכונה. - מפתחים יכולים להטמיע כתובות URL של אירועים שסופקו על ידי ODP בתוך פלט ה-HTML כדי להפעיל את הרישום והעיבוד של אינטראקציות משתמשים עם ה-HTML שעבר עיבוד. ODP מיירט בקשות לכתובות ה-URL האלה ומפעיל קוד ליצירת נתונים שנכתבים בטבלה
EVENTS.
- מפתחים יכולים להחזיר קוד HTML שעובר עיבוד על ידי ODP בתוך
אפליקציות לקוח וערכות SDK יכולות להפעיל את ODP כדי להציג תוכן HTML ב-SurfaceView באמצעות ממשקי ODP API. התוכן שמוצג ב-SurfaceView לא גלוי לאפליקציה שמתקשרת. אפליקציית הלקוח או ה-SDK יכולים להיות ישות שונה מזו שמפתחת באמצעות ODP.
שירות ה-ODP מנהל את אפליקציית הלקוח שרוצה להפעיל את ה-ODP כדי להציג תוכן בהתאמה אישית בממשק המשתמש שלה. הוא מוריד תוכן מנקודות קצה שסופקו על ידי המפתח ומפעיל לוגיקה לעיבוד שלאחר ההורדה של הנתונים שהורדו. הוא גם מתווך בכל התקשורת בין IsolatedProcess לבין שירותים ואפליקציות אחרים.
אפליקציות לקוח משתמשות בשיטות במחלקה OnDevicePersonalizationManager כדי ליצור אינטראקציה עם קוד המפתח שפועל ב-IsolatedProcess. קוד של מפתח שפועל ב-IsolatedProcess מרחיב את המחלקה IsolatedService ומטמיע את הממשק IsolatedWorker. ה-IsolatedService צריך ליצור מופע של IsolatedWorker לכל בקשה.
בתרשים הבא מוצג הקשר בין השיטות ב-OnDevicePersonalizationManager וב-IsolatedWorker.
OnDevicePersonalizationManager לבין IsolatedWorker.אפליקציית לקוח קוראת ל-ODP באמצעות ה-method execute עם IsolatedService בשם. שירות ה-ODP מעביר את הקריאה ל-method onExecute של IsolatedWorker. האפליקציה IsolatedWorker מחזירה רשומות שצריך לשמור ותוכן שצריך להציג. שירות ה-ODP כותב את הפלט הקבוע לטבלה REQUESTS או EVENTS, ומחזיר הפניה אטומה לפלט המוצג לאפליקציית הלקוח. אפליקציית הלקוח יכולה להשתמש בהפניה האטומה הזו בקריאה עתידית של requestSurfacePackage כדי להציג תוכן כלשהו שמוצג בממשק המשתמש שלה.
פלט מתמשך
שירות ה-ODP שומר רשומה בטבלה REQUESTS אחרי שהטמעת onExecute של המפתח מחזירה ערך. כל רשומה בטבלה REQUESTS מכילה נתונים משותפים לכל בקשה שנוצרו על ידי שירות ה-ODP, ורשימה של Rows שהוחזרו. כל Row מכיל רשימה של זוגות (key, value). כל ערך הוא סקלר, מחרוזת או blob. אפשר לדווח על הערכים המספריים אחרי צבירה, ואפשר לדווח על נתוני המחרוזת או ה-blob אחרי החלת פרטיות דיפרנציאלית מקומית או מרכזית. מפתחים יכולים גם לכתוב אירועים של אינטראקציות עתידיות של משתמשים בטבלה EVENTS – כל רשומה בטבלה EVENTS משויכת לשורה בטבלה REQUESTS. שירות ה-ODP מתעד באופן שקוף בכל רשומה חותמת זמן, את שם החבילה של האפליקציה שקוראת ואת קובץ ה-APK של מפתח ה-ODP.
לפני שמתחילים
לפני שמתחילים לפתח באמצעות ODP, צריך להגדיר את קובץ המניפסט של החבילה ולהפעיל את מצב הפיתוח.
הגדרות של קובץ המניפסט של החבילה
כדי להשתמש ב-ODP, צריך:
- תג
<property>ב-AndroidManifest.xmlשמפנה למשאב XML בחבילה שמכילה את פרטי ההגדרה של ODP. - תג
<service>ב-AndroidManifest.xmlשמזהה את המחלקה שמרחיבה אתIsolatedService, כמו בדוגמה הבאה. בתג<service>של השירות צריך להגדיר את המאפייניםexportedו-isolatedProcessלערךtrue. - תג
<service>במשאב ה-XML שצוין בשלב 1, שמזהה את סוג השירות משלב 2. תג<service>צריך לכלול גם הגדרות נוספות שספציפיות ל-ODP בתוך התג עצמו, כמו בדוגמה השנייה.
AndroidManifest.xml
<!-- Contents of AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.odpsample" >
<application android:label="OdpSample">
<!-- XML resource that contains other ODP settings. -->
<property android:name="android.ondevicepersonalization.ON_DEVICE_PERSONALIZATION_CONFIG"
android:resource="@xml/OdpSettings"></property>
<!-- The service that ODP binds to. -->
<service android:name="com.example.odpsample.SampleService"
android:exported="true" android:isolatedProcess="true" />
</application>
</manifest>
מניפסט ספציפי ל-ODP במשאב XML
קובץ משאבי ה-XML שצוין בתג <property> חייב גם להצהיר על מחלקת השירות בתג <service>, ולציין את נקודת הקצה של כתובת ה-URL שממנה ODP יוריד תוכן כדי לאכלס את הטבלה RemoteData, כמו בדוגמה הבאה. אם אתם משתמשים בתכונות של מחשוב מאוחד, אתם צריכים לציין גם את נקודת הקצה של כתובת ה-URL של שרת המחשוב המאוחד שאליו יתחבר הלקוח של המחשוב המאוחד.
<!-- Contents of res/xml/OdpSettings.xml -->
<on-device-personalization>
<!-- Name of the service subclass -->
<service name="com.example.odpsample.SampleService">
<!-- If this tag is present, ODP will periodically poll this URL and
download content to populate REMOTE_DATA. Developers that do not need to
download content from their servers can skip this tag. -->
<download-settings url="https://example.com/get" />
<!-- If you want to use federated compute feature to train a model, you
need to specify this tag. -->
<federated-compute-settings url="https://fcpserver.example.com/" />
</service>
</on-device-personalization>
הפעלת מצב פיתוח
מפעילים את מצב הפיתוח לפי ההוראות שבקטע הפעלת אפשרויות למפתחים בתיעוד של Android Studio.
הגדרות של החלפה וסימון
ל-ODP יש קבוצה של מתגים ודגלים שמשמשים לשליטה בפונקציות מסוימות:
- switch _global_kill: המתג הגלובלי לכל התכונות של ODP. צריך להגדיר את הערך שלו כ-false כדי להשתמש ב-ODP
- _federated_compute_kill_switch: _המתג ששולט בכל הפונקציות של אימון (למידה מאוחדת) ב-ODP. צריך להגדיר את הערך שלו כ-false כדי להשתמש באימון.
- _caller_app_allowlist: קובעת למי מותר להתקשר ל-ODP. אפשר להוסיף לכאן אפליקציות (שם חבילה, [אופציונלי] אישור) או להגדיר את ההגדרה כ-*, כדי לאפשר לכולם להתקשר.
- _isolated_service_allowlist: קובע אילו שירותים יכולים לפעול בתהליך Isolated Service.
כדי להגדיר את כל המתגים והדגלים לשימוש ב-ODP ללא הגבלות, אפשר להריץ את הפקודות הבאות:
# Set flags and killswitches
adb shell device_config set_sync_disabled_for_tests persistent
adb shell device_config put on_device_personalization global_kill_switch false
adb shell device_config put on_device_personalization federated_compute_kill_switch false
adb shell device_config put on_device_personalization caller_app_allow_list \"*\"
adb shell device_config put on_device_personalization isolated_service_allow_list \"*\"
ממשקי API בצד המכשיר
מידע נוסף על ODP זמין במאמרי העזרה של Android API.
אינטראקציות עם IsolatedService
המחלקות IsolatedService הן מחלקות בסיס מופשטות שכל המפתחים שמתכוונים לפתח אפליקציות ל-ODP חייבים להרחיב, ולהצהיר במניפסט החבילה שהן פועלות בתהליך מבודד. שירות ה-ODP מפעיל את השירות הזה בתהליך מבודד ושולח אליו בקשות. IsolatedService מקבל בקשות משירות ה-ODP ויוצר IsolatedWorker כדי לטפל בבקשה.
מפתחים צריכים להטמיע את השיטות מהממשק IsolatedWorker כדי לטפל בבקשות של אפליקציות לקוח, בהשלמות של הורדות ובאירועים שמופעלים על ידי ה-HTML שעבר רינדור. לכל השיטות האלה יש יישומי no-op כברירת מחדל, כך שמפתחים יכולים לדלג על יישום השיטות שלא מעניינות אותם.
המחלקות OnDevicePersonalizationManager מספקות API לאפליקציות ול-SDK כדי ליצור אינטראקציה עם IsolatedService שהוטמע על ידי מפתח ופועל בתהליך מבודד. הנה כמה תרחישי שימוש מומלצים:
יצירת תוכן HTML להצגה ב-SurfaceView
כדי ליצור תוכן שיוצג באמצעות OnDevicePersonalizationManager#execute, אפליקציית השיחות יכולה להשתמש באובייקט SurfacePackageToken שמוחזר בקריאה הבאה ל-requestSurfacePackage כדי לבקש שהתוצאה תעבור עיבוד ב-SurfaceView .
אם הפעולה מצליחה, מתבצעת קריאה למקבל עם SurfacePackage עבור View שמעובד על ידי שירות ה-ODP. אפליקציות לקוח צריכות להוסיף את התג SurfacePackage ל-SurfaceView בהיררכיית התצוגה שלהן.
כשבאפליקציה מתבצעת קריאה ל-requestSurfacePackage עם SurfacePackageToken שמוחזר על ידי קריאה קודמת ל-OnDevicePersonalizationManager#execute, שירות ה-ODP קורא ל-IsolatedWorker#onRender כדי לאחזר את קטע ה-HTML שיוצג בתוך מסגרת מגודרת. למפתח אין גישה ל-LocalData או ל-UserData בשלב הזה. הפעולה הזו מונעת מהמפתח להטמיע UserData שעלול להיות רגיש בכתובות URL לאחזור נכסים ב-HTML שנוצר. מפתחים יכולים להשתמש ב-IsolatedService#getEventUrlProvider כדי ליצור כתובות URL למעקב ולכלול אותן ב-HTML שנוצר. כשקוד ה-HTML מעובד, שירות ה-ODP מיירט בקשות לכתובות ה-URL האלה ומבצע קריאה ל-IsolatedWorker#onEvent. אפשר להפעיל את getRemoteData() כשמטמיעים את onRender().
מעקב אחר אירועים בתוכן HTML
המחלקה EventUrlProvider מספקת ממשקי API ליצירת כתובות URL למעקב אחרי אירועים, שמפתחים יכולים לכלול בפלט ה-HTML שלהם. כשקוד ה-HTML עובר רינדור, ODP מפעיל את IsolatedWorker#onEvent עם מטען הייעודי (payload) של כתובת ה-URL של האירוע.
שירות ה-ODP מיירט בקשות לכתובות URL של אירועים שנוצרו על ידי ODP בתוך ה-HTML שעבר עיבוד, קורא ל-IsolatedWorker#onEvent ומתעד את הערך המוחזר EventLogRecord בטבלה EVENTS.
כתיבת תוצאות קבועות
ב-OnDevicePersonalizationManager#execute, לשירות יש אפשרות לכתוב נתונים לאחסון קבוע (טבלאות REQUESTS ו-EVENTS). אלה הערכים שאפשר לכתוב בטבלאות האלה:
-
RequestLogRecordשיוסף לטבלהREQUESTS. - רשימה של אובייקטים מסוג
EventLogRecordשיוספו לטבלהEVENTS, כל אחד מהם מכיל מצביע ל-RequestLogRecordשנכתב קודם .
התוצאות הקבועות באחסון המכשיר יכולות לשמש את הגישה החדשה הזו לאימון המודל.
ניהול משימות אימון במכשיר
שירות ה-ODP קורא ל-IsolatedWorker#onTrainingExample כשמתחילה משימת אימון מאוחדת של מחשוב, ורוצה לקבל דוגמאות לאימון שסופקו על ידי מפתחים שמשתמשים ב-ODP. אפשר להפעיל את getRemoteData(), getLocalData(), getUserData() ו-getLogReader() כשמטמיעים את onTrainingExample().
כדי לתזמן או לבטל עבודות חישוב מאוחדות, אפשר להשתמש במחלקה FederatedComputeScheduler שמספקת ממשקי API לכל ה-ODP IsolatedService. אפשר לזהות כל משימת חישוב מאוחדת לפי שם האוכלוסייה שלה.
לפני שמגדירים משימת מחשוב מאוחדת חדשה:
- משימה עם שם האוכלוסייה הזה כבר אמורה להיווצר בשרת המרוחק של חישוב מאוחד.
- נקודת הקצה של כתובת ה-URL של שרת המחשוב המאוחד צריכה להיות מוגדרת כבר בהגדרות של מניפסט החבילה באמצעות התג
federated-compute-settings.
אינטראקציות עם פלט מתמשך
בקטע הבא מוסבר איך לעבוד עם פלט קבוע ב-ODP.
קריאת טבלאות מקומיות
המחלקות LogReader מספקות ממשקי API לקריאת הטבלאות REQUESTS ו-EVENTS. הטבלאות האלה מכילות נתונים שנכתבו על ידי IsolatedService במהלך שיחות onExecute() או onEvent(). אפשר להשתמש בנתונים שבטבלאות האלה כדי לאמן מודלים באמצעות למידה משותפת (Federated Learning), או כדי לבצע ניתוח סטטיסטי של נתונים ממכשירים שונים באמצעות ניתוח נתונים משותף (Federated Analytics).
אינטראקציות עם תוכן שהורד
בקטע הבא מוסבר איך ליצור אינטראקציה עם תוכן שהורד ב-ODP.
הורדת תוכן משרתים
שירות ה-ODP מוריד תוכן מעת לעת מכתובת ה-URL שמוצהרת במניפסט החבילה של IsolatedService, ומבצע קריאה ל-onDownloadCompleted אחרי שההורדה מסתיימת. ההורדה היא קובץ JSON שמכיל צמדי מפתח-ערך.
מפתחים שמשתמשים ב-ODP יכולים לבחור איזה חלק מהתוכן שהורד יתווסף לטבלה RemoteData ואיזה חלק יוסר. מפתחים לא יכולים לשנות את התוכן שהם מורידים – כך מוודאים שטבלת RemoteData לא מכילה נתוני משתמשים. בנוסף, המפתחים יכולים לאכלס את הטבלה LocalData איך שהם רוצים. לדוגמה, הם יכולים לשמור במטמון חלק מהתוצאות שחושבו מראש.
פורמט בקשת הורדה
ה-ODP שולח מדי פעם שאילתות לנקודת הקצה של כתובת ה-URL שמוצהרת במניפסט החבילה של המפתח כדי לאחזר תוכן לאכלוס הטבלה RemoteData.
נקודת הקצה צפויה להחזיר תגובת JSON כפי שמתואר בהמשך. תגובת ה-JSON חייבת להכיל את הערך syncToken שמזהה את גרסת הנתונים שנשלחים, יחד עם רשימה של צמדי מפתח-ערך שיש לאכלס. הערך של syncToken חייב להיות חותמת זמן בשניות, שמוגבלת לגבול של שעה ב-UTC. כחלק מבקשת ההורדה, ODP מספק את syncToken של ההורדה הקודמת שהושלמה ואת המדינה שבה נמצא המכשיר כפרמטרים syncToken ו-country בכתובת ה-URL של ההורדה. השרת יכול להשתמש ב-syncToken הקודם כדי להטמיע הורדות מצטברות.
פורמט הורדת הקובץ
הקובץ שמורידים הוא קובץ JSON עם המבנה הבא. קובץ ה-JSON אמור להכיל syncToken כדי לזהות את גרסת הנתונים שמורידים. הערך של syncToken חייב להיות חותמת זמן ב-UTC שמוגבלת לגבול של שעה, והוא צריך להיות גדול מהערך של syncToken בהורדה הקודמת. אם syncToken לא עומד בשתי הדרישות, התוכן שהורד נמחק בלי לעבור עיבוד.
השדה contents הוא רשימה של טפלים (key, data, encoding). הערך key צריך להיות מחרוזת UTF-8. השדה encoding הוא פרמטר אופציונלי שמציין איך השדה data מקודד – אפשר להגדיר אותו כ-utf8 או כ-base64, והוא מוגדר כ-utf8 כברירת מחדל. השדה key מומר לאובייקט String והשדה data מומר למערך בייטים לפני הקריאה ל-onDownloadCompleted().
{
// syncToken must be a UTC timestamp clamped to an hour boundary, and must be
// greater than the syncToken of the previously completed download.
"syncToken": <timeStampInSecRoundedToUtcHour>,
"contents": [
// List of { key, data } pairs.
{ "key": "key1",
"data": "data1"
},
{ "key": "key2",
"data": "data2",
"encoding": "base64"
},
// ...
]
}
ממשקי API בצד השרת
בקטע הזה מוסבר איך ליצור אינטראקציה עם ממשקי ה-API של שרת מחשוב מאוחד.
Federated Compute Server APIs
כדי לתזמן משימת מחשוב מאוחדת בצד הלקוח, צריך משימה עם שם אוכלוסייה שנוצרה בשרת המחשוב המאוחד המרוחק. בקטע הזה נסביר איך ליצור משימה כזו בשרת מחשוב מאוחד.
כשיוצרים משימה חדשה בכלי ליצירת משימות, מפתחי ODP צריכים לספק שני סטים של קבצים:
- מודל tff.learning.models.FunctionalModel שנשמר באמצעות קריאה ל-API tff.learning.models.save_functional_model. דוגמה אחת נמצאת במאגר שלנו ב-GitHub.
- קובץ fcp_server_config.json שכולל מדיניות, הגדרות של למידה משותפת (Federated) והגדרות של פרטיות דיפרנציאלית. הנה דוגמה לקובץ fcp_server_config.json:
{
# Task execution mode.
mode: TRAINING_AND_EVAL
# Identifies the set of client devices that participate.
population_name: "mnist_cnn_task"
policies {
# Policy for sampling on-device examples. It is checked every
# time a device is attempting to start a new training.
min_separation_policy {
# The minimum separation required between two successful
# consective task executions. If a client successfully contributes
# to a task at index `x`, the earliest they can contribute again
# is at index `(x + minimum_separation)`. This is required by
# DP.
minimum_separation: 1
}
data_availability_policy {
# The minimum number of examples on a device to be considered
# eligible for training.
min_example_count: 1
}
# Policy for releasing training results to developers adopting ODP.
model_release_policy {
# The maximum number of training rounds.
num_max_training_rounds: 512
}
}
# Federated learning setups. They are applied inside Task Builder.
federated_learning {
# Use federated averaging to build federated learning process.
# Options you can choose:
# * FED_AVG: Federated Averaging algorithm
# (https://arxiv.org/abs/2003.00295)
# * FED_SGD: Federated SGD algorithm
# (https://arxiv.org/abs/1602.05629)
type: FED_AVG
learning_process {
# Optimizer used at client side training. Options you can choose:
# * ADAM
# * SGD
client_optimizer: SGD
# Learning rate used at client side training.
client_learning_rate: 0.02
# Optimizer used at server side training. Options you can choose:
# * ADAM
# * SGD
server_optimizer: SGD
# Learning rate used at server side training.
server_learning_rate: 1.0
runtime_config {
# Number of participating devices for each round of training.
report_goal: 2
}
metrics {
name: "sparse_categorical_accuracy"
}
}
evaluation {
# A checkpoint selector controls how checkpoints are chosen for
# evaluation. One evaluation task typically runs per training
# task, and on each round of execution, the eval task
# randomly picks one checkpoint from the past 24 hours that has
# been selected for evaluation by these rules.
# Every_k_round and every_k_hour are definitions of quantization
# buckets which each checkpoint is placed in for selection.
checkpoint_selector: "every_1_round"
# The percentage of a populate that should delicate to this
# evaluation task.
evaluation_traffic: 0.2
# Number of participating devices for each round of evaluation.
report_goal: 2
}
}
# Differential Privacy setups. They are enforced inside the Task
# Builder.
differential_privacy {
# * fixed_gaussian: DP-SGD with fixed clipping norm described in
# "Learning Differentially Private Recurrent
# Language Models"
# (https://arxiv.org/abs/1710.06963).
type: FIXED_GAUSSIAN
# The value of the clipping norm.
clip_norm: 0.1
# Noise multiplier for the Gaussian noise.
noise_multiplier: 0.1
}
}
דוגמאות נוספות זמינות במאגר שלנו ב-GitHub.
אחרי שמכינים את שני הקלטים האלה, מפעילים את הכלי ליצירת משימות כדי ליצור ארטיפקטים וליצור משימות חדשות. הוראות מפורטות יותר זמינות במאגר שלנו ב-GitHub.