Algoritmos federados personalizados, parte 1: introdução ao núcleo federado

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Este tutorial é a primeira parte de uma série de duas partes que demonstra como implementar tipos personalizados de algoritmos federados em TensorFlow Federados (TFF), utilizando a Federated Núcleo (FC) - um conjunto de interfaces de nível inferior que servem como uma base sobre a qual temos implementado o Federated Aprendizagem (FL) camada.

Esta primeira parte é mais conceitual; apresentamos alguns dos principais conceitos e abstrações de programação usados ​​no TFF e demonstramos seu uso em um exemplo muito simples com uma matriz distribuída de sensores de temperatura. Na segunda parte desta série , usamos os mecanismos que introduzem aqui para implementar uma versão simples de algoritmos de treinamento e avaliação federados. Como um follow-up, nós encorajamos você a estudar a implementação de média federado em tff.learning .

Ao final desta série, você deve ser capaz de reconhecer que os aplicativos do Federated Core não se limitam necessariamente ao aprendizado. As abstrações de programação que oferecemos são bastante genéricas e podem ser usadas, por exemplo, para implementar análises e outros tipos de cálculos personalizados sobre dados distribuídos.

Embora este tutorial é projetado para ser auto-suficiente, nós encorajamos você a primeiros tutoriais de leitura em classificação de imagens e geração de texto para um nível superior e introdução mais suave para o quadro TensorFlow Federated ea Federated Aprendizagem APIs ( tff.learning ), como isso o ajudará a contextualizar os conceitos que descrevemos aqui.

Usos pretendidos

Em poucas palavras, Federated Núcleo (FC) é um ambiente de desenvolvimento que faz com que seja possível expressar de forma compacta lógica do programa que o código combina TensorFlow com operadores distribuídos de comunicação, tais como aqueles que são usados em Federated Média - Computação somas distribuídas, médias e outros tipos de agregações distribuídas sobre um conjunto de dispositivos clientes no sistema, modelos de transmissão e parâmetros para esses dispositivos, etc.

Você pode estar ciente de tf.contrib.distribute , e uma pergunta natural perguntar neste ponto pode ser: de que modo esse quadro diferem? Afinal, as duas estruturas tentam fazer com que os cálculos do TensorFlow sejam distribuídos.

Uma maneira de pensar sobre isso é que, enquanto a meta declarada de tf.contrib.distribute é permitir que os usuários usem os modelos existentes e código de formação, com alterações mínimas para permitir a formação distribuída, e muito foco é sobre como tirar proveito da infra-estrutura distribuída para tornar o código de treinamento existente mais eficiente, o objetivo do Federated Core da TFF é dar aos pesquisadores e profissionais controle explícito sobre os padrões específicos de comunicação distribuída que usarão em seus sistemas. O foco do FC é fornecer uma linguagem flexível e extensível para expressar algoritmos de fluxo de dados distribuídos, em vez de um conjunto concreto de recursos de treinamento distribuídos implementados.

Um dos principais públicos-alvo da API FC da TFF são pesquisadores e profissionais que podem querer experimentar novos algoritmos de aprendizagem federados e avaliar as consequências de escolhas de design sutis que afetam a maneira como o fluxo de dados no sistema distribuído é orquestrado, ainda sem se atrapalhar com os detalhes de implementação do sistema. O nível de abstração que a API FC visa corresponde aproximadamente ao pseudocódigo que alguém poderia usar para descrever a mecânica de um algoritmo de aprendizagem federado em uma publicação de pesquisa - quais dados existem no sistema e como eles são transformados, mas sem cair para o nível de trocas de mensagens de rede ponto a ponto individuais.

A TFF como um todo visa cenários nos quais os dados são distribuídos e devem permanecer assim, por exemplo, por motivos de privacidade, e onde a coleta de todos os dados em um local centralizado pode não ser uma opção viável. Isso tem implicações na implementação de algoritmos de aprendizado de máquina que requerem um maior grau de controle explícito, em comparação com cenários em que todos os dados podem ser acumulados em um local centralizado em um data center.

Antes de começarmos

Antes de mergulharmos no código, tente executar o seguinte exemplo "Hello World" para garantir que seu ambiente esteja configurado corretamente. Se isso não funcionar, consulte a instalação guia para obter instruções.

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

Dados federados

