التعلم الموحد لتصنيف الصور

عرض على TensorFlow.org تشغيل في Google Colab عرض المصدر على جيثب تحميل دفتر

في هذا البرنامج التعليمي، ونحن نستخدم المثال التدريب MNIST الكلاسيكية لإدخال الاتحادية التعلم (FL) طبقة API من TFF، tff.learning - مجموعة من واجهات ذات المستوى العالي التي يمكن استخدامها لتنفيذ الأنواع الشائعة من مهام التعلم الاتحادية، مثل تدريب متحد ، مقابل النماذج التي يوفرها المستخدم المطبقة في TensorFlow.

هذا البرنامج التعليمي و Federated Learning API مخصصان بشكل أساسي للمستخدمين الذين يرغبون في توصيل نماذج TensorFlow الخاصة بهم في TFF ، مع التعامل مع الأخير في الغالب على أنه صندوق أسود. من أجل فهم أكثر عمقا من TFF وكيفية تنفيذ الخاصة خوارزميات التعلم الاتحادية، راجع الدروس على API FC الأساسية - مخصص الاتحادية الخوارزميات الجزء 1 و الجزء 2 .

لمعرفة المزيد عن tff.learning ، مع الاستمرار في التعلم الاتحادية لتوليد النص ، والبرنامج التعليمي الذي بالإضافة إلى تغطية النماذج المتكررة، يدلل أيضا تحميل نموذج Keras تسلسل تدريب قبل لصقل مع التعلم الاتحادية جنبا إلى جنب مع تقييم تستخدم Keras.

قبل أن نبدأ

قبل أن نبدأ ، يرجى تشغيل ما يلي للتأكد من إعداد بيئتك بشكل صحيح. إذا كنت لا ترى تحية، يرجى الرجوع إلى تركيب دليل للتعليمات.

# tensorflow_federated_nightly also bring in tf_nightly, which
# can causes a duplicate tensorboard install, leading to errors.
!pip uninstall --yes tensorboard tb-nightly

!pip install --quiet --upgrade tensorflow-federated-nightly
!pip install --quiet --upgrade nest-asyncio
!pip install --quiet --upgrade tb-nightly  # or tensorboard, but not both

import nest_asyncio
nest_asyncio.apply()
%load_ext tensorboard
import collections

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

np.random.seed(0)

tff.federated_computation(lambda: 'Hello, World!')()
b'Hello, World!'

تجهيز بيانات الإدخال

لنبدأ بالبيانات. يتطلب التعلم الموحد مجموعة بيانات موحدة ، أي مجموعة بيانات من مستخدمين متعددين. البيانات الاتحادية هو عادة غير IID ، الأمر الذي يشكل مجموعة فريدة من التحديات.

من أجل تسهيل التجريب، ونحن المصنف مستودع TFF مع عدد قليل من مجموعات البيانات، بما في ذلك نسخة الاتحادية من MNIST التي تحتوي على نسخة من بيانات NIST الأصلي الذي تم إعادة معالجتها باستخدام ورقة بحيث البيانات مرتبطا الكاتب الأصلي لل الأرقام. نظرًا لأن لكل كاتب أسلوبًا فريدًا ، تعرض مجموعة البيانات هذه نوع السلوك غير المعتمد المتوقع لمجموعات البيانات الموحدة.

إليك كيف يمكننا تحميله.

emnist_train, emnist_test = tff.simulation.datasets.emnist.load_data()

مجموعات البيانات التي تم إرجاعها من قبل load_data() هي حالات tff.simulation.ClientData ، واجهة تسمح لك لتعداد مجموعة من المستخدمين، لبناء tf.data.Dataset التي تمثل البيانات من مستخدم معين، والاستعلام عن هيكل العناصر الفردية. إليك كيفية استخدام هذه الواجهة لاستكشاف محتوى مجموعة البيانات. ضع في اعتبارك أنه على الرغم من أن هذه الواجهة تسمح لك بالتكرار عبر معرفات العملاء ، فإن هذه ليست سوى ميزة لبيانات المحاكاة. كما سترى قريبًا ، لا يتم استخدام هويات العميل بواسطة إطار عمل التعلم الموحد - والغرض الوحيد منها هو السماح لك بتحديد مجموعات فرعية من البيانات من أجل عمليات المحاكاة.

len(emnist_train.client_ids)
3383
emnist_train.element_type_structure
OrderedDict([('label', TensorSpec(shape=(), dtype=tf.int32, name=None)), ('pixels', TensorSpec(shape=(28, 28), dtype=tf.float32, name=None))])
example_dataset = emnist_train.create_tf_dataset_for_client(
    emnist_train.client_ids[0])

example_element = next(iter(example_dataset))

example_element['label'].numpy()
1
from matplotlib import pyplot as plt
plt.imshow(example_element['pixels'].numpy(), cmap='gray', aspect='equal')
plt.grid(False)
_ = plt.show()

بي إن جي

استكشاف عدم التجانس في البيانات الموحدة

البيانات الاتحادية هو عادة غير IID ، يكون لدى المستخدمين عادة توزيعات مختلفة من البيانات اعتمادا على أنماط الاستخدام. قد يكون لدى بعض العملاء عدد أقل من الأمثلة التدريبية على الجهاز ، حيث يعانون من ندرة البيانات محليًا ، بينما سيكون لدى بعض العملاء أكثر من أمثلة تدريبية كافية. دعنا نستكشف مفهوم عدم تجانس البيانات هذا النموذجي للنظام الموحد مع بيانات EMNIST المتوفرة لدينا. من المهم ملاحظة أن هذا التحليل العميق لبيانات العميل متاح لنا فقط لأن هذه بيئة محاكاة حيث تتوفر جميع البيانات لنا محليًا. في بيئة اتحاد إنتاج حقيقي ، لن تتمكن من فحص بيانات عميل واحد.

