Przewodnik dla programistów po personalizacji na urządzeniu

Personalizacja na urządzeniu (ODP) ma na celu ochronę danych użytkowników przed dostępem do aplikacji. Aplikacje korzystają z ODP, aby dostosowywać swoje produkty i usługi pod kątem użytkowników, ale nie będą widzieć dokładnych dostosowań wprowadzonych dla użytkownika (chyba że występują bezpośrednie interakcje poza ODP między aplikacją a użytkownikiem). W przypadku aplikacji z modelami uczenia maszynowego lub analiz statystycznych ODP udostępnia zestaw usług i algorytmów, które zapewniają odpowiednie zanonimizowanie za pomocą odpowiednich mechanizmów prywatności różnicowej. Więcej informacji znajdziesz w artykule Personalizacja na urządzeniu.

ODP uruchamia kod dewelopera w procesie IsolatedProcess, który nie ma bezpośredniego dostępu do sieci, dysków lokalnych ani innych usług działających na urządzeniu, ale ma dostęp do tych lokalnie zapisanych źródeł danych:

  • RemoteData – stałe dane w postaci pary klucz-wartość pobierane ze zdalnych backendów obsługiwanych przez dewelopera (w stosownych przypadkach).
  • LocalData – zmienne dane pary klucz-wartość są zachowywane lokalnie przez dewelopera (w stosownych przypadkach).
  • UserData – dane użytkownika dostarczone przez platformę.

Obsługiwane są te dane wyjściowe:

  • Trwałe dane wyjściowe: tych danych wyjściowych można używać w przyszłym przetwarzaniu lokalnym, co umożliwia generowanie wyświetlanych wyników, trenowanie modelu wspomaganego sfederowanego uczenia się lub analizę statystyczną między urządzeniami przez Federated Analytics.
    • Deweloperzy mogą zapisywać żądania i ich wyniki przetwarzania w lokalnej tabeli REQUESTS.
    • Deweloperzy mogą zapisać w tabeli EVENTS dodatkowe dane związane z wcześniejszą prośbą.
  • Wyświetlane dane wyjściowe:
    • Deweloperzy mogą zwracać kod HTML renderowany przez ODP w kodzie WebView wewnątrz SurfaceView. Treści renderowane w tym miejscu nie będą widoczne dla wywołującej aplikacji.
    • Deweloperzy mogą umieszczać w wyjściowym kodzie HTML adresy URL zdarzeń udostępniane przez ODP, aby wywołać rejestrowanie i przetwarzanie interakcji użytkowników z wyrenderowanym kodem HTML. ODP przechwytuje żądania wysyłane do tych adresów URL i wywołuje kod do generowania danych, które są zapisywane w tabeli EVENTS.

Aplikacje klienta i pakiety SDK mogą wywoływać ODP, aby wyświetlać treści HTML w ramach SurfaceView, korzystając z interfejsów API ODP. Treści renderowane w narzędziu SurfaceView nie są widoczne dla aplikacji wywołującej. Aplikacja kliencka lub pakiet SDK może być inną jednostką niż aplikacja tworząca za pomocą ODP.

Usługa ODP zarządza aplikacją klienta, która chce wywołać ODP, aby wyświetlić spersonalizowane treści w interfejsie. Pobiera treści z punktów końcowych udostępnionych przez dewelopera i wywołuje logikę do dalszego przetwarzania pobranych danych. Pośredniczy też w komunikacji między IsolatedProcess a innymi usługami i aplikacjami.

Aplikacje klienckie korzystają z metod klasy OnDevicePersonalizationManager, aby wchodzić w interakcje z kodem dewelopera działającym w kontekście IsolatedProcess. Kod dewelopera uruchomiony w IsolatedProcess rozszerza klasę IsolatedService i implementuje interfejs IsolatedWorker. IsolatedService musi utworzyć instancję IsolatedWorker dla każdego żądania.

Poniższy diagram przedstawia zależności między metodami OnDevicePersonalizationManager i IsolatedWorker.

Schemat relacji między OnDevicePersonalizationManager i IsolatedWorker.

Aplikacja kliencka wywołuje ODP za pomocą metody execute o nazwie IsolatedService. Usługa ODP przekazuje wywołanie do metody onExecute IsolatedWorker. Funkcja IsolatedWorker zwraca rekordy, które mają być utrwalone, oraz treści, które mają być wyświetlane. Usługa ODP zapisuje trwały wynik w tabeli REQUESTS lub EVENTS i zwraca nieprzezroczyste odwołanie do wyświetlanego wyniku w aplikacji klienta. Aplikacja klienta może użyć tego nieprzezroczystego odwołania w przyszłym wywołaniu funkcji requestSurfacePackage, aby wyświetlić dowolną zawartość w interfejsie.