Uma das características distintivas da TFF é que ele permite que você expresse de forma compacta cálculos baseados em TensorFlow sobre dados federados. Nós estaremos usando os dados federados prazo neste tutorial para se referir a uma coleção de itens de dados hospedados em um grupo de dispositivos em um sistema distribuído. Por exemplo, aplicativos executados em dispositivos móveis podem coletar dados e armazená-los localmente, sem fazer upload para um local centralizado. Ou, uma matriz de sensores distribuídos pode coletar e armazenar leituras de temperatura em seus locais.

Dados federados como aqueles nos exemplos acima são tratados da TFF como cidadãos de primeira classe , isto é, eles podem aparecer como parâmetros e resultados de funções, e eles têm tipos. Para reforçar esta ideia, vamos nos referir a conjuntos de dados federados como valores federados, ou como valores de tipos federados.

O ponto importante a entender é que estamos modelando toda a coleção de itens de dados em todos os dispositivos (por exemplo, toda a coleção de leituras de temperatura de todos os sensores em uma matriz distribuída) como um único valor federado.

Por exemplo, aqui está como se definiria em TFF o tipo de um flutuador federado hospedado por um grupo de dispositivos de cliente. Uma coleção de leituras de temperatura que se materializam em uma série de sensores distribuídos pode ser modelada como um valor desse tipo federado.

federated_float_on_clients = tff.type_at_clients(tf.float32)

Mais geralmente, um tipo federado em TFF é definido através da especificação do tipo T dos seus constituintes membro - os itens de dados que residem em dispositivos individuais, e o grupo G de dispositivos nos quais estão alojados os valores federados deste tipo (além de um terço, informação opcional que mencionaremos em breve). Referimo-nos ao grupo G de dispositivos de hospedagem um valor federada como a colocação do valor. Assim, tff.CLIENTS é um exemplo de um canal.

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

Um tipo agrupado com o membro constituintes T e colocação G pode ser representado de forma compacta como {T}@G , como mostrado abaixo.

str(federated_float_on_clients)
'{float32}@CLIENTS'

As chaves {} neste concisa notação servir como um lembrete de que os constituintes membros (itens de dados em dispositivos diferentes) podem ser diferentes, como seria de esperar, por exemplo, de leituras de sensores de temperatura, de modo que os clientes como um grupo está hospedando conjuntamente um de multi -defina de T -typed itens que, juntos, constituem o valor federado.

É importante notar que os componentes que integram um valor federado são geralmente opaco para o programador, ou seja, um valor federado não deve ser pensado como um simples dict chaveado por um identificador de um dispositivo no sistema - esses valores são destinados a ser colectivamente transformada apenas por operadores federados que abstracto representam vários tipos de protocolos de comunicação distribuída (tais como agregação). Se isso soar muito abstrato, não se preocupe - retornaremos a isso em breve e o ilustraremos com exemplos concretos.

Os tipos federados na TFF vêm em dois sabores: aqueles em que os constituintes membros de um valor federado podem ser diferentes (conforme visto acima) e aqueles em que todos são iguais. Isto é controlado pela terceira, opcional all_equal parâmetro no tff.FederatedType construtor (falta para False ).

federated_float_on_clients.all_equal
False

Um tipo federado com uma colocação de G em que todo o T -typed constituintes membros são conhecidos por ser igual podem ser compactamente representada como T@G (em oposição a {T}@G , isto é, com as chaves caiu para refletir o fato de que o conjunto múltiplo de constituintes dos membros consiste em um único item).

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

Um exemplo de um valor federado desse tipo que pode surgir em cenários práticos é um hiperparâmetro (como uma taxa de aprendizado, uma norma de recorte, etc.) que foi transmitido por um servidor para um grupo de dispositivos que participam de um treinamento federado.

Outro exemplo é um conjunto de parâmetros para um modelo de aprendizado de máquina pré-treinado no servidor, que depois são transmitidos para um grupo de dispositivos clientes, onde podem ser personalizados para cada usuário.

Por exemplo, suponha que temos um par de float32 parâmetros a e b para um modelo de regressão linear simples unidimensional. Podemos construir o tipo (não federado) de tais modelos para uso na TFF da seguinte maneira. As chaves angulares <> do tipo string impressa são uma notação TFF compacto para tuplas nomeado ou não.

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

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

Note que estamos especificando única dtype s acima. Tipos não escalares também são suportados. No código acima, tf.float32 é uma notação de atalho para a mais geral tff.TensorType(dtype=tf.float32, shape=[]) .