أولاً ، دعنا نأخذ عينة من بيانات أحد العملاء للتعرف على الأمثلة الموجودة على جهاز محاكاة واحد. نظرًا لأن مجموعة البيانات التي نستخدمها تم ترميزها بواسطة كاتب فريد ، فإن بيانات عميل واحد تمثل الكتابة اليدوية لشخص واحد لعينة من الأرقام من 0 إلى 9 ، مما يحاكي "نمط الاستخدام" الفريد لمستخدم واحد.

## Example MNIST digits for one client
figure = plt.figure(figsize=(20, 4))
j = 0

for example in example_dataset.take(40):
  plt.subplot(4, 10, j+1)
  plt.imshow(example['pixels'].numpy(), cmap='gray', aspect='equal')
  plt.axis('off')
  j += 1

بي إن جي

الآن دعنا نتخيل عدد الأمثلة على كل عميل لكل تسمية رقم MNIST. في البيئة الموحدة ، يمكن أن يختلف عدد الأمثلة على كل عميل قليلاً ، اعتمادًا على سلوك المستخدم.

# Number of examples per layer for a sample of clients
f = plt.figure(figsize=(12, 7))
f.suptitle('Label Counts for a Sample of Clients')
for i in range(6):
  client_dataset = emnist_train.create_tf_dataset_for_client(
      emnist_train.client_ids[i])
  plot_data = collections.defaultdict(list)
  for example in client_dataset:
    # Append counts individually per label to make plots
    # more colorful instead of one color per plot.
    label = example['label'].numpy()
    plot_data[label].append(label)
  plt.subplot(2, 3, i+1)
  plt.title('Client {}'.format(i))
  for j in range(10):
    plt.hist(
        plot_data[j],
        density=False,
        bins=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

بي إن جي

الآن دعنا نتخيل الصورة المتوسطة لكل عميل لكل تسمية MNIST. سينتج عن هذا الرمز متوسط ​​قيمة كل بكسل لجميع أمثلة المستخدم لتسمية واحدة. سنرى أن الصورة المتوسطة لأحد العملاء لرقم ما ستبدو مختلفة عن الصورة المتوسطة لعميل آخر لنفس الرقم ، بسبب أسلوب خط اليد الفريد لكل شخص. يمكننا أن نفكر في كيفية قيام كل جولة تدريب محلية بدفع النموذج في اتجاه مختلف لكل عميل ، حيث نتعلم من البيانات الفريدة لهذا المستخدم في تلك الجولة المحلية. سنرى لاحقًا في البرنامج التعليمي كيف يمكننا أخذ كل تحديث للنموذج من جميع العملاء وتجميعهم معًا في نموذجنا العالمي الجديد ، والذي تعلم من كل من البيانات الفريدة لعملائنا.

# Each client has different mean images, meaning each client will be nudging
# the model in their own directions locally.

for i in range(5):
  client_dataset = emnist_train.create_tf_dataset_for_client(
      emnist_train.client_ids[i])
  plot_data = collections.defaultdict(list)
  for example in client_dataset:
    plot_data[example['label'].numpy()].append(example['pixels'].numpy())
  f = plt.figure(i, figsize=(12, 5))
  f.suptitle("Client #{}'s Mean Image Per Label".format(i))
  for j in range(10):
    mean_img = np.mean(plot_data[j], 0)
    plt.subplot(2, 5, j+1)
    plt.imshow(mean_img.reshape((28, 28)))
    plt.axis('off')

بي إن جي

بي إن جي

بي إن جي

بي إن جي

بي إن جي

يمكن أن تكون بيانات المستخدم صاخبة ولا يمكن الاعتماد عليها. على سبيل المثال ، بالنظر إلى بيانات العميل رقم 2 أعلاه ، يمكننا أن نرى أنه بالنسبة للتسمية 2 ، من المحتمل أنه قد تكون هناك بعض الأمثلة التي تم تسميتها بشكل خاطئ مما أدى إلى إنشاء صورة وسط أكثر ضجيجًا.

المعالجة المسبقة لبيانات الإدخال

لأن البيانات بالفعل tf.data.Dataset ، وتجهيزها ويمكن تحقيق ذلك باستخدام تحولات الإدراجات. هنا، نحن لشد 28x28 الصور إلى 784 صفائف -element، خلط الأمثلة الفردية، تنظيمها في مجموعات، وإعادة تسمية الميزات من pixels و label ل x و y للاستخدام مع Keras. نحن أيضا رمي في repeat خلال مجموعة البيانات لتشغيل العديد من العهود.

NUM_CLIENTS = 10
NUM_EPOCHS = 5
BATCH_SIZE = 20
SHUFFLE_BUFFER = 100
PREFETCH_BUFFER = 10

def preprocess(dataset):

  def batch_format_fn(element):
    """Flatten a batch `pixels` and return the features as an `OrderedDict`."""
    return collections.OrderedDict(
        x=tf.reshape(element['pixels'], [-1, 784]),
        y=tf.reshape(element['label'], [-1, 1]))

  return dataset.repeat(NUM_EPOCHS).shuffle(SHUFFLE_BUFFER, seed=1).batch(
      BATCH_SIZE).map(batch_format_fn).prefetch(PREFETCH_BUFFER)

دعونا نتحقق من نجاح هذا الأمر.

preprocessed_example_dataset = preprocess(example_dataset)

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

sample_batch
OrderedDict([('x', array([[1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       ...,
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.]], dtype=float32)), ('y', array([[2],
       [1],
       [5],
       [7],
       [1],
       [7],
       [7],
       [1],
       [4],
       [7],
       [4],
       [2],
       [2],
       [5],
       [4],
       [1],
       [1],
       [0],
       [0],
       [9]], dtype=int32))])

لدينا جميع اللبنات الأساسية تقريبًا لإنشاء مجموعات بيانات موحدة.

واحدة من الطرق لتغذية البيانات الاتحادية لTFF في محاكاة هو مجرد قائمة بيثون، مع كل عنصر من عناصر القائمة عقد بيانات مستخدم فردي، سواء على شكل قائمة أو في شكل tf.data.Dataset . نظرًا لأن لدينا بالفعل واجهة توفر الأخير ، فلنستخدمها.

إليك وظيفة مساعد بسيطة ستنشئ قائمة بمجموعات البيانات من مجموعة معينة من المستخدمين كمدخلات في جولة تدريب أو تقييم.

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

الآن كيف نختار العملاء؟

في سيناريو نموذجي للتدريب الموحد ، نتعامل مع عدد كبير جدًا من أجهزة المستخدمين ، والتي قد يكون جزء صغير منها متاحًا للتدريب في وقت معين. هذا هو الحال ، على سبيل المثال ، عندما تكون أجهزة العميل عبارة عن هواتف محمولة تشارك في التدريب فقط عند توصيلها بمصدر طاقة ، أو خارج شبكة مقننة ، أو خاملة بخلاف ذلك.

بالطبع ، نحن في بيئة محاكاة ، وجميع البيانات متاحة محليًا. عندئذٍ عادةً ، عند تشغيل عمليات المحاكاة ، سنقوم ببساطة بأخذ عينات من مجموعة فرعية عشوائية من العملاء للمشاركة في كل جولة تدريب ، تختلف عمومًا في كل جولة.

وقال، كما يمكنك معرفة ذلك من خلال دراسة ورقة على المتوسط الاتحادية خوارزمية، وتحقيق التقارب في النظام مع مجموعات فرعية عينة عشوائية من العملاء في كل جولة يمكن أن يستغرق بعض الوقت، وأنه سيكون غير عملي لدينا لتشغيل المئات من قذائف في هذا البرنامج التعليمي التفاعلي.

ما سنفعله بدلاً من ذلك هو أخذ عينات من مجموعة العملاء مرة واحدة ، وإعادة استخدام نفس المجموعة عبر الجولات لتسريع التقارب (الإفراط في الملاءمة المتعمدة لبيانات المستخدم القليلة هذه). نتركه كتدريب للقارئ لتعديل هذا البرنامج التعليمي لمحاكاة أخذ العينات العشوائية - من السهل القيام بذلك (بمجرد القيام بذلك ، ضع في اعتبارك أن جعل النموذج يتقارب قد يستغرق بعض الوقت).

sample_clients = emnist_train.client_ids[0:NUM_CLIENTS]

federated_train_data = make_federated_data(emnist_train, sample_clients)

print('Number of client datasets: {l}'.format(l=len(federated_train_data)))
print('First dataset: {d}'.format(d=federated_train_data[0]))
Number of client datasets: 10
First dataset: <DatasetV1Adapter shapes: OrderedDict([(x, (None, 784)), (y, (None, 1))]), types: OrderedDict([(x, tf.float32), (y, tf.int32)])>

إنشاء نموذج مع Keras

إذا كنت تستخدم Keras ، فمن المحتمل أن يكون لديك بالفعل رمز يقوم بإنشاء نموذج Keras. فيما يلي مثال لنموذج بسيط يفي باحتياجاتنا.

def create_keras_model():
  return tf.keras.models.Sequential([
      tf.keras.layers.InputLayer(input_shape=(784,)),
      tf.keras.layers.Dense(10, kernel_initializer='zeros'),
      tf.keras.layers.Softmax(),
  ])

من أجل استخدام أي نموذج مع TFF، فإنه يحتاج إلى أن تكون ملفوفة في مثيل tff.learning.Model واجهة، الذي يعرض طرق للقضاء تمريرة إلى الأمام للنموذج، والخصائص الوصفية، الخ، على غرار Keras، ولكنه يقدم أيضا إضافي العناصر ، مثل طرق التحكم في عملية حساب المقاييس الموحدة. دعونا لا نقلق بشأن هذا الآن ؛ إذا كان لديك نموذج Keras مثل تلك التي حددناها للتو أعلاه، هل يمكن أن يكون TFF ألفه لك من خلال التذرع tff.learning.from_keras_model ، ويمر النموذج ودفعة بيانات العينة كوسائط، كما هو مبين أدناه.

def model_fn():
  # We _must_ create a new model here, and _not_ capture it from an external
  # scope. TFF will call this within different graph contexts.
  keras_model = create_keras_model()
  return tff.learning.from_keras_model(
      keras_model,
      input_spec=preprocessed_example_dataset.element_spec,
      loss=tf.keras.losses.SparseCategoricalCrossentropy(),
      metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])