Dane wyjściowe trwałe

Usługa ODP zachowa rekord w tabeli REQUESTS po powrocie implementacji onExecute przez dewelopera. Każdy rekord w tabeli REQUESTS zawiera niektóre typowe dane na żądanie generowane przez usługę ODP oraz listę zwróconych wartości Rows. Każdy element Row zawiera listę par (key, value). Każda wartość jest ciągiem skalarnym, ciągiem znaków lub obiektem blob. Wartości liczbowe można raportować po agregacji, a dane w postaci ciągu znaków lub bloba – po zastosowaniu lokalnej lub centralnej prywatności różnicowej. Deweloperzy mogą też zapisywać kolejne zdarzenia interakcji użytkownika w tabeli EVENTS. Każdy rekord w tabeli EVENTS jest powiązany z wierszem w tabeli REQUESTS. Usługa ODP w sposób przejrzysty rejestruje przy każdym rekordzie sygnaturę czasową i nazwę pakietu aplikacji wywołującej oraz plik APK dewelopera ODP.

Zanim zaczniesz

Zanim zaczniesz tworzyć aplikacje z wykorzystaniem ODP, musisz skonfigurować plik manifestu pakietu i włączyć tryb programisty.

Ustawienia pliku manifestu pakietu

Aby korzystać z ODP, musisz spełniać te wymagania:

  1. Tag <property> w AndroidManifest.xml, który wskazuje na zasób XML w pakiecie, zawierający informacje o konfiguracji ODP.
  2. Tag <service>AndroidManifest.xml, który identyfikuje klasę rozszerzającą klasę IsolatedService, jak w tym przykładzie. Usługa w tagu <service> musi mieć atrybuty exported i isolatedProcess ustawione na true.
  3. Tag <service> w zasobie XML określonym w kroku 1, który identyfikuje klasę usługi z kroku 2. Tag <service> musi też zawierać dodatkowe ustawienia specyficzne dla ODP w samym tagu, jak pokazano w 2 przykładzie.

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>

Plik manifestu specyficzny dla ODP w zasobach XML

Plik zasobów XML określony w tagu <property> musi też zadeklarować klasę usługi w tagu <service> i określić punkt końcowy adresu URL, z którego ODP będzie pobierać treści w celu wypełnienia tabeli RemoteData, jak pokazano w przykładzie poniżej. Jeśli używasz sfederowanych funkcji obliczeniowych, musisz też podać adres URL punktu końcowego sfederowanego serwera obliczeniowego, z którym będzie się łączyć sfederowany klient obliczeniowy.

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

Włączanie trybu programisty

Włącz tryb programisty, postępując zgodnie z instrukcjami podanymi w sekcji Włączanie opcji programisty w dokumentacji Androida Studio.

Ustawienia przełączników i flag

ODP zawiera zestaw przełączników i flag, które służą do sterowania określonymi funkcjami:

  • _global_killswitch: globalny przełącznik dla wszystkich funkcji ODP; ustaw na wartość false, aby korzystać z ODP
  • _federated_compute_kill_switch: _przełącznik kontrolujący wszystkie funkcje ODP dotyczące uczenia (uczenie federacyjne); ustaw na wartość false, aby korzystać z uczenia
  • _caller_app_allowlist: określa, kto może wywoływać ODP. W tym miejscu można dodać aplikacje (nazwa pakietu, [opcjonalny] certyfikat) lub ustawić symbol *, aby zezwolić na wszystkie aplikacje.
  • _isolated_service_allowlist: określa, które usługi mogą działać w procesie usługi izolowanej.

Aby skonfigurować wszystkie przełączniki i flagi pod kątem używania ODP bez ograniczeń, możesz uruchomić te polecenia:

# 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 \"*\"

Interfejsy API po stronie urządzenia

Zapoznaj się z dokumentacją ODP API na Androida.

Interakcje z IsolatedService

Klasa IsolatedService to abstrakcyjna klasa podstawowa, którą muszą rozszerzać wszyscy deweloperzy, którzy chcą tworzyć aplikacje korzystające z ODP, oraz deklarować w pliku manifestu pakietu jako proces izolowany. Usługa ODP uruchamia tę usługę w ramach izolowanego procesu i wysyła do niej żądania. Usługa IsolatedService odbiera żądania od usługi ODP i tworzy IsolatedWorker, aby je obsłużyć.

Deweloperzy muszą wdrożyć metody z interfejsu IsolatedWorker do obsługi żądań aplikacji klienckich, pełnych pobrań i zdarzeń wywoływanych przez wyrenderowany kod HTML. Wszystkie te metody mają domyślne implementacje bezobsługowe, więc deweloperzy mogą pominąć wdrażanie metod, które ich nie interesują.