Quando este modelo é transmitido aos clientes, o tipo do valor federado resultante pode ser representado conforme mostrado abaixo.

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

Por simetria com flutuador federado acima, vamos nos referir a esse tipo de como uma tupla federado. De modo mais geral, vamos muitas vezes usam a XYZ federado termo para se referir a um valor federado no qual os constituintes membros são XYZ -como. Assim, vamos falar sobre coisas como tuplas federados, seqüências federados, modelos federados, e assim por diante.

Agora, voltando ao float32@CLIENTS - enquanto ele aparece replicado em vários dispositivos, é na verdade um único float32 , já que todos os membros são os mesmos. Em geral, você pode pensar em qualquer tipo federado tudo igual, isto é, um de forma a T@G , como isomorfo a um tipo não-federado T , uma vez que em ambos os casos, há realmente apenas um único (embora potencialmente replicado) artigo de tipo T .

Dado o isomorfismo entre T e T@G , você pode se perguntar que fim, se houver, os últimos tipos podem servir. Leia.

Posicionamentos

Visão geral do design

Na seção anterior, nós introduzimos o conceito de colocações - grupos de participantes do sistema que podem ser conjuntamente hospedagem um valor federado, e temos demonstrado o uso de tff.CLIENTS como um exemplo de especificação de uma colocação.

Para explicar por que a noção de colocação é tão fundamental que precisávamos para incorporá-la no sistema de tipo TFF, recordar o que dissemos no início deste tutorial sobre algumas das utilizações previstas da TFF.

Embora neste tutorial, você só verá o código TFF sendo executado localmente em um ambiente simulado, nosso objetivo é que o TFF habilite a escrita de código que você possa implantar para execução em grupos de dispositivos físicos em um sistema distribuído, potencialmente incluindo dispositivos móveis ou incorporados executando o Android. Cada um desses dispositivos receberia um conjunto separado de instruções para executar localmente, dependendo da função que desempenha no sistema (um dispositivo de usuário final, um coordenador centralizado, uma camada intermediária em uma arquitetura multicamadas, etc.). É importante ser capaz de raciocinar sobre quais subconjuntos de dispositivos executam qual código e onde diferentes partes dos dados podem se materializar fisicamente.

Isso é especialmente importante ao lidar com, por exemplo, dados de aplicativos em dispositivos móveis. Como os dados são privados e podem ser confidenciais, precisamos verificar estaticamente se esses dados nunca sairão do dispositivo (e provar fatos sobre como os dados estão sendo processados). As especificações de posicionamento são um dos mecanismos projetados para oferecer suporte a isso.

TFF foi concebido como um ambiente de programação centrada em dados, e como tal, ao contrário de alguns dos quadros existentes que incidem sobre operações e onde essas operações pode ser executado, TFF se concentra em dados, onde que se materializa de dados, e como ele está sendo transformado. Consequentemente, o posicionamento é modelado como uma propriedade de dados no TFF, ao invés de uma propriedade de operações em dados. De fato, como você verá na próxima seção, algumas das operações TFF abrangem vários locais e são executadas "na rede", por assim dizer, em vez de serem executadas por uma única máquina ou grupo de máquinas.

Representando o tipo de um determinado valor como T@G ou {T}@G (em oposição a apenas T ) toma decisões de colocação de dados explícita, e em conjunto com uma análise estática de programas escritos em TFF, ele pode servir como uma base para a prestação de garantias formais de privacidade para dados confidenciais no dispositivo.

Uma coisa importante a nota, neste ponto, no entanto, é que, enquanto nós encorajamos os usuários TFF ser explícito sobre grupos de dispositivos participantes que hospedam os dados (as colocações), o programador nunca vai lidar com os dados brutos ou identidades dos participantes individuais .

Dentro do corpo de código TFF, pelo projeto, não há nenhuma maneira para enumerar os dispositivos que constituem o grupo representado por tff.CLIENTS , ou sonda para a existência de um dispositivo específico no grupo. Não há conceito de dispositivo ou identidade de cliente em qualquer lugar na API Federated Core, no conjunto subjacente de abstrações arquitetônicas ou na infraestrutura de tempo de execução principal que fornecemos para dar suporte às simulações. Toda a lógica de computação que você escrever será expressa como operações em todo o grupo de clientes.

