<p>There are a few nuances and a few open research problems in Federated Learning and this question has struck a couple of them.</p>

<ol>
<li><p><strong>Training loss looks <em>much</em> better than evaluation loss</strong>: when using Federated Averaging (the optimization algorithm used in the <a href="https://www.tensorflow.org/federated/tutorials/federated_learning_for_image_classification" rel="nofollow noreferrer">Federated Learning for Image Classification tutorial</a>) one needs to be careful interpreting metrics as they have nuanced differences from centralized model training. Especially <em>training loss</em>, which is the average over many sequence steps or batches. This means after one round, each client may have fit the model to their local data very well (obtaining a high accuracy), but after averaging these updates into the global model the global model may still be far away from "good", resulting in a low test accuracy. Additionally, 10 rounds may be too few; one of the original academic papers on Federated Learning demonstrated at least 20 rounds until 99% accuracy (<a href="https://arxiv.org/abs/1602.05629" rel="nofollow noreferrer">McMahan 2016</a>) with IID data, and more than 100 rounds in with non-IID data.</p></li>
<li><p><strong>BatchNorm in the federated setting</strong>: its an open research problem on how to combine the batchnorm parameters, particularly with non-IID client data. Should each new client start with fresh parameters, or receive the global model parameters? TFF may not be communicating them between the server and client (since it currently is implemented only to communicate <em>trainable</em> variables), and may be leading to unexpected behavior. It may we good to print the <code>state</code> parameters watch what happens each round to them.</p></li>
</ol>
Low evaluation accuracy of Resnet in TensorFlow Federated
<p>I implemented Resnet34 model in federated images classification tutorial. After 10 rounds the training accuracy can be higher than 90%, however, the evaluation accuracy using the last round's <code>state.model</code> is always around 50%.</p>

<pre><code>    evaluation = tff.learning.build_federated_evaluation(model_fn)
    federated_test_data = make_federated_data(emnist_test, sample_clients)
    test_metrics = evaluation(state.model, federated_test_data)
    str(test_metrics)
</code></pre>

<p>I am very confused what's possibly wrong with the evaluation part? Also, I printed the untrainable variables (mean and variance in BatchNorm) of the server's model, which are 0 and 1 with no updates/averaging after those rounds. Should they be like that or that could be the problem?
Thanks very much! </p>

<p><strong>Updates:</strong> </p>

<p>The codes to prepare training data and printed results:</p>

<pre><code>len(emnist_train.client_ids)
4

emnist_train.element_type_structure
OrderedDict([('label', TensorSpec(shape=(), dtype=tf.int64, name=None)),('pixels',TensorSpec(shape=(256, 256, 3), dtype=tf.float32, name=None))])


NUM_CLIENTS = 4
NUM_EPOCHS = 1
BATCH_SIZE = 30
SHUFFLE_BUFFER = 500

def preprocess(dataset):
  def element_fn(element):
    return collections.OrderedDict([
        ('x', element['pixels']),
        ('y', tf.reshape(element['label'], [1])),
    ])
  return dataset.repeat(NUM_EPOCHS).map(element_fn).shuffle(
      SHUFFLE_BUFFER).batch(BATCH_SIZE)



sample_clients = emnist_train.client_ids[0:NUM_CLIENTS]

federated_train_data = make_federated_data(emnist_train, sample_clients)

preprocessed_example_dataset = preprocess(example_dataset)

sample_batch = tf.nest.map_structure(
    lambda x: x.numpy(), iter(preprocessed_example_dataset).next())

def make_federated_data(client_data, client_ids):
      return [preprocess(client_data.create_tf_dataset_for_client(x))
          for x in client_ids]



len(federated_train_data), federated_train_data[0]
(4,&lt;BatchDataset shapes: OrderedDict([(x, (None, 256, 256, 3)), (y, (None, 1))]), types: OrderedDict([(x, tf.float32), (y, tf.int64)])&gt;)
</code></pre>

<p>The training and evaluation codes:</p>

<pre><code> def create_compiled_keras_model():
  base_model = tf.keras.applications.resnet.ResNet50(include_top=False, weights='imagenet', input_shape=(256,256,3,))
  global_average_layer = tf.keras.layers.GlobalAveragePooling2D()
  prediction_layer = tf.keras.layers.Dense(2, activation='softmax')

  model = tf.keras.Sequential([
                               base_model,
                               global_average_layer,
                               prediction_layer
                               ])
  model.compile(optimizer = tf.keras.optimizers.SGD(lr = 0.001, momentum=0.9), loss = tf.keras.losses.SparseCategoricalCrossentropy(), metrics = [tf.keras.metrics.SparseCategoricalAccuracy()])
  return model

def model_fn():
  keras_model = create_compiled_keras_model()
  return tff.learning.from_compiled_keras_model(keras_model, sample_batch)
iterative_process = tff.learning.build_federated_averaging_process(model_fn)
state = iterative_process.initialize()
for round_num in range(2, 12):
  state, metrics = iterative_process.next(state, federated_train_data)
  print('round {:2d}, metrics={}'.format(round_num, metrics, state))


evaluation = tff.learning.build_federated_evaluation(model_fn)
federated_test_data = make_federated_data(emnist_test, sample_clients)

len(federated_test_data), federated_test_data[0]
(4,
 &lt;BatchDataset shapes: OrderedDict([(x, (None, 256, 256, 3)), (y, (None, 1))]), types: OrderedDict([(x, tf.float32), (y, tf.int64)])&gt;)

test_metrics = evaluation(state.model, federated_test_data)
str(test_metrics)
</code></pre>

<p>The training and evaluations results after each round:</p>

<pre><code>round  1, metrics=&lt;sparse_categorical_accuracy=0.5089045763015747,loss=0.7813001871109009,keras_training_time_client_sum_sec=0.008826255798339844&gt;

&lt;sparse_categorical_accuracy=0.49949443340301514,loss=8.0671968460083,keras_training_time_client_sum_sec=0.0&gt;


round  2, metrics=&lt;sparse_categorical_accuracy=0.519825279712677,loss=0.7640910148620605,keras_training_time_client_sum_sec=0.011750459671020508&gt;

&lt;sparse_categorical_accuracy=0.49949443340301514,loss=8.0671968460083,keras_training_time_client_sum_sec=0.0&gt;


round  3, metrics=&lt;sparse_categorical_accuracy=0.5099126100540161,loss=0.7513422966003418,keras_training_time_client_sum_sec=0.0039823055267333984&gt;

&lt;sparse_categorical_accuracy=0.49949443340301514,loss=8.0671968460083,keras_training_time_client_sum_sec=0.0&gt;


round  4, metrics=&lt;sparse_categorical_accuracy=0.5278897881507874,loss=0.7905193567276001,keras_training_time_client_sum_sec=0.0010638236999511719&gt;

&lt;sparse_categorical_accuracy=0.49949443340301514,loss=8.0671968460083,keras_training_time_client_sum_sec=0.0&gt;


round  5, metrics=&lt;sparse_categorical_accuracy=0.5199933052062988,loss=0.7782396674156189,keras_training_time_client_sum_sec=0.012729644775390625&gt;

&lt;sparse_categorical_accuracy=0.49949443340301514,loss=8.0671968460083,keras_training_time_client_sum_sec=0.0&gt;
</code></pre>