이 페이지는 Cloud Translation API를 통해 번역되었습니다.
Switch to English

사용자 지정 연합 알고리즘, 2 부 : 연합 평균 구현

TensorFlow.org에서보기 Google Colab에서 실행 GitHub에서 소스보기

이 자습서는 Federated Learning (FL) 계층 ( tff.learning )의 기반 역할을하는 Federated Core (FC)를 사용하여 TFF에서 사용자 지정 유형의 연합 알고리즘을 구현하는 방법을 보여주는 2 부작 시리즈의 두 번째 부분입니다. .

여기에 사용 된 핵심 개념과 프로그래밍 추상화를 소개하는 이 시리즈첫 번째 부분 을 먼저 읽어 보시기 바랍니다.

시리즈의 두 번째 부분에서는 첫 번째 부분에서 소개 한 메커니즘을 사용하여 연합 된 교육 및 평가 알고리즘의 간단한 버전을 구현합니다.

TFF의 연합 학습 API에 대한 더 높은 수준의 부드러운 소개를 위해 이미지 분류텍스트 생성 자습서를 검토하는 것이 좋습니다. 여기에서 설명하는 개념을 컨텍스트에 넣는 데 도움이됩니다.

시작하기 전에

시작하기 전에 다음 "Hello World"예제를 실행하여 환경이 올바르게 설정되었는지 확인하십시오. 작동하지 않으면 설치 안내서를 참조하십시오.


!pip install --quiet --upgrade tensorflow_federated_nightly
!pip install --quiet --upgrade nest_asyncio

import nest_asyncio
nest_asyncio.apply()
import collections

import numpy as np
import tensorflow as tf
import tensorflow_federated as tff

# TODO(b/148678573,b/148685415): must use the reference context because it
# supports unbounded references and tff.sequence_* intrinsics.
tff.backends.reference.set_reference_context()
@tff.federated_computation
def hello_world():
  return 'Hello, World!'

hello_world()
'Hello, World!'

연합 평균화 구현

이미지 분류를위한 Federated Learning 에서와 같이 MNIST 예제를 사용할 것입니다. 그러나 이것은 저수준 튜토리얼을위한 tff.simulation API 및 tff.simulation 을 우회하고 원시 모델 코드를 작성하고 처음부터 연합 데이터 세트.

연합 데이터 세트 준비

데모를 위해 10 명의 사용자의 데이터가 있고 각 사용자가 서로 다른 숫자를 인식하는 방법에 대한 지식을 제공하는 시나리오를 시뮬레이션 할 것입니다. 이것은 얻는 것만 큼 비유 동적 입니다.

먼저 표준 MNIST 데이터를로드하겠습니다.

mnist_train, mnist_test = tf.keras.datasets.mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
11501568/11490434 [==============================] - 0s 0us/step

[(x.dtype, x.shape) for x in mnist_train]
[(dtype('uint8'), (60000, 28, 28)), (dtype('uint8'), (60000,))]

데이터는 Numpy 배열로 제공되며, 하나는 이미지가 있고 다른 하나는 숫자 레이블이 있으며, 둘 다 개별 예제를 살펴 보는 첫 번째 차원입니다. 연합 시퀀스를 TFF 계산에 공급하는 방식과 호환되는 방식으로 형식화하는 도우미 함수를 작성해 보겠습니다. 즉, 목록 목록 (사용자 (숫자)에 걸친 외부 목록, 내부 데이터 배치에 걸친 내부 목록) 각 클라이언트의 순서. 관례 적으로, 우리는 각각 선행 배치 차원을 갖는 xy 라는 텐서 쌍으로 각 배치를 구조화합니다. 그 동안 각 이미지를 784 개 요소 벡터로 평평하게 만들고 그 안의 픽셀을 0..1 범위로 다시 조정하여 데이터 변환으로 모델 논리를 복잡하게 만들 필요가 없습니다.

NUM_EXAMPLES_PER_USER = 1000
BATCH_SIZE = 100


def get_data_for_digit(source, digit):
  output_sequence = []
  all_samples = [i for i, d in enumerate(source[1]) if d == digit]
  for i in range(0, min(len(all_samples), NUM_EXAMPLES_PER_USER), BATCH_SIZE):
    batch_samples = all_samples[i:i + BATCH_SIZE]
    output_sequence.append({
        'x':
            np.array([source[0][i].flatten() / 255.0 for i in batch_samples],
                     dtype=np.float32),
        'y':
            np.array([source[1][i] for i in batch_samples], dtype=np.int32)
    })
  return output_sequence