Lembre-se aqui o que dissemos anteriormente sobre valores de tipos federados sendo ao contrário de Python dict , em que não se pode simplesmente enumerar os seus constituintes membros. Pense nos valores que a lógica do seu programa TFF manipula como sendo associados a colocações (grupos), ao invés de participantes individuais.

As colocações são projetados para ser um cidadão de primeira classe em TFF, bem como, e pode aparecer como parâmetros e resultados de uma placement tipo (a ser representados por tff.PlacementType na API). No futuro, planejamos fornecer uma variedade de operadores para transformar ou combinar canais, mas isso está fora do escopo deste tutorial. Por enquanto, basta pensar de placement como um opaco primitivo tipo interno em TFF, semelhante à forma como int e bool são opacos tipos built-in em Python, com tff.CLIENTS ser um literal constante deste tipo, não muito diferente de 1 sendo um literal constante do tipo int .

Especificação de canais

TFF fornece dois literais colocação básicos, tff.CLIENTS e tff.SERVER , para tornar mais fácil para expressar a rica variedade de cenários práticos que são naturalmente modeladas como arquiteturas cliente-servidor, com vários dispositivos clientes (telefones celulares, dispositivos embarcados, bancos de dados distribuídos , sensores, etc.) orquestradas por um coordenador único servidor centralizado. A TFF também foi projetada para oferecer suporte a posicionamentos personalizados, grupos de clientes múltiplos, arquiteturas distribuídas em várias camadas e outras arquiteturas distribuídas mais gerais, mas discuti-las está fora do escopo deste tutorial.

Não TFF não prescrever o que quer os tff.CLIENTS ou o tff.SERVER realmente representam.

Em particular, tff.SERVER pode ser um único dispositivo físico (um membro de um grupo Singleton), mas pode muito bem ser um grupo de réplicas em uma replicação máquina de estado do cluster tolerante a falhas em execução - não fazemos qualquer arquitetônico especial suposições. Em vez disso, usamos o all_equal pouco mencionado na seção anterior para expressar o fato de que estamos geralmente lidando com apenas um único item de dados no servidor.

Da mesma forma, tff.CLIENTS em algumas aplicações pode representar todos os clientes no sistema - o que no contexto da aprendizagem federado que às vezes se referem como a população, mas, por exemplo, em implementações de produção de Federated Média , pode representar um grupo - um subconjunto de os clientes selecionados para participação em uma determinada rodada de treinamento. Os posicionamentos definidos abstratamente recebem um significado concreto quando um cálculo no qual eles aparecem é implantado para execução (ou simplesmente invocado como uma função Python em um ambiente simulado, como é demonstrado neste tutorial). Em nossas simulações locais, o grupo de clientes é determinado pelos dados federados fornecidos como entrada.

Cálculos federados

Declaração de cálculos federados

O TFF foi projetado como um ambiente de programação funcional fortemente tipado que oferece suporte ao desenvolvimento modular.

A unidade básica de composição em TFF é uma computação federado - uma secção de lógica que pode aceitar valores de rede como entrada e retorno valores federados como saída. Veja como você pode definir um cálculo que calcula a média das temperaturas relatadas pela matriz de sensores de nosso exemplo anterior.

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

Olhando para o código acima, neste momento você deve estar se perguntando - não estão já decorador construções para definir unidades combináveis, como tf.function em TensorFlow, e em caso afirmativo, por que introduzir ainda um outro, e como é que é diferente?

A resposta curta é que o código gerado pelo tff.federated_computation invólucro é nem TensorFlow, nem é Python - é uma especificação de um sistema distribuído em uma linguagem de cola independente de plataforma interna. Neste ponto, isso sem dúvida parecerá enigmático, mas tenha em mente essa interpretação intuitiva de uma computação federada como uma especificação abstrata de um sistema distribuído. Explicaremos em um minuto.

Primeiro, vamos brincar um pouco com a definição. Os cálculos TFF são geralmente modelados como funções - com ou sem parâmetros, mas com assinaturas de tipo bem definidas. Você pode imprimir a assinatura tipo de um cálculo, consultando a sua type_signature propriedade, como mostrado abaixo.

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

A assinatura de tipo nos diz que o cálculo aceita uma coleção de diferentes leituras de sensores em dispositivos clientes e retorna uma única média no servidor.