تدريب النموذج على البيانات الموحدة

الآن أن لدينا نموذج ملفوفة كما tff.learning.Model للاستخدام مع TFF، يمكننا أن نسمح TFF بناء المتوسط خوارزمية الاتحادية من خلال التذرع وظيفة مساعد tff.learning.build_federated_averaging_process ، على النحو التالي.

نضع في اعتبارنا أن الجدل يجب أن يكون منشئ (مثل model_fn أعلاه)، لا مثيل شيدت بالفعل، حتى أن بناء النموذج الخاص بك يمكن أن يحدث في سياق تسيطر عليها TFF (إذا كنت غريبة عن أسباب هذا، ونحن ندعوك لقراءة البرنامج التعليمي المتابعة على خوارزميات مخصصة ).

ملاحظة واحدة حاسمة على خوارزمية المتوسط الاتحادية أدناه، وهناك 2 أبتيميزر: محسن _client ومحسن _SERVER. يستخدم محسن _client فقط لحساب التحديثات نموذج المحلية على كل عميل. ينطبق محسن _SERVER التحديث المتوسط إلى نموذج عالمي في الملقم. على وجه الخصوص ، هذا يعني أن اختيار المُحسِّن ومعدل التعلم المستخدم قد يحتاج إلى أن يكون مختلفًا عن ذلك الذي استخدمته لتدريب النموذج على مجموعة بيانات iid القياسية. نوصي بالبدء بـ SGD العادي ، ربما بمعدل تعلم أقل من المعتاد. لم يتم ضبط معدل التعلم الذي نستخدمه بعناية ، فلا تتردد في التجربة.