federated_train_data = [get_data_for_digit(mnist_train, d) for d in range(10)]

federated_test_data = [get_data_for_digit(mnist_test, d) for d in range(10)]

빠른 온 전성 검사로 다섯 번째 클라이언트 (숫자 5 해당하는 것)가 제공 한 데이터의 마지막 배치에서 Y 텐서를 살펴 보겠습니다.

federated_train_data[5][-1]['y']
array([5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
       5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5], dtype=int32)

확실히하기 위해 해당 배치의 마지막 요소에 해당하는 이미지도 살펴 보겠습니다.

from matplotlib import pyplot as plt

plt.imshow(federated_train_data[5][-1]['x'][-1].reshape(28, 28), cmap='gray')
plt.grid(False)
plt.show()

png

TensorFlow와 TFF 결합

이 튜토리얼에서는 간결함을 위해 tff.tf_computation TensorFlow 로직을 도입하는 함수를 즉시 장식합니다. 그러나 더 복잡한 논리의 경우 권장되는 패턴이 아닙니다. TensorFlow 디버깅은 이미 도전이 될 수 있으며, TensorFlow를 완전히 직렬화 한 다음 다시 가져온 후 디버깅하면 반드시 일부 메타 데이터가 손실되고 상호 작용이 제한되므로 디버깅이 훨씬 더 어려워집니다.

따라서 복잡한 TF 로직을 독립 실행 형 Python 함수로 작성하는 것이 좋습니다 (즉, tff.tf_computation 장식없이). 이렇게하면 TFF에 대한 계산을 직렬화하기 전에 (예 : Python 함수를 인수로 사용하여 tff.tf_computation 을 호출하여) TF 모범 사례 및 도구 (예 : eager 모드)를 사용하여 TensorFlow 논리를 개발하고 테스트 할 수 있습니다.

손실 함수 정의

이제 데이터를 얻었으므로 학습에 사용할 수있는 손실 함수를 정의하겠습니다. 먼저 입력 유형을 튜플이라는 TFF로 정의하겠습니다. 데이터 배치의 크기가 다를 수 있으므로 배치 차원을 None 으로 설정하여이 차원의 크기를 알 수 None 을 나타냅니다.

BATCH_SPEC = collections.OrderedDict(
    x=tf.TensorSpec(shape=[None, 784], dtype=tf.float32),
    y=tf.TensorSpec(shape=[None], dtype=tf.int32))
BATCH_TYPE = tff.to_type(BATCH_SPEC)

str(BATCH_TYPE)
'<x=float32[?,784],y=int32[?]>'

왜 우리가 일반적인 파이썬 유형을 정의 할 수 없는지 궁금 할 것입니다. 1 부 에서 논의한 내용을 떠올려보십시오. 여기서는 Python을 사용하여 TFF 계산의 논리를 표현할 수 있지만 내부적으로 TFF 계산 Python 이 아니라고 설명했습니다 . 위에 정의 된 BATCH_TYPE 기호는 추상적 인 TFF 유형 사양을 나타냅니다. 이 추상 TFF 유형을 구체적인 Python 표현 유형 (예 : Python 함수 본문에서 TFF 유형을 나타내는 데 사용할 수있는 dict 또는 collections.namedtuple 과 같은 컨테이너)과 구별하는 것이 중요합니다. Python과 달리 TFF에는 개별적으로 이름을 지정하거나 이름을 지정하지 않을 수있는 요소가있는 튜플과 같은 컨테이너에 대한 단일 추상 유형 생성자 tff.StructType 이 있습니다. TFF 계산은 공식적으로 하나의 매개 변수와 하나의 결과 만 선언 할 수 있기 때문에이 유형은 계산의 공식 매개 변수를 모델링하는데도 사용됩니다. 곧 이에 대한 예를 볼 수 있습니다.

이제 TFF 유형의 모델 매개 변수를 다시 가중치편향의 튜플이라는 TFF로 정의 해 보겠습니다.

MODEL_SPEC = collections.OrderedDict(
    weights=tf.TensorSpec(shape=[784, 10], dtype=tf.float32),
    bias=tf.TensorSpec(shape=[10], dtype=tf.float32))