Antes de irmos adiante, vamos refletir sobre isso por um minuto - a entrada e saída deste cálculo estão em lugares diferentes (sobre CLIENTS vs. no SERVER ). Lembre-se do que dissemos na seção anterior em posicionamentos sobre como operações TFF pode abranger entre locais, e executar na rede, eo que acabamos de dizer sobre cálculos federados como representando especificações abstractas de sistemas distribuídos. Acabamos de definir uma computação - um sistema distribuído simples no qual os dados são consumidos nos dispositivos do cliente e os resultados agregados surgem no servidor.

Em muitos cenários práticos, os cálculos que representam tarefas de nível superior tendem a aceitar as suas entradas e reportar suas saídas no servidor - isso reflete a idéia de que cálculos pode ser desencadeada por consultas com origem e destino no servidor.

No entanto, o FC API não impõe essa suposição, e muitos dos blocos de construção que usamos internamente (incluindo numerosos tff.federated_... operadores que você pode encontrar na API) tem entradas e saídas de práticas distintas, de modo em geral, você deve não pensar em uma computação federada como algo que é executado no servidor ou é executada por um servidor. O servidor é apenas um tipo de participante em uma computação federada. Ao pensar sobre a mecânica de tais cálculos, é melhor sempre usar como padrão a perspectiva global da rede, em vez da perspectiva de um único coordenador centralizado.

Em geral, as assinaturas dos tipos funcionais são compactamente representado como (T -> U) para os tipos de T e U de entradas e saídas, respectivamente. O tipo do parâmetro formal (tais sensor_readings neste caso) é especificado como o argumento para o decorador. Você não precisa especificar o tipo de resultado - ele é determinado automaticamente.

Embora o TFF ofereça formas limitadas de polimorfismo, os programadores são fortemente encorajados a serem explícitos sobre os tipos de dados com os quais trabalham, pois isso torna mais fácil a compreensão, depuração e verificação formal das propriedades do seu código. Em alguns casos, especificar explicitamente os tipos é um requisito (por exemplo, cálculos polimórficos atualmente não são executáveis ​​diretamente).

Execução de cálculos federados

Para oferecer suporte ao desenvolvimento e à depuração, o TFF permite invocar diretamente cálculos definidos dessa forma como funções Python, conforme mostrado abaixo. Onde o cálculo espera um valor de um tipo federado com o all_equal conjunto de bits para False , você pode alimentá-lo como uma simples list em Python, e para tipos federados com o all_equal conjunto de bits para True , você pode apenas alimentar diretamente o (único) membro constituinte. É também assim que os resultados são informados a você.

get_average_temperature([68.5, 70.3, 69.8])
69.53334

Ao executar cálculos como este no modo de simulação, você atua como um observador externo com uma visão de todo o sistema, que tem a capacidade de fornecer entradas e consumir saídas em qualquer local da rede, como de fato é o caso aqui - você forneceu valores de cliente na entrada e consumiu o resultado do servidor.

Agora, o retorno de deixar uma nota que fizemos anteriormente sobre a tff.federated_computation decorador emissor de código em uma linguagem de cola. Embora a lógica de cálculos TFF pode ser expressa como funções comuns em Python (você só precisa decorá-los com tff.federated_computation como fizemos acima), e você pode diretamente invocá-los com argumentos Python assim como quaisquer outras funções Python neste notebook, nos bastidores, como observamos anteriormente, cálculos TFF não são realmente Python.

O que queremos dizer com isso é que quando o interpretador Python encontra uma função decorado com tff.federated_computation , ele traça as declarações em corpo desta função uma vez (em tempo de definição), e em seguida, constrói uma representação serializada da lógica da computação para uso futuro - seja para execução ou para ser incorporado como um subcomponente em outro cálculo.

Você pode verificar isso adicionando uma declaração de impressão, como segue:

@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".

Você pode pensar em um código Python que define uma computação federada de maneira semelhante a como você pensaria em um código Python que constrói um gráfico TensorFlow em um contexto não antecipado (se você não estiver familiarizado com os usos não ansiosos do TensorFlow, pense no seu O código Python que define um gráfico de operações a serem executadas posteriormente, mas não as executa imediatamente). O código de construção de gráfico não ansioso no TensorFlow é Python, mas o gráfico do TensorFlow construído por este código é independente da plataforma e serializável.

Da mesma forma, os cálculos TFF são definidos em Python, mas os comandos Python nos seus corpos, tais como tff.federated_mean no exemplo weve apenas mostrado, são compilados em uma representação seriável portátil e independente da plataforma sob o capô.

