Núcleo Federado

Este documento apresenta a camada principal do TFF que serve como base para o Aprendizado Federado e possíveis algoritmos federados sem aprendizado no futuro.

Para uma introdução suave ao Federated Core, leia os tutoriais a seguir, pois eles apresentam alguns dos conceitos fundamentais por exemplo e demonstram passo a passo a construção de um algoritmo simples de média federada.

Também recomendamos que você se familiarize com o Federated Learning e os tutoriais associados sobre classificação de imagens e geração de texto , pois os usos da Federated Core API (FC API) para o federated learning fornecem um contexto importante para algumas das escolhas que fizemos em projetar esta camada.

Visão geral

Objetivos, usos pretendidos e escopo

Federated Core (FC) é melhor entendido como um ambiente de programação para implementar computações distribuídas, ou seja, computações que envolvem vários computadores (telefones celulares, tablets, dispositivos embarcados, computadores desktop, sensores, servidores de banco de dados, etc.) processamento trivial localmente e se comunicam pela rede para coordenar seu trabalho.

O termo distribuído é muito genérico e o TFF não tem como alvo todos os tipos possíveis de algoritmos distribuídos, então preferimos usar o termo menos genérico computação federada para descrever os tipos de algoritmos que podem ser expressos nessa estrutura.

Embora definir o termo computação federada de maneira totalmente formal esteja fora do escopo deste documento, pense nos tipos de algoritmos que você pode ver expressos em pseudocódigo em uma publicação de pesquisa que descreve um novo algoritmo de aprendizado distribuído.

O objetivo do FC, em poucas palavras, é permitir uma representação similarmente compacta, em um nível de abstração semelhante ao pseudocódigo, da lógica do programa que não é pseudocódigo, mas sim executável em uma variedade de ambientes de destino.

A principal característica definidora dos tipos de algoritmos que o FC é projetado para expressar é que as ações dos participantes do sistema são descritas de maneira coletiva. Assim, tendemos a falar sobre cada dispositivo transformando dados localmente, e os dispositivos coordenando o trabalho por um coordenador centralizado transmitindo , coletando ou agregando seus resultados.

Embora o TFF tenha sido projetado para ir além das simples arquiteturas cliente-servidor , a noção de processamento coletivo é fundamental. Isso se deve às origens do TFF no aprendizado federado, uma tecnologia originalmente projetada para suportar cálculos em dados potencialmente confidenciais que permanecem sob controle de dispositivos clientes e que não podem ser simplesmente baixados para um local centralizado por motivos de privacidade. Embora cada cliente em tais sistemas contribua com dados e poder de processamento para computar um resultado pelo sistema (um resultado que geralmente esperamos que seja de valor para todos os participantes), também nos esforçamos para preservar a privacidade e o anonimato de cada cliente.

Assim, enquanto a maioria das estruturas para computação distribuída são projetadas para expressar o processamento da perspectiva de participantes individuais - isto é, no nível de trocas de mensagens ponto a ponto individuais e a interdependência das transições de estado local do participante com as mensagens recebidas e enviadas , o Federated Core do TFF é projetado para descrever o comportamento do sistema a partir da perspectiva global do sistema (semelhante a, por exemplo, MapReduce ).

Consequentemente, enquanto frameworks distribuídos para propósitos gerais podem oferecer operações como enviar e receber como blocos de construção, o FC fornece blocos de construção como tff.federated_sum , tff.federated_reduce ou tff.federated_broadcast que encapsulam protocolos distribuídos simples.

Linguagem

Interface Python

TFF usa uma linguagem interna para representar computações federadas, cuja sintaxe é definida pela representação serializável em computation.proto . Os usuários da API FC geralmente não precisarão interagir diretamente com essa linguagem. Em vez disso, fornecemos uma API Python (o namespace tff ) que a envolve como uma forma de definir cálculos.

Especificamente, o TFF fornece decoradores de função Python, como tff.federated_computation , que rastreiam os corpos das funções decoradas e produzem representações serializadas da lógica de computação federada na linguagem do TFF. Uma função decorada com tff.federated_computation atua como portadora de tal representação serializada e pode incorporá-la como um bloco de construção no corpo de outra computação ou executá-la sob demanda quando invocada.

Aqui está apenas um exemplo; mais exemplos podem ser encontrados nos tutoriais de algoritmos personalizados .

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

Leitores familiarizados com o TensorFlow não ansiosos acharão essa abordagem análoga à escrita de código Python que usa funções como tf.add ou tf.reduce_sum em uma seção do código Python que define um gráfico do TensorFlow. Embora o código seja tecnicamente expresso em Python, seu objetivo é construir uma representação serializável de um tf.Graph abaixo, e é o gráfico, não o código Python, que é executado internamente pelo tempo de execução do TensorFlow. Da mesma forma, pode-se pensar em tff.federated_mean como inserir um op federado em uma computação federada representada por get_average_temperature .