MODEL_TYPE = tff.to_type(MODEL_SPEC)

print(MODEL_TYPE)
<weights=float32[784,10],bias=float32[10]>

이러한 정의가 적용되면 이제 단일 배치에 대해 주어진 모델에 대한 손실을 정의 할 수 있습니다. @tf.function 데코레이터 내부의 @tff.tf_computation 데코레이터 사용법에 유의하세요. 이를 통해 tff.tf_computation 데코레이터에 의해 생성 된 tf.Graph 컨텍스트 내부에 있음에도 불구하고 의미 체계와 같은 Python을 사용하여 TF를 작성할 수 있습니다.

# NOTE: `forward_pass` is defined separately from `batch_loss` so that it can 
# be later called from within another tf.function. Necessary because a
# @tf.function  decorated method cannot invoke a @tff.tf_computation.

@tf.function
def forward_pass(model, batch):
  predicted_y = tf.nn.softmax(
      tf.matmul(batch['x'], model['weights']) + model['bias'])
  return -tf.reduce_mean(
      tf.reduce_sum(
          tf.one_hot(batch['y'], 10) * tf.math.log(predicted_y), axis=[1]))

@tff.tf_computation(MODEL_TYPE, BATCH_TYPE)
def batch_loss(model, batch):
  return forward_pass(model, batch)

예상대로 계산 batch_loss 는 모델과 단일 데이터 배치가 주어지면 float32 손실을 반환합니다. MODEL_TYPEBATCH_TYPE 이 어떻게 2 튜플의 형식 매개 변수로 묶여 있는지 주목하십시오. batch_loss 유형을 (<MODEL_TYPE,BATCH_TYPE> -> float32) 로 인식 할 수 있습니다.

str(batch_loss.type_signature)
'(<<weights=float32[784,10],bias=float32[10]>,<x=float32[?,784],y=int32[?]>> -> float32)'

온 전성 검사로 0으로 채워진 초기 모델을 구성하고 위에서 시각화 한 데이터 배치에 대한 손실을 계산해 보겠습니다.

initial_model = collections.OrderedDict(
    weights=np.zeros([784, 10], dtype=np.float32),
    bias=np.zeros([10], dtype=np.float32))

sample_batch = federated_train_data[5][-1]

batch_loss(initial_model, sample_batch)
2.3025854

이를 정의하는 Python 함수의 본문이 model['weight']model['bias']model['weight'] 매개 변수를 사용하더라도 dict 로 정의 된 초기 모델로 TFF 계산을 제공합니다. batch_loss 호출의 인수는 단순히 해당 함수의 본문에 전달되는 것이 아닙니다.

batch_loss 를 호출하면 어떻게됩니까? batch_loss 의 Python 본문은 이미 정의 된 위 셀에서 추적 및 직렬화되었습니다. TFF는 계산 정의 시간에 batch_loss 에 대한 호출자 역할을하고 batch_loss 가 호출 될 때 호출 대상 역할을합니다. 두 역할 모두에서 TFF는 TFF의 추상 유형 시스템과 Python 표현 유형 사이의 다리 역할을합니다. 호출 시간에 TFF는 추상 TFF 튜플의 구체적인 표현으로 대부분의 표준 Python 컨테이너 유형 ( dict , list , tuple , collections.namedtuple 등)을 허용합니다. 또한 위에서 언급했듯이 TFF 계산은 공식적으로 단일 매개 변수 만 허용하지만 매개 변수 유형이 튜플 인 경우 위치 및 / 또는 키워드 인수와 함께 익숙한 Python 호출 구문을 사용할 수 있습니다. 예상대로 작동합니다.

단일 배치의 경사 하강 법

이제이 손실 함수를 사용하여 단일 단계의 경사 하강 법을 수행하는 계산을 정의 해 보겠습니다. 이 함수를 정의 할 때 batch_loss 를 하위 구성 요소로 사용하는 방법에 유의하십시오. 다른 계산의 본문 내에서 tff.tf_computation 구성된 계산을 호출 할 수 있지만 일반적으로 필요하지 않습니다. 위에서 언급했듯이 직렬화는 일부 디버깅 정보를 tff.tf_computation 때문에 모든 TensorFlow를 작성하고 테스트하는 데 더 복잡한 계산이 선호되는 경우가 많습니다. tff.tf_computation 데코레이터없이.