Como desenvolvedor, você não precisa se preocupar com os detalhes desta representação, pois você nunca precisará trabalhar diretamente com ela, mas deve estar ciente de sua existência, o fato de que os cálculos TFF são fundamentalmente não ansiosos, e não pode capturar o estado Python arbitrário. Código Python contida no corpo de uma computação TFF é executado em tempo de definição, quando o corpo da função Python decorado com tff.federated_computation é rastreada antes de ser serializado. Ele não é rastreado novamente no momento da invocação (exceto quando a função é polimórfica; consulte as páginas de documentação para obter detalhes).

Você pode se perguntar por que optamos por introduzir uma representação interna dedicada não-Python. Um dos motivos é que, em última análise, os cálculos TFF devem ser implantados em ambientes físicos reais e hospedados em dispositivos móveis ou incorporados, onde o Python pode não estar disponível.

Outra razão é que os cálculos TFF expressam o comportamento global de sistemas distribuídos, ao contrário dos programas Python, que expressam o comportamento local de participantes individuais. Você pode ver que no exemplo simples acima, com o operador especial tff.federated_mean que aceita dados em dispositivos de cliente, mas depósitos os resultados no servidor.

O operador tff.federated_mean não podem ser facilmente modelados como um operador comum em Python, uma vez que não executar localmente - como observado anteriormente, representa um sistema distribuído que coordena o comportamento dos vários participantes do sistema. Nós enviamos a esses operadores como operadores federados, para distingui-los dos operadores comuns (locais) em Python.

O sistema de tipo TFF e o conjunto fundamental de operações suportadas na linguagem do TFF, portanto, divergem significativamente daqueles em Python, necessitando o uso de uma representação dedicada.

Compondo cálculos federados

Conforme observado acima, as computações federadas e seus constituintes são mais bem compreendidas como modelos de sistemas distribuídos, e você pode pensar em compor computações federadas como a composição de sistemas distribuídos mais complexos a partir de sistemas mais simples. Você pode pensar no tff.federated_mean operador como uma espécie de built-in modelo de computação federada com uma assinatura de tipo ({T}@CLIENTS -> T@SERVER) (na verdade, assim como cálculos de escrever, este operador tem também um complexo estrutura - por baixo do capô, nós a dividimos em operadores mais simples).

O mesmo é verdadeiro para compor cálculos federados. O cálculo get_average_temperature pode ser invocado em um corpo de outra função Python decorado com tff.federated_computation - isso irá fazer com que seja incorporado no corpo do pai, muito da mesma maneira tff.federated_mean foi incorporado em seu próprio corpo antes.

Uma restrição importante estar ciente de que corpos de funções Python decorados com tff.federated_computation deve consistir apenas de operadores federados, ou seja, eles não podem conter diretamente as operações TensorFlow. Por exemplo, você não pode usar diretamente tf.nest interfaces para adicionar um par de valores federados. Código TensorFlow deve limitar-se blocos de código decorados com um tff.tf_computation discutido na seção seguinte. Apenas quando enrolado desta maneira pode o código TensorFlow embrulhado ser invocada no corpo de um tff.federated_computation .

As razões para esta separação são técnicos (é difícil para enganar operadoras como a tf.add para trabalhar com não-tensores), bem como arquitectónico. O idioma de computações federados (isto é, a lógica construído a partir de corpos de serializados funções Python decorados com tff.federated_computation ) é concebido para servir como uma linguagem cola independente da plataforma. Esta linguagem cola é actualmente utilizado para construir sistemas distribuídos a partir de secções embebidas de código TensorFlow (confinado ao tff.tf_computation blocos). Na plenitude dos tempos, prevemos a necessidade de seções incorporar de outra lógica, não TensorFlow, como consultas de banco de dados relacionais que possam representar tubulações de entrada, todos ligados entre si usando a mesma linguagem de cola (as tff.federated_computation blocos).

Lógica do TensorFlow

Declaração de cálculos do TensorFlow

O TFF foi projetado para uso com o TensorFlow. Dessa forma, a maior parte do código que você escreverá no TFF provavelmente será código do TensorFlow comum (ou seja, execução local). Para utilizar tal código com TFF, como mencionado acima, ele só precisa ser decorado com tff.tf_computation .

Por exemplo, aqui está como poderíamos implementar uma função que recebe um número e acrescenta 0.5 a ele.

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