Klasa OnDevicePersonalizationManager udostępnia interfejs API, który umożliwia aplikacjom i pakietom SDK komunikowanie się z usługą IsolatedService uruchomioną w odseparowanym procesie. Oto kilka przykładowych przypadków użycia:

Wygeneruj treść HTML do wyświetlania w SurfaceView

Aby wygenerować treść do wyświetlenia z użyciem OnDevicePersonalizationManager#execute, aplikacja wywołująca może użyć zwróconego obiektu SurfacePackageToken w kolejnych wywołaniach requestSurfacePackage, aby poprosić o wyświetlenie wyniku w komponencie SurfaceView.

Po udanym działaniu odbiorca jest wywoływany za pomocą polecenia SurfacePackage w celu wyświetlenia renderowanego przez usługę ODP. Aplikacje klienckie muszą wstawić SurfacePackage do SurfaceView w hierarchii widoku.

Gdy aplikacja wywołuje funkcję requestSurfacePackage z argumentem SurfacePackageToken zwróconym przez wcześniejsze wywołanie funkcji OnDevicePersonalizationManager#execute, usługa ODP wywołuje funkcję IsolatedWorker#onRender, aby pobrać fragment kodu HTML, który ma zostać wyrenderowany w ramce odizolowanej. Na tym etapie deweloper nie ma dostępu do usług LocalData ani UserData. Zapobiega to umieszczaniu przez dewelopera potencjalnie poufnych informacji UserData w adresach URL pobierania zasobów w wygenerowanym kodzie HTML. Deweloperzy mogą używać IsolatedService#getEventUrlProvider do generowania adresów URL śledzenia, które można uwzględnić w wygenerowanym kodzie HTML. Po zrenderowaniu kodu HTML usługa ODP przechwytuje żądania wysyłane do tych adresów URL i wywołuje funkcję IsolatedWorker#onEvent. Można wywołać getRemoteData() podczas implementowania onRender().

Śledzenie zdarzeń w treści HTML

Klasa EventUrlProvider udostępnia interfejsy API do generowania linków monitorujących zdarzenia, które deweloperzy mogą uwzględniać w wyjściowym kodzie HTML. Po wyrenderowaniu kodu HTML ODP wywoła funkcję IsolatedWorker#onEvent z ładunkiem adresu URL zdarzenia.

Usługa ODP przechwytuje żądania do adresów URL zdarzeń wygenerowanych przez ODP w wyrenderowanym kodzie HTML, wywołuje funkcję IsolatedWorker#onEvent i zapisują zwrócone EventLogRecord w tabeli EVENTS.

Zapisywanie wyników trwałych

Dzięki OnDevicePersonalizationManager#execute usługa ma możliwość zapisywania danych w trwałym miejscu przechowywania (tabelach REQUESTSEVENTS). Oto wpisy, które można zapisać w tych tabelach:

  • RequestLogRecord dodawane do tabeli REQUESTS.
  • lista obiektów EventLogRecord do dodania do tabeli EVENTS, z których każdy zawiera wskaźnik do wcześniej zapisanego obiektu RequestLogRecord.

Trwałe wyniki przechowywania na urządzeniu mogą być wykorzystywane przez sfederowane uczenie się na potrzeby trenowania modelu.

Zarządzanie zadaniami trenowania na urządzeniu

Usługa ODP wywołuje IsolatedWorker#onTrainingExample, gdy rozpoczyna się zadanie trenowania sfederowanego przetwarzania danych i chce uzyskać przykłady treningowe dostarczone przez deweloperów stosujących ODP. Podczas implementowania onTrainingExample() możesz wywoływać getRemoteData(), getLocalData(), getUserData()getLogReader().

Aby zaplanować lub anulować sfederowane zadania obliczeniowe, możesz użyć klasy FederatedComputeScheduler, która udostępnia interfejsy API dla wszystkich zasobów IsolatedService ODP. Każde sfederowane zadanie obliczeniowe można zidentyfikować na podstawie jego nazwy populacji.

Zanim zaplanujesz nowe sfederowane zadanie obliczeniowe:

  • Na zdalnym zfederowanym serwerze obliczeniowym powinno już być utworzone zadanie o tej nazwie populacji.
  • Punkt końcowy adresu URL sfederowanego serwera obliczeniowego powinien być już określony w ustawieniach pliku manifestu pakietu za pomocą tagu federated-compute-settings.

Interakcje ze stałymi danymi wyjściowymi