@tff.tf_computation(MODEL_TYPE, BATCH_TYPE, tf.float32)
def batch_train(initial_model, batch, learning_rate):
  # Define a group of model variables and set them to `initial_model`. Must
  # be defined outside the @tf.function.
  model_vars = collections.OrderedDict([
      (name, tf.Variable(name=name, initial_value=value))
      for name, value in initial_model.items()
  ])
  optimizer = tf.keras.optimizers.SGD(learning_rate)

  @tf.function
  def _train_on_batch(model_vars, batch):
    # Perform one step of gradient descent using loss from `batch_loss`.
    with tf.GradientTape() as tape:
      loss = forward_pass(model_vars, batch)
    grads = tape.gradient(loss, model_vars)
    optimizer.apply_gradients(
        zip(tf.nest.flatten(grads), tf.nest.flatten(model_vars)))
    return model_vars

  return _train_on_batch(model_vars, batch)
str(batch_train.type_signature)
'(<<weights=float32[784,10],bias=float32[10]>,<x=float32[?,784],y=int32[?]>,float32> -> <weights=float32[784,10],bias=float32[10]>)'

다른 함수의 본문 내에서 tff.tf_computation 장식 된 Python 함수를 호출하면 내부 TFF 계산의 논리가 외부 논리에 포함 (본질적으로 인라인 됨)됩니다. 위에서 언급했듯이 두 계산을 모두 작성하는 경우 내부 함수 (이 경우 batch_loss )를 tf.function 대신 일반 Python 또는 tf.function 것이 tff.tf_computation . 그러나 여기에서는 다른 tff.tf_computation 을 호출하는 것이 기본적으로 예상대로 작동 함을 보여줍니다. 예를 들어 batch_loss 정의하는 Python 코드가없고 직렬화 된 TFF 표현 만있는 경우에 필요할 수 있습니다.

이제이 함수를 초기 모델에 몇 번 적용하여 손실이 감소하는지 확인합니다.

model = initial_model
losses = []
for _ in range(5):
  model = batch_train(model, sample_batch, 0.1)
  losses.append(batch_loss(model, sample_batch))
losses
[0.19690022, 0.13176313, 0.10113226, 0.082738124, 0.0703014]

일련의 로컬 데이터에 대한 경사 하강 법

이제 batch_train 이 작동하는 것처럼 batch_train 때문에 단일 배치가 아닌 한 사용자로부터 모든 배치의 전체 시퀀스를 소비하는 유사한 훈련 함수 local_train 을 작성해 보겠습니다. 이제 소비에 새로운 계산이 필요합니다 tff.SequenceType(BATCH_TYPE) 대신 BATCH_TYPE .

LOCAL_DATA_TYPE = tff.SequenceType(BATCH_TYPE)

@tff.federated_computation(MODEL_TYPE, tf.float32, LOCAL_DATA_TYPE)
def local_train(initial_model, learning_rate, all_batches):

  # Mapping function to apply to each batch.
  @tff.federated_computation(MODEL_TYPE, BATCH_TYPE)
  def batch_fn(model, batch):
    return batch_train(model, batch, learning_rate)

  return tff.sequence_reduce(all_batches, initial_model, batch_fn)
str(local_train.type_signature)
'(<<weights=float32[784,10],bias=float32[10]>,float32,<x=float32[?,784],y=int32[?]>*> -> <weights=float32[784,10],bias=float32[10]>)'

이 짧은 코드 섹션에는 몇 가지 세부 사항이 숨겨져 있으므로 하나씩 살펴 보겠습니다.

첫째,이 로직을 TensorFlow에서 완전히 구현할 수 있었지만 tf.data.Dataset.reduce 를 사용하여 이전에 수행 한 것과 유사하게 시퀀스를 처리 할 수 ​​있었지만 이번에는 로직을 글루 언어로 표현하도록 선택했습니다. , tff.federated_computation . 축소를 수행하기 위해 연합 연산자 tff.sequence_reduce 를 사용했습니다.