iterative_process = tff.learning.build_federated_averaging_process(
    model_fn,
    client_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=0.02),
    server_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=1.0))

ماذا حدث للتو؟ وقد شيدت TFF زوج من الحسابات الاتحادية وتعبئتها لهم في tff.templates.IterativeProcess التي تكون هذه الحسابات المتاحة كزوج من خصائص initialize و next .

باختصار، الحسابية الاتحادية هي برامج باللغة الداخلية TFF التي يمكن أن تعبر عن مختلف خوارزميات الاتحادية (يمكنك العثور على المزيد حول هذا الموضوع في العرف الخوارزميات البرنامج التعليمي). في هذه الحالة، فإن الحسابات اثنين ولدت ومعبأة في iterative_process تنفيذ الاتحادية في المتوسط .

إن هدف TFF هو تحديد العمليات الحسابية بطريقة يمكن تنفيذها في إعدادات التعلم الفيدرالي الحقيقي ، ولكن يتم حاليًا تنفيذ وقت تشغيل محاكاة التنفيذ المحلي فقط. لتنفيذ عملية حسابية في جهاز محاكاة ، يمكنك ببساطة استدعاؤها مثل دالة Python. هذه البيئة المفسرة الافتراضية ليست مصممة للأداء العالي ، لكنها ستكون كافية لهذا البرنامج التعليمي ؛ نتوقع توفير أوقات تشغيل محاكاة عالية الأداء لتسهيل البحث على نطاق واسع في الإصدارات المستقبلية.

دعونا نبدأ مع initialize حساب. كما هو الحال بالنسبة لجميع الحسابات الموحدة ، يمكنك التفكير في الأمر كدالة. لا يأخذ الحساب أي وسيطات ، ويعيد نتيجة واحدة - تمثيل حالة عملية المتوسطات الموحدة على الخادم. على الرغم من أننا لا نريد الغوص في تفاصيل TFF ، فقد يكون من المفيد معرفة شكل هذه الحالة. يمكنك تصور ذلك على النحو التالي.

str(iterative_process.initialize.type_signature)
'( -> <model=<trainable=<float32[784,10],float32[10]>,non_trainable=<>>,optimizer_state=<int64>,delta_aggregate_state=<value_sum_process=<>,weight_sum_process=<>>,model_broadcast_state=<>>@SERVER)'

في حين أن النوع توقيع أعلاه قد يبدو في البداية وخفي بعض الشيء، يمكنك ندرك أن حالة الملقم يتكون من model (معالم النموذج الأولي لMNIST التي سيتم توزيعها على جميع الأجهزة)، و optimizer_state المحافظة (معلومات إضافية من قبل الملقم، مثل عدد الدورات لاستخدامها في جداول المعامِلات الفائقة ، وما إلى ذلك).

دعونا استدعاء initialize حساب لبناء الدولة الخادم.

state = iterative_process.initialize()

الثاني من زوج من الحسابات الاتحادية، next ، يمثل دورة واحدة من اتحاد المتوسط، الذي يتألف من دفع حالة الملقم (بما في ذلك معالم النموذج) للعملاء، على جهاز التدريب على البيانات المحلية، وجمع وتحديث النموذج المتوسط ، وإنتاج نموذج جديد محدث على الخادم.

من الناحية النظرية، يمكن ان يخطر لك next وجود نوع توقيع الوظيفي الذي يبدو على النحو التالي.

SERVER_STATE, FEDERATED_DATA -> SERVER_STATE, TRAINING_METRICS

على وجه الخصوص، ينبغي للمرء أن نفكر في next() لا على أنها وظيفة أن يعمل على الخادم، وإنما يجري تمثيل وظيفية التعريفي للحساب مركزي كامل - يتم توفير بعض المدخلات من قبل الملقم ( SERVER_STATE )، ولكن المشاركة كل يساهم الجهاز في مجموعة البيانات المحلية الخاصة به.

دعونا نجري جولة واحدة من التدريب ونتخيل النتائج. يمكننا استخدام البيانات الموحدة التي أنشأناها بالفعل أعلاه لعينة من المستخدمين.

state, metrics = iterative_process.next(state, federated_train_data)
print('round  1, metrics={}'.format(metrics))
round  1, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('sparse_categorical_accuracy', 0.12345679), ('loss', 3.1193738)])), ('stat', OrderedDict([('num_examples', 4860)]))])