Uma parte da razão para o FC definir uma linguagem tem a ver com o fato de que, como observado acima, as computações federadas especificam comportamentos coletivos distribuídos e, como tal, sua lógica não é local. Por exemplo, o TFF fornece operadores, entradas e saídas que podem existir em diferentes locais da rede.

Isso exige uma linguagem e um sistema de tipos que capture a noção de distribuição.

Tipo de sistema

O Federated Core oferece as seguintes categorias de tipos. Ao descrever esses tipos, apontamos para os construtores de tipos, bem como introduzimos uma notação compacta, pois é uma maneira prática de descrever tipos de cálculos e operadores.

Primeiro, aqui estão as categorias de tipos que são conceitualmente semelhantes aos encontrados em linguagens convencionais existentes:

  • Tipos de tensor ( tff.TensorType ). Assim como no TensorFlow, eles têm dtype e shape . A única diferença é que objetos desse tipo não estão limitados a instâncias tf.Tensor em Python que representam saídas de operações do TensorFlow em um gráfico do TensorFlow, mas também podem incluir unidades de dados que podem ser produzidas, por exemplo, como uma saída de um protocolo de agregação. Assim, o tipo tensor TFF é simplesmente uma versão abstrata de uma representação física concreta desse tipo em Python ou TensorFlow.

    Os TensorTypes do TFF podem ser mais rigorosos em seu tratamento (estático) de formas do que o TensorFlow. Por exemplo, o typesystem do TFF trata um tensor com classificação desconhecida como atribuível de qualquer outro tensor do mesmo dtype , mas não atribuível a qualquer tensor com classificação fixa. Esse tratamento evita certas falhas de tempo de execução (por exemplo, tentar remodelar um tensor de classificação desconhecida em uma forma com número incorreto de elementos), ao custo de maior rigor nos cálculos que o TFF aceita como válidos.

    A notação compacta para tipos de tensor é dtype ou dtype[shape] . Por exemplo, int32 e int32[10] são os tipos de inteiros e vetores int, respectivamente.

  • Tipos de sequência ( tff.SequenceType ). Esses são o equivalente abstrato do TFF do conceito concreto do TensorFlow de tf.data.Dataset s. Elementos de sequências podem ser consumidos de maneira sequencial e podem incluir tipos complexos.

    A representação compacta dos tipos de sequência é T* , onde T é o tipo de elementos. Por exemplo, int32* representa uma sequência inteira.

  • Tipos de tupla nomeados ( tff.StructType ). Essa é a maneira do TFF de construir tuplas e estruturas semelhantes a dicionários que possuem um número predefinido de elementos com tipos específicos, nomeados ou não nomeados. É importante ressaltar que o conceito de tupla nomeada do TFF engloba o equivalente abstrato das tuplas de argumento do Python, ou seja, coleções de elementos dos quais alguns, mas não todos, são nomeados, e alguns são posicionais.

    A notação compacta para tuplas nomeadas é <n_1=T_1, ..., n_k=T_k> , onde n_k são nomes de elementos opcionais e T_k são tipos de elementos. Por exemplo, <int32,int32> é uma notação compacta para um par de inteiros sem nome e <X=float32,Y=float32> é uma notação compacta para um par de floats nomeados X e Y que podem representar um ponto em um plano . Tuplas podem ser aninhadas ou misturadas com outros tipos, por exemplo, <X=float32,Y=float32>* seria uma notação compacta para uma sequência de pontos.

  • Tipos de função ( tff.FunctionType ). TFF é um framework de programação funcional, com funções tratadas como valores de primeira classe . As funções têm no máximo um argumento e exatamente um resultado.

    A notação compacta para funções é (T -> U) , onde T é o tipo de um argumento e U é o tipo do resultado, ou ( -> U) se não houver argumento (embora as funções sem argumento sejam degeneradas conceito que existe principalmente apenas no nível do Python). Por exemplo (int32* -> int32) é uma notação para um tipo de função que reduz uma sequência inteira a um único valor inteiro.