운영자 tff.sequence_reduce 유사하게 사용된다 tf.data.Dataset.reduce . 본질적으로 tf.data.Dataset.reduce 와 동일하다고 생각할 수 있지만, 기억할 수 있듯이 TensorFlow 코드를 포함 할 수없는 연합 계산 내에서 사용하기위한 것입니다. T 유형 요소 시퀀스 , U 유형의 축소 초기 상태 (추상적으로 0이라고 함 ) 및 축소 연산자 로 구성된 형식 매개 변수 3- 튜플이있는 템플릿 연산자 입니다. 단일 요소를 처리하여 축소 상태를 변경하는 유형 (<U,T> -> U) . 결과는 모든 요소를 ​​순차적으로 처리 한 후 축소의 최종 상태입니다. 이 예에서 축소 상태는 데이터의 접두사에 대해 학습 된 모델이고 요소는 데이터 배치입니다.

둘째, 한 계산 ( batch_train )을 다른 계산 ( local_train ) 내의 구성 요소로 다시 사용했지만 직접 사용하지는 않았습니다. 학습률이라는 추가 매개 변수가 필요하기 때문에 축소 연산자로 사용할 수 없습니다. 이 문제를 해결하기 위해 본문에서 local_train 의 매개 변수 learning_rate 에 바인딩되는 내장 된 통합 계산 batch_fn 을 정의합니다. 자식 계산이 부모 본문 외부에서 호출되지 않는 한 이러한 방식으로 정의 된 자식 계산이 부모의 공식 매개 변수를 캡처 할 수 있습니다. 이 패턴은 Python의 functools.partial 과 동일하다고 생각할 수 있습니다.

이 방법으로 learning_rate 를 캡처하는 실제 의미는 물론 모든 배치에서 동일한 학습률 값이 사용된다는 것입니다.

이제 샘플 배치를 제공 한 동일한 사용자 (숫자 5 )의 전체 데이터 시퀀스에 대해 새로 정의 된 로컬 훈련 기능을 시도해 보겠습니다.

locally_trained_model = local_train(initial_model, 0.1, federated_train_data[5])

작동 했나요? 이 질문에 답하려면 평가를 구현해야합니다.

지역 평가

다음은 모든 데이터 배치에 걸쳐 손실을 합산하여 로컬 평가를 구현하는 한 가지 방법입니다 (평균을 잘 계산할 수 있었으므로 독자를위한 연습으로 남겨 둘 것입니다).

@tff.federated_computation(MODEL_TYPE, LOCAL_DATA_TYPE)
def local_eval(model, all_batches):
  # TODO(b/120157713): Replace with `tff.sequence_average()` once implemented.
  return tff.sequence_sum(
      tff.sequence_map(
          tff.federated_computation(lambda b: batch_loss(model, b), BATCH_TYPE),
          all_batches))
str(local_eval.type_signature)
'(<<weights=float32[784,10],bias=float32[10]>,<x=float32[?,784],y=int32[?]>*> -> float32)'

다시 말하지만,이 코드에 설명 된 몇 가지 새로운 요소가 있습니다. 하나씩 살펴 보겠습니다.

먼저, 시퀀스를 처리하기위한 두 개의 새로운 연합 연산자를 사용했다 : tff.sequence_map 매핑 함수 얻어 T->U시퀀스 T , 그리고 연속 발광 U 매핑 함수 점별를 적용하여 얻어지고, tff.sequence_sum 그 모든 요소를 ​​추가합니다. 여기에서는 각 데이터 배치를 손실 값에 매핑 한 다음 결과 손실 값을 추가하여 총 손실을 계산합니다.

다시 tff.sequence_reduce 사용할 수 있었지만 이것이 최선의 선택은 아닙니다. 축소 프로세스는 정의에 따라 순차적 인 반면 매핑과 합계는 병렬로 계산 될 수 있습니다. 선택이 주어지면 구현 선택을 제한하지 않는 연산자를 고수하는 것이 가장 좋습니다. 그러면 향후 TFF 계산이 특정 환경에 배포되도록 컴파일 될 때 모든 잠재적 기회를 최대한 활용할 수 있습니다. , 더 확장 가능하고 자원 효율적인 실행.

둘째, local_train 에서와 마찬가지로 필요한 구성 요소 함수 ( batch_loss )는 연합 연산자 ( tff.sequence_map )가 예상하는 것보다 더 많은 매개 변수를 사용하므로 이번에는 lambdatff.federated_computation 로 직접 래핑하여 부분을 다시 정의합니다. tff.federated_computation . 함수와 함께 인라인 래퍼를 인수로 사용하는 tff.tf_computation 을 사용하여 TFF에 TensorFlow 논리를 포함하는 데 권장되는 방법입니다.