دعونا نجري بضع جولات أخرى. كما ذكرنا سابقًا ، عادةً في هذه المرحلة ، يمكنك اختيار مجموعة فرعية من بيانات المحاكاة الخاصة بك من عينة جديدة محددة عشوائيًا من المستخدمين لكل جولة من أجل محاكاة نشر واقعي يأتي فيه المستخدمون ويذهبون باستمرار ، ولكن في هذا الكمبيوتر الدفتري التفاعلي ، من أجل من أجل العرض التوضيحي ، سنقوم فقط بإعادة استخدام نفس المستخدمين ، بحيث يتقارب النظام بسرعة.

NUM_ROUNDS = 11
for round_num in range(2, NUM_ROUNDS):
  state, metrics = iterative_process.next(state, federated_train_data)
  print('round {:2d}, metrics={}'.format(round_num, metrics))
round  2, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('sparse_categorical_accuracy', 0.13518518), ('loss', 2.9834728)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  3, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('sparse_categorical_accuracy', 0.14382716), ('loss', 2.861665)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  4, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('sparse_categorical_accuracy', 0.17407407), ('loss', 2.7957022)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  5, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('sparse_categorical_accuracy', 0.19917695), ('loss', 2.6146567)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  6, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('sparse_categorical_accuracy', 0.21975309), ('loss', 2.529761)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  7, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('sparse_categorical_accuracy', 0.2409465), ('loss', 2.4053504)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  8, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('sparse_categorical_accuracy', 0.2611111), ('loss', 2.315389)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  9, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('sparse_categorical_accuracy', 0.30823046), ('loss', 2.1240263)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round 10, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('sparse_categorical_accuracy', 0.33312756), ('loss', 2.1164262)])), ('stat', OrderedDict([('num_examples', 4860)]))])

يتناقص فقدان التدريب بعد كل جولة من التدريب الموحد ، مما يشير إلى أن النموذج يتقارب. هناك بعض المحاذير المهمة مع هذه المقاييس التدريب، ومع ذلك، راجع المقطع على تقييم في وقت لاحق في هذا البرنامج التعليمي.

عرض مقاييس النموذج في TensorBoard

بعد ذلك ، دعنا نتخيل المقاييس من هذه الحسابات الموحدة باستخدام Tensorboard.

لنبدأ بإنشاء الدليل وكاتب الملخص المقابل لكتابة المقاييس إليه.

logdir = "/tmp/logs/scalars/training/"
summary_writer = tf.summary.create_file_writer(logdir)
state = iterative_process.initialize()

ارسم المقاييس العددية ذات الصلة بنفس كاتب الملخص.

with summary_writer.as_default():
  for round_num in range(1, NUM_ROUNDS):
    state, metrics = iterative_process.next(state, federated_train_data)
    for name, value in metrics['train'].items():
      tf.summary.scalar(name, value, step=round_num)

ابدأ TensorBoard بدليل السجل الجذر المحدد أعلاه. قد يستغرق تحميل البيانات بضع ثوانٍ.

!ls {logdir}
%tensorboard --logdir {logdir} --port=0
events.out.tfevents.1629557449.ebe6e776479e64ea-4903924a278.borgtask.google.com.458912.1.v2
Launching TensorBoard...
Reusing TensorBoard on port 50681 (pid 292785), started 0:30:30 ago. (Use '!kill 292785' to kill it.)
<IPython.core.display.Javascript at 0x7fd6617e02d0>
# Uncomment and run this this cell to clean your directory of old output for
# future graphs from this directory. We don't run it by default so that if 
# you do a "Runtime > Run all" you don't lose your results.

# !rm -R /tmp/logs/scalars/*

لعرض مقاييس التقييم بالطريقة نفسها ، يمكنك إنشاء مجلد تقييم منفصل ، مثل "السجلات / الحجم / التقييم" ، للكتابة إلى TensorBoard.

تخصيص تنفيذ النموذج

Keras هو الموصى رفيع المستوى API نموذجا للTensorFlow ، ونحن نشجع باستخدام نماذج Keras (عبر tff.learning.from_keras_model ) في TFF كلما أمكن ذلك.

ومع ذلك، tff.learning يوفر واجهة نموذج المستوى الأدنى، tff.learning.Model ، الذي يعرض وظيفة الحد الأدنى اللازمة لاستخدام نموذج للتعلم الاتحادية. تنفيذ مباشرة هذه الواجهة (ربما لا تزال تستخدم بناء كتل مثل tf.keras.layers ) يسمح لأقصى قدر من التخصيص دون تعديل الأجزاء الداخلية من خوارزميات التعلم الاتحادية.

لذلك دعونا نفعل ذلك مرة أخرى من الصفر.

تحديد متغيرات النموذج ، والمرور الأمامي ، والمقاييس

الخطوة الأولى هي تحديد متغيرات TensorFlow التي سنعمل معها. لجعل الكود التالي أكثر وضوحًا ، دعنا نحدد بنية بيانات لتمثيل المجموعة بأكملها. وسيشمل ذلك متغيرات مثل weights و bias أننا سوف تدريب، فضلا عن المتغيرات التي ستعقد إحصاءات مختلفة التراكمية وعدادات سنقوم بتحديث أثناء التدريب، مثل loss_sum ، accuracy_sum ، و num_examples .

MnistVariables = collections.namedtuple(
    'MnistVariables', 'weights bias num_examples loss_sum accuracy_sum')

إليك طريقة لإنشاء المتغيرات. من أجل البساطة، ونحن نمثل جميع الإحصاءات كما tf.float32 ، والتي من شأنها القضاء على الحاجة إلى نوع التحويلات في مرحلة لاحقة. التفاف المهيآت متغير كما lambdas هو الشرط الذي فرضته المتغيرات الموارد .