Os tipos a seguir abordam o aspecto de sistemas distribuídos de cálculos de TFF. Como esses conceitos são exclusivos do TFF, recomendamos que você consulte o tutorial de algoritmos personalizados para obter comentários e exemplos adicionais.

  • Tipo de veiculação . Esse tipo ainda não está exposto na API pública, exceto na forma de 2 literais tff.SERVER e tff.CLIENTS que você pode considerar como constantes desse tipo. Ele é usado internamente, no entanto, e será introduzido na API pública em versões futuras. A representação compacta desse tipo é a placement .

    Um posicionamento representa um coletivo de participantes do sistema que desempenham um papel específico. A versão inicial tem como alvo cálculos cliente-servidor, nos quais existem 2 grupos de participantes: clientes e um servidor (você pode pensar no último como um grupo singleton). No entanto, em arquiteturas mais elaboradas, pode haver outras funções, como agregadores intermediários em um sistema de várias camadas, que podem estar executando diferentes tipos de agregação ou usar tipos diferentes de compactação/descompactação de dados do que os usados ​​pelo servidor ou os clientes.

    O objetivo principal de definir a noção de posicionamentos é como base para definir tipos federados .

  • Tipos federados ( tff.FederatedType ). Um valor de um tipo federado é aquele que é hospedado por um grupo de participantes do sistema definido por um posicionamento específico (como tff.SERVER ou tff.CLIENTS ). Um tipo federado é definido pelo valor de posicionamento (portanto, é um tipo dependente ), o tipo de membros constituintes (que tipo de conteúdo cada um dos participantes está hospedando localmente) e o bit adicional all_equal que especifica se todos os participantes estão localmente hospedar o mesmo item.

    A notação compacta para o tipo federado de valores que incluem itens (constituintes de membros) do tipo T , cada um hospedado pelo grupo (posicionamento) G é T@G ou {T}@G com o bit all_equal definido ou não definido, respectivamente.

    Por exemplo:

    • {int32}@CLIENTS representa um valor federado que consiste em um conjunto de inteiros potencialmente distintos, um por dispositivo cliente. Observe que estamos falando de um único valor federado que abrange vários itens de dados que aparecem em vários locais na rede. Uma maneira de pensar sobre isso é como uma espécie de tensor com uma dimensão de "rede", embora essa analogia não seja perfeita porque o TFF não permite acesso aleatório a constituintes membros de um valor federado.

    • {<X=float32,Y=float32>*}@CLIENTS representa um conjunto de dados federado , um valor que consiste em várias sequências de coordenadas XY , uma sequência por dispositivo cliente.

    • <weights=float32[10,5],bias=float32[5]>@SERVER representa uma tupla nomeada de peso e tensores de polarização no servidor. Como eliminamos as chaves, isso indica que o bit all_equal está definido, ou seja, há apenas uma única tupla (independentemente de quantas réplicas de servidor possam existir em um cluster que hospeda esse valor).

Blocos de construção

A linguagem do Federated Core é uma forma de lambda-calculus , com alguns elementos adicionais.

Ele fornece as seguintes abstrações de programação atualmente expostas na API pública:

  • Cálculos do TensorFlow ( tff.tf_computation ). Essas são seções do código do TensorFlow encapsuladas como componentes reutilizáveis ​​no TFF usando o decorador tff.tf_computation . Eles sempre têm tipos funcionais e, diferentemente das funções do TensorFlow, podem receber parâmetros estruturados ou retornar resultados estruturados de um tipo de sequência.

    Aqui está um exemplo, uma computação TF do tipo (int32* -> int) que usa o operador tf.data.Dataset.reduce para calcular uma soma de inteiros:

    @tff.tf_computation(tff.SequenceType(tf.int32))
    def add_up_integers(x):
      return x.reduce(np.int32(0), lambda x, y: x + y)
    
  • Operadores intrínsecos ou federados ( tff.federated_... ). Esta é uma biblioteca de funções como tff.federated_sum ou tff.federated_broadcast que constituem a maior parte da API FC, a maioria das quais representa operadores de comunicação distribuídos para uso com TFF.

    Referimo-nos a eles como intrínsecos porque, um pouco como funções intrínsecas , eles são um conjunto aberto e extensível de operadores que são entendidos pelo TFF e compilados em código de nível inferior.

    A maioria desses operadores possui parâmetros e resultados de tipos federados, e a maioria são modelos que podem ser aplicados a vários tipos de dados.

    Por exemplo, tff.federated_broadcast pode ser pensado como um operador de modelo de um tipo funcional T@SERVER -> T@CLIENTS .

  • Expressões lambda ( tff.federated_computation ). Uma expressão lambda em TFF é equivalente a lambda ou def em Python; ele consiste no nome do parâmetro e em um corpo (expressão) que contém referências a esse parâmetro.

    No código Python, eles podem ser criados decorando funções Python com tff.federated_computation e definindo um argumento.

    Aqui está um exemplo de uma expressão lambda que já mencionamos anteriormente:

    @tff.federated_computation(tff.type_at_clients(tf.float32))
    def get_average_temperature(sensor_readings):
      return tff.federated_mean(sensor_readings)
    
  • Literais de posicionamento . Por enquanto, apenas tff.SERVER e tff.CLIENTS para permitir a definição de cálculos cliente-servidor simples.

  • Invocações de função ( __call__ ). Qualquer coisa que tenha um tipo funcional pode ser invocada usando a sintaxe __call__ padrão do Python. A invocação é uma expressão, cujo tipo é o mesmo que o tipo do resultado da função que está sendo invocada.

    Por exemplo:

    • add_up_integers(x) representa uma invocação da computação TensorFlow definida anteriormente em um argumento x . O tipo dessa expressão é int32 .

    • tff.federated_mean(sensor_readings) representa uma invocação do operador de média federada em sensor_readings . O tipo dessa expressão é float32@SERVER (assumindo o contexto do exemplo acima).

  • Formando tuplas e selecionando seus elementos. Expressões Python da forma [x, y] , x[y] ou xy que aparecem nos corpos das funções decoradas com tff.federated_computation .