이제 우리의 훈련이 효과가 있었는지 살펴 보겠습니다.

print('initial_model loss =', local_eval(initial_model,
                                         federated_train_data[5]))
print('locally_trained_model loss =',
      local_eval(locally_trained_model, federated_train_data[5]))
initial_model loss = 23.025854
locally_trained_model loss = 0.4348469

실제로 손실이 감소했습니다. 하지만 다른 사용자의 데이터로 평가하면 어떻게 될까요?

print('initial_model loss =', local_eval(initial_model,
                                         federated_train_data[0]))
print('locally_trained_model loss =',
      local_eval(locally_trained_model, federated_train_data[0]))
initial_model loss = 23.025854
locally_trained_model loss = 74.50075

예상대로 상황이 악화되었습니다. 이 모델은 5 를 인식하도록 훈련되었으며 0 본 적이 없습니다. 이로 인해 지역 교육이 글로벌 관점에서 모델의 품질에 어떤 영향을 미쳤습니까?

연합 평가

이것이 우리가 시작했던 주제 인 연합 된 유형과 연합 된 계산으로 돌아가는 여정의 지점입니다. 다음은 서버에서 시작된 모델에 대한 TFF 유형 정의 쌍과 클라이언트에 남아있는 데이터입니다.

SERVER_MODEL_TYPE = tff.FederatedType(MODEL_TYPE, tff.SERVER)
CLIENT_DATA_TYPE = tff.FederatedType(LOCAL_DATA_TYPE, tff.CLIENTS)

지금까지 소개 된 모든 정의를 통해 TFF에서 연합 평가를 표현하는 것은 한 줄로 이루어집니다. 모델을 클라이언트에 배포하고 각 클라이언트가 데이터의 로컬 부분에 대해 로컬 평가를 호출 한 다음 손실을 평균화합니다. 이것을 작성하는 한 가지 방법이 있습니다.

@tff.federated_computation(SERVER_MODEL_TYPE, CLIENT_DATA_TYPE)
def federated_eval(model, data):
  return tff.federated_mean(
      tff.federated_map(local_eval, [tff.federated_broadcast(model), data]))

우리는 이미 더 간단한 시나리오에서 tff.federated_meantff.federated_map 예를 tff.federated_mean 으며 직관적 인 수준에서는 예상대로 작동하지만이 코드 섹션에는 눈에 보이는 것보다 더 많은 것이 있으므로 자세히 살펴 보겠습니다.

먼저 각 클라이언트가 데이터 부분 의 로컬 부분에서 로컬 평가를 호출하도록 하는 방법을 분석해 보겠습니다 . 이전 섹션에서 기억할 수 있듯이 local_eval 에는 (<MODEL_TYPE, LOCAL_DATA_TYPE> -> float32) 형식의 유형 서명이 있습니다.

연합 연산자 tff.federated_map 은 일부 유형 T->U매핑 함수{T}@CLIENTS 유형의 연합 값으로 구성된 2- 튜플을 매개 변수로 허용하는 템플릿입니다 (즉, 매핑 함수의 매개 변수와 동일한 유형), {U}@CLIENTS 유형의 결과를 반환합니다.

클라이언트별로 적용 할 매핑 함수로 local_eval 을 제공하므로 두 번째 인수는 연합 유형 {<MODEL_TYPE, LOCAL_DATA_TYPE>}@CLIENTS . 즉, 이전 섹션의 명명법에서 연합 된 튜플이어야합니다. 각 클라이언트는 구성원 구성 local_evallocal_eval 대한 전체 인수 집합을 보유해야합니다. 대신, 2 요소 Python list . 여기서 무슨 일이 일어나고 있습니까?

당신이 공급 될 때 실제로,이, 예를 들어 다른 곳에서 발생했을 수 있습니다 암시 타입 캐스트, 유사 TFF에서 암시 적 타입 캐스트의 예입니다 int 받아들이는 함수에 float . 이 시점에서 암시 적 캐스팅은 거의 사용되지 않지만, 상용구를 최소화하는 방법으로 TFF에서보다 널리 사용되도록 할 계획입니다.