def create_mnist_variables():
  return MnistVariables(
      weights=tf.Variable(
          lambda: tf.zeros(dtype=tf.float32, shape=(784, 10)),
          name='weights',
          trainable=True),
      bias=tf.Variable(
          lambda: tf.zeros(dtype=tf.float32, shape=(10)),
          name='bias',
          trainable=True),
      num_examples=tf.Variable(0.0, name='num_examples', trainable=False),
      loss_sum=tf.Variable(0.0, name='loss_sum', trainable=False),
      accuracy_sum=tf.Variable(0.0, name='accuracy_sum', trainable=False))

مع وجود متغيرات معلمات النموذج والإحصاءات التراكمية في مكانها الصحيح ، يمكننا الآن تحديد طريقة المرور إلى الأمام التي تحسب الخسارة وتصدر التنبؤات وتحدّث الإحصائيات التراكمية لمجموعة واحدة من بيانات الإدخال ، على النحو التالي.

def predict_on_batch(variables, x):
  return tf.nn.softmax(tf.matmul(x, variables.weights) + variables.bias)

def mnist_forward_pass(variables, batch):
  y = predict_on_batch(variables, batch['x'])
  predictions = tf.cast(tf.argmax(y, 1), tf.int32)

  flat_labels = tf.reshape(batch['y'], [-1])
  loss = -tf.reduce_mean(
      tf.reduce_sum(tf.one_hot(flat_labels, 10) * tf.math.log(y), axis=[1]))
  accuracy = tf.reduce_mean(
      tf.cast(tf.equal(predictions, flat_labels), tf.float32))

  num_examples = tf.cast(tf.size(batch['y']), tf.float32)

  variables.num_examples.assign_add(num_examples)
  variables.loss_sum.assign_add(loss * num_examples)
  variables.accuracy_sum.assign_add(accuracy * num_examples)

  return loss, predictions

بعد ذلك ، نحدد دالة تقوم بإرجاع مجموعة من المقاييس المحلية ، مرة أخرى باستخدام TensorFlow. هذه هي القيم (بالإضافة إلى تحديثات النموذج ، التي يتم التعامل معها تلقائيًا) المؤهلة لتجميعها على الخادم في عملية التعلم أو التقييم الموحدة.

هنا، ونحن ببساطة العودة متوسط loss و accuracy ، فضلا عن num_examples ، والتي سنحتاج إلى الوزن المساهمات من مختلف المستخدمين بشكل صحيح عند حساب المجاميع الاتحادية.

def get_local_mnist_metrics(variables):
  return collections.OrderedDict(
      num_examples=variables.num_examples,
      loss=variables.loss_sum / variables.num_examples,
      accuracy=variables.accuracy_sum / variables.num_examples)

وأخيرا، نحن بحاجة إلى تحديد كيفية تجميع المقاييس المحلية المنبعثة من كل جهاز عن طريق get_local_mnist_metrics . هذا هو الجزء الوحيد من التعليمات البرمجية التي لم تكتب في TensorFlow - انها حساب الاتحادية التي أعرب عنها في TFF. إذا كنت ترغب في حفر أعمق، الخالي من الدسم على العرف الخوارزميات البرنامج التعليمي، ولكن في معظم التطبيقات، لن تحتاج حقا إلى؛ يجب أن تكون متغيرات النمط الموضح أدناه كافية. هذا ما يبدو عليه:

@tff.federated_computation
def aggregate_mnist_metrics_across_clients(metrics):
  return collections.OrderedDict(
      num_examples=tff.federated_sum(metrics.num_examples),
      loss=tff.federated_mean(metrics.loss, metrics.num_examples),
      accuracy=tff.federated_mean(metrics.accuracy, metrics.num_examples))

إدخال metrics يتوافق حجة ل OrderedDict إرجاعها بواسطة get_local_mnist_metrics أعلاه، ولكن خطيرة القيم لم تعد tf.Tensors - فهي "محاصر"، كما tff.Value الصورة، لجعلها واضحة لك لم تعد قادرة على التلاعب بها باستخدام TensorFlow، ولكن فقط باستخدام مشغلي TFF والاتحادية مثل tff.federated_mean و tff.federated_sum . يحدد القاموس الذي تم إرجاعه للتجمعات العالمية مجموعة المقاييس التي ستكون متاحة على الخادم.

بناء مثيل tff.learning.Model

مع كل ما سبق في مكانه الصحيح ، نحن على استعداد لإنشاء تمثيل نموذج للاستخدام مع TFF مشابه للتمثيل الذي تم إنشاؤه لك عندما تسمح لـ TFF باستيعاب نموذج Keras.

from typing import Callable, List, OrderedDict