Mais uma vez, olhar para isso, você pode estar se perguntando por que devemos definir outro decorador tff.tf_computation em vez de simplesmente usar um mecanismo existente, como tf.function . Ao contrário da seção anterior, aqui estamos lidando com um bloco comum de código do TensorFlow.

Existem alguns motivos para isso, cujo tratamento completo vai além do escopo deste tutorial, mas vale a pena citar o principal:

  • Para incorporar blocos de construção reutilizáveis ​​implementados usando o código do TensorFlow nos corpos de cálculos federados, eles precisam satisfazer certas propriedades - como rastrear e serializar no momento da definição, ter assinaturas de tipo etc. Isso geralmente requer alguma forma de decorador.

Em geral, recomendamos o uso de mecanismos nativos de TensorFlow para a composição, tais como tf.function , sempre que possível, como a maneira exata em que se pode esperar interage decorador da TFF com funções ansiosos para evoluir.

Agora, voltando ao exemplo de código trecho acima, o cálculo add_half que acabamos de definir pode ser tratada por TFF como qualquer outro cálculo TFF. Em particular, ele tem uma assinatura do tipo TFF.

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

Observe que este tipo de assinatura não possui colocações. Os cálculos do TensorFlow não podem consumir ou retornar tipos federados.

Agora também é possível usar add_half como um bloco de construção em outros cálculos. Por exemplo, aqui está como você pode usar o tff.federated_map operador aplique add_half pontual a todos os componentes que integram uma bóia federado em dispositivos cliente.

@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)'

Execução de cálculos do TensorFlow

Execução de cálculos definidos com tff.tf_computation segue as mesmas regras que as que descrevemos para tff.federated_computation . Eles podem ser chamados como chamáveis ​​comuns em Python, como segue.

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

Mais uma vez, vale a pena notar que invocando o cálculo add_half_on_clients desta maneira simula um processo distribuído. Os dados são consumidos nos clientes e devolvidos nos clientes. Na verdade, esse cálculo faz com que cada cliente execute uma ação local. Não há tff.SERVER explicitamente mencionada neste sistema (mesmo que, na prática, orquestrar tal processamento pode envolver um). Pense em um cálculo definido desta forma como conceitualmente análogo ao Map palco em MapReduce .

Além disso, mantenha em mente que o que dissemos na seção anterior sobre cálculos TFF ficar serializado no momento de definição permanece verdadeiro para tff.tf_computation código, bem como - o corpo Python de add_half_on_clients fica traçou uma vez em tempo de definição. Em invocações subsequentes, o TFF usa sua representação serializada.

A única diferença entre os métodos Python decorados com tff.federated_computation e aqueles decorado com tff.tf_computation é que o último está na forma de gráficos serializados TensorFlow (ao passo que o primeiro não são permitidos para conter código TensorFlow directamente incorporados neles).

Sob o capô, cada método decorado com tff.tf_computation desactiva temporariamente a execução ansioso, a fim de permitir que a estrutura da computação a ser capturado. Embora a execução antecipada esteja desativada localmente, você pode usar as construções TensorFlow, AutoGraph, TensorFlow 2.0 etc., contanto que escreva a lógica de sua computação de maneira que ela possa ser serializada corretamente.

Por exemplo, o código a seguir falhará:

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.

O acima falha porque constant_10 já foi construído fora do gráfico que tff.tf_computation constrói internamente no corpo de add_ten durante o processo de serialização.

Por outro lado, invocando funções Python que modificam o gráfico atual quando chamado dentro de um tff.tf_computation é bom:

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

Observe que os mecanismos de serialização no TensorFlow estão evoluindo, e esperamos que os detalhes de como o TFF serializa os cálculos também evoluam.

Trabalhando com tf.data.Dataset s

Como observado anteriormente, uma característica única de tff.tf_computation s é que eles lhe permite trabalhar com tf.data.Dataset s definidos abstratamente como parâmetros formais por seu código. Os parâmetros a serem representados em TensorFlow como conjuntos de dados precisam ser declaradas usando a tff.SequenceType construtor.

Por exemplo, a especificação de tipo tff.SequenceType(tf.float32) define uma sequência de resumo de elementos de flutuador em TFF. As sequências podem conter tensores ou estruturas aninhadas complexas (veremos exemplos disso mais tarde). A representação concisa de uma sequência de T -typed itens é 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 .