이 경우에 적용되는 암시 적 캐스트는 {<X,Y>}@Z 의 연합 튜플과 연합 값 <{X}@Z,{Y}@Z> 튜플 간의 동등성입니다. 공식적으로이 두 가지 유형은 프로그래머의 관점에서 보면 서로 다른 유형 시그니처이지만 Z 각 장치는 두 단위의 데이터 XY 보유합니다. 여기서 일어나는 일은 Python의 zip 과 다르지 않으며 실제로 이러한 변환을 명시 적으로 수행 할 수있는 연산자 tff.federated_zip 을 제공합니다. tff.federated_map 이 두 번째 인수로 튜플을 만나면 단순히 tff.federated_zip 을 호출합니다.

위와 같이 이제 tff.federated_broadcast(model) 표현식을 TFF 유형 {MODEL_TYPE}@CLIENTS 의 값으로 인식하고 data 를 TFF 유형 {LOCAL_DATA_TYPE}@CLIENTS (또는 간단히 CLIENT_DATA_TYPE )의 값으로 인식 할 수 있습니다. 두는 암시 통해 함께 여과하기 tff.federated_zip 두 번째 인수 형성 tff.federated_map .

운영자 tff.federated_broadcast 는 예상대로 서버에서 클라이언트로 데이터를 전송합니다.

이제 현지 교육이 시스템의 평균 손실에 어떤 영향을 미쳤는지 살펴 보겠습니다.

print('initial_model loss =', federated_eval(initial_model,
                                             federated_train_data))
print('locally_trained_model loss =',
      federated_eval(locally_trained_model, federated_train_data))
initial_model loss = 23.025852
locally_trained_model loss = 54.432625

실제로 예상대로 손실이 증가했습니다. 모든 사용자를위한 모델을 개선하려면 모든 사용자의 데이터를 학습해야합니다.

연합 교육

연합 훈련을 구현하는 가장 간단한 방법은 로컬에서 훈련 한 다음 모델을 평균화하는 것입니다. 이것은 아래에서 볼 수 있듯이 이미 논의한 것과 동일한 빌딩 블록과 패턴을 사용합니다.

SERVER_FLOAT_TYPE = tff.FederatedType(tf.float32, tff.SERVER)


@tff.federated_computation(SERVER_MODEL_TYPE, SERVER_FLOAT_TYPE,
                           CLIENT_DATA_TYPE)
def federated_train(model, learning_rate, data):
  return tff.federated_mean(
      tff.federated_map(local_train, [
          tff.federated_broadcast(model),
          tff.federated_broadcast(learning_rate), data
      ]))

tff.learning 에서 제공하는 Federated Averaging의 모든 기능을 갖춘 구현에서 모델을 평균화하는 것보다 모델 델타를 평균화하는 것을 선호합니다. 예를 들어, 여러 가지 이유로 인해 (예 : 압축을 위해 업데이트 기준을 클리핑하는 기능) .

몇 라운드의 훈련을 실행하고 전후 평균 손실을 비교하여 훈련이 작동하는지 살펴 보겠습니다.

model = initial_model
learning_rate = 0.1
for round_num in range(5):
  model = federated_train(model, learning_rate, federated_train_data)
  learning_rate = learning_rate * 0.9
  loss = federated_eval(model, federated_train_data)
  print('round {}, loss={}'.format(round_num, loss))
round 0, loss=21.60552406311035
round 1, loss=20.365678787231445
round 2, loss=19.27480125427246
round 3, loss=18.31110954284668
round 4, loss=17.45725440979004

완전성을 위해 이제 테스트 데이터에서 실행하여 모델이 잘 일반화되는지 확인하겠습니다.

print('initial_model test loss =',
      federated_eval(initial_model, federated_test_data))
print('trained_model test loss =', federated_eval(model, federated_test_data))
initial_model test loss = 22.795593
trained_model test loss = 17.278767

이것으로 튜토리얼을 마칩니다.

물론, 우리의 단순화 된 예는보다 현실적인 시나리오에서 수행해야하는 많은 일을 반영하지 않습니다. 예를 들어 손실 이외의 메트릭을 계산하지 않았습니다. 보다 완전한 예로서 그리고 우리가 장려하고 싶은 일부 코딩 관행을 보여주는 방법으로 tff.learning 에서 연합 평균화 의 구현 을 연구 하는 것이 좋습니다.