class MnistModel(tff.learning.Model):

  def __init__(self):
    self._variables = create_mnist_variables()

  @property
  def trainable_variables(self):
    return [self._variables.weights, self._variables.bias]

  @property
  def non_trainable_variables(self):
    return []

  @property
  def local_variables(self):
    return [
        self._variables.num_examples, self._variables.loss_sum,
        self._variables.accuracy_sum
    ]

  @property
  def input_spec(self):
    return collections.OrderedDict(
        x=tf.TensorSpec([None, 784], tf.float32),
        y=tf.TensorSpec([None, 1], tf.int32))

  @tf.function
  def predict_on_batch(self, x, training=True):
    del training
    return predict_on_batch(self._variables, x)

  @tf.function
  def forward_pass(self, batch, training=True):
    del training
    loss, predictions = mnist_forward_pass(self._variables, batch)
    num_exmaples = tf.shape(batch['x'])[0]
    return tff.learning.BatchOutput(
        loss=loss, predictions=predictions, num_examples=num_exmaples)

  @tf.function
  def report_local_outputs(self):
    return get_local_mnist_metrics(self._variables)

  @property
  def federated_output_computation(self):
    return aggregate_mnist_metrics_across_clients

  @tf.function
  def report_local_unfinalized_metrics(
      self) -> OrderedDict[str, List[tf.Tensor]]:
    """Creates an `OrderedDict` of metric names to unfinalized values."""
    return collections.OrderedDict(
        num_examples=[self._variables.num_examples],
        loss=[self._variables.loss_sum, self._variables.num_examples],
        accuracy=[self._variables.accuracy_sum, self._variables.num_examples])

  def metric_finalizers(
      self) -> OrderedDict[str, Callable[[List[tf.Tensor]], tf.Tensor]]:
    """Creates an `OrderedDict` of metric names to finalizers."""
    return collections.OrderedDict(
        num_examples=tf.function(func=lambda x: x[0]),
        loss=tf.function(func=lambda x: x[0] / x[1]),
        accuracy=tf.function(func=lambda x: x[0] / x[1]))

كما ترون، وأساليب مجردة والخصائص التي تحددها tff.learning.Model يتوافق مع التعليمات البرمجية المتكررة في المقطع السابق الذي قدم المتغيرات وتعريف الخسارة والإحصاءات.

فيما يلي بعض النقاط التي تستحق التركيز عليها:

  • كل دولة أن النموذج الخاص بك سوف تستخدم يجب أن يتم القبض كمتغيرات TensorFlow، كما TFF لا تستخدم بايثون في وقت التشغيل (تذكر التعليمات البرمجية الخاصة بك يجب أن تكون مكتوبة من النوع الذي يمكن نشرها إلى الأجهزة النقالة، انظر العرف الخوارزميات البرنامج التعليمي لأكثر تعمقا التعليق على الأسباب).
  • النموذج الخاص بك ينبغي أن يصف ما شكل بيانات يقبل عليه ( input_spec )، وبشكل عام، TFF هي بيئة كتابة بشدة ويريد لتحديد نوع التوقيعات لجميع المكونات. يُعد الإعلان عن تنسيق مدخلات النموذج جزءًا أساسيًا منه.
  • وعلى الرغم من الناحية الفنية ليس مطلوبا، نوصي التفاف كل منطق TensorFlow (تمريرة إلى الأمام، وحسابات متري، الخ) كما tf.function الصورة، وهذا يساعد على ضمان TensorFlow يمكن إجراء تسلسل، ويزيل الحاجة إلى تبعيات سيطرة واضحة.

ما ورد أعلاه كافٍ للتقييم والخوارزميات مثل SGD الموحدة. ومع ذلك ، بالنسبة إلى Federated Averaging ، نحتاج إلى تحديد كيفية تدريب النموذج محليًا على كل دفعة. سنحدد مُحسِّنًا محليًا عند بناء خوارزمية المتوسطات الموحدة.

محاكاة التدريب الفدرالي بالنموذج الجديد

مع كل ما سبق في مكانه الصحيح ، تبدو بقية العملية كما رأينا بالفعل - فقط استبدل مُنشئ النموذج بمنشئ فئة النموذج الجديد ، واستخدم الحسابين المتحدتين في العملية التكرارية التي قمت بإنشائها للتنقل من خلالها جولات التدريب.

iterative_process = tff.learning.build_federated_averaging_process(
    MnistModel,
    client_optimizer_fn=lambda: tf.keras.optimizers.SGD(learning_rate=0.02))
state = iterative_process.initialize()
state, metrics = iterative_process.next(state, federated_train_data)
print('round  1, metrics={}'.format(metrics))
round  1, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('num_examples', 4860.0), ('loss', 3.0708053), ('accuracy', 0.12777779)])), ('stat', OrderedDict([('num_examples', 4860)]))])
for round_num in range(2, 11):
  state, metrics = iterative_process.next(state, federated_train_data)
  print('round {:2d}, metrics={}'.format(round_num, metrics))
round  2, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('num_examples', 4860.0), ('loss', 3.011699), ('accuracy', 0.13024691)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  3, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('num_examples', 4860.0), ('loss', 2.7408307), ('accuracy', 0.15576132)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  4, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('num_examples', 4860.0), ('loss', 2.6761012), ('accuracy', 0.17921811)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  5, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('num_examples', 4860.0), ('loss', 2.675567), ('accuracy', 0.1855967)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  6, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('num_examples', 4860.0), ('loss', 2.5664043), ('accuracy', 0.20329218)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  7, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('num_examples', 4860.0), ('loss', 2.4179392), ('accuracy', 0.24382716)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  8, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('num_examples', 4860.0), ('loss', 2.3237286), ('accuracy', 0.26687244)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round  9, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('num_examples', 4860.0), ('loss', 2.1861682), ('accuracy', 0.28209877)])), ('stat', OrderedDict([('num_examples', 4860)]))])
round 10, metrics=OrderedDict([('broadcast', ()), ('aggregation', OrderedDict([('mean_value', ()), ('mean_weight', ())])), ('train', OrderedDict([('num_examples', 4860.0), ('loss', 2.046388), ('accuracy', 0.32037038)])), ('stat', OrderedDict([('num_examples', 4860)]))])

للاطلاع على هذه المقاييس داخل TensorBoard ، راجع الخطوات المذكورة أعلاه في "عرض مقاييس النموذج في TensorBoard".

تقييم