W tej sekcji opisano, jak korzystać z trwałego wyjścia w ODP.

Odczytuj tabele lokalne

Klasa LogReader udostępnia interfejsy API do odczytu tabel REQUESTS i EVENTS. Te tabele zawierają dane zapisane przez funkcję IsolatedService podczas wywołań onExecute() lub onEvent(). Dane w tych tabelach można wykorzystać do trenowania modeli za pomocą sfederowanego uczenia się lub do przeprowadzania analizy statystycznej na różnych urządzeniach za pomocą sfederowanych funkcji Analytics.

Interakcje z pobranymi treściami

Z tej sekcji dowiesz się, jak korzystać z pobranych treści w ODP.

Pobieranie treści z serwerów

Usługa ODP okresowo pobiera treści z adresu URL zadeklarowanego w pliku manifestu pakietu IsolatedService i po zakończeniu pobierania wywołuje funkcję onDownloadCompleted. Pobierany jest plik JSON zawierający pary klucz-wartość.

Deweloperzy korzystający z ODP mogą wybrać podzbiór pobranych treści, które powinny zostać dodane do tabeli RemoteData, a które powinny zostać usunięte. Deweloperzy nie mogą modyfikować pobranej zawartości – dzięki temu tabela RemoteData nie zawiera żadnych danych użytkownika. Ponadto deweloperzy mogą dowolnie wypełniać tabelę LocalData, np. mogą przechowywać w niej w pamięci podręcznej niektóre z wyliczonych wcześniej wyników.

Format żądania pobierania

ODP okresowo sprawdza punkt końcowy URL zadeklarowany w pliku manifestu pakietu dewelopera, aby pobierać treści do wypełniania tabeli RemoteData.

Punkt końcowy powinien zwrócić odpowiedź JSON w sposób opisany poniżej. Odpowiedź JSON musi zawierać syncToken, który identyfikuje wersję wysyłanych danych, oraz listę par klucz-wartość do wypełnienia. Wartość syncToken musi być sygnaturą czasową w sekundach, ograniczoną do godziny UTC. W ramach żądania pobierania ODP udostępnia syncToken wcześniejszego pobierania oraz kraj urządzenia jako parametry syncToken i country w adresie URL pobierania. Serwer może używać poprzednich zasad syncToken do implementacji przyrostowego pobierania.

Format pliku do pobrania

Pobrany plik to plik JSON o tej strukturze. Plik JSON powinien zawierać token syncToken identyfikujący wersję pobieranych danych. SyncToken musi być sygnaturą czasową UTC ograniczoną do granicy godzinowej i musi przekraczać SyncToken poprzedniego pobierania. Jeśli token synchronizacji nie spełnia obu tych wymagań, pobrane treści są odrzucane bez przetwarzania.

Pole treści zawiera listę krotek (klucz, dane, kodowanie). Wartość key powinna być ciągiem znaków w formacie UTF-8. Pole encoding to opcjonalny parametr, który określa sposób kodowania pola data. Może ono mieć wartość „utf8” lub „base64”. Domyślnie przyjmuje się kodowanie „utf8”. Pole key jest konwertowane na obiekt String, a pole data jest konwertowane na tablicę bajtów przed wywołaniem obiektu 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"
    },
    // ...
  ]
}

Interfejsy API po stronie serwera

W tej sekcji opisano, jak korzystać z interfejsów API sfederowanego serwera obliczeniowego.

Interfejsy Federated Compute Server API

Aby zaplanować sfederowane zadanie obliczeniowe po stronie klienta, musisz utworzyć zadanie z nazwą populacji utworzone na zdalnym sfederowanym serwerze obliczeniowym. W tej sekcji opisujemy, jak utworzyć takie zadanie na serwerze obliczeniowym federacji.

Diagram topologii sfederowanej platformy obliczeniowej klient-serwer.

Podczas tworzenia nowego zadania w narzędziu do tworzenia zadań deweloperzy ODP powinni przesłać 2 zbiory plików:

  1. Zapisany model tff.learning.models.FunctionalModel utworzony przez wywołanie wywołania interfejsu API tff.learning.models.save_functional_model. Jeden przykład znajdziesz w naszym repozytorium GitHub.
  2. Plik fcp_server_config.json zawierający zasady, sfederowane uczenie się i konfigurację prywatności różnicowej. Poniżej znajduje się przykład pliku 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
  }
}

Więcej przykładów znajdziesz w naszym repozytorium GitHub.

Po przygotowaniu tych 2 danych wejściowych wywołaj konstruktor zadań, aby utworzyć artefakty i wygenerować nowe zadania. Bardziej szczegółowe instrukcje znajdziesz w naszym repozytorium GitHub.