قدمت جميع تجاربنا حتى الآن مقاييس تدريب موحدة فقط - متوسط ​​المقاييس على جميع مجموعات البيانات التي تم تدريبها عبر جميع العملاء في الجولة. يقدم هذا المخاوف العادية بشأن فرط التخصيص ، خاصة وأننا استخدمنا نفس مجموعة العملاء في كل جولة من أجل البساطة ، ولكن هناك فكرة إضافية عن التخصيص الزائد في مقاييس التدريب الخاصة بخوارزمية المتوسطات الموحدة. هذا أسهل لمعرفة ما إذا كنا نتخيل أن كل عميل لديه مجموعة واحدة من البيانات ، ونحن نتدرب على هذه المجموعة للعديد من التكرارات (العهود). في هذه الحالة ، سيتناسب النموذج المحلي تمامًا مع تلك الدُفعة الواحدة ، وبالتالي فإن مقياس الدقة المحلي الذي نتوسطه سيقترب من 1.0. وبالتالي ، يمكن اعتبار مقاييس التدريب هذه علامة على أن التدريب يتقدم ، ولكن ليس أكثر من ذلك بكثير.

لإجراء التقييم على بيانات اتحادية، يمكنك بناء حساب اتحادية أخرى مصممة لهذا الغرض فقط، وذلك باستخدام tff.learning.build_federated_evaluation وظيفة، ويمر في منشئ النموذج الخاص بك كحجة. علما بأن خلافا مع اتحاد المتوسط، حيث استخدمنا MnistTrainableModel ، يكفي لتمرير MnistModel . لا يؤدي التقييم نزولًا متدرجًا ، وليست هناك حاجة لإنشاء أدوات تحسين.

للتجريب والبحث، عند مجموعة بيانات الاختبار مركزية متاح، اتحاد التعلم عن نص الجيل يدل خيار تقييم آخر: أخذ الأوزان المدربين من التعلم الاتحادية، تطبيقها على نموذج Keras القياسية، ومن ثم الدعوة ببساطة tf.keras.models.Model.evaluate() على مجموعة بيانات مركزية.

evaluation = tff.learning.build_federated_evaluation(MnistModel)

يمكنك فحص توقيع النوع المجرد لوظيفة التقييم على النحو التالي.

str(evaluation.type_signature)
'(<server_model_weights=<trainable=<float32[784,10],float32[10]>,non_trainable=<>>@SERVER,federated_dataset={<x=float32[?,784],y=int32[?,1]>*}@CLIENTS> -> <eval=<num_examples=float32,loss=float32,accuracy=float32>,stat=<num_examples=int64>>@SERVER)'

لا حاجة للقلق بشأن التفاصيل في هذه المرحلة، يكون مجرد علم أنه يأخذ شكل العام التالي، على غرار tff.templates.IterativeProcess.next لكن مع اثنين من الاختلافات الهامة. أولاً ، لا نعيد حالة الخادم ، نظرًا لأن التقييم لا يعدل النموذج أو أي جانب آخر من جوانب الحالة - يمكنك اعتباره عديم الحالة. ثانيًا ، يحتاج التقييم إلى النموذج فقط ، ولا يتطلب أي جزء آخر من حالة الخادم قد يكون مرتبطًا بالتدريب ، مثل متغيرات المُحسِّن.

SERVER_MODEL, FEDERATED_DATA -> TRAINING_METRICS

دعنا نستدعي التقييم على أحدث حالة وصلنا إليها أثناء التدريب. من أجل انتزاع أحدث طراز المدربين من حالة الملقم، يمكنك ببساطة الوصول إلى .model الأعضاء، على النحو التالي.

train_metrics = evaluation(state.model, federated_train_data)

هذا ما نحصل عليه. لاحظ أن الأرقام تبدو أفضل بشكل هامشي مما تم الإبلاغ عنه في الجولة الأخيرة من التدريب أعلاه. وفقًا للاتفاقية ، تعكس مقاييس التدريب التي تم الإبلاغ عنها بواسطة عملية التدريب التكراري بشكل عام أداء النموذج في بداية جولة التدريب ، وبالتالي فإن مقاييس التقييم ستكون دائمًا متقدمًا بخطوة.

str(train_metrics)
"OrderedDict([('eval', OrderedDict([('num_examples', 4860.0), ('loss', 1.7510437), ('accuracy', 0.2788066)])), ('stat', OrderedDict([('num_examples', 4860)]))])"

الآن ، دعنا نقوم بتجميع عينة اختبار من البيانات الموحدة وإعادة التقييم على بيانات الاختبار. ستأتي البيانات من نفس العينة من المستخدمين الحقيقيين ، ولكن من مجموعة بيانات مميزة ومثبتة.

federated_test_data = make_federated_data(emnist_test, sample_clients)

len(federated_test_data), federated_test_data[0]
(10,
 <DatasetV1Adapter shapes: OrderedDict([(x, (None, 784)), (y, (None, 1))]), types: OrderedDict([(x, tf.float32), (y, tf.int32)])>)
test_metrics = evaluation(state.model, federated_test_data)
str(test_metrics)
"OrderedDict([('eval', OrderedDict([('num_examples', 580.0), ('loss', 1.8361608), ('accuracy', 0.2413793)])), ('stat', OrderedDict([('num_examples', 580)]))])"

هذا يختتم البرنامج التعليمي. نحن نشجعك على اللعب بالمعلمات (على سبيل المثال ، أحجام الدُفعات ، وعدد المستخدمين ، والعهود ، ومعدلات التعلم ، وما إلى ذلك) ، لتعديل الكود أعلاه لمحاكاة التدريب على عينات عشوائية من المستخدمين في كل جولة ، واستكشاف البرامج التعليمية الأخرى لقد طورنا.